plugins {
    id "de.undercouch.download" version "3.4.3"
}

ant.echo('Java version: ' + JavaVersion.current());

def dirBuildFile =  "./build/main"
def dirBuildNative = "./build/main/native"
def dirNative = "./native"
def dirWorkspace = "./"
def dirPack = "./pack"
def dirLibFile = "lib"
def dirModFile ="./mod"
def javaHome = System.getProperty('java.home')
def vmVersion = "0.4.0"
def dirRuntimeJars = 'jars' // we'll store mod and lib stuff in here
def brandName = "oan"

allprojects { 
    apply plugin: 'java'
    apply plugin: 'idea'

    targetCompatibility = 10
    sourceCompatibility = 10

    repositories {
        jcenter()
        maven { url "https://oss.sonatype.org/content/groups/staging" }

        flatDir {
            dirs './lib' // does not recurse, don't make subdirectories in here
        }

        maven { 
            url "${rootDir}/lib/maven_repo"
        }

        repositories {
            maven { url 'https://jitpack.io' }
        }
    }
}

task compileNative(type:Exec) {
    // seems kinda hacky, but need big reorganization to fix
    dependsOn 'modCrypto:compileNative'
    dependsOn 'modAionImpl:compileJava'

    doFirst {
        mkdir "${dirBuildFile}"
        mkdir "${dirNative}/linux/equihash"
        mkdir "${dirBuildNative}/linux/equihash"
        ant.copy(includeemptydirs: "false", todir: "./build/native") {
            fileset(dir: "./modAionImpl/src/org/aion/equihash/native")
        }
        ant.copy(includeemptydirs: "false", todir: "./build/native") {
            fileset(dir: "./modAionImpl/build/native")
        }
    }

    commandLine "g++", 
        "-fPIC", 
        "-shared", 
        "-I${javaHome}/include", 
        "-I${javaHome}/include/linux", 
        "-I${dirBuildNative}",
        "-I${dirNative}/linux/sodium", 
        "-mtune=generic", 
        "-m64", 
        "-std=c++11", 
        "-Wall", 
        "-Wno-deprecated-declarations", 
        "-D_POSIX_C_SOURCE=200112L", 
        "-O3", 
        "./build/native/equi.h", 
        "./build/native/equi_miner.h", 
        "./build/native/equiMiner.cpp", 
        "-L${dirWorkspace}/native/linux/sodium", 
        "-lsodiumjni",
        "-o", 
        "${dirNative}/linux/equihash/equiMiner.so", 
        "-Wl,-rpath,${dirNative}/linux/sodium"
    def stdout = new ByteArrayOutputStream()
    ext.output = {
        return stdout.toString()
    }
}

subprojects {
    test { 
        maxHeapSize = "6g"
    }

    sourceSets { 
        main {
            java.srcDirs = ['src']
        }
        test {
            java.srcDirs = ['test']
        }
        integTest { 
            java { 
                srcDirs = ['integration-test']
                compileClasspath += main.output + test.output
                runtimeClasspath += main.output + test.output
            }
        }
        unitTest {
            java {
                srcDirs = ['test']
                compileClasspath += main.output + test.output
                runtimeClasspath += main.output + test.output
            }
        }
        benchmarkTest {
            java {
                srcDirs = ['test']
                compileClasspath += main.output + test.output
                runtimeClasspath += main.output + test.output
            }
        }
    }

    task integTest(type: Test) { 
        testClassesDirs = sourceSets.integTest.output.classesDirs
        classpath = sourceSets.integTest.runtimeClasspath
    }

    task unitTest(type: Test) {
        maxHeapSize = "6g"
        testClassesDirs = sourceSets.unitTest.output.classesDirs
        classpath += sourceSets.unitTest.runtimeClasspath
    }

    task benchmarkTest(type: Test) {
	testClassesDirs = sourceSets.benchmarkTest.output.classesDirs
        classpath = sourceSets.benchmarkTest.runtimeClasspath
    }

    configurations { 
        integTestCompile.extendsFrom testCompile
        integTestRuntime.extendsFrom testRuntime
    }

    task copyNativeLibsForModuleTests(type: Copy) { 
        dependsOn rootProject.compileNative
        from rootProject.file('native') into file('native')
    }

    task deleteNativeLibs(type: Delete) {
        delete 'native'
    }

    afterEvaluate {
        if(hasProperty('moduleName')) { 
            ant.echo('moduleName: ' + moduleName);
        }

        if (hasProperty('moduleName') && moduleName.equals("aion.gui")) { 
        // Override java plug-in behaviour to make JDK9+ module logic work
        // From: https://guides.gradle.org/building-java-9-modules/#step_2_produce_java_9_modules_for_all_subprojects
        compileJava {
            inputs.property("moduleName", moduleName)
            doFirst {
                options.compilerArgs = [
                    '--module-path', classpath.asPath,
                    '--add-modules', 'javafx.controls'
                ]
                classpath = files()
            }
        }
        } else {
        compileJava {
            inputs.property("moduleName", moduleName)
            doFirst {
                options.compilerArgs = [
                    '--module-path', classpath.asPath,
                ]
                classpath = files()
            }
        }
        }

/*
        test { 
            outputs.upToDateWhen { false } 
        }
  */      
        // Need to comment this out for now, modules-info.java doesn't have 'require' declarations
        // used by the test code.  The ant build.xml, similarly, used module-path for build, but 
        // classpath for building the tests (at least in modRlp).  Need to sort out how to properly set the module-path
        // for the tests before using the stuff below.
        /*
        compileTestJava {
            inputs.property("moduleName", moduleName)
            doFirst {
                options.compilerArgs = [
                    '--module-path', classpath.asPath,
                    '--add-modules', 'junit',
                    '--add-reads', "$moduleName=junit",
                    '--patch-module', "$moduleName=" + files(sourceSets.test.java.srcDirs).asPath,
                ]
                classpath = files()
            }
        }

        test {
            inputs.property("moduleName", moduleName)
            doFirst {
                jvmArgs = [
                    '--module-path', classpath.asPath,
                    '--add-modules', 'ALL-MODULE-PATH',
                    '--add-reads', "$moduleName=junit",
                    '--patch-module', "$moduleName=" + files(sourceSets.test.java.outputDir).asPath,
                ]
                classpath = files()
            }
        }
        */
    }
}

project(':modAionImpl') { build.finalizedBy(compileNative) }

task downloadFastVmGit(type:Download) { 
    overwrite false // TODO This seems to work only when this task is directly invoked
    onlyIfModified true
    src "https://github.com/aionnetwork/aion_fastvm/releases/download/v${vmVersion}/fastvm_v${vmVersion}.tar.gz"
    dest buildDir
}

task downloadSolidityGit(type:Download) {
    overwrite false // TODO This seems to work only when this task is directly invoked
    onlyIfModified true
    src "https://github.com/aionnetwork/aion_fastvm/releases/download/v${vmVersion}/solidity_v${vmVersion}.tar.gz"
    dest buildDir
}

task downloadGoogleFormatJar(type:Download) { 
    overwrite false // TODO This seems to work only when this task is directly invoked
    onlyIfModified true
    src "https://github.com/google/google-java-format/releases/download/google-java-format-1.7/google-java-format-1.7-all-deps.jar"
    dest buildDir
}

task buildVmDependencies(type: Exec) { 
    commandLine 'sh', 'aion_fastvm/scripts/release.sh', vmVersion, '1'
}

task getVmDependencies { 
    if(project.hasProperty('vmFromSource')) { 
        getVmDependencies.dependsOn buildVmDependencies
    } else {
        // 'overwrite' property of Download task doesn't seem to work when
        // there's multiple files in the task, so splitting them into two
        dependsOn downloadFastVmGit
        dependsOn downloadSolidityGit
    }
}

task extractVmDepsGit(dependsOn: getVmDependencies, type: Copy) { 
    from tarTree("build/fastvm_v${vmVersion}.tar.gz") 
    from tarTree("build/solidity_v${vmVersion}.tar.gz") 
    into "${dirNative}/linux"
    doLast {
        ant.move file:  "${dirNative}/linux/fastvm_v${vmVersion}",
                 tofile: "${dirNative}/linux/fastvm"
        ant.move file:  "${dirNative}/linux/solidity_v${vmVersion}",
                 tofile: "${dirNative}/linux/solidity"
    }
}

task cleanJars { 
    delete 'jars'
}

task collectDependentJars(type: Copy) { 
    dependsOn cleanJars
    into dirRuntimeJars
    from { subprojects.configurations.runtime } 
    from { subprojects.jar}
    from { file("lib/libminiupnpc.so") } // called by a jar that expects this to be in same dir
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    exclude "modAvmVersion*.jar"
    exclude "org-aion-avm-*.jar"
}

build { 
    dependsOn("extractVmDepsGit")
    dependsOn("collectDependentJars")
}

task prePack(type:Exec)  {
    if(findProject(":modGui") != null && gradle.useGui) { 
        dependsOn ':modGui:setupAionRootProject';
        environment "useGui", "true"
    }
    commandLine 'bash', 'script/prepack.sh' 
}

task postPack(type:Exec) { commandLine 'sh', 'script/postpack.sh' }

task checkFormat(type:Exec) {
    dependsOn downloadGoogleFormatJar
    commandLine 'sh', 'script/check-format.sh'
}

task checkCommitFormat(type:Exec) {
    dependsOn downloadGoogleFormatJar
    commandLine 'sh', 'script/check-commit-format.sh'
}

/** Replaces `ant pack_build` */
task pack(type: Tar) {
    dependsOn build
    dependsOn prePack
    dependsOn collectDependentJars 
    dependsOn compileNative 
    finalizedBy(postPack)

    archiveName = "${brandName}.tar.bz2"
    destinationDir = file(dirPack)
    compression = Compression.BZIP2

    into("/${brandName}/jars") { 
        from dirRuntimeJars 
        include '*.jar', '*.so'
    }
    into("/${brandName}/lib") {
        from dirLibFile
        include 'org-aion-avm-core-v*.jar', 'org-aion-avm-api-v*.jar', 'org-aion-avm-rt-v*.jar', 'org-aion-avm-userlib-v*.jar',  'org-aion-avm-tooling-v*.jar', 'org-aion-avm-utilities-v*.jar', 'modAvmVersion*.jar'
    }
    into("/${brandName}/") { 
        from dirWorkspace
        include 'aion.sh'
    }
    into("/${brandName}/native") {
        from dirNative
        include '**'
    }
    into("/${brandName}/networks") {
        from "${dirPack}/networks"
        include '**'
    }
    into("/${brandName}/rt") {
        from "${dirPack}/rt"
        include '**'
    }
    into("/${brandName}/web-console") {
        from "${dirPack}/web3"
        include '**'
    }

    into("/${brandName}/script") {
        from "${dirPack}/script"
        include 'generateSslCert.sh', 'nohup_wrapper.sh'
    }

    into("/${brandName}/tooling") {
        from "${dirWorkspace}/tooling"
        include '**'
    }

    into("/${brandName}/") {
        from dirWorkspace
        include 'CUSTOM_NETWORK_SETUP.md'
    }
}

task createPackConfigOverride(type: Copy) { 
    from "${rootDir}/networks"
    into "${dirPack}/networks"
}

task overridePackConfigForDocker(type: Exec) { 
    dependsOn createPackConfigOverride
    commandLine 'python', 'DockerAutomation/bin/enable-api-servers.py', "${dirPack}/networks"
}

task copyPackForDocker(type: Copy) { 
    dependsOn pack, overridePackConfigForDocker
    pack.mustRunAfter overridePackConfigForDocker

    from "${dirPack}/${brandName}.tar.bz2"
    from "${rootDir}/DockerAutomation"
    into "${buildDir}/docker"
}

task packDocker(type: Exec) { 
    dependsOn copyPackForDocker 

    def distImageTag = project.findProperty('dist_image_tag') ?: 'aionnetwork/aion:latest'
    commandLine 'docker', 'build',
        '--target', 'dist', '-t', distImageTag,
        "${buildDir}/docker"
}

task packDockerWithVersion(type: Exec) {
    dependsOn copyPackForDocker

    def stdout = new ByteArrayOutputStream()
    def stdout2 = new ByteArrayOutputStream()

    exec{
        commandLine "./script/build_ver.sh"
        standardOutput = stdout;
    }

    ant.echo("ver:" + stdout)
    exec{
        commandLine 'git', 'log', '-1', '--format=%h'
        standardOutput = stdout2;
    }

    ant.echo("rev:" + stdout2)

    def distImageTag = project.findProperty('dist_image_tag') ?: "aionnetwork/aion:${stdout.toString().trim()}-${stdout2.toString().trim()}"
    ant.echo("distImageTag: " + distImageTag)

    commandLine 'docker', 'build',
        '--target', 'dist', '-t', distImageTag,
        "${buildDir}/docker"
}

task packK8sDocker(type: Exec) { 
    dependsOn copyPackForDocker 

    def k8sImageTag = project.findProperty('k8s_image_tag') ?: 'aionnetwork/aion-k8s:latest'
    commandLine 'docker', 'build', 
        '--target', 'k8s', '-t', k8sImageTag,
        "${buildDir}/docker"
}

clean { 
    dependsOn 'cleanJars'
    delete dirPack
    delete file('report')
}

/** Replaces `ant ci_build` */
task ciBuild {
    dependsOn build
    def ciModules = [
            'modBase',
            'modAionImpl',
            'modApiServer',
            'modBoot',
            'modCrypto',
            'modDbImpl',
            'modEvtMgr',
            'modEvtMgrImpl',
            'modLogger',
            'modP2p',
            'modP2pImpl',
            'modPrecompiled',
            'modRlp',
            'modTxPool',
            'modAvmVersion1',
            'modAvmVersion2',
            'modAvmStub'
    ] 

    configure(subprojects.findAll { it.name in ciModules }) {
        it.test { testResultsDirName = "${rootProject.projectDir}/report/${project.name}" }
        dependsOn it.test
    } 
}

task unitTest {
    dependsOn build
    def ciModules = [
            'modBase',
            'modAionImpl',
            'modApiServer',
            'modBoot',
            'modCrypto',
            'modDbImpl',
            'modEvtMgr',
            'modEvtMgrImpl',
            'modLogger',
            'modP2p',
            'modP2pImpl',
            'modPrecompiled',
            'modRlp',
            'modTxPool',
            'modAvmVersion1',
            'modAvmVersion2',
            'modAvmStub'
    ]

    configure(subprojects.findAll { it.name in ciModules }) {
        it.unitTest { testResultsDirName = "${rootProject.projectDir}/report/${project.name}" }
        dependsOn it.unitTest
    }
}

task benchmarkTest {
    dependsOn build
    def ciModules = [
            'modAionImpl',
            'modApiServer',
            'modTxPool'
    ]

    configure(subprojects.findAll { it.name in ciModules }) {
        it.benchmarkTest { testResultsDirName = "${rootProject.projectDir}/report/${project.name}" }
        dependsOn it.benchmarkTest
    }
}



idea { 
    project {
        jdkName = '11.0'
    }
}

task fixIdea { 
    // when importing project in IDEA, some of the library paths are not where they need to be
    // these are set up via dependencies on build and test, but IDEA doesn't actually use them
    // this task puts those files where IDEA expects them by running build and test

    dependsOn build
    subprojects {
        dependsOn test
    }
}


/** 
 * Returns the value of the constant src.org.aion.zero.impl.Version.KERNEL_VERSION 
 * as defined in the Java source (the Java code is not compiled, this just greps it 
 * out of the file).
 */
def kernelVersionFromSrc() {
    def src = file("./modAionImpl/src/org/aion/zero/impl/Version.java").readLines();
    def version = src.find { l -> l =~ /KERNEL_VERSION/  }.split("=")[1].trim().replaceAll('[;"]', '')
    if(version.empty) {
        throw new GradleException("Could not determine kernel version.");
    }
    return version;
}

subprojects  { 
	apply plugin: 'maven-publish'
	publishing {
		publications {
			mavenJava(MavenPublication) {
				groupId = 'network.aion'
				artifactId = project.hasProperty('mavenPubArtifactId')
                                ? mavenPubArtifactId
                                : project.name
				version = project.hasProperty('mavenPubVersion')
                                ? mavenPubVersion
                                : kernelVersionFromSrc();

				from components.java
			}   
		}   

        repositories {
            maven {
                if(project.hasProperty('publishTarget')) { 
                    url = publishTarget
                } else {
                    url = "$buildDir/repos"
                }
            }   
        }   

        // need to reference the publish-related tasks this way, since they're 
        // generated during evaluation.
        // https://docs.gradle.org/current/userguide/publishing_overview.html#sec:configuring_publishing_tasks
        tasks.withType(PublishToMavenRepository).all { pubTask -> 
            // tidy up maven output if the task inputted by user contains 'publish'.
            // this is to get rid of the checksum files .md5, .sha1, etc. since we are
            // always publishing to a local directory.  if we start publishing to an
            // actual remote Maven repo, need to detect that and avoid this deletion
            if(! gradle.startParameter.taskNames.findAll {it.contains('publish')}.empty) {
                tasks.register("tidyUpMavenOutput", Delete) {
                    mustRunAfter pubTask
                    doLast { 
                        delete.each { 
                            logger.info('Deleting Maven publish metadata ' + it)
                        }
                    }
                }
                finalizedBy tidyUpMavenOutput
                
                doLast{
                    file(pubTask.repository.url).eachFileRecurse { 
                        if(! it.toString().endsWith('jar') 
                            && ! it.toString().endsWith('pom')
                            && ! it.isDirectory()) {
                                logger.info "found Maven metadata: " + it
                                tidyUpMavenOutput.delete(it.canonicalPath)
                        }
                    }
                }
            }
        }

	} //publishing
}

build.mustRunAfter checkCommitFormat
