buildscript {
    repositories {
        mavenLocal()
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M2'
    }
}

plugins {
    id 'com.github.johnrengelman.shadow' version '1.2.3'
    id 'me.champeau.gradle.jmh' version '0.3.1'
}

import org.gradle.internal.jvm.Jvm
import org.apache.tools.ant.util.TeeOutputStream
boolean debug = false

class Tags {
    Boolean hasMainMethod = false
    Boolean compileTimeError = false
    Boolean throwsException = false
    Boolean errorOutputExpected = false
    Boolean validateByHand = false
    Boolean ignoreOutput = false // This tag isn't used in the build...
    String fileRoot
    String mainClass
    String javaCmd = null
    List<String> args = []
    List<String> jVMArgs = []
    String javap = null
    String runFirst = null
    String outputLine = null
    private String block
    def Tags(File file) {
        block = file.text
        hasMainMethod = block.contains('main(String[] args)')
        def firstLine = block.substring(0, block.indexOf("\n"))
        fileRoot = (firstLine.split("/")[-1] - ".java").trim() // Remove \r if it exists
        mainClass = fileRoot
        javaCmd = extract('java')
        if(javaCmd) {
            def pieces = javaCmd.split()
            mainClass = pieces[0]
            if(pieces.size() > 1)
                for(p in pieces[1..-1])
                    if(p.startsWith("-"))
                        jVMArgs << p
                    else
                        args << p
        }
        compileTimeError = hasTag('CompileTimeError')
        throwsException = hasTag('ThrowsException')
        errorOutputExpected = hasTag('ErrorOutputExpected')
        validateByHand = hasTag('ValidateByHand')
        ignoreOutput = hasTag('IgnoreOutput')
        javap = extract('javap') // Includes only arguments to command
        runFirst = extract('RunFirst:')
        outputLine = extractOutputLine()
    }
    private def hasTag(String marker) {
        return block.contains("// {" + marker + "}")
    }
    def extractOutputLine() {
        def matcher = (block =~ /(?m)^(\/\* Output:.*)$/)
        if (matcher) {
            return matcher[0][1]
        } else {
            return null
        }
    }
    private def extract(String marker) {
        // Assume some whitespace is after marker
        if(!block.contains("// {${marker} "))
            return null
        def matcher = (block =~ /\/\/ \{${marker}\s+([^}]+)/)
        if (matcher) {
            def matched = matcher[0][1].trim()
            return matched.replaceAll("\n?//", "")
        } else {
            println "Searching for: " + matcher
            println block
            System.exit(1)
        }
    }
    public boolean hasTags() {
        return compileTimeError ||
        throwsException ||
        errorOutputExpected ||
        validateByHand ||
        ignoreOutput ||
        javaCmd ||
        args ||
        jVMArgs ||
        javap ||
        runFirst
    }
    public String toString() {
        String result = ""
        block.eachLine{ ln ->
            if(ln.startsWith("//") || ln.startsWith("package "))
                result += ln + "\n"
        }
        """
        hasMainMethod
        compileTimeError
        throwsException
        errorOutputExpected
        validateByHand
        ignoreOutput
        fileRoot
        mainClass
        javaCmd
        args
        jVMArgs
        javap
        runFirst
        """.split().each { str ->
            if(this[str])
                result += str + ": " + this[str] + "\n"
        }
        result
    }
}

ext {
    junitJupiterVersion  = '5.0.0-M2'
}

subprojects {
    apply plugin: 'com.github.johnrengelman.shadow'
    apply plugin: 'me.champeau.gradle.jmh'
    apply plugin: 'java'
    apply plugin: 'org.junit.platform.gradle.plugin'
    //apply plugin: 'checkstyle'
    //apply plugin: 'findbugs'

    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'

    repositories {
        mavenLocal()
        jcenter()
        mavenCentral()
    }

    dependencies {
        // Logging:
        compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.+'
        compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.+'
        // You can also use the JDK's built-in logging as the back end:
        // compile group: 'org.slf4j', name: 'slf4j-jdk14', version: '1.7.5'

        // JUnit testing:
        compile     "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
        testCompile "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
        testRuntime "org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}"
    }

    junitPlatform {
        includeClassNamePattern '.*'
    }

    // See: http://blog.jessitron.com/2012/07/using-checkstyle-in-gradle.html
    // https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml
    // http://checkstyle.sourceforge.net/reports/google-java-style.html
/*    checkstyle {
        // configFile = new File(rootDir, "checkstyle.xml")
        toolVersion = '6.7'
    }*/

/*    findbugsMain {
      reports {
        xml.enabled = false
        html.enabled = true
      }
      ignoreFailures = true
    }

    findbugsJmh {
      reports {
        xml.enabled = false
        html.enabled = true
      }
      ignoreFailures = true
    }
*/
    sourceSets {
        main {
            java {
                srcDir projectDir
                exclude "tests/**"
            }
            resources {
                srcDir projectDir
                include '*.xml'
            }
        }
        jmh {
            java {
                srcDir projectDir
            }
        }
        test {
            java {
                srcDir new File(projectDir, "tests")
            }
        }
    }

    jmh {
        jmhVersion = '1.13'
        duplicateClassesStrategy = 'warn'
    }

    List createdTasks = []

    projectDir.eachFileRecurse { file ->
        if (file.name.endsWith('.java')) {

            Tags tags = new Tags(file)
            if(debug && tags.hasTags()) println tags

            // Exclude java sources that will not compile
            if (tags.compileTimeError) {
                sourceSets.main.java.excludes.add(file.name)
            } else {
                JavaExec javaTask = null
                // Add tasks for java sources with main methods
                if (tags.hasMainMethod || tags.javaCmd) {
                    javaTask = tasks.create(name: tags.fileRoot, type: JavaExec, dependsOn: tags.runFirst) {
                        main = tags.mainClass
                        classpath = sourceSets.main.runtimeClasspath
                        args = tags.args
                        jvmArgs = tags.jVMArgs
                    }
                } else if (tags.javap) {
                    // Create task for running javap
                    javaTask = tasks.create(name: "${tags.fileRoot}", type: JavaExec, dependsOn: tags.runFirst) {
                        main = "com.sun.tools.javap.Main"
                        classpath = sourceSets.main.runtimeClasspath + files(Jvm.current().toolsJar)
                        // Assuming javap represents all the args and there's no need to jVMArgs
                        args tags.javap.split()
                    }
                }

                if (javaTask) {
                    def baseName = file.name.substring(0, file.name.lastIndexOf('.'))
                    File outFile = new File(file.parentFile, baseName + '.out')
                    File errFile = new File(file.parentFile, baseName + '.err')

                    javaTask.configure {
                        ignoreExitValue = tags.validateByHand || tags.throwsException
                        doFirst {
                            if(outFile.exists())
                                outFile.delete()
                            if(tags.outputLine)
                                outFile << tags.outputLine + "\n"

                            standardOutput = new TeeOutputStream(new FileOutputStream(outFile, true), System.out)
                            errorOutput = new TeeOutputStream(new FileOutputStream(errFile), System.err)
                        }
                        doLast {
                            if(outFile.size() == 0)
                                outFile.delete()
                            else if(!outFile.text.contains("/* Output:"))
                                outFile.delete()
                            if(errFile.size() == 0) errFile.delete()
                       }
                    }

                    if (!tags.validateByHand) {
                        // Only add tasks that we know we can run successfully to the task list
                        createdTasks.add(javaTask)
                    }
                }
            }
        }
    }
    task run(dependsOn: createdTasks)

    /* Store test output in $projectName/tests
      JUnit 5's junitPlatformTest runs as a "javaExec" rather than a "test",
      so we can't hook into the before/after test behavior.
    */
    tasks.findByPath(":$name:junitPlatformTest").configure {
        File testDir = new File(project.name + "/tests")
        if(testDir.exists()) {
            File outFile = new File(testDir, 'report.txt')
            doFirst {
                standardOutput = new TeeOutputStream(new FileOutputStream(outFile, true), System.out)
            }
            doLast {
                if(outFile.size() == 0)
                    outFile.delete()
                else if(outFile.text.contains("0 tests found"))
                    outFile.delete()
            }
        }
    }
}

project(':validating') {
    jmh {
        include = 'validating.jmh.*'
    }
}

project(':understandingcollections') {
    dependencies {
        compile project(':typeinfo')
        compile project(':collections')
    }
    jmh {
        include = 'understandingcollections.jmh.*'
    }
}

project(':threads') {
    dependencies {
        compile project(':enums')
    }
}

project(':strings') {
    dependencies {
        compile project(':generics')
    }
}

project(':serialization') {
    configurations.all {
        resolutionStrategy {
            force 'xml-apis:xml-apis:1.0.b2'
        }
    }
    dependencies {
        compile 'com.io7m.xom:xom:1.2.10'
    }
}

project(':interfaces') {
    dependencies {
        compile project(':polymorphism')
    }
}

project(':hiding') {
    dependencies {
        compile project(':com')
    }
}

project(':generics') {
    dependencies {
        compile project(':typeinfo')
    }
}

project(':collections') {
    dependencies {
        compile project(':typeinfo')
    }
}

configure(subprojects - project(':onjava')) {
    dependencies {
        compile project(':onjava')
        compile group: 'com.google.guava', name: 'guava', version: '19.0'
        compile "org.openjdk.jmh:jmh-core:${jmh.jmhVersion}"
        compile 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M2'
    }
}

task verify(type:Exec) {
    description 'Uses Python tool to verify example output'
    commandLine 'python', '_verify_output.py'
    doFirst {
        println("execute 'gradlew run' first")
    }
}