import java.nio.file.Files

ext.moduleName = 'stroom.dashboard.gwt'

def getSysPropertyOrDefault = { propName, defaultValue ->
    def val
    if (System.hasProperty(propName)
            && System.getProperty(propName) != "unspecified"
            && System.getProperty(propName) != "") {
        val = System.getProperty(propName)
        logger.info("Using property [${propName}] with value [${val}]")
    } else {
        val = defaultValue
        logger.info("Property [${propName}] has no value, using default value [${val}]")
    }
    return val
}

def resourcesDir = project(':stroom-app').file('src/main/resources')
def warDir = "${resourcesDir}/ui"
def outputDir = "${warDir}/dashboard"

def javaIoTmpDir = getSysPropertyOrDefault("java.io.tmpdir", "/tmp")
logger.info('javaIoTmpDir: ' + javaIoTmpDir)
// Append the rootDir so gwt builds from different dirs don't conflict
def gwtBuildDir = "${javaIoTmpDir}/gwt/${rootProjectAbsPathStr}/dashboard-gwt_build"
gwtBuildDir = gwtBuildDir.replaceAll('//', '/')
logger.info('gwtBuildDir: ' + gwtBuildDir)

def gwtTmpDir = "${gwtBuildDir}/tmp"
logger.info('gwtTmpDir: ' + gwtTmpDir)
def workDir = "${gwtBuildDir}/work"
def deployDir = "${gwtBuildDir}/deploy"
def extraDir = "${gwtBuildDir}/extra"

def allSourceDir = "${gwtBuildDir}/all-source"
def transformedSourceDir = "${gwtBuildDir}/transformed-source"
def transformedBuildDir = "${gwtBuildDir}/transformed-build"
def rsyncOutputDir = "${gwtBuildDir}/rsync-output"

def gwtSourceDirs = []

configurations.configureEach {
    resolutionStrategy {
        eachDependency { DependencyResolveDetails details ->
            if (details.requested.group == 'javax.xml.bind') {
                details.useVersion "2.3.1"
            }
            if (details.requested.group == 'com.fasterxml.jackson.core') {
                details.useVersion "2.9.6"
            }
        }
    }
}

// Declare a source set just for our GWT compile that is separate from the Java compilation source set.
sourceSets {
    gwtSource
    gwtDevSource
    jakartaTransformer
}

dependencies {
    implementation project(':stroom-core-client')
    implementation project(':stroom-core-client-widget')
    implementation project(':stroom-core-shared')
    implementation project(':stroom-dashboard:stroom-dashboard-client')
    implementation project(':stroom-docref')
    implementation project(':stroom-gwt')
    implementation project(':stroom-query:stroom-query-api')

    // These source deps are all needed for the GWT transpiling to javascript
    implementation libs.gin
    implementation libs.guice.gwt
    implementation libs.gwt.dev
    implementation libs.gwt.user
    implementation libs.gwtp.mvp.client
    implementation libs.jackson.annotations
    implementation libs.jaxb.api.gwt
    // needed so gwt can compile the stroom-query code that contains swagger annotations
    implementation libs.swagger.annotations
    implementation libs.ws.rs.api.gwt

    gwtSourceImplementation libs.gin
    gwtSourceImplementation libs.gwt.user
    gwtSourceImplementation libs.gwtp.mvp.client
    gwtSourceImplementation libs.jaxb.api.gwt
    // needed so gwt can compile the stroom-query code that contains swagger annotations
    gwtSourceImplementation libs.swagger.annotations
    gwtSourceImplementation libs.restygwt
    gwtSourceImplementation libs.ws.rs.api.gwt

    gwtDevSourceImplementation project(':stroom-gwt')
    gwtDevSourceImplementation libs.gwt.dev

    jakartaTransformerImplementation libs.eclipse.transformer.cli
}

// Define a list of projects that contain the source code we want to compile with GWT.
def clientProjects = [
        project(':stroom-core-client'),
        project(':stroom-core-client-widget'),
        project(':stroom-dashboard-gwt'),
        project(':stroom-dashboard:stroom-dashboard-client'),
        project(':stroom-core-shared'),
        project(':stroom-docref'),
        project(':stroom-query:stroom-query-api'),
        project(':stroom-util-shared'),
]

// Task to make a copy of each project in projectsToRename then change all imports
// from jakarta => javax (in source/classses/resources) so that GWT can use them.
// It adds the modified projects to GWTs compile classpath.
// This is a temporary hack until there is a jakarta friendly version of GWT.
// See https://github.com/gwtproject/gwt/issues/9727 for details on jakarta progress but
// be aware that restgwt is not jakarta friendly and is a dormant project so that will need
// sorting somehow.
tasks.register('copySources') {
    doLast {
        // This file has all the rename mappings for TransformerCLI to use
        def propFilePath = rootDir.toPath().resolve('jakarta-renames-reverse.properties')
        def inputProjectPath = "${gwtBuildDir}/raw-source"

        // Delete current source output.
        delete inputProjectPath

        def start = Instant.now()

        // Copy all code into source output.
        println("copy project source (java/resources) => ${inputProjectPath}")
        clientProjects.each { prj ->
            println('  ' + prj.projectDir)
            copy {
                from prj.projectDir.toPath().resolve('src').resolve('main').resolve('java')
                into inputProjectPath
            }
            copy {
                from prj.projectDir.toPath().resolve('src').resolve('main').resolve('resources')
                into inputProjectPath
            }
        }
        delete "${inputProjectPath}/module-info.java"
        println("${Duration.between(start, Instant.now())}")
        start = Instant.now()

        // Transform code.
        println 'Running eclipse TransformerCLI'
        println('  input: ' + inputProjectPath)
        println('  output: ' + transformedSourceDir)
        delete transformedSourceDir

        providers.javaexec {
            classpath = files(sourceSets.jakartaTransformer.compileClasspath)
            mainClass = "org.eclipse.transformer.cli.TransformerCLI"
            args = [
                    inputProjectPath,
                    transformedSourceDir,
                    '-tr', propFilePath,
            ]
        }.result.get()
        println("${Duration.between(start, Instant.now())}")
        start = Instant.now()

        delete allSourceDir

        println("copy dep jars => ${allSourceDir}")
        sourceSets.gwtSource.compileClasspath.files.forEach { zipFile ->
            println('  ' + zipFile)
            copy {
                from zipTree(zipFile)
                into allSourceDir
            }
        }
        println("${Duration.between(start, Instant.now())}")
        start = Instant.now()

        println("copy ${transformedSourceDir} => ${allSourceDir}")
        copy {
            from transformedSourceDir
            into allSourceDir
        }
        println("${Duration.between(start, Instant.now())}")
    }
}

tasks.register('compileSources', JavaCompile) {
    dependsOn copySources
    def sources = transformedSourceDir
    new File(sources).mkdirs()
    def build = transformedBuildDir
    new File(build).mkdirs()

    source = file(sources)
    destinationDirectory.set(file(build))
    classpath = files(sources, sourceSets.gwtSource.compileClasspath)
    options.sourcepath = files(sources)

    outputs.dir(destinationDirectory.get())
}

tasks.register('makeGwtSourceDirs') {
    dependsOn compileSources
    group = "gwt"

    doLast {
        def rsyncInputDir = "${gwtBuildDir}/rsync-input"
        delete rsyncInputDir
        new File(rsyncInputDir).mkdirs()

        def start = Instant.now()

        // Do this as two copies then one rsync rather than just two rsyncs
        // so that we can be sure that any GWT classes that we have cloned and
        // changed overwrite the original GWT version in  rsyncInputDir

        println("copy ${allSourceDir} => ${rsyncInputDir}")
        copy {
            from allSourceDir
            into rsyncInputDir
        }

        println("${Duration.between(start, Instant.now())}")
        start = Instant.now()

        println("copy ${transformedBuildDir} => ${rsyncInputDir}")
        copy {
            from transformedBuildDir
            into rsyncInputDir
        }

        println("${Duration.between(start, Instant.now())}")
        start = Instant.now()

        // Rsync the files so that we don't delete all the files that superdev is monitoring,
        // which it doesn't like. Rsync should only change the files that have actually changed
        // i.e. when doing superdev code changes.
        println "rsync ${rsyncInputDir} => ${rsyncOutputDir}"
        providers.exec {
            executable = 'rsync'
            args = [
                    "--recursive",
                    "--checksum",
                    "--delete",
                    "--stats",
                    rsyncInputDir + "/",
                    rsyncOutputDir]
        }.result.get()

        println("${Duration.between(start, Instant.now())}")
        start = Instant.now()

        // Now assemble the sourceDirs array for GWT to compile
        gwtSourceDirs = []
        // Add all of the library sources from the gwtSource source set defined above.
        gwtSourceDirs.addAll(sourceSets.gwtDevSource.compileClasspath)
        // Add the rsync clone of our transformed build
        gwtSourceDirs.add(rsyncOutputDir)

        println "Dumping ${gwtSourceDirs.size()} gwtSourceDirs"
        gwtSourceDirs.each {
            println "  ${it.toString()}"
        }
    }
}

tasks.register('gwtCompile', JavaExec) {
    dependsOn classes
    group = "gwt"
    dependsOn 'makeGwtSourceDirs'

    def module = 'stroom.dashboard.DashboardApp'

    doFirst {
        println 'Deleting GWT build dirs in ' + gwtBuildDir.toString()
        delete gwtTmpDir
        delete workDir
        delete deployDir
        delete extraDir

        logger.info("Ensuring directory gwtBuildDir ${gwtBuildDir}")
        new File(gwtBuildDir).mkdirs()
        logger.info("Ensuring directory gwtTmpDir ${gwtTmpDir}")
        new File(gwtTmpDir).mkdirs()
        logger.info("Ensuring directory workDir ${workDir}")
        new File(workDir).mkdirs()
        logger.info("Ensuring directory deployDir ${deployDir}")
        new File(deployDir).mkdirs()
        logger.info("Ensuring directory extraDir ${extraDir}")
        new File(extraDir).mkdirs()

        logger.info("gwtSourceDirs count: ${gwtSourceDirs.size()}")

        //println "Dumping $gwtSourceDirs.size gwtSourceDirs"
        //gwtSourceDirs.each {
        //println "${it.toString()}"
        //}

        gwtSourceDirs.each {
            if (it instanceof java.nio.file.Path && Files.exists(it)) {
                println "Can't find gwtSourceDir" + it
                //throw new GradleException("Can't find gwtSourceDir ${it}")
            } else if (it instanceof File && !Files.exists(it.toPath())) {
                println "Can't find gwtSourceDir" + it
            }
        }
    }

    mainClass.set(gwtCompilerProps.mainClass.toString())

    classpath {
        gwtSourceDirs
    }

    args = [
            '-sourceLevel', '17',
            '-logLevel', 'INFO',
            '-workDir', workDir,
            '-XfragmentCount', '5',
            '-optimize', '9',
            '-failOnError',
            '-localWorkers', gwtCompilerProps.workers,
            '-war', warDir,
            '-deploy', deployDir,
            '-extra', extraDir,
            '-strict',
            module                               // Your GWT module
    ]

    jvmArgs = [
            "-Djava.io.tmpdir=${gwtTmpDir}",
            //'-Dgwt.persistentunitcache=false',
            '--add-opens', 'java.base/java.lang=ALL-UNNAMED',  // needed for GWT
    ]
    minHeapSize = gwtCompilerProps.minHeap
    maxHeapSize = gwtCompilerProps.maxHeap
}

tasks.register('gwtDraftCompile', JavaExec) {
    dependsOn classes
    group = "gwt"
    dependsOn 'makeGwtSourceDirs'

    def module = 'stroom.dashboard.DashboardAppSuperDevModeChrome'

    doFirst {
        println 'Deleting GWT compiled output: ' + outputDir.toString()
        delete outputDir

        logger.info("Ensuring directory gwtBuildDir ${gwtBuildDir}")
        new File(gwtBuildDir).mkdirs()
        logger.info("Ensuring directory gwtTmpDir ${gwtTmpDir}")
        new File(gwtTmpDir).mkdirs()
        logger.info("Ensuring directory workDir ${workDir}")
        new File(workDir).mkdirs()
        logger.info("Ensuring directory deployDir ${deployDir}")
        new File(deployDir).mkdirs()
        logger.info("Ensuring directory extraDir ${extraDir}")
        new File(extraDir).mkdirs()

        gwtSourceDirs.each {
            if (it instanceof java.nio.file.Path && Files.exists(it)) {
                println "Can't find gwtSourceDir" + it
                //throw new GradleException("Can't find gwtSourceDir ${it}")
            } else if (it instanceof File && !Files.exists(it.toPath())) {
                println "Can't find gwtSourceDir" + it
            }
        }
    }

    mainClass = 'com.google.gwt.dev.Compiler'

    classpath {
        gwtSourceDirs
    }

    args = [
            '-sourceLevel', '17',
            '-logLevel', 'INFO',
            '-workDir', workDir,
            '-draftCompile',                        // Speeds up compile with 25%
            '-XfragmentCount', '0',
            '-optimize', '0',
            '-failOnError',
            '-localWorkers', '5',
            '-incremental',
            '-war', warDir,
            '-deploy', deployDir,
            '-extra', extraDir,
            '-strict',
            module                                  // Your GWT module
    ]

    jvmArgs = [
            "-Djava.io.tmpdir=${gwtTmpDir}",
            '--add-opens', 'java.base/java.lang=ALL-UNNAMED',  // needed for GWT
    ]
    minHeapSize = '1G'
    maxHeapSize = '4G'
}

tasks.register('gwtSuperDevMode', JavaExec) {
    dependsOn classes
    group = "gwt"
    dependsOn 'makeGwtSourceDirs'

    def module = 'stroom.dashboard.DashboardAppSuperDevModeChrome'

//    def dirs = []
//    parent.file(".").eachDir {it.eachDirMatch("src") {it.eachDirMatch("main") { it.eachDirMatch("java") {dirs << it}}}}
//    parent.file(".").eachDir {it.eachDirMatch("src") {it.eachDirMatch("main") { it.eachDirMatch("resources") {dirs << it}}}}
//    println dirs

    doFirst {
        logger.info("Ensuring directory gwtBuildDir ${gwtBuildDir}")
        new File(gwtBuildDir).mkdirs()
        logger.info("Ensuring directory gwtTmpDir ${gwtTmpDir}")
        new File(gwtTmpDir).mkdirs()
        logger.info("Ensuring directory workDir ${workDir}")
        new File(workDir).mkdirs()
        logger.info("Ensuring directory deployDir ${deployDir}")
        new File(deployDir).mkdirs()
        logger.info("Ensuring directory extraDir ${extraDir}")
        new File(extraDir).mkdirs()

        def GREEN = '\033[1;32m'
        def NC = '\033[0m'

        println """
        ${NC}To run stroom in SuperDevMode:${GREEN}
          * Visit the 'code server' (link further down) and copy the dev mode buttons to your browser bookmarks
          * Visit stroom at http://localhost:8080 (NOTE: http, not https), or https://localhost if running Nginx
          * Click 'Dev Mode On'${NC}

          If you change any code shared between client and server, you will need to run :stroom-app-gwt:jakarta to
          update GWT's view of the code.
        """.stripIndent().trim()
    }

    mainClass = 'com.google.gwt.dev.DevMode'

    classpath {
        gwtSourceDirs
    }

    args = [
            '-sourceLevel', '17',
            '-logLevel', 'INFO',
            '-workDir', workDir,
            '-failOnError',
            '-war', warDir,
            '-deploy', deployDir,
            '-extra', extraDir,
            '-strict',
            '-noserver',
            module                                  // Your GWT module
    ]

    jvmArgs = [
            "-Djava.io.tmpdir=${gwtTmpDir}",
            '--add-opens', 'java.base/java.lang=ALL-UNNAMED',  // needed for GWT
    ]
    minHeapSize = gwtCompilerProps.minHeap
    maxHeapSize = gwtCompilerProps.maxHeap
}

tasks.register('gwtClean') {
    group = "gwt"
    doLast {
//        println 'Deleting GWT unit cache: ' + unitCacheDir.toString()
//        delete unitCacheDir

        logger.info('Deleting gwtBuildDir: ' + gwtBuildDir.toString())
        delete gwtBuildDir

        logger.info("Ensuring directory gwtBuildDir ${gwtBuildDir}")
        new File(gwtBuildDir).mkdirs()
        logger.info("Ensuring directory gwtTmpDir ${gwtTmpDir}")
        new File(gwtTmpDir).mkdirs()
    }
}

tasks.clean.dependsOn(tasks.gwtClean)
tasks.copySources.mustRunAfter(tasks.gwtClean)
