// Global settings
project.ext {
    // Source JRE used for compilation, etc
    if (!project.hasProperty('jre')) {
        jre = System.getProperty('java.home')
    }
    os = Os.current()
    arch = Arch.current()

    if (!project.hasProperty('targetJre')) {
        targetJre = jre
    }
}

def targetJreFile = file(targetJre)
def jreFile = file(jre)

// HotSpot itself
project('hotspot') {
    task clean(type: InvokeMake) {
        onlyIf { file('hotspot/make').exists() }
        args 'clean'
    }

    task prepareJvm {
        onlyIf { jreFile.canonicalPath != targetJreFile.canonicalPath }
        doLast {
            // FIXME: We probably should check if source path is different and delete old JRE in that case
            ant.copy(todir: targetJreFile, overwrite: true) {
                fileset(dir: jreFile)
            }
            ant.chmod(file: new File(targetJreFile, 'bin/java'), perm: '0755')
        }
    }

    task checkMercurial(description: 'Verify Mercurial is installed') << {
        def os = new ByteArrayOutputStream()
        try {
            def result = exec {
                executable 'hg'
                args 'help', 'qinit'
                standardOutput = os
                errorOutput = os
                ignoreExitValue = true
            }
            if (result.getExitValue() != 0) {
                throw new GradleException("Mercurial does not have mq extension installed! Consult README.md for details.")
            }
        } catch (GradleException e) {
            throw new GradleException("Failed to execute 'hg'. Make sure you have Mercurial installed!")
        }
    }

    task init(description: 'Initialize HotSpot repository', dependsOn: checkMercurial) << {
        file('hotspot').mkdir()
        exec {
            executable 'hg'
            args 'init'
            ignoreExitValue = true
        }
    }

    task pull(description: 'Pull OpenJDK HotSpot changes', dependsOn: init) {
        doLast {
            def hotspotRepository = hotspotTag.contains('jdk7') ?
                    'http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot' :
                    'http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot'
            exec {
                executable 'hg'
                args 'pull', hotspotRepository
            }
        }
    }

    task patch(description: 'Patch HotSpot sources', dependsOn: pull) {

        doLast {
            exec {
                executable 'hg'
                args 'qpop', '-a'
                ignoreExitValue = true
            }
            exec {
                executable 'hg'
                args 'update', '-C', '-r', hotspotTag
            }
            new ByteArrayOutputStream().withStream { os ->
                exec {
                    workingDir 'hotspot'
                    executable 'hg'
                    args 'status'
                    standardOutput os
                }
                // Purge unversioned files
                def str = os.toString()
                def matcher = str =~ /(?m)^\?\s+(.*)$/
                matcher.each {
                    ant.delete(file: new File(file('hotspot'), it[1]))
                }
            }
            def guards = [flavor, hotspotTag.contains('jdk7') ? 'jdk7' : 'jdk8',
                          hotspotTag, flavor + '-' + hotspotTag]
            exec {
                executable 'hg'
                args 'qselect'
                args guards
            }
            exec {
                executable 'hg'
                args 'qpush', '-a'
            }
        }
    }

    def arguments = ['product'  : ['ENABLE_FULL_DEBUG_SYMBOLS=0'],
                     'fastdebug': ['ENABLE_FULL_DEBUG_SYMBOLS=1', 'STRIP_POLICY=no_strip', 'ZIP_DEBUGINFO_FILES=0']]

    ['product', 'fastdebug'].each { k ->
        // Compile given kind of DCEVM
        def compile = task("compile${k.capitalize()}", type: InvokeMake) {
            args arguments[k]
            args k
            doLast {
                ant.copy(todir: "build/${k}", overwrite: true) {
                    fileset(dir: "build/${os.buildPath}/${os.buildPath}_${arch.buildArch}_${compiler}/${k}",
                            includes: 'libjvm.so,libjsig.so,jvm.dll,jsig.dll,libjvm.dylib,libjsig.dylib')
                }
            }
        }
        compile.mustRunAfter(patch)

        // Install given kind of DCEVM into destination JRE
        task("install${k.capitalize()}", dependsOn: [prepareJvm, compile]) << {
            def installPath = new File(new File(targetJreFile, arch == Arch.X86 ? os.installPath32 : os.installPath64), jvmName)

            logger.info("Installing DCEVM runtime into JRE with JVM name '${jvmName}' and kind '${k}' at ${installPath}")
            ant.copy(todir: installPath, overwrite: true) {
                fileset(dir: "build/${os.buildPath}/${os.buildPath}_${arch.buildArch}_${compiler}/${k}",
                        includes: 'libjvm.so,libjsig.so,jvm.dll,jsig.dll,libjvm.dylib,libjsig.dylib')
            }
        }
    }
}

configure([project(':agent'), project(':dcevm')]) {
    apply plugin: 'java'
    apply plugin: 'idea'

    repositories {
        mavenCentral()
    }
}

project('agent') {
    jar {
        manifest {
            from "src/main/java/META-INF/MANIFEST.MF"
        }
    }
}

project('native') {
    apply plugin: 'base'
    task compile(type: Exec) {
        doFirst {
            buildDir.mkdirs()
        }
        if (os != Os.WINDOWS) {
            commandLine 'gcc'
            args "-I${jre}/../include"
            args '-shared'
            args 'natives.c'
            if (os == Os.UNIX) {
                args "-I${jre}/../include/linux"
                args '-fPIC'
                args '-o'
                args 'build/libnatives.so'
                args(arch == Arch.X86 ? '-m32' : '-m64')
            } else if (os == Os.MAC) {
                args "-I${jre}/../include/darwin"
                args '-o'
                args 'build/libnatives.dylib'
            }
        } else {
            commandLine 'cmd', '/c', 'compile.cmd'
            environment ARCH: arch == Arch.X86 ? 'x86' : 'x64'
            environment JAVA_HOME: jre
        }
    }
}

project('dcevm') {
    dependencies {
        compile project(':agent')
        compile group: 'org.ow2.asm', name: 'asm-all', version: '5.0.2'
        compile files(jre + '/../lib/tools.jar')

        testCompile group: 'junit', name: 'junit', version: '4.11'
    }

    def m = System.getProperty('java.version') =~ /^1\.([0-9]+)\.*/
    def major = m[0][1].toInteger()
    if (major >= 7) {
        sourceSets.test.java.srcDirs += 'src/test/java7'
    }
    if (major >= 8) {
        sourceSets.test.java.srcDirs += 'src/test/java8'
    }

    test {
        executable new File(targetJreFile, 'bin/java')

        systemProperty 'dcevm.test.light', (flavor == 'light')

        if (kind == 'fastdebug') {
            jvmArgs '-XX:LogFile=build/hotspot.log'
        }
        jvmArgs "-XXaltjvm=${jvmName}"
        jvmArgs '-javaagent:../agent/build/libs/agent.jar'
        if (arch == Arch.X86_64) {
            jvmArgs(project.oops == "compressed" ? '-XX:+UseCompressedOops' : "-XX:-UseCompressedOops")
        }
        jvmArgs "-XX:TraceRedefineClasses=${traceRedefinition}"
        jvmArgs "-Djava.library.path=../native/build"

        ignoreFailures = true
        outputs.upToDateWhen { false }
        useJUnit {
            excludeCategories('com.github.dcevm.test.category.' + (flavor == 'light' ? 'Full' : 'Light'))
        }
    }

    test.dependsOn project(':native').tasks['compile']
    test.dependsOn project(':hotspot').tasks[kind == 'fastdebug' ? 'installFastdebug' : 'installProduct']
}


enum Arch {
    X86(["i386", "i486", "i586", "x86"], 'i486', 32),
    X86_64(["x86_64", "x64", "amd64"], 'amd64', 64)

    final List<String> names
    final String buildArch
    final int bits

    Arch(List<String> names, String buildArch, int bits) {
        this.names = names
        this.buildArch = buildArch
        this.bits = bits
    }

    static Arch find(String token) {
        Arch res = values().find({ v -> v.names.contains(token) })
        return res
    }

    static Arch current() {
        return find(System.getProperty("os.arch"))
    }
}

enum Os {
    MAC('bsd', 'lib', 'lib'),
    WINDOWS('windows', 'bin', 'bin'),
    UNIX('linux', 'lib/i386', 'lib/amd64')

    final String buildPath;
    final String installPath32;
    final String installPath64;

    Os(String buildPath, String installPath32, String installPath64) {
        this.buildPath = buildPath
        this.installPath32 = installPath32
        this.installPath64 = installPath64
    }

    static Os current() {
        return values().find { os -> org.apache.tools.ant.taskdefs.condition.Os.isFamily(os.name().toLowerCase(java.util.Locale.ENGLISH)) }
    }
}

// Helper task to run make targets against hotspot
class InvokeMake extends org.gradle.api.tasks.Exec {
    InvokeMake() {
        def root = project.rootProject
        logging.captureStandardOutput LogLevel.INFO
        if (root.os != Os.WINDOWS) {
            commandLine 'make', '-C', 'make'
        } else {
            // Using launcher script
            commandLine 'cmd', '/c', '..\\build.cmd'
            environment ARCH: root.arch == Arch.X86 ? 'x86' : 'x64'
        }
        args 'OPENJDK=true'
        args "HOTSPOT_BUILD_VERSION=dcevm${root.flavor}-${root.buildNumber}"
        args "ARCH_DATA_MODEL=${root.arch.bits}"
        args "ALT_BOOTDIR=${root.jre.replace('\\', '/')}/.."
        // Replacing backslashes is essential for Windows!
        args 'COMPILER_WARNINGS_FATAL=false' // Clang is very serious about warnings
        args 'HOTSPOT_BUILD_JOBS=4'
    }
}
