/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#include "CommonUtils.h"

jbyteArray getStringBytes(JNIEnv* jEnv, jstring jString) {
    jbyteArray result = NULL;
    
    if (jString != NULL) {
        jmethodID jGetBytesMethod = (*jEnv)->GetMethodID(jEnv, (*jEnv)->GetObjectClass(jEnv, jString), "getBytes", "()[B");
        
        if (jGetBytesMethod != NULL) {
            jbyteArray jBuffer = (jbyteArray) (*jEnv)->CallObjectMethod(jEnv, jString, jGetBytesMethod);
            
            if (jBuffer != NULL) {
                jsize jLength = (*jEnv)->GetArrayLength(jEnv, jBuffer);
                
                result = (*jEnv)->NewByteArray(jEnv, jLength + 1);
                if (jLength != 0) {
                    jbyte* jChars = (*jEnv)->GetByteArrayElements(jEnv, jBuffer, NULL);
                    (*jEnv)->SetByteArrayRegion(jEnv, result, 0, jLength, jChars);
                    (*jEnv)->ReleaseByteArrayElements(jEnv, jBuffer, jChars, JNI_ABORT);
                }
                (*jEnv)->DeleteLocalRef(jEnv, jBuffer);
            }
            //(*jEnv)->DeleteLocalRef(jEnv, getBytesMethod);
        }
    }
    
    return result;
}


jstring newStringFromJByteArray(JNIEnv* jEnv, jbyteArray jByteArray, int length) {
    jstring result = NULL;
    
    jclass jStringClass = (*jEnv)->FindClass(jEnv, "java/lang/String");
    if (jStringClass != NULL) {
        jmethodID jStringConstructor = (*jEnv)->GetMethodID(jEnv, jStringClass, "<init>", "([BII)V");
        
        if (jStringConstructor != NULL) {
            result = (jstring) (*jEnv)->NewObject(jEnv, jStringClass, jStringConstructor, jByteArray, 0, length);
            //(*jEnv)->DeleteLocalRef(jEnv, stringConstructor);
        }
        (*jEnv)->DeleteLocalRef(jEnv, jStringClass);
    }
    
    return result;
}


jstring newStringFromJCharArray(JNIEnv* jEnv, jcharArray jCharArray, int length) {
    jstring result = NULL;
    
    jclass jStringClass = (*jEnv)->FindClass(jEnv, "java/lang/String");
    if (jStringClass != NULL) {
        jmethodID jStringConstructor = (*jEnv)->GetMethodID(jEnv, jStringClass, "<init>", "([CII)V");
        
        if (jStringConstructor != NULL) {
            result = (jstring) (*jEnv)->NewObject(jEnv, jStringClass, jStringConstructor, jCharArray, 0, length);            
        }
        (*jEnv)->DeleteLocalRef(jEnv, jStringClass);
    }
    
    return result;
}

jstring getString(JNIEnv* jEnv, const char* chars) {
    return (jstring) getStringWithLength(jEnv, chars, (int) STRLEN(chars));
}

jstring getStringW(JNIEnv* jEnv, const wchar_t * chars) {
    return (jstring) getStringWithLengthW(jEnv, chars, (int) WCSLEN(chars));
}

jstring getStringWithLength(JNIEnv* jEnv, const char* chars, int length) {
    jstring result = NULL;
    
    if (chars != NULL) {
        if (length == 0) {
            result = (*jEnv)->NewString(jEnv, (const jchar *) L"", 0);
        } else {
            jbyteArray jByteArray = (*jEnv)->NewByteArray(jEnv, length);
            
            if (jByteArray != NULL) {
                (*jEnv)->SetByteArrayRegion(jEnv, jByteArray, 0, length, (jbyte*) chars);
                result = newStringFromJByteArray(jEnv, jByteArray, length);
                (*jEnv)->DeleteLocalRef(jEnv, jByteArray);
            }
        }
    }
    
    return result;
}


jstring getStringWithLengthW(JNIEnv* jEnv, const wchar_t* chars, int length) {
    jstring result = NULL;
    
    if (chars != NULL) {
        if (length == 0) {
            result = (*jEnv)->NewString(jEnv, (const jchar *) L"", 0);
        } else {
            jcharArray jCharArray = (*jEnv)->NewCharArray(jEnv, length);
            
            if (jCharArray != NULL) {
                (*jEnv)->SetCharArrayRegion(jEnv, jCharArray, 0, length, (jchar*) chars);
                result = newStringFromJCharArray(jEnv, jCharArray, length);
                (*jEnv)->DeleteLocalRef(jEnv, jCharArray);
            }
        }
    }
    
    return result;
}


char* getChars(JNIEnv* jEnv, jstring jString) {
    char* result = NULL;
    
    jbyteArray jByteArray = getStringBytes(jEnv, jString);
    if (jByteArray != NULL) {
        jbyte* jBytes = (*jEnv)->GetByteArrayElements(jEnv, jByteArray, NULL);
        
        long index = 0;
        if (jBytes != NULL) {
            int length = (int) STRLEN((char*) jBytes);
            
            result = (char*) MALLOC(sizeof(char) * (length + 1));
            if (result != NULL) {
                ZERO(result, length);
                STRNCPY(result, (char*) jBytes, length);
                result[length] = 0;
            }
            (*jEnv)->ReleaseByteArrayElements(jEnv, jByteArray, jBytes, JNI_ABORT);
        }
        (*jEnv)->DeleteLocalRef(jEnv, jByteArray);
    }
    
    return result;
}

char* getStringFromMethod(JNIEnv* jEnv, jobject object, const char* methodName) {
    char* result = NULL;
    
    jclass clazz = (*jEnv)->GetObjectClass(jEnv, object);
    if (clazz != NULL) {
        jmethodID method = (*jEnv)->GetMethodID(jEnv, clazz, methodName, "()Ljava/lang/String;");
        if (method != NULL) {
            jstring string = (jstring) (*jEnv)->CallObjectMethod(jEnv, object, method);
            if (string != NULL) {
                result = getChars(jEnv, string);
                (*jEnv)->DeleteLocalRef(jEnv, string);
            }
            //(*jEnv)->DeleteLocalRef(jEnv, method);
        }
        (*jEnv)->DeleteLocalRef(jEnv, clazz);
    }
    
    return result;
}

wchar_t * getWideStringFromMethod(JNIEnv* jEnv, jobject object, const char* methodName) {
    wchar_t* result = NULL;
    
    jclass clazz = (*jEnv)->GetObjectClass(jEnv, object);
    if (clazz != NULL) {
        jmethodID method = (*jEnv)->GetMethodID(jEnv, clazz, methodName, "()Ljava/lang/String;");
        if (method != NULL) {
            jstring string = (jstring) (*jEnv)->CallObjectMethod(jEnv, object, method);
            if (string != NULL) {
                result = getWideChars(jEnv, string);
                (*jEnv)->DeleteLocalRef(jEnv, string);
            }
            //(*jEnv)->DeleteLocalRef(jEnv, method);
        }
        (*jEnv)->DeleteLocalRef(jEnv, clazz);
    }
    
    return result;
}

jboolean isInstanceOf(JNIEnv* jEnv, jobject object, const char* className) {
    jboolean result = 0;
    jclass clazz = clazz = (*jEnv)->FindClass(jEnv, className);
    
    if (clazz != NULL) {
        result = (*jEnv)->IsInstanceOf(jEnv, object, clazz);
        (*jEnv)->DeleteLocalRef(jEnv, clazz);
    }
    return result;
}

jint getIntFromMethod(JNIEnv* jEnv, jobject object, const char* methodName) {
    jint value = 0;
    
    jclass clazz = (*jEnv)->GetObjectClass(jEnv, object);
    if (clazz != NULL) {
        jmethodID method = (*jEnv)->GetMethodID(jEnv, clazz, methodName, "()I");
        if (method != NULL) {
            value = (*jEnv)->CallIntMethod(jEnv, object, method);
        }
        (*jEnv)->DeleteLocalRef(jEnv, clazz);
    }
    
    return value;
}

void throwException(JNIEnv* jEnv, const char* message) {
    jclass clazz = (*jEnv)->FindClass(jEnv, "org/netbeans/installer/utils/exceptions/NativeException");
    if (clazz != NULL) {
        (*jEnv)->ThrowNew(jEnv, clazz, message);
        (*jEnv)->DeleteLocalRef(jEnv, clazz);
    }
}

void writeLog(JNIEnv* jEnv, int level, const char* message) {
    const char* prefix = "[jni] ";
    
    jclass clazz = (*jEnv)->FindClass(jEnv, "org/netbeans/installer/utils/LogManager");
    if (clazz != NULL) {
        jmethodID method = (*jEnv)->GetStaticMethodID(jEnv, clazz, "log", "(ILjava/lang/String;)V");
        if (method != NULL) {
            jstring jMessage = NULL;
            int prefix_length = STRLEN(prefix);
            int message_length = STRLEN(message);
            char* string = (char*) MALLOC(sizeof(char) * (prefix_length + message_length + 1));
            int i = 0;
            for(i=0;i<prefix_length;i++) {
               string[i]=prefix[i];
            }
            
            for(i=0;i<message_length;i++) {
               string[i + prefix_length]=message[i];
            }
            string[i+prefix_length + message_length] = '\0';

            
            jMessage = getString(jEnv, string);
            
            if (jMessage != NULL) {
                (*jEnv)->CallStaticVoidMethod(jEnv, clazz, method, (jint) level, jMessage);
                (*jEnv)->DeleteLocalRef(jEnv, jMessage);
            }
            
            FREE(string);
            //(*jEnv)->DeleteLocalRef(jEnv, method);
        }
        (*jEnv)->DeleteLocalRef(jEnv, clazz);
    }
}

int createDirs(JNIEnv* jEnv, jstring jPath) {
    int result = 0;
    jclass jFileClass = (*jEnv)->FindClass(jEnv, "java/io/File");
    if (jFileClass != NULL) {
        jmethodID jFileConstructor     = (*jEnv)->GetMethodID(jEnv, jFileClass, "<init>", "(Ljava/lang/String;)V");
        jmethodID jGetParentFileMethod = (*jEnv)->GetMethodID(jEnv, jFileClass, "getParentFile", "()Ljava/io/File;");
        jmethodID jExistsMethod        = (*jEnv)->GetMethodID(jEnv, jFileClass, "exists", "()Z");
        jmethodID jMkdirsMethod        = (*jEnv)->GetMethodID(jEnv, jFileClass, "mkdirs", "()Z");
        
        if ((jFileConstructor != NULL) && (jGetParentFileMethod != NULL) && (jExistsMethod != NULL) && (jMkdirsMethod != NULL)) {            
            jobject jFile = (*jEnv)->NewObject(jEnv, jFileClass, jFileConstructor, jPath);
            if (jFile != NULL ) {
                if(!((*jEnv)->CallBooleanMethod(jEnv, jFile, jExistsMethod))) {
                    jobject jParent = (*jEnv)->CallObjectMethod(jEnv, jFile, jGetParentFileMethod);
                    if (jParent != NULL) {
                        result = (*jEnv)->CallBooleanMethod(jEnv, jParent, jExistsMethod);
                        if (!result) {
                            result = (*jEnv)->CallBooleanMethod(jEnv, jParent, jMkdirsMethod);
                        }
                        (*jEnv)->DeleteLocalRef(jEnv, jParent);
                    }
                } else {
                    result = 1;
                }
                (*jEnv)->DeleteLocalRef(jEnv, jFile);
            }
        }
        (*jEnv)->DeleteLocalRef(jEnv, jFileClass);
    }
    return result;
}

int mkdirs(JNIEnv* jEnv, const char *path) {
    int result = 1;
    jstring jPath  = getString(jEnv, path);
    if (jPath != NULL) {
        result = createDirs(jEnv, jPath);
        (*jEnv)->DeleteLocalRef(jEnv, jPath);
    }
    return result;
}


int mkdirsW(JNIEnv* jEnv, const wchar_t *path) {
    int result = 1;
    jstring jPath  = getStringW(jEnv, path);
    if (jPath != NULL) {        
        result = createDirs(jEnv, jPath);
        (*jEnv)->DeleteLocalRef(jEnv, jPath);
    }
    
    return result;
}



unsigned char* getByteFromMultiString(JNIEnv *jEnv, jobjectArray jObjectArray, unsigned long* size) {
    unsigned short * result = NULL;
    
    int     totalLength = 0;
    unsigned int arrayLength = (*jEnv)->GetArrayLength(jEnv, jObjectArray);
    jstring jString     = NULL;
    
    unsigned int i, j; // just counters
    
    for (i = 0; i < arrayLength; i++) {
        jString = (jstring) (*jEnv)->GetObjectArrayElement(jEnv, jObjectArray, i);
        totalLength += (*jEnv)->GetStringLength(jEnv, jString) + 1;
    }
    totalLength++; // add null to the end of array
      
    result = (unsigned short*) MALLOC(sizeof(unsigned short) * totalLength);
    if (result != NULL) {
        int index = 0 ; 
        
        for (i = 0; i < arrayLength; i++) {
            jString = (jstring) (*jEnv)->GetObjectArrayElement(jEnv, jObjectArray, i);
            
            if (jString != NULL) {
                wchar_t * chars = getWideChars(jEnv, jString);
                if (chars != NULL) {
                    for (j = 0; j < (WCSLEN(chars)); j++) {
                        result[index++] = chars[j];
                    }
                    
                    FREE(chars);
                }
            }
            result[index++] = '\0';            
        }
        result[index++] = '\0'; //double \0 at the end        
    }    
    * size = sizeof(unsigned short) * totalLength;
    return (unsigned char*)result;
}

wchar_t * getWideChars(JNIEnv *jEnv, jstring jString) {
    if(jString==NULL) {
        return NULL;
    } else {
        long length = (*jEnv)->GetStringLength( jEnv, jString);
        const jchar * unicodeStr = (*jEnv)->GetStringChars( jEnv, jString, 0 );
        wchar_t * copy = (wchar_t *) MALLOC(sizeof(wchar_t) * (length + 1));
        ZERO(copy, sizeof(wchar_t)*(length + 1));        
        WCSNCPY(copy, (const wchar_t *) unicodeStr, length + 1);
        (*jEnv)->ReleaseStringChars( jEnv, jString, unicodeStr);
        return copy;
    }
    
}
