/* Modified by MikuInstaller project on 2008-06-27. */
#ifdef __APPLE__

#include "wine/debug.h"
#include <dlfcn.h>  // for workaround

#define BOOL MacBOOL

#include "IMKClient.h"

#import <Foundation/Foundation.h>

#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
    #import <InputMethodKit/IMKInputController.h>
#else
    @protocol IMKTextInput
    @end
    @protocol IMKUnicodeTextInput
    @end
    typedef long NSInteger;
    typedef unsigned long NSUInteger;
    typedef NSInteger IMKLocationToOffsetMappingMode;
#endif

#include <unistd.h>

#define debugstr_ns(s) \
[([(s) isKindOfClass:[NSAttributedString class]] ? [((id)s) string] : (s)) \
 UTF8String]


WINE_DEFAULT_DEBUG_CHANNEL(mac_imk);


@interface NSObject (wine_IMKEvent)
+ (id) packageEventRef:(EventRef)eventRef;
@end


// This protocol has several close relatives in Leopard's
// <InputMethodKit/IMKInputController.h> header.  The IMKStateSetting protocol
// also describes methods implemented by the server object.  Where this protocol
// describes the interface between a client and the input method server, the
// IMKServerInput informal protocol describes the complementary interface
// between the server object and the IMKInputController inside the server
// process which provides the logic of how a specific input method behaves.  As
// such, the semantics for IMKStateSetting and IMKServerInput help elucidate the
// semantics of this protocol.
@protocol IMKServerProxy
- (void)activateServer:(id)sender;
- (void)deactivateServer:(id)sender;
- (void)sessionFinished:(id)sender;
- (id)valueForTag:(unsigned long)tag client:(id)sender;
- (void)setValue:(id)value forTag:(unsigned long)tag client:(id)sender;
- (NSDictionary*)modes:(id)sender;
- (id)menusDictionary:(id)sender;
- (BOOL)handleEvent:(bycopy id)event characterIndex:(unsigned long)characterIndex edge:(unsigned long)edge client:(id)sender;
- (void)commitComposition:(id)sender;
- (void)hidePalettes:(id)sender;
- (BOOL)didCommandBySelector:(SEL)aSelector client:(id)sender;
- (void)doCommandBySelector:(SEL)aSelector commandDictionary:(NSDictionary*)infoDictionary client:(id)sender;
- (NSUInteger)recognizedEvents:(id)sender;
@end


// See Leopard's <HIToolbox/IMKInputSession.h> header for documentation of
// the protocols to which this class conforms.
@interface MyTextInput : NSObject <IMKTextInput, IMKUnicodeTextInput>
{
    CXIMKClientRef client;
    CXIMKClientUpdatedEdit updateEditCallback;
    CXIMKClientCompletedText completedTextCallback;
    CXIMKClientModeChanged modeChangedCallback;
    void *data;
}

@end

@implementation MyTextInput

- (id) initWithClientRef:(CXIMKClientRef)inClient
          updateCallback:(CXIMKClientUpdatedEdit)inUpdateEditCallback
       completedCallback:(CXIMKClientCompletedText)inCompletedTextCallback
       modeChangedCallback:(CXIMKClientModeChanged)inModeChangedCallback
        clientData:(void*)inData
{
    self = [super init];
    if (self != nil)
    {
        client = inClient;
        updateEditCallback = inUpdateEditCallback;
        completedTextCallback = inCompletedTextCallback;
        modeChangedCallback = inModeChangedCallback;
        data = inData;
    }
    return self;
}


- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
    TRACE("%s { %d, %d }\n", debugstr_ns(string), replacementRange.length, replacementRange.location);

    if ([string isKindOfClass:[NSAttributedString class]])
        string = [string string];

    if (string)
        completedTextCallback(client, (CFStringRef)[[string copy] autorelease], replacementRange.location, replacementRange.length, data);
}


- (void)setMarkedText:(id)string selectionRange:(NSRange)selectionRange replacementRange:(NSRange)replacementRange
{
    TRACE("%s { %d, %d } { %d, %d }\n", debugstr_ns(string), selectionRange.length, selectionRange.location, replacementRange.length, replacementRange.location);

    if ([string isKindOfClass:[NSAttributedString class]])
        string = [string string];

    if (string)
        updateEditCallback(client, (CFStringRef)[[string copy] autorelease], replacementRange.location, replacementRange.length, data);
}


- (NSRange)selectedRange
{
    TRACE("\n");
    return NSMakeRange(NSNotFound, NSNotFound);
}


- (NSRange)markedRange
{
    TRACE("\n");
    return NSMakeRange(NSNotFound, NSNotFound);
}


- (NSAttributedString*)attributedSubstringFromRange:(NSRange)range
{
    TRACE("{ %d, %d }\n", range.length, range.location);
    return [[[NSAttributedString alloc] init] autorelease];
}


- (NSInteger)length
{
    TRACE("\n");
    return NSNotFound;
}


- (NSInteger)characterIndexForPoint:(NSPoint)point tracking:(IMKLocationToOffsetMappingMode)mappingMode inMarkedRange:(BOOL*)inMarkedRange
{
    TRACE("{ %f, %f } %ld %p\n", point.x, point.y, (long)mappingMode, inMarkedRange);
    if (inMarkedRange)
        *inMarkedRange = NO;
    return NSNotFound;
}


- (NSDictionary*)attributesForCharacterIndex:(NSUInteger)index lineHeightRectangle:(NSRect*)lineRect
{
    TRACE("%lu %p\n", (unsigned long)index, lineRect);
    *lineRect = NSMakeRect(100, 100, 1, 12);
    return [NSDictionary dictionary];
}


- (NSArray*)validAttributesForMarkedText
{
    TRACE("\n");
    return [NSArray array];
}


-(void)overrideKeyboardWithKeyboardNamed:(NSString*)keyboardUniqueName
{
    TRACE("%s\n", debugstr_ns(keyboardUniqueName));
}


-(void)selectInputMode:(NSString*)modeIdentifier
{
    TRACE("%s\n", debugstr_ns(modeIdentifier));

    modeChangedCallback(client, (CFStringRef)[[modeIdentifier copy] autorelease], data);
}


-(BOOL)supportsUnicode
{
    TRACE("\n");
    return TRUE;
}


-(NSString*)bundleIdentifier
{
    TRACE("\n");
    return [NSString stringWithFormat:@"com.codeweavers.CXIMKClient.%d", getpid()];
}


-(CGWindowLevel)windowLevel
{
    TRACE("\n");
    return kCGNormalWindowLevelKey;
}


-(void)insertText:(id)string
{
    TRACE("%s\n", debugstr_ns(string));

    if ([string isKindOfClass:[NSAttributedString class]])
        string = [string string];

    if (string)
        completedTextCallback(client, (CFStringRef)[[string copy] autorelease], NSNotFound, NSNotFound, data);
}

@end



struct CXIMKClient
{
    MyTextInput* ti;
    id<IMKServerProxy> im;
};


CXIMKClientRef CXIMKClientInitialize(
        CFStringRef inServerName,
        CXIMKClientUpdatedEdit inUpdateEditCallback,
        CXIMKClientCompletedText inCompletedTextCallback,
        CXIMKClientModeChanged inModeChangedCallback,
        void *data)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    CXIMKClientRef client = malloc(sizeof(*client));
    if (!client)
        goto out;

    client->ti = [[MyTextInput alloc] initWithClientRef:client
                                         updateCallback:inUpdateEditCallback
                                      completedCallback:inCompletedTextCallback
                                      modeChangedCallback:inModeChangedCallback
                                         clientData:data];
    if (!client->ti)
    {
        free(client);
        client = NULL;
        goto out;
    }

    client->im = (id<IMKServerProxy>)[NSConnection rootProxyForConnectionWithRegisteredName:(NSString*)inServerName host:nil];
    if (!client->im)
    {
        [client->ti release];
        free(client);
        client = NULL;
        goto out;
    }

    [(id)client->im retain];
    [(NSDistantObject*)client->im setProtocolForProxy:@protocol(IMKServerProxy)];

out:
    [pool release];
    return client;
}


void CXIMKClientCleanup(CXIMKClientRef inClient)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    [(id)inClient->im release];
    [inClient->ti release];
    free(inClient);

    [pool release];
}


int CXIMKClientActivate(CXIMKClientRef inClient)
{
    NSUInteger recognizedEvents;
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    [inClient->im activateServer:inClient->ti];
    recognizedEvents = [inClient->im recognizedEvents:inClient->ti];

    [pool release];

    return (recognizedEvents & NX_KEYDOWNMASK) != 0;
}


void CXIMKClientDeactivate(CXIMKClientRef inClient)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    [inClient->im deactivateServer:inClient->ti];

    [pool release];
}


void CXIMKClientSetInputMode(CXIMKClientRef inClient, CFStringRef inMode)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    [inClient->im setValue:(NSString*)inMode forTag:kTSMDocumentInputModePropertyTag client:inClient->ti];

    [pool release];

    // 10.5.3: setValue for kTSMDocumentInputModePropertyTag doesn't seem to work.
    // This is an workaround to select an input source by TISSelectInputSource.
    {
#if !defined(MAC_OS_X_VERSION_10_5) || !defined(MAC_OS_X_VERSION_MAX_ALLOWED) \
    || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
typedef void *TISInputSourceRef;           
#endif
        static CFArrayRef (*pTISCreateInputSourceList)(CFDictionaryRef, Boolean);
        static void *(*pTISGetInputSourceProperty)(TISInputSourceRef, CFStringRef);
        static OSStatus (*pTISSelectInputSource)(TISInputSourceRef);
        static CFStringRef *kTISPropertyInputModeID;
        CFArrayRef ary;
        CFIndex i, len;

        if (!pTISCreateInputSourceList || !pTISGetInputSourceProperty
            || !pTISSelectInputSource || !kTISPropertyInputModeID) {
            pTISCreateInputSourceList = dlsym(RTLD_DEFAULT,"TISCreateInputSourceList");
            pTISGetInputSourceProperty = dlsym(RTLD_DEFAULT,"TISGetInputSourceProperty");
            pTISSelectInputSource = dlsym(RTLD_DEFAULT,"TISSelectInputSource");
            kTISPropertyInputModeID = dlsym(RTLD_DEFAULT,"kTISPropertyInputModeID");
            if (!pTISCreateInputSourceList || !pTISGetInputSourceProperty
                || !pTISSelectInputSource || !kTISPropertyInputModeID) {
                TRACE("load failed\n");
                return;
            }
        }

        ary = pTISCreateInputSourceList(NULL, false);
        len = CFArrayGetCount(ary);

        for (i = 0; i < len; i++) {
            TISInputSourceRef is = (TISInputSourceRef)CFArrayGetValueAtIndex(ary, i);
            CFStringRef mode = pTISGetInputSourceProperty(is, *kTISPropertyInputModeID);
            TRACE("mode:%s\n", debugstr_ns((NSString*)mode));
            if (CFStringCompare(mode, inMode, 0) == kCFCompareEqualTo) {
                TRACE("select\n");
                pTISSelectInputSource(is);
                CFRelease(mode);
                break;
            }
        }

        CFRelease(ary);
    }
}

int CXIMKClientSendEvent(CXIMKClientRef inClient, EventRef inEvent)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    id imkEvent = [NSClassFromString(@"IMKEvent") packageEventRef:inEvent];
    int handled = [inClient->im handleEvent:imkEvent characterIndex:0 edge:0 client:inClient->ti];

    [pool release];

    return handled;
}


void CXIMKClientForceComplete(CXIMKClientRef inClient)
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    [inClient->im commitComposition:inClient->ti];

    [pool release];
}


#endif /* __APPLE__ */
