#include <windows.h>
#include <imm.h>
#include "imeattr.h"
#include "imedefs.h"
#if defined(UNIIME)
#include "uniime.h"
#endif

#if !defined(ROMANIME)
/**********************************************************************/
/* Conversion()                                                       */
/**********************************************************************/
DWORD PASCAL Conversion(
#if defined(UNIIME)
    LPINSTDATAL     lpInstL,
    LPIMEL          lpImeL,
#endif
    LPCTSTR         lpszReading,
    LPCANDIDATELIST lpCandList,
    DWORD           dwBufLen)
{
    UINT        uMaxCand;
    DWORD       dwSize =        // similar to ClearCand
        // header length
        sizeof(CANDIDATELIST) +
        // candidate string pointers
        sizeof(DWORD) * MAX_COMP +
        // string plus NULL terminator
        (sizeof(WCHAR) + sizeof(TCHAR)) * MAX_COMP;
    DWORD       dwPattern;
    PRIVCONTEXT ImcP;
#if !defined(WINIME) && !defined(UNICDIME)
    BOOL        fNeedUnload;
#endif

    if (!dwBufLen) {
        return (dwSize);
    }

    uMaxCand = dwBufLen - sizeof(CANDIDATELIST);

    uMaxCand /= sizeof(DWORD) + sizeof(WCHAR) + sizeof(TCHAR);
    if (!uMaxCand) {
        // can not even put one string
        return (0);
    }

    lpCandList->dwSize = dwSize;
    lpCandList->dwStyle = IME_CAND_READ;    // candidate having same reading
    lpCandList->dwCount = 0;
    lpCandList->dwSelection = 0;
    lpCandList->dwPageStart = 0;
    lpCandList->dwPageSize = CANDPERPAGE;
    lpCandList->dwOffset[0] = sizeof(CANDIDATELIST) + sizeof(DWORD) *
        (uMaxCand - 1);

    dwPattern = ReadingToPattern(
#if defined(UNIIME)
        lpImeL,
#endif
        lpszReading, ImcP.bSeq, TRUE);

    if (!dwPattern) {
        return (0);
    }

#if !defined(WINIME) && !defined(UNICDIME)
    if (lpInstL->fdwTblLoad == TBL_LOADED) {
        fNeedUnload = FALSE;
    } else if (lpInstL->fdwTblLoad == TBL_NOTLOADED) {
        LoadTable(lpInstL, lpImeL);
        fNeedUnload = TRUE;
    } else {
       return (0);
    }
#endif

    if (!ConvertSeqCode2Pattern(
#if defined(UNIIME)
        lpImeL,
#endif
        ImcP.bSeq, &ImcP)) {
        return (0);
    }

    SearchTbl(
#if defined(UNIIME)
        lpImeL,
#endif
        0, lpCandList, &ImcP);

#if defined(WINAR30)
    if (!lpCandList->dwCount) {
        SearchTbl(3, lpCandList, &ImcP);
    }
#endif

#if !defined(WINIME) && !defined(UNICDIME)
    if (lpInstL->hUsrDicMem) {
        SearchUsrDic(
#if defined(UNIIME)
            lpImeL,
#endif
            lpCandList, &ImcP);
    }

    if (fNeedUnload) {
        FreeTable(lpInstL);
    }
#endif

    return (dwSize);
}

/**********************************************************************/
/* SearchOffset()                                                     */
/* Return Value :                                                     */
/*      the offset in table file which include this uOffset           */
/**********************************************************************/
UINT PASCAL SearchOffset(
    LPBYTE lpTbl,
    UINT   uTblSize,
    UINT   uOffset)
{
    int    iLo, iMid, iHi;
    LPWORD lpwPtr;

    iLo = 0;
    iHi = uTblSize / sizeof(WORD);
    iMid = (iLo + iHi) / 2;

    // binary search
    for (; iLo <= iHi; ) {
        lpwPtr = (LPWORD)lpTbl + iMid;

        if (uOffset > *lpwPtr) {
            iLo = iMid + 1;
        } else if (uOffset < *lpwPtr) {
            iHi = iMid - 1;
        } else {
            break;
        }

        iMid = (iLo + iHi) / 2;
    }

    if (iMid > 0) {
        iLo = iMid - 1;
    } else {
        iLo = 0;
    }

    iHi = uTblSize / sizeof(WORD);

    lpwPtr = (LPWORD)lpTbl + iLo;

    for (; iLo < iHi; iLo++, lpwPtr++) {
        if (*lpwPtr > uOffset) {
            return (iLo - 1);
        }
    }

#ifdef DEBUG
    _asm int 3
#endif

    return (0);
}

/**********************************************************************/
/* ReverseConversion()                                                */
/**********************************************************************/
#if defined(PHON)
typedef struct {
    BYTE szTmpBuf[sizeof(WORD) * 4 + sizeof(TCHAR)];
} PHONREVERSEBUF;

typedef PHONREVERSEBUF FAR *LPPHONREVERSEBUF;
#endif

DWORD PASCAL ReverseConversion(
#if defined(UNIIME)
    LPINSTDATAL     lpInstL,
    LPIMEL          lpImeL,
#endif
    UINT            uCode,
    LPCANDIDATELIST lpCandList,
    DWORD           dwBufLen)
{
    UINT   uMaxCand;
    DWORD  dwSize =         // similar to ClearCand
        // header length
        sizeof(CANDIDATELIST) +
        // candidate string pointers
        sizeof(DWORD) * MAX_COMP +
        // string plus NULL terminator
#if defined(QUICK)
        (sizeof(WCHAR) * 2 + sizeof(TCHAR)) * MAX_COMP;
#elif defined(WINIME) || defined(UNICDIME)
        (sizeof(WCHAR) * lpImeL->nMaxKey + sizeof(TCHAR));
#else
        (sizeof(WCHAR) * lpImeL->nMaxKey + sizeof(TCHAR)) * MAX_COMP;
#endif

#if defined(WINIME) || defined(UNICDIME)
    UINT   uTmpCode;
    int    i;
#else
    BOOL   fNeedUnload;
    HANDLE hTbl;
    LPBYTE lpTbl, lpStart, lpEnd;
#endif
#if defined(CHAJEI) || defined(PHON) || defined(QUICK)
    HANDLE hTbl0;
    LPBYTE lpTbl0;
#endif
#if defined(CHAJEI) || defined(PHON)
    HANDLE hTbl1;
    LPBYTE lpTbl1;
#endif

    if (!dwBufLen) {
        return (dwSize);
    }

    uMaxCand = dwBufLen - sizeof(CANDIDATELIST);

#if defined(QUICK)
    uMaxCand /= sizeof(DWORD) +
        (sizeof(WCHAR) * 2 + sizeof(TCHAR));
#else
    uMaxCand /= sizeof(DWORD) +
        (sizeof(WCHAR) * lpImeL->nMaxKey + sizeof(TCHAR));
#endif
    if (uMaxCand == 0) {
        // can not put one string
        return (0);
    }

    lpCandList->dwSize = sizeof(CANDIDATELIST) +
        sizeof(DWORD) * uMaxCand +
#if defined(QUICK)
        (sizeof(WCHAR) * 2 + sizeof(TCHAR)) * uMaxCand;
#elif defined(WINIME) || defined(UNICDIME)
        (sizeof(WCHAR) * lpImeL->nMaxKey + sizeof(TCHAR));
#else
        (sizeof(WCHAR) * lpImeL->nMaxKey + sizeof(TCHAR)) * uMaxCand;
#endif
    lpCandList->dwStyle = IME_CAND_READ;
    lpCandList->dwCount = 0;
    lpCandList->dwSelection = 0;
    lpCandList->dwPageSize = CANDPERPAGE;
    lpCandList->dwOffset[0] = sizeof(CANDIDATELIST) + sizeof(DWORD) *
        (uMaxCand - 1);

#if defined(WINIME) || defined(UNICDIME)
#if defined(WINIME) && defined(UNICODE)
    {
        int  iChars;
        CHAR szCode[4];

        iChars = WideCharToMultiByte(sImeG.uAnsiCodePage, WC_COMPOSITECHECK,
            (LPCWSTR)&uCode, 1, szCode, sizeof(szCode), NULL, NULL);

        if (iChars >= 2) {
            uTmpCode = ((UINT)(BYTE)szCode[0] << 8) | (BYTE)szCode[1];
        } else {
            uTmpCode = (BYTE)szCode[0];
        }

        if (uCode == 0x003F) {
        } else if (uTmpCode == 0x003F) {
            // no cooresponding BIG5 code
            return (0);
        } else {
        }

        uCode = uTmpCode;
    }
#endif

    uTmpCode = uCode;

    for (i = lpImeL->nMaxKey - 1; i >= 0; i--) {
        UINT uCompChar;

        uCompChar = lpImeL->wSeq2CompTbl[(uTmpCode & 0xF) + 1];

        *(LPWSTR)((LPBYTE)lpCandList + lpCandList->dwOffset[
            lpCandList->dwCount] + sizeof(WCHAR) * i) = (WCHAR)uCompChar;

        uTmpCode >>= 4;
    }

    // null terminator
    *(LPTSTR)((LPBYTE)lpCandList + lpCandList->dwOffset[
        lpCandList->dwCount] + sizeof(WCHAR) * lpImeL->nMaxKey) = '\0';

    // string count ++
    lpCandList->dwCount++;
#else
    if (lpInstL->fdwTblLoad == TBL_LOADED) {
        fNeedUnload = FALSE;
    } else if (lpInstL->fdwTblLoad == TBL_NOTLOADED) {
        LoadTable(lpInstL, lpImeL);
        fNeedUnload = TRUE;
    } else {
       return (0);
    }

#if defined(CHAJEI) || defined(PHON) || defined(QUICK)
    hTbl = OpenFileMapping(FILE_MAP_READ, FALSE, lpImeL->szTblFile[2]);
#else
    hTbl = OpenFileMapping(FILE_MAP_READ, FALSE, lpImeL->szTblFile[0]);
#endif
    if (!hTbl) {
        return (0);
    }

    lpTbl = MapViewOfFile(hTbl, FILE_MAP_READ, 0, 0, 0);
    if (!lpTbl) {
        dwSize = 0;
        goto RevConvCloseCodeTbl;
    }

#if defined(CHAJEI) || defined(PHON) || defined(QUICK)
    hTbl0 = OpenFileMapping(FILE_MAP_READ, FALSE, lpImeL->szTblFile[0]);
    if (!hTbl0) {
        dwSize = 0;
        goto RevConvUnmapCodeTbl;
    }

    lpTbl0 = MapViewOfFile(hTbl0, FILE_MAP_READ, 0, 0, 0);
    if (!lpTbl0) {
        dwSize = 0;
        goto RevConvCloseTbl0;
    }
#endif

#if defined(CHAJEI) || defined(PHON)
    hTbl1 = OpenFileMapping(FILE_MAP_READ, FALSE, lpImeL->szTblFile[1]);
    if (!hTbl1) {
        dwSize = 0;
        goto RevConvUnmapTbl0;
    }

    lpTbl1 = MapViewOfFile(hTbl1, FILE_MAP_READ, 0, 0, 0);
    if (!lpTbl1) {
        dwSize = 0;
        goto RevConvCloseTbl1;
    }
#endif

#if defined(CHAJEI) || defined(PHON) || defined(QUICK)
    lpStart = lpTbl;
    lpEnd = lpTbl + lpImeL->uTblSize[2];
#else
    lpStart = lpTbl + lpImeL->nSeqBytes;
    lpEnd = lpTbl + lpImeL->uTblSize[0];
#endif

#if defined(CHAJEI) || defined(PHON) || defined(QUICK)
    for (; lpStart < lpEnd; lpStart += sizeof(WORD)) {
#else
    for (; lpStart < lpEnd; lpStart += lpImeL->nSeqBytes + sizeof(WORD)) {
#endif
        DWORD dwPattern;
        int   i;
#if defined(CHAJEI) || defined(PHON) || defined(QUICK)
        UINT  uOffset;
#endif
#if defined(CHAJEI) || defined(QUICK)
        DWORD dwSeqA1, dwSeqA5;
#endif
#if defined(PHON)
        UINT  uPhoneticCode;
#endif

#if defined(PHON)
        uPhoneticCode = *(LPWORD)lpStart;

#ifdef UNICODE
        if (IsValidCode(uPhoneticCode)) {
            if (uPhoneticCode != uCode) {
                continue;
            }
        } else {
            if (InverseEncode(uPhoneticCode) != uCode) {
                continue;
            }
        }
#else
        if ((uPhoneticCode | 0x8000) != uCode) {
            continue;
        }
#endif
#else
        if (*(LPUNAWORD)lpStart != uCode) {
            continue;
        }
#endif

        // find the code
#if defined(CHAJEI) || defined(QUICK)
        uOffset = SearchOffset(lpTbl0, lpImeL->uTblSize[0],
            (lpStart - lpTbl) / sizeof(WORD));
        dwSeqA1 = uOffset / 27;         // seq code of A1
        dwSeqA5 = uOffset % 27;         // seq code of A5
#endif
#if defined(PHON)
        uOffset = SearchOffset(lpTbl1, lpImeL->uTblSize[1],
            (lpStart - lpTbl) / sizeof(WORD));
#endif

#if defined(CHAJEI)
        // pattern of A234
        dwPattern = *(LPWORD)(lpTbl1 + (lpStart - lpTbl)) << lpImeL->nSeqBits;
        // sequence code of A1
        dwPattern |= dwSeqA1 << (lpImeL->nSeqBits * 4);
        // test if 0 for A234 pattern
        dwSeqA1 = lpImeL->dwSeqMask << (lpImeL->nSeqBits * 3);

        // sequence code of A5
        for (i = 1; i < lpImeL->nMaxKey; i++) {
            if (!(dwPattern & dwSeqA1)) {
                dwPattern |= dwSeqA5 <<
                    (lpImeL->nSeqBits * (lpImeL->nMaxKey - i - 1));
                break;
            } else {
                dwSeqA1 >>= lpImeL->nSeqBits;
            }
        }
#elif defined(PHON)
        dwPattern = *(LPUNADWORD)(lpTbl0 + uOffset * lpImeL->nSeqBytes) &
            lpImeL->dwPatternMask;
#elif defined(QUICK)
        dwPattern = (DWORD)dwSeqA1 << lpImeL->nSeqBits;
        dwPattern |= dwSeqA5;
#else
        dwPattern = *(LPUNADWORD)(lpStart - lpImeL->nSeqBytes) &
            lpImeL->dwPatternMask;
#endif

#if defined(QUICK)
        for (i = 2 - 1; i >= 0; i--) {
#else
        for (i = lpImeL->nMaxKey - 1; i >= 0; i--) {
#endif
            WORD wCompChar;

            wCompChar = lpImeL->wSeq2CompTbl[dwPattern & lpImeL->dwSeqMask];

            *(LPUNAWORD)((LPBYTE)lpCandList + lpCandList->dwOffset[
                lpCandList->dwCount] + sizeof(WORD) * i) = wCompChar;

            dwPattern >>= lpImeL->nSeqBits;
        }

        // null terminator
#if defined(QUICK)
        *(LPTSTR)((LPBYTE)lpCandList + lpCandList->dwOffset[
            lpCandList->dwCount] + sizeof(WCHAR) * 2) = '\0';
#else
        *(LPTSTR)((LPBYTE)lpCandList + lpCandList->dwOffset[
            lpCandList->dwCount] + sizeof(WCHAR) * lpImeL->nMaxKey) = '\0';
#endif

#if defined(PHON)
        if (!lpCandList->dwCount) {
#ifdef UNICODE
        } else if (IsValidCode(uPhoneticCode)) {
#else
        } else if (uPhoneticCode & 0x8000) {
#endif
            PHONREVERSEBUF phTmpBuf;

            phTmpBuf = *(LPPHONREVERSEBUF)((LPBYTE)
                lpCandList + lpCandList->dwOffset[lpCandList->dwCount]);

            *(LPPHONREVERSEBUF)((LPBYTE)lpCandList +
                lpCandList->dwOffset[lpCandList->dwCount]) =
                *(LPPHONREVERSEBUF)((LPBYTE)lpCandList +
                lpCandList->dwOffset[0]);

            *(LPPHONREVERSEBUF)((LPBYTE)lpCandList +
                lpCandList->dwOffset[0]) = phTmpBuf;
        } else {
        }
#endif

        // string count ++
        lpCandList->dwCount++;

#if defined(CHAJEI) || defined(WINAR30) || defined(DAYI)
        // do not need to search more canidate
        break;
#endif

        if (lpCandList->dwCount >= (DWORD)uMaxCand) {
            break;
        }

#if defined(QUICK)
        lpCandList->dwOffset[lpCandList->dwCount] =
            lpCandList->dwOffset[lpCandList->dwCount - 1] +
            sizeof(WCHAR) * 2 + sizeof(TCHAR);
#else
        lpCandList->dwOffset[lpCandList->dwCount] =
            lpCandList->dwOffset[lpCandList->dwCount - 1] +
            sizeof(WCHAR) * lpImeL->nMaxKey + sizeof(TCHAR);
#endif
    }

    if (!lpCandList->dwCount) {
        dwSize = 0;
    }

#if defined(CHAJEI) || defined(PHON)
    UnmapViewOfFile(lpTbl1);

RevConvCloseTbl1:
    CloseHandle(hTbl1);

RevConvUnmapTbl0:
#endif

#if defined(CHAJEI) || defined(PHON) || defined(QUICK)
    UnmapViewOfFile(lpTbl0);

RevConvCloseTbl0:
    CloseHandle(hTbl0);

RevConvUnmapCodeTbl:
#endif

    UnmapViewOfFile(lpTbl);

RevConvCloseCodeTbl:
    CloseHandle(hTbl);

    if (fNeedUnload) {
        FreeTable(lpInstL);
    }
#endif

    return (dwSize);
}
#endif

/**********************************************************************/
/* ImeConversionList() / UniImeConversionList()                       */
/**********************************************************************/
#if defined(UNIIME)
DWORD WINAPI UniImeConversionList(
    LPINSTDATAL     lpInstL,
    LPIMEL          lpImeL,
#else
DWORD WINAPI ImeConversionList(
#endif
    HIMC            hIMC,
    LPCTSTR         lpszSrc,
    LPCANDIDATELIST lpCandList,
    DWORD           dwBufLen,
    UINT            uFlag)
{
#if defined(ROMANIME)
    return (0);
#else
    UINT uCode;

    if (!dwBufLen) {
    } else if (!lpszSrc) {
        return (0);
    } else if (!*lpszSrc) {
        return (0);
    } else if (!lpCandList) {
        return (0);
    } else if (dwBufLen <= sizeof(CANDIDATELIST)) {
        // buffer size can not even put the header information
        return (0);
    } else {
    }

    switch (uFlag) {
    case GCL_CONVERSION:
        return Conversion(
#if defined(UNIIME)
            lpInstL, lpImeL,
#endif
            lpszSrc, lpCandList, dwBufLen);
        break;
    case GCL_REVERSECONVERSION:
        if (!dwBufLen) {
            return ReverseConversion(
#if defined(UNIIME)
                lpInstL, lpImeL,
#endif
                0, lpCandList, dwBufLen);
        }

        // only support one DBCS char reverse conversion
        if (*(LPTSTR)((LPBYTE)lpszSrc + sizeof(WORD)) != '\0') {
            return (0);
        }

        uCode = *(LPUNAWORD)lpszSrc;

#ifndef UNICODE
        // swap lead byte & second byte, UNICODE don't need it
        uCode = HIBYTE(uCode) | (LOBYTE(uCode) << 8);
#endif

        return ReverseConversion(
#if defined(UNIIME)
            lpInstL, lpImeL,
#endif
            uCode, lpCandList, dwBufLen);
        break;
    case GCL_REVERSE_LENGTH:
        return sizeof(WCHAR);
        break;
    default:
        return (0);
        break;
    }
#endif
}
