/*
 * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#import <Cocoa/Cocoa.h>
#include <objc/objc-runtime.h>

#import "sun_lwawt_macosx_CInputMethod.h"
#import "sun_lwawt_macosx_CInputMethodDescriptor.h"
#import "ThreadUtilities.h"
#import "AWTView.h"
#import "JNIUtilities.h"

#import <JavaRuntimeSupport/JavaRuntimeSupport.h>

#define JAVA_LIST @"JAVA_LIST"
#define CURRENT_KB_DESCRIPTION @"CURRENT_KB_DESCRIPTION"

//
// NOTE: This returns a JNI Local Ref. Any code that calls must call DeleteLocalRef with the return value.
//
static jobject CreateLocaleObjectFromNSString(JNIEnv *env, NSString *name)
{
    DECLARE_CLASS_RETURN(jc_localeClass, "java/util/Locale", NULL);
    DECLARE_METHOD_RETURN(jm_localeCons, jc_localeClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", NULL);
    // Break apart the string into its components.
    // First, duplicate the NSString into a C string, since we're going to modify it.
    char * language = strdup([name UTF8String]);
    char * country;
    char * variant;

    // Convert _ to NULL -- this gives us three null terminated strings in place.
    for (country = language; *country != '_' && *country != '\0'; country++);
    if (*country == '_') {
        *country++ = '\0';
        for (variant = country; *variant != '_' && *variant != '\0'; variant++);
        if (*variant == '_') {
            *variant++ = '\0';
        }
    } else {
        variant = country;
    }

    // Create the java.util.Locale object
    jobject localeObj = NULL;
    jobject langObj = (*env)->NewStringUTF(env, language);
    if (langObj != NULL) {
        jobject ctryObj = (*env)->NewStringUTF(env, country);
        if(ctryObj != NULL) {
            jobject vrntObj = (*env)->NewStringUTF(env, variant);
            if (vrntObj != NULL) {
                localeObj = (*env)->NewObject(env, jc_localeClass, jm_localeCons,langObj, ctryObj,
                                         vrntObj);
                CHECK_EXCEPTION();
                (*env)->DeleteLocalRef(env, vrntObj);
            }
            (*env)->DeleteLocalRef(env, ctryObj);
        }
        (*env)->DeleteLocalRef(env, langObj);
    }
    // Clean up and return.
    free(language);
    return localeObj;
}

static id inputMethodController = nil;

static void initializeInputMethodController() {
    static BOOL checkedJRSInputMethodController = NO;
    if (!checkedJRSInputMethodController && (inputMethodController == nil)) {
        id jrsInputMethodController = objc_lookUpClass("JRSInputMethodController");
        if (jrsInputMethodController != nil) {
            inputMethodController = [jrsInputMethodController performSelector:@selector(controller)];
        }
        checkedJRSInputMethodController = YES;
    }
}


@interface CInputMethod : NSObject {}
@end

@implementation CInputMethod

+ (void) setKeyboardLayout:(NSString *)theLocale
{
    AWT_ASSERT_APPKIT_THREAD;
    if (!inputMethodController) return;

    [inputMethodController performSelector:@selector(setCurrentInputMethodForLocale) withObject:theLocale];
}

+ (void) _nativeNotifyPeerWithView:(AWTView *)view inputMethod:(jobject) inputMethod {
    AWT_ASSERT_APPKIT_THREAD;

    if (!view) return;
    [view setInputMethod:inputMethod]; // inputMethod is a GlobalRef or null to disable.
}

+ (void) _nativeEndComposition:(AWTView *)view {
    if (view == nil) return;

    [view abandonInput];
}


@end

/*
 * Class:     sun_lwawt_macosx_CInputMethod
 * Method:    nativeInit
 * Signature: ();
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeInit
(JNIEnv *env, jclass klass)
{
    initializeInputMethodController();
}

/*
 * Class:     sun_lwawt_macosx_CInputMethod
 * Method:    nativeGetCurrentInputMethodInfo
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeGetCurrentInputMethodInfo
(JNIEnv *env, jclass klass)
{
    if (!inputMethodController) return NULL;
    jobject returnValue = 0;
    __block NSString *keyboardInfo = NULL;
JNI_COCOA_ENTER(env);

    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
        keyboardInfo = [inputMethodController performSelector:@selector(currentInputMethodName)];
        [keyboardInfo retain];
    }];

    if (keyboardInfo == nil) return NULL;
    returnValue = NSStringToJavaString(env, keyboardInfo);
    [keyboardInfo release];

JNI_COCOA_EXIT(env);
    return returnValue;
}

/*
 * Class:     sun_lwawt_macosx_CInputMethod
 * Method:    nativeActivate
 * Signature: (JLsun/lwawt/macosx/CInputMethod;)V
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeNotifyPeer
(JNIEnv *env, jobject this, jlong nativePeer, jobject inputMethod)
{
JNI_COCOA_ENTER(env);
    AWTView *view = (AWTView *)jlong_to_ptr(nativePeer);
    jobject inputMethodRef = (*env)->NewGlobalRef(env, inputMethod);
    [ThreadUtilities performOnMainThreadWaiting:NO block:^(){
        [CInputMethod _nativeNotifyPeerWithView:view inputMethod:inputMethodRef];
    }];

JNI_COCOA_EXIT(env);

}

/*
 * Class:     sun_lwawt_macosx_CInputMethod
 * Method:    nativeEndComposition
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethod_nativeEndComposition
(JNIEnv *env, jobject this, jlong nativePeer)
{
JNI_COCOA_ENTER(env);
   AWTView *view = (AWTView *)jlong_to_ptr(nativePeer);

   [ThreadUtilities performOnMainThreadWaiting:NO block:^(){
        [CInputMethod _nativeEndComposition:view];
    }];

JNI_COCOA_EXIT(env);
}

/*
 * Class:     sun_lwawt_macosx_CInputMethod
 * Method:    getNativeLocale
 * Signature: ()Ljava/util/Locale;
 */
JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CInputMethod_getNativeLocale
(JNIEnv *env, jobject this)
{
    if (!inputMethodController) return NULL;
    jobject returnValue = 0;
    __block NSString *isoAbbreviation;
JNI_COCOA_ENTER(env);

    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
        isoAbbreviation = (NSString *) [inputMethodController performSelector:@selector(currentInputMethodLocale)];
        [isoAbbreviation retain];
    }];

    if (isoAbbreviation == nil) return NULL;

    static NSString *sLastKeyboardStr = nil;
    static jobject sLastKeyboardLocaleObj = NULL;

    if (![isoAbbreviation isEqualTo:sLastKeyboardStr]) {
        [sLastKeyboardStr release];
        sLastKeyboardStr = [isoAbbreviation retain];
        jobject localObj = CreateLocaleObjectFromNSString(env, isoAbbreviation);
        [isoAbbreviation release];

        if (sLastKeyboardLocaleObj) {
            (*env)->DeleteGlobalRef(env, sLastKeyboardLocaleObj);
            sLastKeyboardLocaleObj = NULL;
        }
        if (localObj != NULL) {
            sLastKeyboardLocaleObj = (*env)->NewGlobalRef(env, localObj);
            (*env)->DeleteLocalRef(env, localObj);
        }
    }

    returnValue = sLastKeyboardLocaleObj;

JNI_COCOA_EXIT(env);
    return returnValue;
}


/*
 * Class:     sun_lwawt_macosx_CInputMethod
 * Method:    setNativeLocale
 * Signature: (Ljava/lang/String;Z)Z
 */
JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CInputMethod_setNativeLocale
(JNIEnv *env, jobject this, jstring locale, jboolean isActivating)
{
JNI_COCOA_ENTER(env);
    NSString *localeStr = JavaStringToNSString(env, locale);
    [localeStr retain];

    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
        [CInputMethod setKeyboardLayout:localeStr];
    }];

    [localeStr release];
JNI_COCOA_EXIT(env);
    return JNI_TRUE;
}

/*
 * Class:     sun_lwawt_macosx_CInputMethodDescriptor
 * Method:    nativeInit
 * Signature: ();
 */
JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CInputMethodDescriptor_nativeInit
(JNIEnv *env, jclass klass)
{
    initializeInputMethodController();
}

/*
 * Class:     sun_lwawt_macosx_CInputMethodDescriptor
 * Method:    nativeGetAvailableLocales
 * Signature: ()[Ljava/util/Locale;
     */
JNIEXPORT jobject JNICALL Java_sun_lwawt_macosx_CInputMethodDescriptor_nativeGetAvailableLocales
(JNIEnv *env, jclass klass)
{
    DECLARE_CLASS_RETURN(jc_arrayListClass, "java/util/ArrayList", NULL);
    DECLARE_METHOD_RETURN(jm_arrayListCons, jc_arrayListClass, "<init>", "()V", NULL);
    DECLARE_METHOD_RETURN(jm_listAdd, jc_arrayListClass, "add", "(Ljava/lang/Object;)Z", NULL);
    DECLARE_METHOD_RETURN(jm_listContains, jc_arrayListClass, "contains", "(Ljava/lang/Object;)Z", NULL);

    if (!inputMethodController) return NULL;
    jobject returnValue = 0;

    __block NSArray *selectableArray = nil;
JNI_COCOA_ENTER(env);

    [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
        selectableArray = (NSArray *)[inputMethodController performSelector:@selector(availableInputMethodLocales)];
        [selectableArray retain];
    }];

    if (selectableArray == nil) return NULL;

     // Create an ArrayList to return back to the caller.
    returnValue = (*env)->NewObject(env, jc_arrayListClass, jm_arrayListCons);
    CHECK_EXCEPTION_NULL_RETURN(returnValue, NULL);

    for(NSString *locale in selectableArray) {
        jobject localeObj = CreateLocaleObjectFromNSString(env, locale);
        if (localeObj == NULL) {
            break;
        }

        if ((*env)->CallBooleanMethod(env, returnValue, jm_listContains, localeObj) == JNI_FALSE) {
            if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
            (*env)->CallBooleanMethod(env, returnValue, jm_listAdd, localeObj);
        }
        if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);

        (*env)->DeleteLocalRef(env, localeObj);
    }
    [selectableArray release];
JNI_COCOA_EXIT(env);
    return returnValue;
}

