#include <windows.h>
#include <winerror.h>
#include <imm.h>
#include "imeattr.h"
#include "imedefs.h"
#include "imerc.h"

#if !defined(ROMANIME)
#if !defined(WINIME) && !defined(UNICDIME)
/**********************************************************************/
/* MemoryLess()                                                       */
/**********************************************************************/
void PASCAL MemoryLess(
#if defined(UNIIME)
    LPINSTDATAL lpInstL,
    LPIMEL      lpImeL,
#endif
    DWORD       fdwErrMsg)
{
    TCHAR szErrMsg[64];

    if (lpImeL->fdwErrMsg & fdwErrMsg) {
        // message already prompted
        return;
    }

    LoadString(hInst, IDS_MEM_LESS_ERR, szErrMsg, sizeof(szErrMsg));

    lpImeL->fdwErrMsg |= fdwErrMsg;
    MessageBeep((UINT)-1);
    MessageBox((HWND)NULL, szErrMsg, lpImeL->szIMEName,
        MB_OK|MB_ICONHAND|MB_TASKMODAL|MB_TOPMOST);

    return;
}

/**********************************************************************/
/* ReadUsrDicToMem()                                                  */
/* Return Value:                                                      */
/*      TRUE - successful, FALSE - failure                            */
/**********************************************************************/
BOOL PASCAL ReadUsrDicToMem(
#if defined(UNIIME)
    LPINSTDATAL lpInstL,
    LPIMEL      lpImeL,
#endif
    HANDLE      hUsrDicFile,
    DWORD       dwUsrDicSize,
    UINT        uUsrDicSize,
    UINT        uRecLen,
    UINT        uReadLen,
    UINT        uWriteLen)
{
    LPBYTE lpUsrDicMem, lpMem, lpMemLimit;
    DWORD  dwPos, dwReadByte;

    if (dwUsrDicSize < 258) {   // no char in this dictionary
        return (TRUE);
    }

    lpUsrDicMem = MapViewOfFile(lpInstL->hUsrDicMem, FILE_MAP_WRITE, 0, 0,
        uUsrDicSize + 20);

    if (!lpUsrDicMem) {
        CloseHandle(lpInstL->hUsrDicMem);
        MemoryLess(
#if defined(UNIIME)
            lpInstL, lpImeL,
#endif
            ERRMSG_MEM_USRDIC);
        lpInstL->hUsrDicMem = NULL;
        return (FALSE);
    }

    lpMemLimit = lpUsrDicMem + uUsrDicSize;

    // read in data, skip header - two headers are similiar
    dwPos = SetFilePointer(hUsrDicFile, 258, (LPLONG)NULL, FILE_BEGIN);

    for (lpMem = lpUsrDicMem; dwPos < dwUsrDicSize; lpMem += uWriteLen) {
        short i;
        DWORD dwPattern;

        if (lpMem >= lpMemLimit) {
            break;
        }

        ReadFile(hUsrDicFile, lpMem, uReadLen, &dwReadByte,
            (LPOVERLAPPED)NULL);
        // Compress the sequence code and put the first char most significant.
        // Limitation - 32 bits only

        dwPattern = 0;

        for (i = 0; i < lpImeL->nMaxKey; i++) {
            dwPattern <<= lpImeL->nSeqBits;
            dwPattern |= *(lpMem + 2 + i);
        }

        *(LPUNADWORD)(lpMem + 2) = dwPattern;

        // go to next record
        dwPos = SetFilePointer(hUsrDicFile, dwPos + uRecLen, (LPLONG)NULL,
            FILE_BEGIN);
    }

    UnmapViewOfFile(lpUsrDicMem);

    return (TRUE);
}

/**********************************************************************/
/* LoadUsrDicFile()                                                   */
/* Description:                                                       */
/*      try to convert to sequence code format, compression and       */
/*      don't use two way to search                                   */
/**********************************************************************/
void PASCAL LoadUsrDicFile(             // load user dic file into memory
    LPINSTDATAL lpInstL,
    LPIMEL      lpImeL)
{
    HANDLE hReadUsrDicMem;
    HANDLE hUsrDicFile;
    DWORD  dwUsrDicFileSize;
    UINT   uRecLen, uReadLen, uWriteLen;
    UINT   uUsrDicSize;
    BOOL   fRet;

    // no user dictionary
    if (!lpImeL->szUsrDicMap[0]) {
        lpImeL->uUsrDicSize = 0;
        CloseHandle(lpInstL->hUsrDicMem);
        lpInstL->hUsrDicMem = NULL;
        lpImeL->fdwErrMsg &= ~(ERRMSG_LOAD_USRDIC | ERRMSG_MEM_USRDIC);
        return;
    }

    if (lpInstL->hUsrDicMem) {
        // the memory is already here
        goto LoadUsrDicErrMsg;
    }

    hReadUsrDicMem = OpenFileMapping(FILE_MAP_READ, FALSE,
        lpImeL->szUsrDicMap);

    if (hReadUsrDicMem) {
        // another process already create a mapping file, we will use it
        goto LoadUsrDicMem;
    }

    // read the user dic file into memory
    hUsrDicFile = CreateFile(lpImeL->szUsrDic, GENERIC_READ,
        FILE_SHARE_READ|FILE_SHARE_WRITE,
        (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);

    if (hUsrDicFile != INVALID_HANDLE_VALUE) {  // OK
        goto OpenUsrDicFile;
    }

    // if the work station version, SHARE_WRITE may fail
    hUsrDicFile = CreateFile(lpImeL->szUsrDic, GENERIC_READ,
        FILE_SHARE_READ,
        (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);

OpenUsrDicFile:
    if (hUsrDicFile != INVALID_HANDLE_VALUE) {  // OK
        lpImeL->fdwErrMsg &= ~(ERRMSG_LOAD_USRDIC);
    } else if (lpImeL->fdwErrMsg & ERRMSG_LOAD_USRDIC) {
        // already prompt error message before, no more
        return;
    } else {
        TCHAR szFmtStr[64];
        TCHAR szErrMsg[2 * MAX_PATH];

        // temp use szIMEName as format string buffer of error message
        LoadString(hInst, IDS_FILE_OPEN_ERR, szFmtStr, sizeof(szFmtStr));
        wsprintf(szErrMsg, szFmtStr, lpImeL->szUsrDic);

        lpImeL->fdwErrMsg |= ERRMSG_LOAD_USRDIC;
        MessageBeep((UINT)-1);
        MessageBox((HWND)NULL, szErrMsg, lpImeL->szIMEName,
            MB_OK|MB_ICONHAND|MB_TASKMODAL|MB_TOPMOST);
        return;
    }

    // one record length - only sequence code, now
    uRecLen = lpImeL->nMaxKey + 4;
    // read sequence code and internal code
    uReadLen = lpImeL->nMaxKey + 2;
    // length write into memory handle
    uWriteLen = lpImeL->nSeqBytes + 2;

    // get the length of the file
    dwUsrDicFileSize = GetFileSize(hUsrDicFile, (LPDWORD)NULL);
    uUsrDicSize = (UINT)(dwUsrDicFileSize - 256) / uRecLen * uWriteLen;

    // max EUDC chars
    lpInstL->hUsrDicMem = CreateFileMapping((HANDLE)0xFFFFFFFF,
        NULL, PAGE_READWRITE, 0, MAX_EUDC_CHARS * uWriteLen + 20,
        lpImeL->szUsrDicMap);

    if (!lpInstL->hUsrDicMem) {
        MemoryLess(
#if defined(UNIIME)
            lpInstL, lpImeL,
#endif
            ERRMSG_MEM_USRDIC);
        fRet = FALSE;
    } else if (GetLastError() == ERROR_ALREADY_EXISTS) {
        // another process also create another one, we will use it
        hReadUsrDicMem = OpenFileMapping(FILE_MAP_READ, FALSE,
            lpImeL->szUsrDicMap);
        CloseHandle(lpInstL->hUsrDicMem);
        CloseHandle(hUsrDicFile);

        if (hReadUsrDicMem != INVALID_HANDLE_VALUE) {  // OK
            lpInstL->hUsrDicMem = hReadUsrDicMem;
            lpImeL->uUsrDicSize = uUsrDicSize;
            lpImeL->fdwErrMsg &= ~(ERRMSG_MEM_USRDIC);
        } else {
            MemoryLess(
#if defined(UNIIME)
                lpInstL, lpImeL,
#endif
                ERRMSG_MEM_USRDIC);
            lpInstL->hUsrDicMem = NULL;
        }

        return;
    } else {
        fRet = ReadUsrDicToMem(
#if defined(UNIIME)
            lpInstL, lpImeL,
#endif
            hUsrDicFile, dwUsrDicFileSize, uUsrDicSize, uRecLen,
            uReadLen, uWriteLen);
    }

    CloseHandle(hUsrDicFile);

    if (!fRet) {
        if (lpInstL->hUsrDicMem) {
            CloseHandle(lpInstL->hUsrDicMem);
            lpInstL->hUsrDicMem = NULL;
        }

        return;
    }

    // open a read only memory for EUDC table
    hReadUsrDicMem = OpenFileMapping(FILE_MAP_READ, FALSE,
        lpImeL->szUsrDicMap);

    // reopen a read file and close the original write file
    CloseHandle(lpInstL->hUsrDicMem);

    lpImeL->uUsrDicSize = uUsrDicSize;

LoadUsrDicMem:
    lpInstL->hUsrDicMem = hReadUsrDicMem;
LoadUsrDicErrMsg:
    lpImeL->fdwErrMsg &= ~(ERRMSG_LOAD_USRDIC | ERRMSG_MEM_USRDIC);

    return;
}

/**********************************************************************/
/* LoadOneTable()                                                     */
/* Description:                                                       */
/*      memory handle & size of .TBL file will be assigned to         */
/*      lpImeL                                                        */
/* Eeturn Value:                                                      */
/*      length of directory of the .TBL file                          */
/**********************************************************************/
UINT PASCAL LoadOneTable(       // load one of table file
#if defined(UNIIME)
    LPINSTDATAL lpInstL,
    LPIMEL      lpImeL,
#endif
    LPTSTR      szTable,        // file name of .TBL
    UINT        uIndex,         // the index of array to store memory handle
    UINT        uLen,           // length of the directory
    LPTSTR      szPath)         // buffer for directory
{
    HANDLE  hTblFile;
    HGLOBAL hMap;
    DWORD   dwFileSize;

    if (lpInstL->hMapTbl[uIndex]) {    // already loaded
        CloseHandle(lpInstL->hMapTbl[uIndex]);
        lpInstL->hMapTbl[uIndex] = (HANDLE)NULL;
    }

    if (uLen) {
        lstrcpy((LPTSTR)&szPath[uLen], szTable);
        hTblFile = CreateFile(szPath, GENERIC_READ,
            FILE_SHARE_READ|FILE_SHARE_WRITE,
            (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);

        if (hTblFile != INVALID_HANDLE_VALUE) {
            goto OpenDicFile;
        }

        // if the work station version, SHARE_WRITE will fail
        hTblFile = CreateFile(szPath, GENERIC_READ,
            FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
    } else {
        // try system directory next
        uLen = GetSystemDirectory(szPath, MAX_PATH);
        if (szPath[uLen - 1] != '\\') {  // consider N:\ ;
            szPath[uLen++] = '\\';
        }

        lstrcpy((LPTSTR)&szPath[uLen], szTable);
        hTblFile = CreateFile(szPath, GENERIC_READ,
            FILE_SHARE_READ|FILE_SHARE_WRITE,
            (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);

        if (hTblFile != INVALID_HANDLE_VALUE) {
            goto OpenDicFile;
        }

        // if the work station version, SHARE_WRITE will fail
        hTblFile = CreateFile(szPath, GENERIC_READ,
            FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
    }

OpenDicFile:
    // can not find the table file
    if (hTblFile != INVALID_HANDLE_VALUE) {     // OK
    } else if (lpImeL->fdwErrMsg & (ERRMSG_LOAD_0 << uIndex)) {
        // already prompt error message before, no more
        return (0);
    } else {                    // prompt error message
        TCHAR szFmtStr[64];
        TCHAR szErrMsg[2 * MAX_PATH];

        // temp use szIMEName as format string buffer of error message
        LoadString(hInst, IDS_FILE_OPEN_ERR, szFmtStr, sizeof(szFmtStr));
        wsprintf(szErrMsg, szFmtStr, szTable);

        lpImeL->fdwErrMsg |= ERRMSG_LOAD_0 << uIndex;
        MessageBeep((UINT)-1);
        MessageBox((HWND)NULL, szErrMsg, lpImeL->szIMEName,
            MB_OK|MB_ICONHAND|MB_TASKMODAL|MB_TOPMOST);
        return (0);
    }

    lpImeL->fdwErrMsg &= ~(ERRMSG_LOAD_0 << uIndex);

    // create file mapping for IME tables
    hMap = CreateFileMapping((HANDLE)hTblFile, NULL, PAGE_READONLY,
        0, 0, szTable);

    dwFileSize = GetFileSize(hTblFile, (LPDWORD)NULL);

    CloseHandle(hTblFile);

    if (!hMap) {
        MemoryLess(
#if defined(UNIIME)
            lpInstL, lpImeL,
#endif
            ERRMSG_MEM_0 << uIndex);
        return (0);
    }

    lpImeL->fdwErrMsg &= ~(ERRMSG_MEM_0 << uIndex);

    lpInstL->hMapTbl[uIndex] = hMap;

    // get file length
    lpImeL->uTblSize[uIndex] = dwFileSize;
    return (uLen);
}
#endif

/**********************************************************************/
/* LoadTable()                                                        */
/* Return Value:                                                      */
/*      TRUE - successful, FALSE - failure                            */
/**********************************************************************/
BOOL PASCAL LoadTable(          // check the table files of IME, include user
                                // defined dictionary
    LPINSTDATAL lpInstL,
    LPIMEL      lpImeL)
{
#if !defined(WINIME) && !defined(UNICDIME)
    int   i;
    UINT  uLen;
    TCHAR szBuf[MAX_PATH];
#endif

#ifdef DEBUG
//  _asm int 3;                 // debug usage
#endif

    if (lpInstL->fdwTblLoad == TBL_LOADED) {
        return (TRUE);
    }

#if !defined(WINIME) && !defined(UNICDIME)
    uLen = 0;

    // A15.TBL, A234.TBL, ACODE.TBL, / PHON.TBL, PHONPTR.TBL, PHONCODE.TBL,

    for (i = 0; i < MAX_IME_TABLES; i++) {
        if (!*lpImeL->szTblFile[i]) {
        } else if (uLen = LoadOneTable(
#if defined(UNIIME)
            lpInstL, lpImeL,
#endif
            lpImeL->szTblFile[i], i, uLen, szBuf)) {
        } else {
            int j;

            for (j = 0; j < i; j++) {
                if (lpInstL->hMapTbl[j]) {
                    CloseHandle(lpInstL->hMapTbl[j]);
                    lpInstL->hMapTbl[j] = (HANDLE)NULL;
                }
            }

            lpInstL->fdwTblLoad = TBL_LOADERR;
            return (FALSE);
        }
    }
#endif

    lpInstL->fdwTblLoad = TBL_LOADED;

#ifdef DEBUG
//  _asm int 3;                 // debug usage
#endif

#if !defined(WINIME) && !defined(UNICDIME)
    if (lpImeL->szUsrDic[0]) {
        LoadUsrDicFile(lpInstL, lpImeL);
    }
#endif

    return (TRUE);
}

/**********************************************************************/
/* FreeTable()                                                        */
/**********************************************************************/
void PASCAL FreeTable(
    LPINSTDATAL lpInstL)
{
#if !defined(WINIME) && !defined(UNICDIME)
    int i;

    // A15.TBL, A234.TBL, ACODE.TBL, / PHON.TBL, PHONPTR.TBL, PHONCODE.TBL,

    for (i = 0; i < MAX_IME_TABLES; i++) {
        if (lpInstL->hMapTbl[i]) {
            CloseHandle(lpInstL->hMapTbl[i]);
            lpInstL->hMapTbl[i] = (HANDLE)NULL;
        }
    }

    // do not need to free phrase data base, maybe next IME will use it
    // uniime.dll will free it on library detach time

    if (lpInstL->hUsrDicMem) {
        CloseHandle(lpInstL->hUsrDicMem);
        lpInstL->hUsrDicMem = (HANDLE)NULL;
    }
#endif

    lpInstL->fdwTblLoad = TBL_NOTLOADED;

    return;
}
#endif // !defined(ROMANIME)
