#include "jvm_utils.h"

enum {
    NO_ENCODING_YET = 0,	/* "file.encoding" not yet set */
    NO_FAST_ENCODING,		/* Platform encoding is not fast */
    FAST_8859_1,		/* ISO-8859-1 */
    FAST_CP1252			/* MS-DOS Cp1252 */
};

static int fastEncoding = NO_ENCODING_YET;

static jmethodID String_getBytes_ID;	/* String.getBytes() */
static jmethodID String_init_ID;	/* String(byte[]) */

static void
initializeEncoding(JNIEnv *env)
{
    jstring propname = 0;
    jstring enc = 0;

    if ((*env)->EnsureLocalCapacity(env, 3) < 0)
        return;

    propname = (*env)->NewStringUTF(env, "file.encoding");
    if (propname) {
        jboolean exc;
        enc = JNU_CallStaticMethodByName
	               (env,
			&exc,
			"java/lang/System",
			"getProperty",
			"(Ljava/lang/String;)Ljava/lang/String;",
			propname).l;
	if (!exc) {
	    if (enc) {
	        const char* encname = (*env)->GetStringUTFChars(env, enc, 0);
		if (encname) {
		    if ((strcmp(encname, "8859_1") == 0) || 
                        (strcmp(encname, "ISO8859-1") == 0) || 
                        (strcmp(encname, "ISO8859_1") == 0))
		        fastEncoding = FAST_8859_1;
                    else if (strcmp(encname, "Cp1252") == 0)
                        fastEncoding = FAST_CP1252;
                    else
                        fastEncoding = NO_FAST_ENCODING;
		    (*env)->ReleaseStringUTFChars(env, enc, encname);
		}
	    }
	} else {
	    (*env)->ExceptionClear(env);
	}
    } else {
        (*env)->ExceptionClear(env);
    }
    (*env)->DeleteLocalRef(env, propname);
    (*env)->DeleteLocalRef(env, enc);

    /* Initialize method-id cache */
    String_getBytes_ID = (*env)->GetMethodID(env, JNU_ClassString(env),
					     "getBytes", "()[B");
    String_init_ID = (*env)->GetMethodID(env, JNU_ClassString(env),
					 "<init>", "([B)V");
}

static const char*
getString8859_1Chars(JNIEnv *env, jstring jstr)
{
    int i;
    char *result;
    jint len = (*env)->GetStringLength(env, jstr);
    const jchar *str = (*env)->GetStringCritical(env, jstr, 0);
    if (str == 0) {
	return 0;
    }
    result = (char *)malloc(len + 1);
    if (result == 0) {
        (*env)->ReleaseStringCritical(env, jstr, str);
        JNU_ThrowOutOfMemoryError(env, 0);
	return 0;
    }
    for (i=0; i<len; i++)
        result[i] = str[i];
    result[len] = 0;
    (*env)->ReleaseStringCritical(env, jstr, str);
    return result;
}

static const char*
getStringCp1252Chars(JNIEnv *env, jstring jstr)
{
    int i;
    char *result;
    jint len = (*env)->GetStringLength(env, jstr);
    const jchar *str = (*env)->GetStringCritical(env, jstr, 0);
    if (str == 0) {
	return 0;
    }
    result = (char *)malloc(len + 1);
    if (result == 0) {
        (*env)->ReleaseStringCritical(env, jstr, str);
        JNU_ThrowOutOfMemoryError(env, 0);
	return 0;
    }

    for (i=0; i<len; i++) {
        jchar c = str[i];
        if (c < 256)
            result[i] = c;
        else switch(c) {
            case 0x20AC: result[i] = (char)0x80; break;
            case 0x201A: result[i] = (char)0x82; break;
            case 0x0192: result[i] = (char)0x83; break;
            case 0x201E: result[i] = (char)0x84; break;
            case 0x2026: result[i] = (char)0x85; break;
            case 0x2020: result[i] = (char)0x86; break;
            case 0x2021: result[i] = (char)0x87; break;
            case 0x02C6: result[i] = (char)0x88; break;
            case 0x2030: result[i] = (char)0x89; break;
            case 0x0160: result[i] = (char)0x8A; break;
            case 0x2039: result[i] = (char)0x8B; break;
            case 0x0152: result[i] = (char)0x8C; break;
            case 0x2018: result[i] = (char)0x91; break;
            case 0x2019: result[i] = (char)0x92; break;
            case 0x201C: result[i] = (char)0x93; break;
            case 0x201D: result[i] = (char)0x94; break;
            case 0x2022: result[i] = (char)0x95; break;
            case 0x2013: result[i] = (char)0x96; break;
            case 0x2014: result[i] = (char)0x97; break;
            case 0x02DC: result[i] = (char)0x98; break;
            case 0x2122: result[i] = (char)0x99; break;
            case 0x0161: result[i] = (char)0x9A; break;
            case 0x203A: result[i] = (char)0x9B; break;
            case 0x0153: result[i] = (char)0x9C; break;
            case 0x0178: result[i] = (char)0x9F; break;
            default:     result[i] = '?';  break;
        }
    }

    result[len] = 0;
    (*env)->ReleaseStringCritical(env, jstr, str);
    return result;
}

/*
 * Load the net library
 */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved)
{
    JNIEnv *env;
    if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_2) == JNI_OK) {
        if (JVM_InitializeSocketLibrary() < 0) {
	    JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError",
			    "failed to initialize net library.");
	}
    }
    return JNI_VERSION_1_2;
}

JNIEXPORT const char * JNICALL
JNU_GetStringPlatformChars(JNIEnv *env, jstring jstr, jboolean *isCopy)
{
    jbyteArray hab = 0;
    char *result = 0;

    if (isCopy)
        *isCopy = JNI_TRUE;

    if (fastEncoding == NO_ENCODING_YET)
        initializeEncoding(env);

    if ((fastEncoding == FAST_8859_1) || (fastEncoding == NO_ENCODING_YET))
        return getString8859_1Chars(env, jstr);
    if (fastEncoding == FAST_CP1252)
        return getStringCp1252Chars(env, jstr);
    
    if ((*env)->EnsureLocalCapacity(env, 2) < 0)
        return 0;

    hab = (*env)->CallObjectMethod(env, jstr, String_getBytes_ID);

    if (!(*env)->ExceptionCheck(env)) {
        jint len = (*env)->GetArrayLength(env, hab);
        result = (char *)malloc(len + 1);
	if (result == 0) {
	    JNU_ThrowOutOfMemoryError(env, 0);
	    (*env)->DeleteLocalRef(env, hab);
	    return 0;
	}
	(*env)->GetByteArrayRegion(env, hab, 0, len, (jbyte *)result);
	result[len] = 0; /* NULL-terminate */
    }

    (*env)->DeleteLocalRef(env, hab);
    return result;
}

JNIEXPORT jvalue JNICALL
JNU_CallStaticMethodByName(JNIEnv *env, 
			   jboolean *hasException,
			   const char *class_name, 
			   const char *name, 
			   const char *signature,
			   ...)
{
    jclass clazz;
    jmethodID mid;
    va_list args;
    jvalue result;
    const char *p = signature;

    /* find out the return type */
    while (*p && *p != ')')
        p++;
    p++;

    result.i = 0;

    if ((*env)->EnsureLocalCapacity(env, 3) < 0)
        goto done2;

    clazz = (*env)->FindClass(env, class_name);
    if (clazz == 0)
        goto done2;
    mid = (*env)->GetStaticMethodID(env, clazz, name, signature);
    if (mid == 0)
        goto done1;
    va_start(args, signature);
    switch (*p) {
    case 'V':
        (*env)->CallStaticVoidMethodV(env, clazz, mid, args); 
	break;
    case '[':
    case 'L':
        result.l = (*env)->CallStaticObjectMethodV(env, clazz, mid, args); 
	break;
    case 'Z':
        result.z = (*env)->CallStaticBooleanMethodV(env, clazz, mid, args); 
	break;
    case 'B':
        result.b = (*env)->CallStaticByteMethodV(env, clazz, mid, args); 
	break;
    case 'C':
        result.c = (*env)->CallStaticCharMethodV(env, clazz, mid, args); 
	break;
    case 'S':
        result.s = (*env)->CallStaticShortMethodV(env, clazz, mid, args); 
	break;
    case 'I':
        result.i = (*env)->CallStaticIntMethodV(env, clazz, mid, args); 
	break;
    case 'J':
        result.j = (*env)->CallStaticLongMethodV(env, clazz, mid, args); 
	break;
    case 'F':
        result.f = (*env)->CallStaticFloatMethodV(env, clazz, mid, args); 
	break;
    case 'D':
        result.d = (*env)->CallStaticDoubleMethodV(env, clazz, mid, args); 
	break;
    default:
        (*env)->FatalError(env, "JNU_CallStaticMethodByName: illegal signature");
    }
    va_end(args);

 done1:
    (*env)->DeleteLocalRef(env, clazz);
 done2:
    if (hasException) {
        *hasException = (*env)->ExceptionCheck(env);
    }
    return result;    
}

JNIEXPORT jclass JNICALL 
JNU_ClassString(JNIEnv *env)
{
    static jclass cls = 0;
    if (cls == 0) {
        jclass c;
        if ((*env)->EnsureLocalCapacity(env, 1) < 0)
	    return 0;
        c = (*env)->FindClass(env, "java/lang/String");
	cls = (*env)->NewGlobalRef(env, c);
	(*env)->DeleteLocalRef(env, c);
    }
    return cls;
}

