
# Arguments:
# SRCPATH = path to classes
# DSTPATH = output file
# JDKROOT = JDK root directory

cmake_policy(SET CMP0007 NEW)

get_filename_component(SRCPATH "${SRCPATH}" ABSOLUTE)
get_filename_component(DSTPATH "${DSTPATH}" ABSOLUTE)

file(MAKE_DIRECTORY "${DSTPATH}")

get_filename_component(basePath "${CMAKE_CURRENT_LIST_FILE}" ABSOLUTE)
get_filename_component(basePath "${basePath}" DIRECTORY)
include("${basePath}/../../CMake/Bin2C.cmake")

macro(writeIfChanged outfile newContents)
    get_filename_component(name "${outfile}" NAME)
    if(EXISTS "${outfile}")
        file(READ "${outfile}" oldContents)
        if("${oldContents}" STREQUAL "${newContents}")
            message(STATUS "Keeping ${name}")
            file(TOUCH_NOCREATE "${outfile}")
        else()
            message(STATUS "Writing ${name}")
            file(WRITE "${outfile}" "${newContents}")
        endif()
    else()
        message(STATUS "Writing ${name}")
        file(WRITE "${outfile}" "${newContents}")
    endif()
endmacro()

file(GLOB_RECURSE classes
    RELATIVE "${SRCPATH}"
    "${SRCPATH}/*.class"
    )

set(cls "")
set(use "")
set(cxx "")
set(ini "")
set(fin "")
set(ext "")

set(cod "${cod}\n")
set(cod "${cod}class JavaClasses : public JavaStdClasses\n")
set(cod "${cod}{\n")
set(cod "${cod}public:\n")

foreach(fileName ${classes})
    if("${fileName}" MATCHES "^drunkfly/jni/JNITool")
        continue()
    endif()

    # Get file name length
    string(LENGTH "${fileName}" fileNameLength)

    # Strip ".class" from the end
    math(EXPR fileNameNewLength "${fileNameLength} - 6")
    string(SUBSTRING "${fileName}" 0 "${fileNameNewLength}" className)

    # Replace '/' and '$' with '_' for symbol name
    string(REPLACE "/" "_" classSymbol "${className}")
    string(REPLACE "$" "_" classSymbol "${classSymbol}")

    set(cls "${cls}    static CLASSREF_${classSymbol} ${classSymbol};\n")
    set(ini "${ini}    ${classSymbol}.loadGlobal(\"${className}\");\n")
    set(fin "${fin}    ${classSymbol}.release();\n")
    set(cxx "${cxx}JavaClasses::CLASSREF_${classSymbol} JavaClasses::${classSymbol};\n")

    # Get fields
    set(cod1 "")
    set(cod2 "")
    set(cod3 "")
    set(cod4 "")
    set(nat "")
    set(release FALSE)
    set(hasNatives FALSE)
    set(empty TRUE)
    if("${className}" MATCHES "^drunkfly/")
        execute_process(COMMAND
                "${JDKROOT}/bin/java"
                    -cp "${CMAKE_CURRENT_SOURCE_DIR}/Build/Tools"
                    drunkfly.jni.JNITool
                    "${className}"
                WORKING_DIRECTORY "${SRCPATH}"
                OUTPUT_VARIABLE jniOutput
                COMMAND_ERROR_IS_FATAL ANY
            )

        foreach(it ${jniOutput})
            string(REPLACE "|" ";" list "${it}")
            list(GET list 0 what)

            if("${what}" STREQUAL "field")
                list(GET list 1 desc)
                list(GET list 2 type)
                list(GET list 3 name)
                list(GET list 4 meth)

                string(REPLACE "@" ";" desc "${desc}")
                string(REPLACE "!" "[" desc "${desc}")

                string(SUBSTRING "${name}" 0 1 firstLetter)
                string(TOUPPER "${firstLetter}" firstLetter)
                string(REGEX REPLACE "^.(.*)" "${firstLetter}\\1" capitalizedName "${name}")

                string(MD5 nameID "${desc}")
                set(nameID "${name}_${nameID}_ID")

                set(cod1 "${cod1}            resolveField(${nameID}, \"${name}\", \"${desc}\");\n")
                set(cod2 "${cod2}        jfieldID ${nameID} = 0;\n")

                set(cod3 "${cod3}\n")
                set(cod3 "${cod3}        ${type} get${capitalizedName}(jobject self) const\n")
                set(cod3 "${cod3}        {\n")
                set(cod3 "${cod3}            assert(${nameID});\n")
                set(cod3 "${cod3}            assert(self);\n")
                set(cod3 "${cod3}            if (!${nameID} || !self)\n")
                set(cod3 "${cod3}                throw CompilerError(nullptr, \"Internal compiler error.\");\n")
                set(cod3 "${cod3}            auto env = JVM::jniEnv();\n")
                set(cod3 "${cod3}            ${type} value = env->vtbl->Get${meth}Field(env, self, ${nameID});\n")
                set(cod3 "${cod3}            JNIThrowableRef::rethrowCurrentException();\n")
                set(cod3 "${cod3}            return value;\n")
                set(cod3 "${cod3}        }\n")
                set(cod3 "${cod3}\n")
                set(cod3 "${cod3}        void set${capitalizedName}(jobject self, ${type} value) const\n")
                set(cod3 "${cod3}        {\n")
                set(cod3 "${cod3}            assert(${nameID});\n")
                set(cod3 "${cod3}            assert(self);\n")
                set(cod3 "${cod3}            if (!${nameID} || !self)\n")
                set(cod3 "${cod3}                throw CompilerError(nullptr, \"Internal compiler error.\");\n")
                set(cod3 "${cod3}            auto env = JVM::jniEnv();\n")
                set(cod3 "${cod3}            env->vtbl->Set${meth}Field(env, self, ${nameID}, value);\n")
                set(cod3 "${cod3}            JNIThrowableRef::rethrowCurrentException();\n")
                set(cod3 "${cod3}        }\n")

                set(cod4 "${cod4}            ${nameID} = 0;\n")
                set(release TRUE)
            elseif("${what}" STREQUAL "method")
                list(GET list 1 desc)
                list(GET list 2 name)
                list(GET list 3 type)
                list(GET list 4 args)
                list(GET list 5 call)
                list(GET list 6 meth)

                string(REPLACE "@" ";" desc "${desc}")
                string(REPLACE "!" "[" desc "${desc}")

                if("${name}" STREQUAL "<init>")
                    string(MD5 nameID "${desc}")
                    set(nameID "constructor_${nameID}_ID")

                    set(cod1 "${cod1}            resolveMethod(${nameID}, \"<init>\", \"${desc}\");\n")
                    set(cod2 "${cod2}        jmethodID ${nameID} = 0;\n")

                    set(cod3 "${cod3}\n")
                    set(cod3 "${cod3}        JNIRef construct(${args}) const\n")
                    set(cod3 "${cod3}        {\n")
                    set(cod3 "${cod3}            assert(${nameID});\n")
                    set(cod3 "${cod3}            if (!${nameID})\n")
                    set(cod3 "${cod3}                throw CompilerError(nullptr, \"Internal compiler error.\");\n")
                    set(cod3 "${cod3}            auto env = JVM::jniEnv();\n")
                    set(cod3 "${cod3}            JNIRef result = env->vtbl->NewObject(env, toJNI(), ${nameID}${call});\n")
                    set(cod3 "${cod3}            if (!result)\n")
                    set(cod3 "${cod3}                JNIThrowableRef::rethrowCurrentException();\n")
                    set(cod3 "${cod3}            return result;\n")
                    set(cod3 "${cod3}        }\n")

                    set(cod4 "${cod4}            ${nameID} = 0;\n")
                    set(release TRUE)
                else()
                    string(MD5 nameID "${desc}")
                    set(nameID "${name}_${nameID}_ID")

                    set(cod1 "${cod1}            resolveMethod(${nameID}, \"${name}\", \"${desc}\");\n")
                    set(cod2 "${cod2}        jmethodID ${nameID} = 0;\n")

                    set(cod3 "${cod3}\n")
                    set(cod3 "${cod3}        ${type} ${name}(jobject self${args}) const\n")
                    set(cod3 "${cod3}        {\n")
                    set(cod3 "${cod3}            assert(${nameID});\n")
                    set(cod3 "${cod3}            assert(self);\n")
                    set(cod3 "${cod3}            if (!${nameID} || !self)\n")
                    set(cod3 "${cod3}                throw CompilerError(nullptr, \"Internal compiler error.\");\n")
                    set(cod3 "${cod3}            auto env = JVM::jniEnv();\n")
                    if("${type}" STREQUAL "void")
                        set(cod3 "${cod3}            env->vtbl->CallVoidMethod(env, self, ${nameID}${call});\n")
                        set(cod3 "${cod3}            JNIThrowableRef::rethrowCurrentException();\n")
                    else()
                        set(cod3 "${cod3}            ${type} value = env->vtbl->Call${meth}Method(env, self, ${nameID}${call});\n")
                        set(cod3 "${cod3}            JNIThrowableRef::rethrowCurrentException();\n")
                        set(cod3 "${cod3}            return value;\n")
                    endif()
                    set(cod3 "${cod3}        }\n")

                    set(cod4 "${cod4}            ${nameID} = 0;\n")
                    set(release TRUE)
                endif()
            elseif("${what}" STREQUAL "native")
                list(GET list 1 desc)
                list(GET list 2 name)
                list(GET list 3 type)
                list(GET list 4 args)

                string(REPLACE "@" ";" desc "${desc}")
                string(REPLACE "!" "[" desc "${desc}")

                set(hasNatives TRUE)

                set(nat "${nat}                    { \"${name}\", \"${desc}\", (void*)NATIVE_${classSymbol}_${name} },\n")
                set(ext "${ext}extern \"C\" ${type} JNICALL NATIVE_${classSymbol}_${name}(JNIEnv* env, jobject self${args});\n")
            elseif("${what}" STREQUAL "hasNative")
                set(cod1 "${cod1}            registerNatives(NATIVES_${classSymbol});\n")
            else()
                message(FATAL_ERROR "unknown what \"${what}\".")
            endif()

            set(empty FALSE)
        endforeach()
    endif()

    if(empty)
        set(use "${use}    using CLASSREF_${classSymbol} = JNIClassRef;\n")
    else()
        set(cod "${cod}    class CLASSREF_${classSymbol} : public JNIClassRef\n")
        set(cod "${cod}    {\n")
        set(cod "${cod}    public:\n")
        set(cod "${cod}        CLASSREF_${classSymbol}() = default;\n")
        set(cod "${cod}\n")
        set(cod "${cod}        void loadGlobal(const char* name)\n")
        set(cod "${cod}        {\n")
        if(hasNatives)
            set(cod "${cod}            const std::vector<JNINativeMethod> NATIVES_${classSymbol} {\n")
            set(cod "${cod}${nat}")
            set(cod "${cod}                };\n")
            set(cod "${cod}\n")
        endif()
        set(cod "${cod}            JNIClassRef::loadGlobal(name);\n")
        set(cod "${cod}${cod1}")
        set(cod "${cod}        }\n")
        if(release)
            set(cod "${cod}\n")
            set(cod "${cod}        void release() override\n")
            set(cod "${cod}        {\n")
            set(cod "${cod}            JNIClassRef::release();\n")
            set(cod "${cod}${cod4}")
            set(cod "${cod}        }\n")
        endif()
        set(cod "${cod}${cod3}")
        set(cod "${cod}\n")
        set(cod "${cod}    private:\n")
        set(cod "${cod}${cod2}")
        set(cod "${cod}        DISABLE_COPY(CLASSREF_${classSymbol});\n")
        set(cod "${cod}    };\n")
        set(cod "${cod}\n")
    endif()
endforeach()

set(out "")
set(out "${out}#ifndef COMPILER_JAVA_JAVACLASSES_H\n")
set(out "${out}#define COMPILER_JAVA_JAVACLASSES_H\n")
set(out "${out}\n")
set(out "${out}#include \"Compiler/CompilerError.h\"\n")
set(out "${out}#include \"Compiler/Java/JavaStdClasses.h\"\n")
set(out "${out}#include \"Compiler/Java/JNIThrowableRef.h\"\n")
set(out "${out}#include \"Compiler/Java/JVM.h\"\n")
set(out "${out}\n")
set(out "${out}${ext}")
set(out "${out}${cod}")
set(out "${out}${use}")
set(out "${out}\n")
set(out "${out}${cls}")
set(out "${out}\n")
set(out "${out}    static void ensureLoaded();\n")
set(out "${out}    static void releaseAll();\n")
set(out "${out}};\n")
set(out "${out}\n")
set(out "${out}#ifdef JAVACLASSES_IMPL\n\n")
set(out "${out}${cxx}\n")
set(out "${out}void JavaClasses::ensureLoaded()\n")
set(out "${out}{\n")
set(out "${out}    JavaStdClasses::ensureLoaded();\n")
set(out "${out}${ini}")
set(out "${out}}\n\n")
set(out "${out}void JavaClasses::releaseAll()\n")
set(out "${out}{\n")
set(out "${out}    JavaStdClasses::releaseAll();\n")
set(out "${out}${fin}")
set(out "${out}}\n\n")
set(out "${out}#endif\n")
set(out "${out}\n")
set(out "${out}#endif\n")

writeIfChanged("${DSTPATH}/JavaClasses.h" "${out}")
