// ============================================================================
// Internet Character Set Conversion: Input from UTF-7
// ============================================================================

#include "private.h"
#include "fechrcnv.h"
#include "utf7obj.h"

//+-----------------------------------------------------------------------
//
//  Function:   IsBase64
//
//  Synopsis:   We use the following table to quickly determine if we have
//              a valid base64 character.
//
//------------------------------------------------------------------------

static UCHAR g_aBase64[256] =
{
  /*            0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   A,   B,   C,   D,   E,   F, */

  /* 00-0f */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* 10-1f */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* 20-2f */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
  /* 30-3f */  52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255, 255, 255, 255, 255,
  /* 40-4f */ 255,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
  /* 50-5f */  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
  /* 60-6f */ 255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
  /* 70-7f */  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51, 255, 255, 255, 255, 255,
  /* 80-8f */  255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* 90-9f */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* a0-af */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* b0-bf */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* c0-cf */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* d0-df */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* e0-ef */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* f0-ff */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
};


// Direct encoded ASCII table
static UCHAR g_aDirectChar[128] =
{
  /*            0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   A,   B,   C,   D,   E,   F, */

  /* 00-0f */ 255, 255, 255, 255, 255, 255, 255, 255, 255,  72,  73, 255, 255,  74, 255, 255,
  /* 10-1f */ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
  /* 20-2f */  71, 255, 255, 255, 255, 255, 255,  62,  63,  64, 255, 255,  65,  66,  67,  68,
  /* 30-3f */  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  69, 255, 255, 255, 255,  70,
  /* 40-4f */ 255,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,
  /* 50-5f */  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
  /* 60-6f */ 255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,
  /* 70-7f */  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51, 255, 255, 255, 255, 255,
};

// Base64 byte value table
static UCHAR g_aInvBase64[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" };

static inline BOOL
IsBase64(UCHAR t )
{
    return g_aBase64[t] < 64;
}

/******************************************************************************
**************************   C O N S T R U C T O R   **************************
******************************************************************************/

CInccUTF7In::CInccUTF7In(UINT uCodePage, int nCodeSet) : CINetCodeConverter(uCodePage, nCodeSet)
{
    Reset();    // initialization
    return ;
}

/******************************************************************************
*******************************   R E S E T   *********************************
******************************************************************************/

void CInccUTF7In::Reset()
{
    m_pfnConv = ConvMain;
    m_pfnCleanUp = CleanUpMain;
    m_fUTF7Mode = FALSE ;
    m_nBitCount = 0 ;
    m_tcUnicode = 0 ;
    m_nOutCount = 0 ;
    return ;
}


/******************************************************************************
*************************   C O N V E R T   C H A R   *************************
******************************************************************************/

HRESULT CInccUTF7In::ConvertChar(UCHAR tc, int cchSrc)
{
	BOOL fDone = (this->*m_pfnConv)(tc);
    if (fDone)
        return S_OK;
    else
        return E_FAIL;
}

/******************************************************************************
*****************************   C L E A N   U P   *****************************
******************************************************************************/

BOOL CInccUTF7In::CleanUp()
{
    return (this->*m_pfnCleanUp)();
}

/******************************************************************************
****************************   C O N V   M A I N   ****************************
******************************************************************************/

BOOL CInccUTF7In::ConvMain(UCHAR tc)
{
    BOOL fDone = TRUE;

    // are we in UTF-7 mode ?
    if (m_fUTF7Mode )
    {
        if ( IsBase64(tc) )
        {
            UCHAR t64, outc ;
            LONG tcUnicode ;

            // save the Base64 value and update bit count
            t64 = g_aBase64[tc] ;
            m_tcUnicode = m_tcUnicode << 6 | t64 ;
            m_nBitCount += 6 ;

            // see if we accumulate enough bits
            if ( m_nBitCount >= 16 )
            {
                // get higher 16 bits data from buffer
                tcUnicode = m_tcUnicode >> ( m_nBitCount - 16 ) ;
                // output one Unicode char
                outc = (UCHAR) tcUnicode ;
                Output( outc );
                outc = (UCHAR) ( tcUnicode >> 8 ) ;
                fDone = Output( outc );

                // update output char count
                m_nOutCount ++ ;
                m_nBitCount -= 16 ;
            }
        }
        // not a Base64 char, reset UTF-7 mode
        else
        {
            // special case +- decodes to +
            if ( tc == '-' && m_nOutCount == 0 && m_nBitCount == 0 )
            {
                Output('+');
                fDone=Output(0);
            }
            // absorb shiht-out char '-', otherwise output char
            else if ( tc != '-')
            {
                Output(tc);
                fDone=Output(0);
            }
            // reset variables and UTF7Mode
            m_fUTF7Mode = FALSE ;
            m_nBitCount = 0 ;
            m_tcUnicode = 0 ;
            m_nOutCount = 0 ;
        }
    }
    // is it a UTF-7 shift-in char ?
    else if ( tc == '+' )
    {
        m_fUTF7Mode = TRUE ;
        m_nBitCount = 0 ;
        m_tcUnicode = 0 ;
        m_nOutCount = 0 ;
    }
    else
    // convert ASCII directly to Unicode if it is not in UFT-7 mode
    {
        Output(tc);
        fDone = Output(0);
    }

    return fDone;
}

/******************************************************************************
************************   C L E A N   U P   M A I N   ************************
******************************************************************************/

BOOL CInccUTF7In::CleanUpMain()
{
    return TRUE;
}

int CInccUTF7In::GetUnconvertBytes()
{
    return  0 ;
}

DWORD CInccUTF7In::GetConvertMode()
{
    DWORD dwMode ;

    if ( m_fUTF7Mode )
    {
        dwMode = ( m_tcUnicode & 0xffff ) | ( m_nBitCount << 16 ) ;
        if ( dwMode == 0 )
            dwMode = 1L ; // it is ok, since bitcount is 0
    }
    else
        dwMode = 0 ;

    return dwMode;
}

void CInccUTF7In::SetConvertMode(DWORD mode)
{
    Reset();    // initialization
    if (mode)
    {
        m_fUTF7Mode = TRUE ;
        m_tcUnicode = ( mode & 0x7fff );
        m_nBitCount = ( mode >> 16 ) & 0xffff ;
    }
    else
        m_fUTF7Mode = FALSE ;
}

// ============================================================================
// Internet Character Set Conversion: Output to UTF-7
// ============================================================================

/******************************************************************************
**************************   C O N S T R U C T O R   **************************
******************************************************************************/

CInccUTF7Out::CInccUTF7Out(UINT uCodePage, int nCodeSet) : CINetCodeConverter(uCodePage, nCodeSet)

{
    Reset();    // initialization
    return ;
}

/******************************************************************************
*******************************   R E S E T   *********************************
******************************************************************************/

void CInccUTF7Out::Reset()
{
    m_fDoubleByte = FALSE;
    m_fUTF7Mode = FALSE ;
    m_nBitCount = 0 ;
    m_tcUnicode = 0 ;
    return;
}

HRESULT CInccUTF7Out::ConvertChar(UCHAR tc, int cchSrc)
{
    BOOL fDone = TRUE;
    WORD uc ;

    // 2nd byte of Unicode
    if (m_fDoubleByte )
    {
        BOOL bNeedShift ;

        // compose the 16 bits char
        uc = ( (WORD) tc << 8 | m_tcFirstByte  ) ;

        // check whether the char can be direct encoded ?
        bNeedShift = uc > 0x7f ? TRUE : g_aDirectChar[(UCHAR)uc] == 255 ;

        if ( bNeedShift && m_fUTF7Mode == FALSE)
        {
            // output Shift-in char to change to UTF-7 Mode
            fDone = Output('+');

            // handle special case '+-'
            if ( uc == '+' ) // single byte "+"
            {
                fDone=Output('-');
            }
            else
                m_fUTF7Mode = TRUE ;
        }

        if (m_fUTF7Mode)
        {
            LONG tcUnicode ;
            UCHAR t64 ;
            int pad_bits ;

            // either write the char to the bit buffer 
            // or pad bit buffer out to a full base64 char
            if (bNeedShift)
            {
                m_tcUnicode = m_tcUnicode << 16 | uc ;
                m_nBitCount += 16 ;
            }
            // pad bit buffer out to a full base64 char
            else if (m_nBitCount % 6 )  
            {
                pad_bits = 6 - (m_nBitCount % 6 ) ;
                // get to next 6 multiple, pad these bits with 0
                m_tcUnicode = m_tcUnicode << pad_bits ;
                m_nBitCount += pad_bits ;
            }

            // flush out as many full base64 char as possible
            while ( m_nBitCount >= 6 && fDone )
            {
                tcUnicode = ( m_tcUnicode >> ( m_nBitCount - 6 ) );
                t64 = (UCHAR) ( tcUnicode & 0x3f ) ;
                fDone = Output(g_aInvBase64[t64]);
                m_nBitCount -= 6 ;
            }

            if (!bNeedShift)
            {
                // output Shift-out char
                fDone = Output('-');

                m_fUTF7Mode = FALSE ;
                m_nBitCount = 0 ;
                m_tcUnicode = 0 ;
            }
        }

        // the character can be directly encoded as ASCII
        if (!bNeedShift)
        {
            fDone = Output(m_tcFirstByte);
        }

        m_fDoubleByte = FALSE ;
    }
    // 1st byte of Unicode
    else
    {
        m_tcFirstByte = tc ;
        m_fDoubleByte = TRUE ;
    }
    
    if (fDone)
        return S_OK;
    else
        return E_FAIL;
}

/******************************************************************************
*****************************   C L E A N   U P   *****************************
******************************************************************************/

BOOL CInccUTF7Out::CleanUp()
{
    BOOL fDone = TRUE;

    if (m_fUTF7Mode)
    {
        UCHAR t64 ;
        LONG tcUnicode ;
        int pad_bits ;

        // pad bit buffer out to a full base64 char
        if (m_nBitCount % 6 )  
        {
            pad_bits = 6 - (m_nBitCount % 6 ) ;
            // get to next 6 multiple, pad these bits with 0
            m_tcUnicode = m_tcUnicode << pad_bits ;
            m_nBitCount += pad_bits ;
        }

        // flush out as many full base64 char as possible
        while ( m_nBitCount >= 6 && fDone )
        {
            tcUnicode = ( m_tcUnicode >> ( m_nBitCount - 6 ) );
            t64 = (UCHAR) ( tcUnicode & 0x3f ) ;
            fDone = Output(g_aInvBase64[t64]);
            m_nBitCount -= 6 ;
        }

        {
            // output Shift-out char
            fDone = Output('-');

            m_fUTF7Mode = FALSE ;
            m_nBitCount = 0 ;
            m_tcUnicode = 0 ;
        }
    }
    return fDone;
}

int CInccUTF7Out::GetUnconvertBytes()
{
    return  m_fDoubleByte ? 1 : 0 ;
}

DWORD CInccUTF7Out::GetConvertMode()
{
    return 0 ;
}

void CInccUTF7Out::SetConvertMode(DWORD mode)
{
    Reset();    // initialization
    return ;
}