/***************************************************************************

  M.A.M.E.32  -  Multiple Arcade Machine Emulator for Win32
  Win32 Portions Copyright (C) 1997-2001 Michael Soderstrom and Chris Kirmse

  This file is part of MAME32, and may only be used, modified and
  distributed under the terms of the MAME license, in "readme.txt".
  By continuing to use, modify or distribute this file you indicate
  that you have read the license and understand and accept it fully.

 ***************************************************************************/

 /***************************************************************************

  KailleraChat.c

  Fullscreen chat for Kaillera.

  Chat Display Functions Created by japmame.

  Japanese IMM Functions Created 15/5/2001  by NJ.

***************************************************************************/

#include <driver.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include "win32.h"
#include "resource.h"
#include "usrintrf.h"
#include "kailleraclient.h"
#include "imm.h"
#include "misc.h"

/***************************************************************************
    Macros and definitions
 ***************************************************************************/

#define FORCE_CLOSE_COMPOSITION_WINDOW 0

#define KC_BUFFER_MAX    128
#define KC_SCROLL_SPD    100
#define KC_BLINK_SPD     20

#define KC_CHATLINE_MAX  8
#define KC_EDITLINE      1
#define KC_CONVSTR       1
#define KC_CURSOR        1
#define KC_IME           1
#define KC_TERMINATE     1

#define DT_MAX           (KC_CHATLINE_MAX + KC_EDITLINE + KC_CONVSTR + KC_CURSOR + KC_IME + KC_TERMINATE)

#define IME_CLOSE         0
#define IME_OPEN          1
#define IME_CONVERSION    2

#define ISSJIS(c) (((c) >= 0x81 && (c) <= 0x9f) || ((c) >= 0xe0 && (c) <= 0xfc))

typedef BOOL (WINAPI *ime_proc)(HWND hWnd, BOOL bflag);

/***************************************************************************
    Function prototypes
 ***************************************************************************/

static LRESULT CALLBACK EditProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
static void             KailleraChatCloseChat(void);
static void             KailleraChatImeGetConversionStatus(void);
static BOOL             MSIME2K_GetReg(void);
static void             GetWinVersion(BOOL *bIsNT, int *nMajor, int *nMinor, int *nBuildNumber);

//extern void             switch_ui_orientation(void);
//extern void             switch_true_orientation(void);
extern void             displaychatlog(struct mame_bitmap *bitmap, char *text);

/***************************************************************************
    External variables
 ***************************************************************************/

int nChatDrawMode;

/***************************************************************************
    Internal variables
 ***************************************************************************/

static BOOL            bChatActive;
static BOOL            bUseIME;
static BOOL            bInputEnable;
static BOOL            bShowLog;
static int             nIMEState;
static int             nPrevIMEState;
static int             nScroll;
static int             nIndex;

static int             nEditMaxLen;
static int             nAdjustHeight;
static int             nCursorPos;
static int             nBlinkCounter;
static BOOL            bShowCursor;

static HANDLE          hUser32DLL;

static char            szRecvBuf[KC_BUFFER_MAX * 4];
static char            szChatText[KC_CHATLINE_MAX][KC_BUFFER_MAX];
static char            szChatLog[65536];

static char            szInputBuf[KC_BUFFER_MAX];
static char            szConvStrBuf[KC_BUFFER_MAX];
static char            szConvAttrBuf[KC_BUFFER_MAX];
static int             pConvClauseBuf[KC_BUFFER_MAX];

static BOOL            bCloseCompositionWindow;
static int             nIMEType;
static char            szIMEName[64];
static char            szIMEStateStr[16];

static ime_proc        _WINNLSEnableIME;
static WNDPROC         DefaultWindowProc;


enum {
   MSIME_2000 = 0,
   DEFAULT_IME,
   MAX_IME_TYPE
};

static int Attribute[MAX_IME_TYPE][5] = {
    /* MS-IME 2000 */
    {
        UI_UNDERLINE_DOT,
        UI_UNDERLINE_BOLD,
        UI_UNDERLINE,
        UI_UNDERLINE_INVERSE,
        UI_UNDERLINE,
    },
    /* default */
    {
        UI_UNDERLINE_DOT,
        UI_UNDERLINE_INVERSE,
        UI_UNDERLINE,
        UI_UNDERLINE_INVERSE,
        UI_UNDERLINE_DOT,
    }
};


/***************************************************************************
    External functions
 ***************************************************************************/

void KailleraChatInit(void)
{
    int  i;
    HIMC hImc;
    HKL  hKeyboardLayout;
    UINT error_mode;


    bChatActive   = FALSE;
    bInputEnable  = FALSE;
    nPrevIMEState = IME_CLOSE;
    nIMEState     = IME_CLOSE;
    nIndex        = 0;
    nScroll       = 0;
    nChatDrawMode = GetChatDrawMode();
    bUseIME       = GetUseImeInChat();
    bShowLog      = FALSE;

    for (i = 0; i < KC_CHATLINE_MAX; i++)
        ZeroMemory(szChatText[i], KC_BUFFER_MAX);

    ZeroMemory(szRecvBuf, KC_BUFFER_MAX * 4);
    ZeroMemory(szInputBuf, KC_BUFFER_MAX);
    ZeroMemory(szConvStrBuf, KC_BUFFER_MAX);
    ZeroMemory(szChatLog, 65536);
#ifdef JAPANESE
    strcat(szChatLog, "\t- チャットログ -\n");
#else
    strcat(szChatLog, "\t- CHAT LOG -\n");
#endif

    hImc = ImmGetContext(MAME32App.m_hWnd);

    if (!ImmGetOpenStatus(hImc))
        ImmSetOpenStatus(hImc, TRUE);

    hKeyboardLayout = GetKeyboardLayout(0);

    if (ImmIsIME(hKeyboardLayout))
    {
        BOOL bIsNT;
        int  nMajor, nMinor, nBuildNumber;

        ImmGetDescription(hKeyboardLayout, szIMEName, 64);

        GetWinVersion(&bIsNT, &nMajor, &nMinor, &nBuildNumber);

#if 0
        {
            FILE *fp;

            fp = fopen("imename.txt", "w");
            if (fp != NULL)
            {
                fprintf(fp, "%s\nOS:Windows%s %d.%02d.%04d\n", szIMEName, bIsNT ? "NT" : "9x", nMajor, nMinor, nBuildNumber);
                fclose(fp);
            }

            /*
              名称確認済み日本語IME

              MSIME95 4.0
              MS-IME97 日本語入力ｼｽﾃﾑ
              Microsoft IME 98 日本語入力システム
              Microsoft IME 2000 (Japanese)
              ATOK12
              ATOK14
              OAK for Windows 95 V4.1
              OAK V7.0
              VJE-Delta Ver.4.0
              日本語入力システム  Wnn98 R3.0
              Canna StandAlone 3.5 (Fix 1.4)
              Canna Client 3.5 (Fix 1.4)
              松 茸 ver4.1
            */
        }
#endif

        nIMEType = DEFAULT_IME;

#if FORCE_CLOSE_COMPOSITION_WINDOW
        bCloseCompositionWindow = TRUE;
#else
        if (bIsNT == TRUE && nMajor >= 5)
        {
            /* Windows 2000の場合 */
            if (!strcmp(szIMEName, "Microsoft IME 2000 (Japanese)"))
            {
                /*
                   MS-IME 2000の場合は、コメントウインドウが無効なら
                   画面に何も\x95\x5c示されない
                */

                nIMEType = MSIME_2000;
                if (MSIME2K_GetReg())
                    bCloseCompositionWindow = FALSE;
                else
                    bCloseCompositionWindow = TRUE;
            }
            else
            if (!strcmp(szIMEName, "MSIME95 4.0")
            ||  !strcmp(szIMEName, "MS-IME97 日本語入力ｼｽﾃﾑ")
            ||  !strcmp(szIMEName, "Microsoft IME 98 日本語入力システム"))
            {
                /* MS-IME 95/97/98の場合は、何もしなくてもOK */
                bCloseCompositionWindow = FALSE;
            }
            else
            {
                /*
                 その他の日本語IMEの場合は強制的に閉じる。
                 終了後にIMEの変換中の文字が見えなくなる。
                */
                bCloseCompositionWindow = TRUE;
            }
        }
        else
        {
            /*
              Win9xではIMEの種類にかかわらず変換中の文字が
              \x95\x5c示されてしまうため、強制的に閉じる。
              終了後にIMEの変換中の文字が見えなくなるが、
              KailleraClientのログを右クリック→すべて選択
              でなぜかIMEが復帰する(NMAME Xの掲示板より)。

              NT4は不明(動作保証外だからいいと思う)。
            */
            bCloseCompositionWindow = TRUE;
        }
#endif

        /*
          WINNLSEnableIMEは、欧米だと使用できないので
          (user32.dll自体に存在しない模様)、存在するか
          どうかチェック。
          本当はImmDisableIME()を使用したいところだけど
          Win95では使用できない。
        */
        error_mode = SetErrorMode(0);
        hUser32DLL = LoadLibrary("user32.dll");
        SetErrorMode(error_mode);

        if (hUser32DLL != NULL)
            _WINNLSEnableIME = (ime_proc)GetProcAddress(hUser32DLL, "WINNLSEnableIME");

        if (_WINNLSEnableIME == NULL)
        {
            FreeLibrary(hUser32DLL);
            hUser32DLL = NULL;
        }
    }
    else
    {
        bUseIME = FALSE;
        hUser32DLL = NULL;
        nIMEType = DEFAULT_IME;
        _WINNLSEnableIME = NULL;
        bCloseCompositionWindow = FALSE;
    }

    if (ImmGetOpenStatus(hImc))
        ImmSetOpenStatus(hImc, FALSE);

    ImmReleaseContext(MAME32App.m_hWnd, hImc);

    if (bUseIME)
    {
        KailleraChatImeGetConversionStatus();
        nEditMaxLen   = (Machine->uiwidth - (5 + strlen(szIMEStateStr)) * Machine->uifontwidth) / Machine->uifontwidth - 1;
        nAdjustHeight = 3; /* IME用に3ドット余分に確保 */
    }
    else
    {
        nEditMaxLen   = (Machine->uiwidth - 5 * Machine->uifontwidth) / Machine->uifontwidth - 1;
        nAdjustHeight = 0;
    }

    if (_WINNLSEnableIME)
        _WINNLSEnableIME(MAME32App.m_hWnd, FALSE);

    DefaultWindowProc = (WNDPROC)(LRESULT)GetWindowLong(MAME32App.m_hWnd, GWL_WNDPROC);
    SetWindowLong(MAME32App.m_hWnd, GWL_WNDPROC, (LONG)EditProc);
}


void KailleraChatExit(void)
{
    SetWindowLong(MAME32App.m_hWnd, GWL_WNDPROC, (LONG)DefaultWindowProc);

    if (_WINNLSEnableIME)
        _WINNLSEnableIME(MAME32App.m_hWnd, TRUE);

    if (hUser32DLL)
    {
        FreeLibrary(hUser32DLL);
        hUser32DLL = NULL;
    }
}

void KailleraChatUpdate(struct mame_bitmap *pBitmap)
{
    struct DisplayText dtx[DT_MAX];
    char szChatLine[KC_BUFFER_MAX + 6];
    char szCursor[4];
    int i, j;

    if (bCloseCompositionWindow)
    {
        if (bUseIME && bChatActive)
        {
            /* IMEのデフォルトのウインドウがあったら有無を言わさず閉じる */
            HWND hIME = ImmGetDefaultIMEWnd(MAME32App.m_hWnd);
            if (hIME != NULL)
                SendMessage(hIME, WM_CLOSE, 0, 0);
        }
    }

    /* keyboard input */
    if (!bChatActive)
    {
        if (input_ui_pressed(IPT_UI_CHAT_OPEN))
        {
            if (bUseIME)
            {
                if (_WINNLSEnableIME)
                    _WINNLSEnableIME(MAME32App.m_hWnd, TRUE);

                if (nPrevIMEState == IME_OPEN)
                {
                    HIMC hImc = ImmGetContext(MAME32App.m_hWnd);
                    ImmSetOpenStatus(hImc, TRUE);
                    ImmReleaseContext(MAME32App.m_hWnd, hImc);
                    nIMEState = IME_OPEN;
                }
                else
                    nIMEState = IME_CLOSE;

                KailleraChatImeGetConversionStatus();
            }

            ZeroMemory(szInputBuf, KC_BUFFER_MAX);
            ZeroMemory(szConvStrBuf, KC_BUFFER_MAX);

            nCursorPos   = 0;
            bInputEnable = TRUE;
            bChatActive  = TRUE;
        }
    }

    /* copy szRecvBuf to szChatText if line available */
    if (szRecvBuf[0])
    {
        strcpy(szChatText[nIndex], szRecvBuf);
        nIndex++;
        nIndex &= KC_CHATLINE_MAX - 1;
        szChatText[nIndex][0] = '\0';
        szRecvBuf[0] = '\0';
        nScroll = 0;
    }

    /* auto scroll out */
    nScroll++;
    if (nScroll > KC_SCROLL_SPD)
    {
        nScroll = 0;
        nIndex++;
        nIndex &= KC_CHATLINE_MAX - 1;
        szChatText[nIndex][0] = '\0';
    }

    /* display chat text */
    j = 0;
    for (i = 0; i < KC_CHATLINE_MAX; i++)
    {
        dtx[i].text  = szChatText[(nIndex + i) % KC_CHATLINE_MAX];
        dtx[i].color = UI_COLOR_TRANS;
        dtx[i].x     = 0;
        dtx[i].y     = j;
        if (szChatText[(nIndex + i) % KC_CHATLINE_MAX][0])
            j += Machine->uifontheight;
    }
    for (j = i; j < DT_MAX; j++)
        dtx[j].text = NULL; /* terminate array */

    /* display input buffer */
    if (bChatActive)
    {
        int x, y, width, height;
        int nCursorDispPos;
        int nCursorColor;

        switch_ui_orientation(pBitmap);
        x      = Machine->uixmin;
        y      = Machine->uiymin + (Machine->uiheight - Machine->uifontheight) - nAdjustHeight;
        width  = Machine->uiwidth;
        height = Machine->uifontheight + nAdjustHeight;
        plot_box(pBitmap, x, y, width, height, Machine->uifont->colortable[0]);
        switch_true_orientation(pBitmap);

        if (strlen(szInputBuf) > nEditMaxLen)
        {
            int nLimit;

            bInputEnable = FALSE;
            if (ISSJIS((BYTE)szInputBuf[nEditMaxLen - 1]))
                nLimit = nEditMaxLen - 1;
            else
                nLimit = nEditMaxLen;

            szInputBuf[nLimit] = '\0';
            if (nCursorPos > nLimit)
                nCursorPos = nLimit;
        }

        sprintf(szChatLine, "Chat:%s", szInputBuf);

        dtx[i].text  = szChatLine;
        dtx[i].color = UI_COLOR_NORMAL;
        dtx[i].x     = 0;
        dtx[i++].y   = (Machine->uiheight - Machine->uifontheight) - nAdjustHeight;

        /* cursor blink */
        nBlinkCounter++;
        if (nBlinkCounter > KC_BLINK_SPD)
        {
            nBlinkCounter = 0;
            if (bShowCursor)
                bShowCursor = FALSE;
            else
                bShowCursor = TRUE;
        }

        ZeroMemory(szCursor, 4);
        nCursorDispPos = 5 + nCursorPos;
        nCursorColor   = bShowCursor ? UI_COLOR_INVERSE : UI_COLOR_NORMAL;
        if (szInputBuf[nCursorPos])
        {
            char *p = &szInputBuf[nCursorPos];

            szCursor[0] = *p;
            if (ISSJIS(*(BYTE *)p))
                szCursor[1] = *(p + 1);
        }
        else
        {
            szCursor[0] = ' ';
        }

        if (bUseIME)
        {
            if (szIMEStateStr[0])
            {
                dtx[i].text  = szIMEStateStr;
                dtx[i].color = UI_COLOR_NORMAL;
                dtx[i].x     = Machine->uiwidth  - strlen(szIMEStateStr) * Machine->uifontwidth;
                dtx[i++].y   = Machine->uiheight - Machine->uifontheight - nAdjustHeight;
            }

            if (nIMEState & IME_CONVERSION)
            {
                if (szConvStrBuf[0] == '\0')
                {
                    nIMEState &= ~IME_CONVERSION;
                }
                else
                {
                    int nStartPos;
                    int nMaxLen;
                    int nStrLen;

                    nStartPos = nCursorDispPos;
                    nStrLen   = strlen(szConvStrBuf);
                    nMaxLen   = 5 + nEditMaxLen;

                    while (nStartPos + nStrLen > nMaxLen)
                        nStartPos--;

                    dtx[i].text  = szConvStrBuf;
                    dtx[i].color = UI_COLOR_ATTRIBUTE;
                    dtx[i].x     = nStartPos * Machine->uifontwidth;
                    dtx[i++].y   = Machine->uiheight - Machine->uifontheight - nAdjustHeight;

                    szCursor[0]    = '|';
                    szCursor[1]    = '\0';
                    nCursorDispPos = nStartPos + nStrLen;
                    nCursorColor   = UI_COLOR_NORMAL;
                }
            }
        }

        dtx[i].text  = szCursor;
        dtx[i].color = nCursorColor;
        dtx[i].x     = nCursorDispPos * Machine->uifontwidth;
        dtx[i++].y   = (Machine->uiheight - Machine->uifontheight) - nAdjustHeight;
    }
    else if (input_ui_pressed(IPT_UI_CHAT_CHANGE_MODE))
    {
        nChatDrawMode++;
        if (nChatDrawMode == 3)
            nChatDrawMode = 0;

        SetChatDrawMode(nChatDrawMode);
    }
    else if (input_ui_pressed(IPT_UI_CHAT_SHOW_LOG))
    {
        bShowLog = !bShowLog;
        displaychatlog(pBitmap, szChatLog);
    }

    dtx[i].text = NULL; /* terminate array */

    /* display */
    displaytext(pBitmap, dtx);

    if (bShowLog)
        displaychatlog(pBitmap, NULL);
}


void KailleraChateReceive(char *szText)
{
    if (szRecvBuf[0])
        strcat(szRecvBuf,"\n");
    strcat(szRecvBuf, szText);
    strcat(szChatLog, szText);
    strcat(szChatLog,"\n");
}


int KailleraChatIsActive(void)
{
    return bChatActive;
}


unsigned char *KailleraChatGetStrAttr(void)
{
    return (unsigned char *)szConvAttrBuf;
}


int *KailleraChatGetStrClause(void)
{
    return pConvClauseBuf;
}


/***************************************************************************
    Internal functions
 ***************************************************************************/

static LRESULT CALLBACK EditProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    /* IMEのデフォルトのUIを無効にする */
    /* ・・・はずなんだけど、完全に無効になるわけではない模様 */
    /* とりあえず、処理するようにしておく                     */
    if (Msg == WM_IME_SETCONTEXT)
    {
        lParam &= ~ISC_SHOWUIALL;
        return CallWindowProc(DefaultWindowProc, hWnd, Msg, wParam, lParam);
    }

    /* チャットアクティブ時のみ、MAME32のウインドウをフック */
    if (bChatActive)
    {
        HIMC  hImc;

        switch (Msg)
        {
        case WM_CHAR:
            switch (wParam)
            {
            case VK_RETURN:
            case VK_ESCAPE:
                return TRUE; /* disable beep */

            default:
                if (bChatActive && bInputEnable)
                {
                    int i, nLen;
                    char *p = &szInputBuf[nCursorPos];

                    nLen = strlen(szInputBuf);

                    if ((wParam & 0xff) > 0x1f)
                    {
                        if (strlen(szInputBuf) < KC_BUFFER_MAX - 1)
                        {
                            for (i = nLen; i >= nCursorPos; i--)
                                szInputBuf[i + 1] = szInputBuf[i];

                            *p = (unsigned char)wParam;
                            nCursorPos++;
                        }
                    }
                }
            }
            break;

        case WM_KEYDOWN:
            if (!(nIMEState & IME_CONVERSION))
            {
                char *p = &szInputBuf[nCursorPos];

                switch (wParam)
                {
                case VK_RETURN:
                    if (szInputBuf[0] == '\0')
                        return TRUE;
                    kailleraChatSend(szInputBuf);
                    KailleraChatCloseChat();
                    return TRUE;

                case VK_ESCAPE:
                    KailleraChatCloseChat();
                    input_ui_pressed(IPT_UI_CANCEL); /* IPT_UI_CANCELの押下フラグをクリア */
                    return TRUE;

                case VK_BACK:
                    if (nCursorPos >= 2)
                    {
                        if (ISSJIS(*(BYTE *)(p - 2)))
                        {
                            while (*(p - 1))
                            {
                                *(p - 2) = *p;
                                p++;
                            }
                            nCursorPos -= 2;
                            bInputEnable = TRUE;
                            return TRUE;
                        }
                    }
                    if (nCursorPos != 0)
                    {
                        while (*(p - 1))
                        {
                            *(p - 1) = *p;
                            p++;
                        }
                        nCursorPos--;
                        bInputEnable = TRUE;
                        return TRUE;
                    }
                    break;

                case VK_LEFT:
                    if (nCursorPos >= 2)
                    {
                        if (ISSJIS(*(BYTE *)(p - 2)))
                        {
                            nCursorPos -= 2;
                            return TRUE;
                        }
                    }
                    if (nCursorPos != 0)
                    {
                        nCursorPos--;
                        return TRUE;
                    }
                    break;

                case VK_RIGHT:
                    {
                        int len = strlen(szInputBuf);

                        if (nCursorPos < len)
                        {
                            if (ISSJIS(*(BYTE *)p))
                                nCursorPos++;
                            nCursorPos++;
                            return TRUE;
                        }
                    }
                    break;

                case VK_DELETE:
                    if (ISSJIS(*(BYTE *)p))
                    {
                        while (*p)
                        {
                            *p = *(p + 2);
                            p++;
                        }
                        bInputEnable = TRUE;
                        return TRUE;
                    }
                    while (*p)
                    {
                        *p = *(p + 1);
                        p++;
                    }
                    bInputEnable = TRUE;
                    return TRUE;
                }
                break;
            }
            break;

        case WM_IME_STARTCOMPOSITION:
            /* コンポジションウィンドウが開かれた */
            if (!bCloseCompositionWindow)
            {
                COMPOSITIONFORM CompForm;

                /* Windows 2000 + MS-IMEの場合、画面外へ  */
                /* 移動することでコンポジットウインドウを */
                /* 非\x95\x5c示にできる */

                hImc = ImmGetContext(hWnd);
                ImmGetCompositionWindow(hImc, &CompForm);
                CompForm.dwStyle = CFS_FORCE_POSITION;
                CompForm.ptCurrentPos.x = 65535;
                CompForm.ptCurrentPos.y = 65535;
                ImmSetCompositionWindow(hImc, &CompForm);
                ImmReleaseContext(hWnd, hImc);
            }
            break;

        case WM_IME_COMPOSITION:
            hImc = ImmGetContext(hWnd);
            nIMEState |= IME_CONVERSION;

            if (lParam & GCS_RESULTSTR)
            {
                /*
                 変換結果の取得
                 変換結果が返るが、UOMAME32jではWM_CHARで拾っている
                 ので、変換が終わったかどうかの判断のみに使用。
                 使用するのであれば、以下のような感じか。

                 unsigned char szTemp[KC_BUFFER_MAX];

                 ZeroMemory(szTemp, KC_BUFFER_MAX);
                 ImmGetCompositionString(hImc, GCS_RESULTSTR, szConvStrBuf, KC_BUFFER_MAX - 1);
                 strcat(szInputBuf, szTemp);
                */

                ZeroMemory(szConvStrBuf, KC_BUFFER_MAX);
            }
            if (lParam & GCS_COMPSTR)
            {
                /*
                 変換中の文章の取得
                 変換中の文章そのものが返ります。
                */

                ZeroMemory(szConvStrBuf, KC_BUFFER_MAX);
                ImmGetCompositionString(hImc, GCS_COMPSTR, szConvStrBuf, KC_BUFFER_MAX - 1);
            }
            if (lParam & GCS_COMPATTR)
            {
                int i, len;
                char *p1, *p2;
                char szTemp[KC_BUFFER_MAX];

                /*
                 文字属性の取得
                 取得したデータは 以下のような感じだと思う
                  0x00 : 未確定文字         MS-IME2000では破線
                  0x01 : 変換対象の文節     MS-IME2000では太い下線
                  0x02 : 未確定の文節       MS-IME2000では下線
                  0x03 : 変換範囲の変更部分 MS-IME2000では反転
                  0x04 : 確定した文節       MS-IME2000では下線
                */
                len = strlen(szConvStrBuf);
                ImmGetCompositionString(hImc, GCS_COMPATTR, szTemp, len);
                p1  = szTemp;
                p2  = szConvAttrBuf;

                for (i = 0; i < len; i++)
                {
                    switch (*p1)
                    {
                    case 0x00:
                    case 0x01:
                    case 0x02:
                    case 0x03:
                    case 0x04:
                        *p2++ = Attribute[nIMEType][*(BYTE *)p1];
                        break;
                    }
                    p1++;
                }
            }
            if (lParam & GCS_COMPCLAUSE)
            {
                int i, j, len;
                int pTemp[KC_BUFFER_MAX];

                /* 
                 文節情報の取得
                 取得したデータは たとえば、今日は|良い|天気|です であれば
                 00000006|0000000a|0000000d|000000012 のように、32bit値で
                 文節の終わりの位置が入っている。
                 ゲームでは無理して取得する必要はないかも。
                */
                len = strlen(szConvStrBuf);
                ImmGetCompositionString(hImc, GCS_COMPCLAUSE, (unsigned char *)&pTemp, len * sizeof(int));

                for (i = 0, j = 0; i < len; i++)
                    if (pTemp[i])
                        pConvClauseBuf[j++] = pTemp[i];
            }
            ImmReleaseContext(hWnd, hImc);
            break;

        case WM_IME_ENDCOMPOSITION:
            /* コンポジションウィンドウが閉じられた */
            ZeroMemory(szConvStrBuf, KC_BUFFER_MAX);
            break;

        case WM_IME_NOTIFY:
            switch (wParam)
            {
            case IMN_SETOPENSTATUS:     /* IME オン/オフ */
                hImc = ImmGetContext(hWnd);
                if (ImmGetOpenStatus(hImc))
                    nIMEState |= IME_OPEN;
                else
                    nIMEState = IME_CLOSE;
                ImmReleaseContext(hWnd, hImc); /* breakしないで、入力モードも更新 */

            case IMN_SETCONVERSIONMODE: /* 入力モード変更     */
            case IMN_SETSENTENCEMODE:   /* 文節変換モード変更 */
                 KailleraChatImeGetConversionStatus();
                 break;
            }
            break;
        }
    }

    return CallWindowProc(DefaultWindowProc, hWnd, Msg, wParam, lParam);
}


static void KailleraChatCloseChat(void)
{
    if (bUseIME && !(nIMEState & IME_CONVERSION))
    {
        nPrevIMEState = nIMEState;

        if (nIMEState != IME_CLOSE)
        {
            HIMC hImc = ImmGetContext(MAME32App.m_hWnd);
            ImmSetOpenStatus(hImc, FALSE);
            ImmReleaseContext(MAME32App.m_hWnd, hImc);
        }
        nIMEState = IME_CLOSE;
    }

    if (_WINNLSEnableIME)
        _WINNLSEnableIME(MAME32App.m_hWnd, FALSE);

    ZeroMemory(szInputBuf, KC_BUFFER_MAX);
    ZeroMemory(szConvStrBuf, KC_BUFFER_MAX);
    bInputEnable = FALSE;
    bChatActive  = FALSE;

    schedule_full_refresh();
}


static void KailleraChatImeGetConversionStatus(void)
{
    HIMC  hImc;
    BOOL  bOpen;
    DWORD dwConversion;
    DWORD dwSentence;

    szIMEStateStr[0] = '\0';

    hImc = ImmGetContext(MAME32App.m_hWnd);
    bOpen = ImmGetOpenStatus(hImc);
    if (bOpen)
        ImmGetConversionStatus(hImc, &dwConversion, &dwSentence);
    ImmReleaseContext(MAME32App.m_hWnd, hImc);

    if (bOpen == TRUE)
    {
#if 0
        /* 全角/半角 */
        if (dwConversion & IME_CMODE_FULLSHAPE)
            strcpy(szIMEStateStr, "全");
        else
            strcpy(szIMEStateStr, "半");
#endif

        /* 入力コード */
        switch (dwConversion & 0x000f)
        {
        case IME_CMODE_CHARCODE:
            strcat(szIMEStateStr, "ｺｰﾄﾞ");
            break;

        case IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE:
            strcat(szIMEStateStr, "あ");
            break;

        case IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_KATAKANA:
            strcat(szIMEStateStr, "カ");
            break;

        case IME_CMODE_NATIVE | IME_CMODE_KATAKANA:
            strcat(szIMEStateStr, "ｶ ");
            break;

        case IME_CMODE_FULLSHAPE:
            strcat(szIMEStateStr, "Ａ");
            break;

        default:
            strcat(szIMEStateStr, "A ");
            break;
        }

#if 0
        /* 変換モード */
        if (dwSentence & IME_SMODE_PLAURALCLAUSE)
            strcat(szIMEStateStr, "複");
        else if (dwSentence & IME_SMODE_AUTOMATIC)
            strcat(szIMEStateStr, "自");
        else if (dwSentence & IME_SMODE_PHRASEPREDICT)
            strcat(szIMEStateStr, "連");
        else if (dwSentence & IME_SMODE_CONVERSATION)
            strcat(szIMEStateStr, "話");
        else if (!(dwConversion & IME_CMODE_CHARCODE))
            strcat(szIMEStateStr, "無");

        /* ローマ字/カナ */
        if (dwConversion & IME_CMODE_ROMAN)
            strcat(szIMEStateStr, "Ｒ");
        else
            strcat(szIMEStateStr, "ｶﾅ");
#endif
    }
    else
        strcat(szIMEStateStr, "  "); /* IMEオフ */
}


static BOOL MSIME2K_GetReg(void)
{
    HKEY  hKey;
    LONG  lResult;

    lResult = RegOpenKeyEx(HKEY_CURRENT_USER,
                           "Software\\Microsoft\\Windows\\CurrentVersion\\Ime\\Japan\\IMEJP\\MSIME",
                           0, KEY_QUERY_VALUE, &hKey);

    if (lResult == ERROR_SUCCESS)
    {
        DWORD dwType;
        DWORD dwSize;
        DWORD dwValue;

        dwType = REG_DWORD;
        dwSize = 4;
        RegQueryValueEx(hKey, "HideComment", 0, &dwType, (LPBYTE)&dwValue, &dwSize);
        RegCloseKey(hKey);
        return (dwValue != 0);
    }
    return FALSE;
}


static void GetWinVersion(BOOL *bIsNT, int *nMajor, int *nMinor, int *nBuildNumber)
{
    OSVERSIONINFO VerInfo;

    VerInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    GetVersionEx(&VerInfo);

    switch (VerInfo.dwPlatformId)
    {
    case VER_PLATFORM_WIN32_WINDOWS:
        *bIsNT        = FALSE;
        *nMajor       = HIWORD(VerInfo.dwBuildNumber) >> 8;
        *nMinor       = HIWORD(VerInfo.dwBuildNumber) & 0xff;
        *nBuildNumber = LOWORD(VerInfo.dwBuildNumber);
        break;

    case VER_PLATFORM_WIN32_NT:
        *bIsNT        = TRUE;
        *nMajor       = VerInfo.dwMajorVersion;
        *nMinor       = VerInfo.dwMinorVersion;
        *nBuildNumber = VerInfo.dwBuildNumber;
        break;

    case VER_PLATFORM_WIN32s:
    default:
        *bIsNT        = FALSE;
        *nMajor       = 0;
        *nMinor       = 0;
        *nBuildNumber = 0;
        break;
    }
}
