/*
 * Functions for Mac Input methods using Carbon
 *
 * Copyright 2006 CodeWeavers, Aric Stewart
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Much of this code based off of Kinput2.
 *
 * Copyright (c) 1990  Software Research Associates, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Software Research
 * Associates makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * Author:  Makoto Ishisone, Software Research Associates, Inc., Japan
 *
 */

/*
 * Copyright 1998 Takanori Sonoda.
 *
 * Author: Takanori Sonoda, Kyushu Univ.  (tsonoda@gynob.med.kyushu-u.ac.jp)
 */

/*
 * Copyright (c) 2004 kenichi kikuchi
 * Author: kenichi kikuchi
 */
/* Modified by MikuInstaller project on 2008-06-27. */

#ifdef __APPLE__

#include "wine/debug.h"
#include <Carbon/Carbon.h>
#include <dlfcn.h>
#include "IMKClient.h"

WINE_DEFAULT_DEBUG_CHANNEL(mac_xim);

#define kUniCharReverseSolidus	0x005c
#define kUniCharTilde			0x007e
#define kUniCharYenSign			0x00a5
#define kUniCharOverline		0x203e
#define kCandWindWidth	(127 + 11 + 80 + 11)
#define kMenuBarHeight	22

enum {TYPE_COMPONENT,TYPE_SERVER};
enum {STATE_NOT_ACTIVATED,STATE_ACTIVATED};

typedef struct{
    int type;
    union {
        CXIMKClientRef server;
        ComponentInstance component;
    } handle;
    int state;
    CFStringRef key;
} MethodTarget;

MethodTarget **AllMethods;

typedef struct
	{
		char           *text;
		int             textsize;
		int             textmax;

		TextRange      *hiliteRange;
		int             hilitesize;
		int             hilitemax;

		int             fixsize;
	} myAERecord;

typedef struct
	{
        int                 isLeopard;

		/* basic objects */
		EventTargetRef 		EventTarget;
		TSMDocumentID		docID;
		MethodTarget        *ts;
	
		/* tracking Modes */
		CFStringRef   		CurrentModeString;
  		CFStringRef   		OldModeString;
  		CFStringRef   		CurrentScript;
		CFStringRef			OldScript;

		/* for event handling */
		EventHandlerRef 	EventHandler;
		WindowRef 			CandWind;
		myAERecord 			AERecord;
	} MacTSMInternals;

typedef MacTSMInternals* TSMHandle;

extern void IME_SetCompositionString(char* text, long textSize);
extern void IME_NotifyComplete();

static void UpdateCandWindow(TSMHandle internals);
static void PumpEvents();

void MyIMKClientUpdatedEdit(CXIMKClientRef inClient, CFStringRef inString,
        long inReplacementRangeLocation, long inReplacementRangeLength,
        void* data)
{
    TSMHandle internals  = (TSMHandle)data;
    CFIndex length = CFStringGetLength(inString);
    UniChar *buffer = malloc(length * sizeof(UniChar));

    if (internals->CandWind)
        UpdateCandWindow(internals);

	TRACE("Entered (Loc %li  Len %li)\n",inReplacementRangeLocation,inReplacementRangeLength);

    CFStringGetCharacters(inString, CFRangeMake(0, length), buffer);
    IME_SetCompositionString((char*)buffer ,length * sizeof (UniChar));
}

void MyIMKClientCompletedText(CXIMKClientRef inClient, CFStringRef inString,
        long inReplacementRangeLocation, long inReplacementRangeLength,
        void *data)
{
    TSMHandle internals  = (TSMHandle)data;
    CFIndex length = CFStringGetLength(inString);
    UniChar *buffer = malloc(length * sizeof(UniChar));

	TRACE("ENTERED (Loc %li  Len %li)\n",inReplacementRangeLocation,inReplacementRangeLength);
    if (internals->CandWind)
        UpdateCandWindow(internals);

    CFStringGetCharacters(inString, CFRangeMake(0, length), buffer);
    IME_SetCompositionString((char*)buffer, length * sizeof (UniChar));
    IME_NotifyComplete();
}

void MyIMKClientModeChanged(CXIMKClientRef inClient, CFStringRef newmode, void* data)
{
    TSMHandle internals  = (TSMHandle)data;

    if (CFStringCompare(newmode, internals->CurrentModeString,0)
            != kCFCompareEqualTo)
    {
        CFRelease(internals->CurrentModeString);
        internals->CurrentModeString = CFStringCreateCopy(NULL,newmode);
    }

    PumpEvents();
}

ScriptLanguageRecord *GetScriptRecord(CFStringRef script)
{
    static ScriptLanguageRecord scriptLanguageTCh = { smTradChinese, langTradChinese};
    static ScriptLanguageRecord scriptLanguageSCh = { smSimpChinese, langSimpChinese};
    static ScriptLanguageRecord scriptLanguageKr = { smKorean, langKorean};
    static ScriptLanguageRecord scriptLanguageJp = { smJapanese, langJapanese};
    static ScriptLanguageRecord scriptLanguageVt = { smVietnamese, langVietnamese};
    if (CFStringCompare(script, CFSTR("smJapanese"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageJp;
    else if (CFStringCompare(script, CFSTR("smTradChinese"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageTCh;
    else if (CFStringCompare(script, CFSTR("smTCIM"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageTCh;
    else if (CFStringCompare(script, CFSTR("smSimpChinese"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageSCh;
    else if (CFStringCompare(script, CFSTR("smSCIM"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageSCh;
    else if (CFStringCompare(script, CFSTR("smKorean"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageKr;
    else if (CFStringCompare(script, CFSTR("smVietnamese"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageVt;
    else if (CFStringCompare(script, CFSTR("smVietnameseSimpleTelex"), 0)
                == kCFCompareEqualTo)
        return &scriptLanguageVt;

    return NULL;
}

static CFStringRef GetServerConnection(CFStringRef bundleid)
{
    CFBundleRef bund;
    bund = CFBundleGetBundleWithIdentifier(bundleid);
    if (bund)
    {
        static CFStringRef name = NULL;

        name = CFBundleGetValueForInfoDictionaryKey(bund,
                                CFSTR("InputMethodConnectionName"));

        if (name)
        {
            if (TRACE_ON(mac_xim))
            {
	            char ss[100];
                CFStringGetCString(name,ss,100,kCFStringEncodingASCII);
                TRACE("Server name is: %s\n", ss);
            }
            return name;
        }

        TRACE("Server name not found\n");
        return CFSTR("none");
    }

    TRACE("Not an Application Bundle\n");
    return CFSTR("none");
}

static MethodTarget* CreateNewMethodTarget( TSMHandle internals,
                                      CFStringRef script,
                                      CFStringRef bundleid)
{
    MethodTarget *tgt = NULL;
    ScriptLanguageRecord *sl = NULL;
    Component gComp = NULL;
    OSStatus status;

    sl = GetScriptRecord(script);
    if (sl)
        status = GetDefaultInputMethodOfClass(&gComp, sl, kKeyboardInputMethodClass);
    else
        status = -1;

    if (status == noErr && gComp)
    {
        tgt = malloc(sizeof(MethodTarget));
        tgt->type = TYPE_COMPONENT;
        OpenTextService(internals->docID, gComp, &tgt->handle.component);
        tgt->state = STATE_ACTIVATED;
        if (bundleid)
            tgt->key = CFStringCreateCopy(NULL,bundleid);
        else
            tgt->key = CFStringCreateCopy(NULL,script);
        TRACE("Created New Component Instance \n");
    }
    else if (internals->isLeopard)
    {
        CXIMKClientRef server;
        TRACE("Attempting Leopard Application Server\n");

        server = CXIMKClientInitialize( GetServerConnection(bundleid) ,
                    &MyIMKClientUpdatedEdit, &MyIMKClientCompletedText,
                    &MyIMKClientModeChanged, internals);

        if (server)
        {
            tgt = malloc(sizeof(MethodTarget));
            tgt->type = TYPE_SERVER;
            tgt->handle.server = server;
            tgt->state = STATE_ACTIVATED;
            if (bundleid)
                tgt->key = CFStringCreateCopy(NULL,bundleid);
            else
                tgt->key = CFStringCreateCopy(NULL,script);
            TRACE("Created New Application Server\n");
        }
    }

    return tgt;
}

static MethodTarget* FindCreateMethodTarget(TSMHandle internals,
                                            CFStringRef script,
                                            CFStringRef bundleid)
{
    MethodTarget *ptr = (MethodTarget *)AllMethods;
    int i = 0;
    CFStringRef key;

    if (bundleid)
        key = bundleid;
    else
        key = script;

    if (ptr != NULL)
    {
        ptr = AllMethods[0];
        while (ptr != NULL)
        {
	        if (CFStringCompare(key, ptr->key, 0)
				== kCFCompareEqualTo)
                break;
            i++;
            ptr = AllMethods[i];
        }
    }

    if (ptr)
        return ptr;

    ptr = CreateNewMethodTarget(internals, script, bundleid);
    if (!ptr)
        return ptr;

    if (!AllMethods)
    {
        AllMethods = malloc(sizeof(MethodTarget*) * 2);
        AllMethods[0] = ptr;
        AllMethods[1] = 0;
    }
    else
    {
        AllMethods = realloc(AllMethods,sizeof(MethodTarget*) * (i + 2));
        AllMethods[i] = ptr;
        AllMethods[i+1] = 0;
    }

    return ptr;
}

static OSErr OpenAppropriateService(TSMHandle internals,
				    CFStringRef script, CFStringRef bundleid)
{
	OSErr err = 1;

	TRACE("\n");

	if (internals->ts)
	{	
		/* Shut down current service */
        if (internals->ts->type == TYPE_COMPONENT)
        {
            DeactivateTextService(internals->ts->handle.component);
            TerminateTextService(internals->ts->handle.component);
        }
        else
            CXIMKClientDeactivate(internals->ts->handle.server);

		internals->ts = NULL;
	}

	if (CFStringCompare(script, CFSTR("smRoman"), 0)
				== kCFCompareEqualTo)
	{
		KeyScript(smKeyRoman);
		err = noErr;
	}
    else
        internals->ts = FindCreateMethodTarget(internals, script, bundleid);

	if (internals->ts)
	{
        if (internals->ts->type == TYPE_COMPONENT)
        {
            err = InitiateTextService(internals->ts->handle.component);
            if (err != noErr)
                WARN("Failure to Initiate Text Service (%i)\n",err);
            else
            {
                err = ActivateTextService(internals->ts->handle.component);
                if (err != noErr)
                {
                    TerminateTextService(internals->ts->handle.component);
                    WARN("Failure to Activate Text Service (%i)\n",err);
                }
            }

            if (err != noErr)
                internals->ts = NULL;
        }
        else
        {
            CXIMKClientActivate(internals->ts->handle.server);
        }
	}

	return err;
}

static Boolean SetInputModeCFS(TSMHandle internals, CFStringRef mode)
{
	OSStatus status = noErr;
	
	if (TRACE_ON(mac_xim))
    {
	    char ss[100];
        CFStringGetCString(mode,ss,100,kCFStringEncodingASCII);
        TRACE("Attempting to set mode to: %s\n", ss);
    }

	if (!internals->ts)
		return false;

    if (internals->ts->type == TYPE_SERVER)
        CXIMKClientSetInputMode(internals->ts->handle.server,mode);
    else
        status = SetTextServiceProperty(internals->ts->handle.component,
                    kTextServiceInputModePropertyTag, (SInt32)mode);

	if (status != noErr)
	{
		ERR("can't set input mode (%ld)\n", status);
		return false;
	}
		
	return true;
}

static Boolean GetTISInputModeCFS( CFStringRef *out_mode,
                        CFStringRef *out_bundle)
{
    void* src;
	CFStringRef string;

    typedef void *tTISCopyCurrentKeyboardInputSource();
    typedef void *tTISGetInputSourceProperty ( void* inputSource, CFStringRef
                    propertyKey);

    static CFStringRef *kTISPropertyInputModeID = NULL;
    static CFStringRef *kTISPropertyBundleID = NULL;
    static CFStringRef *kTISPropertyInputSourceType = NULL;
    tTISCopyCurrentKeyboardInputSource *pTISCopyCurrentKeyboardInputSource = NULL;
    tTISGetInputSourceProperty *pTISGetInputSourceProperty = NULL;

    if (!kTISPropertyInputModeID || !pTISCopyCurrentKeyboardInputSource ||
        !pTISGetInputSourceProperty || !kTISPropertyBundleID)
    {
        pTISCopyCurrentKeyboardInputSource = dlsym(RTLD_DEFAULT,"TISCopyCurrentKeyboardInputSource");
        pTISGetInputSourceProperty = dlsym(RTLD_DEFAULT,"TISGetInputSourceProperty");
        kTISPropertyInputModeID = dlsym(RTLD_DEFAULT,"kTISPropertyInputModeID");
        kTISPropertyBundleID = dlsym(RTLD_DEFAULT,"kTISPropertyBundleID");
        kTISPropertyInputSourceType= dlsym(RTLD_DEFAULT,"kTISPropertyInputSourceType");

        if (!kTISPropertyInputModeID || !pTISCopyCurrentKeyboardInputSource ||
            !pTISGetInputSourceProperty || !kTISPropertyBundleID)
        {
            TRACE("Unable to load functions\n");
            return false;
        }
    }

    PumpEvents();

    src = pTISCopyCurrentKeyboardInputSource();
    TRACE("src %p\n",src);

    string = pTISGetInputSourceProperty(src,*kTISPropertyInputSourceType);
    if (TRACE_ON(mac_xim) && string)
    {
	    char ss[100];
        CFStringGetCString(string,ss,100,kCFStringEncodingASCII);
        TRACE("TIS type %s\n",ss);
    }
	if (CFStringCompare(string, CFSTR("TISTypeKeyboardInputMode"), 0)
				!= kCFCompareEqualTo)
    {
        TRACE("Not an Input mode\n");
        return false;
    }

    string = pTISGetInputSourceProperty(src,*kTISPropertyInputModeID);
    if (TRACE_ON(mac_xim) && string)
    {
	    char ss[100];
        CFStringGetCString(string,ss,100,kCFStringEncodingASCII);
        TRACE("TIS mode %s\n",ss);
    }
    *out_mode = CFStringCreateCopy(NULL,string);

    string = pTISGetInputSourceProperty(src,*kTISPropertyBundleID);
    if (TRACE_ON(mac_xim) && string)
    {
	    char ss[100];
        CFStringGetCString(string,ss,100,kCFStringEncodingASCII);
        TRACE("TIS bundleid %s\n",ss);
    }
    *out_bundle = CFStringCreateCopy(NULL,string);

    return true;
}

static int GetLeopardInputModeCFS( CFStringRef *out_mode,
                                   CFStringRef *out_script,
                                   CFStringRef *out_bundle)
{
	CFStringRef script = NULL;
    int rc = 0;

    rc = GetTISInputModeCFS(out_mode,out_bundle);

	if (rc)
    {
        char ss[100];
        char *scr;
        char *end;

        CFStringGetCString(*out_mode,ss,100,kCFStringEncodingASCII);
        TRACE("aqua default input mode: %s\n", ss);

        /* Determine script */
        scr = strstr(ss,"inputmethod");
        if (scr)
        {
            scr += 12;
            end = strchr(scr,'.');
            if (end)
                *end = 0;
            *(scr-2) = 's';
            *(scr-1) = 'm';
            scr-=2;
            TRACE("aqua default input script: %s\n", scr);
            script = CFStringCreateWithCString(NULL,scr,kCFStringEncodingASCII);
            *out_script = script;

            /* check for roman input modes inside of IMEs */
	        if (CFStringCompare(script, CFSTR("smRoman"), 0)
				== kCFCompareEqualTo)
            {
                TRACE("Found Roman\n");
                rc = 0;
            }
        }
        else
        {
            ERR("Strange Mode line!\n");
            rc = 0;
	        *out_script = CFSTR("smRoman");
        }
    }

	return rc;
}

static CFStringRef GetAquaDefaultInputModeCFS(CFStringRef theScript)
{
	CFDictionaryRef dict;
	CFArrayRef array = NULL;
	CFStringRef string = NULL;
	CFStringRef mode = NULL;

	CFPreferencesSynchronize(CFSTR("com.apple.HIToolbox"),
			kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
	
	dict = CFPreferencesCopyValue(CFSTR("AppleDefaultInputMode"),
			CFSTR("com.apple.HIToolbox"),
			kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
	if (dict)
	{
		array = CFDictionaryGetValue(dict, theScript);
		if (array)
		{
			string = CFArrayGetValueAtIndex(array, 0);
			mode = CFStringCreateCopy(NULL,string);
		}
		CFRelease(dict);
	}

	if (mode && TRACE_ON(mac_xim))
    {
        char ss[100];
        CFStringGetCString(mode,ss,100,kCFStringEncodingASCII);
        TRACE("aqua default input mode: %s\n", ss);
    }
	return mode;
}

static long GetAquaInputModeCFS( CFStringRef *out_mode,
                                 CFStringRef *out_script)
{
	CFStringRef string;
	long inIME = 0;

	CFPreferencesSynchronize(CFSTR("com.apple.HIToolbox"),
		kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
	
	string = CFPreferencesCopyValue(CFSTR("AppleKeyboardScript"),
			CFSTR("com.apple.HIToolbox"),
			kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
	if (string)
	{
        if (TRACE_ON(mac_xim))
        {
	        char ss[100];
            CFStringGetCString(string,ss,100,kCFStringEncodingASCII);
            TRACE("Aqua Script %s\n",ss);
        }
		if (CFStringCompare(string, CFSTR("smRoman"), 0) == kCFCompareEqualTo)
			inIME = 0;
		else
			inIME = 1;

		if (inIME)
			*out_mode = GetAquaDefaultInputModeCFS(string);
		else
			*out_mode = CFStringCreateCopy(NULL, kTextServiceInputModeRoman);

		*out_script = string;
	}
	
	return inIME;
}

Boolean MacTSMRevertToPreviousMode(TSMHandle internals)
{
	if (CFStringCompare(internals->OldScript, internals->CurrentScript, 0) !=
				kCFCompareEqualTo)
	{
		CFStringRef temp = internals->CurrentScript;
		CFStringRef mode, bundleid = NULL;

	    if (TRACE_ON(mac_xim))
        {
		    char ss[100],ss2[100];
            CFStringGetCString(temp,ss,100,kCFStringEncodingASCII);
            CFStringGetCString(internals->OldScript,ss2,100,kCFStringEncodingASCII);
            TRACE("Switching from %s to %s\n",ss,ss2);
        }

		if (internals->isLeopard) {
				GetTISInputModeCFS(&mode, &bundleid);
				CFRelease(mode);
		}
		OpenAppropriateService(internals, internals->OldScript, bundleid);
		if (bundleid)
				CFRelease(bundleid);

		internals->CurrentScript = internals->OldScript;
		internals->OldScript = temp;
	}

	if (CFStringCompare(internals->OldModeString,
						internals->CurrentModeString,0) != kCFCompareEqualTo)
	{
		CFStringRef temp = internals->CurrentModeString;
		SetInputModeCFS(internals,internals->OldModeString);
		internals->CurrentModeString = internals->OldModeString;
		internals->OldModeString = temp;
	}

	return true;
}

Boolean MacTSMCheckModeAndIME(TSMHandle internals)
{
    Boolean inIME;
    CFStringRef mode = NULL;
    CFStringRef script = NULL;
    CFStringRef bundle = NULL;

    TRACE("\n");

    if (internals->isLeopard)
        inIME = GetLeopardInputModeCFS(&mode, &script, &bundle);
    else
        inIME = GetAquaInputModeCFS(&mode, &script);

    if (!mode || !script)
        return false;

    if (CFStringCompare(script, internals->CurrentScript, 0) !=
            kCFCompareEqualTo)
    {
        OpenAppropriateService(internals, script, bundle);

        CFRelease(internals->OldScript);
        internals->OldScript = internals->CurrentScript;
        internals->CurrentScript = script;
    }
    else
        CFRelease(script);

    if (mode)
    {
        if (CFStringCompare(mode, internals->CurrentModeString,0)
            != kCFCompareEqualTo)
        {
            CFRelease(internals->OldModeString);
            internals->OldModeString =
                internals->CurrentModeString;
            internals->CurrentModeString = mode;
            SetInputModeCFS(internals, internals->CurrentModeString);
        }
        else
            CFRelease(mode);
    }

    return inIME;
}


Boolean MacTSMEventCFS(TSMHandle internals, char theChar, unsigned long theCode,
		      unsigned long modifiers, unsigned long unichar)
{
	OSStatus status;
	EventRef event = NULL;
	Boolean result;
	UInt32 Keyboard = LMGetKbdType();
	
	TRACE("(%02x'%c':%02lx:%04lx)\n",
			(unsigned char)theChar, isprint(theChar) ? theChar : '.',
			(long)theCode, (long)modifiers);

	if (!internals->ts)
		return false;

	status = CreateEvent(NULL, kEventClassKeyboard, kEventRawKeyDown, 0,
						kEventAttributeUserEvent, &event);

	if (status == noErr)
		status = SetEventParameter(event, kEventParamKeyMacCharCodes,
								typeChar, sizeof(char), &theChar);
		
	if (status == noErr)
		status = SetEventParameter(event, kEventParamKeyCode, typeUInt32,
									sizeof(UInt32), &theCode);
		
	if (status == noErr)
		status = SetEventParameter(event, kEventParamKeyModifiers, typeUInt32,
									sizeof(UInt32), &modifiers);
		
	if (status == noErr)
		status = SetEventParameter(event, kEventParamKeyboardType, typeUInt32,
								 sizeof(UInt32), &Keyboard);

	if (status == noErr && unichar != 0xffffffff)
		status = SetEventParameter(event, kEventParamKeyUnicodes,
                                 typeUnicodeText, sizeof(UniChar), &unichar);


	if (status == noErr)
	{
        if (internals->ts->type == TYPE_SERVER)
            status = CXIMKClientSendEvent(internals->ts->handle.server,event);
        else
            status = TextServiceEventRef(internals->ts->handle.component,event);

		TRACE("Handled? %li\n",status);
		if (status > 0)
			result = true;
		else
			result = false;
	}
	else
	{
		ERR("can't send key to tsm (%ld)\n", status);
		result = false;
	}
	
	if (event)
		ReleaseEvent(event);

	if (result)
		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode,0.0001,true)
				==kCFRunLoopRunHandledSource);

	return result;
}

TSMHandle MacInitTSMAwareApplication(void)
{
	TSMHandle internals;

	internals = (TSMHandle)malloc(sizeof(MacTSMInternals));
	memset(internals,0,sizeof(MacTSMInternals));

    if (dlsym(RTLD_DEFAULT,"TISCopyInputSourceForLanguage"))
        internals->isLeopard = 1;

    internals->EventTarget = GetApplicationEventTarget();
    memset(&(internals->AERecord),0,sizeof(myAERecord));

    /* Initialize Modes */
    internals->CurrentModeString = CFStringCreateCopy(NULL,
                                        kTextServiceInputModeRoman);
    internals->OldModeString = CFStringCreateCopy(NULL,
                                        kTextServiceInputModeJapanese);
    internals->CurrentScript= CFStringCreateCopy(NULL,CFSTR("smRoman"));
    internals->OldScript= CFStringCreateCopy(NULL,CFSTR("smJapanese"));

    InstallStandardEventHandler(GetApplicationEventTarget());

	return internals;
}

void MacCloseTSMAwareApplication(TSMHandle internals)
{
    MethodTarget *ptr;
	TRACE("\n");
	
	CFRelease(internals->CurrentModeString);
	CFRelease(internals->OldModeString);
	CFRelease(internals->CurrentScript);
	CFRelease(internals->OldScript);

	/* clean out the myAERecord */
	if (internals->AERecord.hiliteRange)
		free(internals->AERecord.hiliteRange);
	if (internals->AERecord.text)
		free(internals->AERecord.text);

    /* Cleanup all the servers and such */
    if (AllMethods)
    {
        int i = 0;
        ptr = AllMethods[0];
        while (ptr)
        {
            if (ptr->type == TYPE_SERVER && ptr->state == STATE_ACTIVATED)
                CXIMKClientCleanup(ptr->handle.server);
            i++;
            ptr = AllMethods[i];
        }
    }

	free(internals);
}


OSErr MacNewTSMDocument(TSMHandle internals, long refcon)
{
	OSErr err = noErr;
	InterfaceTypeList typeList;
	TRACE("\n");
	
	typeList[0] = kUnicodeDocumentInterfaceType;
	err = NewTSMDocument(1, typeList, &(internals->docID), refcon);
	TRACE("(%lx)\n", (long)(internals->docID));

	if (err != noErr)
	{
		ERR("can't create tsm doc (%d)\n", err);
		return err;
	}
	return noErr;
}

OSErr MacDeleteTSMDocument(TSMHandle internals)
{
	OSErr err = noErr;
	
	TRACE("\n");

	if (internals->ts && internals->ts->type == TYPE_COMPONENT)
	{
		DeactivateTextService(internals->ts->handle.component);
		TerminateTextService(internals->ts->handle.component);
	}

	return err;
}

OSErr MacTSMFixTextService(TSMHandle internals)
{
	OSErr err = noErr;

	if (!internals->ts)
		return noErr;

    if (internals->ts->type == TYPE_COMPONENT)
	    err = FixTextService(internals->ts->handle.component);
    else
	    CXIMKClientForceComplete(internals->ts->handle.server);

	return err;
}

OSErr MacActivateTSMDocument(TSMHandle internals)
{
	OSErr err = tsmComponentNoErr;
    TRACE("Checking Active\n");

	if (TSMGetActiveDocument() != internals->docID)
		err = ActivateTSMDocument(internals->docID);

	return err;
}


/**************************************
 * Event Handler Logic
 **************************************/
static unsigned long getText(EventRef event,myAERecord *theRec)
{
	unsigned long   ret;
	char           *theText = NULL;
	OSStatus        myErr;
	EventParamType  textType;

	textType = typeUnicodeText;
	myErr = GetEventParameter(event, kEventParamTextInputSendText, textType,
		   	NULL, 0, &ret, NULL);
	if (myErr != noErr)
		ret = 0;
	theRec->textsize = ret;

	if (ret != 0)
	{
		if (ret >= theRec->textmax)
		{
			free(theRec->text);
			theRec->text = (char *) malloc(sizeof(char) *
					 (theRec->textmax + 256));
			theRec->textmax += 256;
		}

		theText = theRec->text;

		myErr = GetEventParameter(event, kEventParamTextInputSendText, textType,
						NULL, ret, NULL, theText);
		if (myErr != noErr)
		theRec->textsize = ret = 0;
		*(theText + ret) = 0;

	}
	else if (theRec->text)
 		*(theRec->text) = 0;

	if (TRACE_ON(mac_xim))
	{
		int i;

		TRACE("text(%ld): ", ret);
		for (i = 0; i < ret; i++)
		{
			MESSAGE("%02x", (unsigned char)*(theText + i));
			MESSAGE("%s", (i % 2 == 0) ? "" : " ");
		}
		MESSAGE("\n");
	}

	return ret;
}


static void getTextRangeDesc(EventRef event,  myAERecord *theRec)
{
	static TextRangeArray *theArray = NULL;
	short           ret;
	int             i;
 	TextRange      *theRange;
	OSStatus        myErr;
	Size            actualSize;

	myErr = GetEventParameter(event, kEventParamTextInputSendHiliteRng,
				typeTextRangeArray, NULL, 0, (UInt32*)&actualSize, NULL);
	if (myErr == noErr)
	{
		if (theArray == NULL)
		theArray = (TextRangeArray *)NewPtr(actualSize);
		else if (actualSize > GetPtrSize((Ptr)theArray))
		{
			DisposePtr((Ptr)theArray);
			theArray = (TextRangeArray *)NewPtr(actualSize);
		}

		myErr = GetEventParameter(event, kEventParamTextInputSendHiliteRng,
				typeTextRangeArray, NULL, actualSize, NULL, theArray);
		if (myErr != noErr)
			return;

		if (TRACE_ON(mac_xim))
		{
			TRACE("hilite(%d): ", theArray->fNumOfRanges);
			for (i = 0; i < theArray->fNumOfRanges; i++)
			MESSAGE("%s(%ld-%ld:%d)", i == 0 ? "" : ", ",
			theArray->fRange[i].fStart, theArray->fRange[i].fEnd,
			theArray->fRange[i].fHiliteStyle);
			MESSAGE("\n");
		}

		ret = theRec->hilitesize = theArray->fNumOfRanges;
		if (ret == 0)
	    	return;
	}
	else
	{
		if (theArray != NULL)
			return;
		myErr = GetEventParameter(event, kEventParamTextInputSendUpdateRng,
					typeTextRangeArray, NULL, 0, (UInt32*)&actualSize, NULL);
		if (myErr != noErr)
			return;
		theArray = (TextRangeArray *)NewPtr(actualSize);
		myErr = GetEventParameter(event, kEventParamTextInputSendUpdateRng,
					typeTextRangeArray, NULL, actualSize, NULL, theArray);
		if (myErr != noErr)
			return;
		ret = theRec->hilitesize = theArray->fNumOfRanges;
		if (ret != 2)
			return;

		if (TRACE_ON(mac_xim))
		{
			TRACE("update(%d): ", theRec->hilitesize);
			for (i = 0; i < theRec->hilitesize; i++)
				MESSAGE("%s(%ld-%ld)", i == 0 ? "" : ", ",
					theArray->fRange[i].fStart, theArray->fRange[i].fEnd);
			MESSAGE("\n");
		}

		theArray->fRange[0].fStart = theArray->fRange[1].fStart;
		theArray->fRange[0].fEnd = theArray->fRange[1].fEnd;
		theArray->fRange[0].fHiliteStyle = 4;
		theArray->fRange[1].fStart = theArray->fRange[1].fEnd;
		theArray->fRange[1].fHiliteStyle = 1;
	}

	if (ret > theRec->hilitemax)
	{
		free(theRec->hiliteRange);
		theRec->hiliteRange = (TextRange *) malloc(sizeof(TextRange) * ret);
		theRec->hilitemax = ret;
	}

	theRange = theRec->hiliteRange;
	for (i = 0; i < ret; i++)
	{
		(theRange + i)->fStart = theArray->fRange[i].fStart;
		(theRange + i)->fEnd = theArray->fRange[i].fEnd;
		(theRange + i)->fHiliteStyle = theArray->fRange[i].fHiliteStyle;
	}
}

static OSStatus UpAcInputArea(EventRef event, TSMHandle internals)
{
	OSStatus        myErr;
	long            fixLength;
	long            textSize;
	long            refCon;
	myAERecord     *theRec;

	theRec = &(internals->AERecord);

	myErr = GetEventParameter(event, kEventParamTextInputSendRefCon,
				typeLongInteger, NULL, sizeof(long), NULL, &refCon);

	if (myErr == noErr)
	{
		textSize = getText(event,theRec);
		TRACE("MacIM:AEUpdateEvent:textSize=%ld\n", textSize);
	}

	if (myErr != noErr)
	{
		return myErr;
	}

	myErr = GetEventParameter(event, kEventParamTextInputSendFixLen,
				typeLongInteger, NULL, sizeof(long), NULL, &fixLength);
	if (myErr == noErr)
	{
		theRec->fixsize = fixLength;
		TRACE("HERE fix(%d)\n", theRec->fixsize);
	}
	else
		return myErr;

	if (textSize >= fixLength)
		getTextRangeDesc(event,theRec);

	if (textSize > 0)
	{
		IME_SetCompositionString(theRec->text,textSize);

		if (theRec->fixsize == textSize)
			IME_NotifyComplete();
	}
	else
		IME_SetCompositionString(NULL,0);

	return noErr;
}

/* Apple Event Off2Pos */
static OSStatus Off2P(EventRef event)
{
	OSStatus        myErr;
	long            refCon;
	BitMap          screenBits;
	Point           pos = { 0, 0 };

  /* extern void     getCurrTextPos(long, Point *); */

	myErr = GetEventParameter(event, kEventParamTextInputSendRefCon,
				typeLongInteger, NULL, sizeof(long), NULL, &refCon);

/*
	if (myErr == noErr)
		getCurrTextPos(refCon, &pos);
*/

	if (pos.v == 0 && pos.h == 0)
	{
		GetQDGlobalsScreenBits(&screenBits);

		pos.h = screenBits.bounds.right - kCandWindWidth - 4;
		pos.v = screenBits.bounds.top + kMenuBarHeight + 4;
	}

	myErr = SetEventParameter(event, kEventParamTextInputReplyPoint,
					typeQDPoint, sizeof(Point), &pos);
	return myErr;
}

static void UpdateCandWindow(TSMHandle internals)
{
	/*
	 * Mac Documentation says that this call is Deprecated
	 */
	QDFlushPortBuffer(GetWindowPort(internals->CandWind), NULL);
}

static pascal OSStatus MacEventHandler(EventHandlerCallRef nextHandler,
					EventRef event, void *userData)
{
#pragma unused (nextHandler)

	OSStatus        myErr, result = eventNotHandledErr;
	UInt32          class, kind;
	WindowRef       wind;
	TSMHandle		internals = (TSMHandle)userData;

	class = GetEventClass(event);
	kind = GetEventKind(event);

	TRACE("(%c%c%c%c:%ld)\n", (char)(class >> 24) & 0xff,
		(char)(class >> 16) & 0xff, (char)(class >> 8) & 0xff,
		(char)class & 0xff, kind);

	switch (class)
	{
		case kEventClassTextInput:
				switch (kind)
				{
					case kEventTextInputUpdateActiveInputArea:	
						TRACE("kEventTextInputUpdateActiveInputArea\n");
						if (internals->CandWind)
							UpdateCandWindow(internals);
						myErr = UpAcInputArea(event,internals);
						if (myErr == noErr)
							result = noErr;
						break;
					case kEventTextInputOffsetToPos:
						TRACE("kEventTextInputOffsetToPos\n");
						myErr = Off2P(event);
						if (myErr == noErr)
							result = noErr;
						break;
				}
				break;
		case kEventClassWindow:
			TRACE("kEventClassWindow\n");
			myErr = GetEventParameter(event, kEventParamDirectObject,
						typeWindowRef, NULL, sizeof(WindowRef), NULL, &wind);
			if (myErr != noErr)
				break;
			switch (kind)
			{
				case kEventWindowShown:
					TRACE("kEventWindowShown\n");
					if (internals->CandWind == NULL)
					{
						internals->CandWind = wind;
						UpdateCandWindow(internals);
					}
					break;
				case kEventWindowHidden:
					TRACE("kEventWindowHidden\n");
					if (internals->CandWind == wind)
						internals->CandWind = NULL;
					break;
			}
			break;
 	}

	return result;
}

long MacInstallAppEventHandler(TSMHandle internals)
{
	OSStatus        myErr;
	EventTypeSpec   eventType[] = {
			{ kEventClassTextInput, kEventTextInputUpdateActiveInputArea },/*1*/
			{ kEventClassTextInput, kEventTextInputOffsetToPos },	/* 3 */
			{ kEventClassWindow, kEventWindowShown },	/* 24 */
			{ kEventClassWindow, kEventWindowHidden },	/* 25 */
	};
	
	TRACE("Setting up Event Handler\n");
	myErr = InstallApplicationEventHandler(NewEventHandlerUPP(MacEventHandler),
				GetEventTypeCount(eventType), eventType, internals,
				&(internals->EventHandler));

	if (myErr != noErr)
	{
		ERR("can't install app event handler (%ld)\n", myErr);
		return myErr;
	}

	return noErr;
}

void MacRemoveAppEventHandler(TSMHandle internals)
{
	OSStatus        myErr;
	
	myErr = RemoveEventHandler(internals->EventHandler);
}

static void PumpEvents()
{
    EventRef theEvent;
    EventTargetRef theTarget = NULL;
	UInt32          class, kind;

    if (theTarget == NULL)
        theTarget = GetEventDispatcherTarget();

    while (ReceiveNextEvent(0, NULL, 0.0001, TRUE, &theEvent) == noErr)
    {
	class = GetEventClass(theEvent);
	kind = GetEventKind(theEvent);
  
	TRACE("(%c%c%c%c:%ld)\n", (char)(class >> 24) & 0xff, 
		(char)(class >> 16) & 0xff, (char)(class >> 8) & 0xff, 
		(char)class & 0xff, kind);

        SendEventToEventTarget(theEvent,theTarget);
        ReleaseEvent(theEvent);
    }
}

#endif /* __APPLE__ */
