// Enable if you need to check the task tree, gr task taskTree --no-repeat
//plugins {
//    id "com.dorongold.task-tree" version "1.3.1"
//}

description = 'Checks that specs from older versions of Atrium can still be run with the components of the current version.'

repositories {
    maven { url "https://dl.bintray.com/jetbrains/spek" }
}

configurations {
    common
}

dependencies {
    //spek2
    common "org.junit.platform:junit-platform-console-standalone:$junit_platform_version"
    common "org.spekframework.spek2:spek-dsl-jvm:$spek2_version"
    common "org.spekframework.spek2:spek-runner-junit5:$spek2_version"
    common "org.spekframework.spek2:spek-runtime-jvm:$spek2_version"
    common "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"

    //spek1
    common "org.jetbrains.spek:spek-api:$spek_version", excludeKotlin
    common "org.jetbrains.spek:spek-junit-platform-engine:$spek_version", excludeKotlin
    common "org.junit.jupiter:junit-jupiter-engine:$jupiter_version"
    common "org.junit.platform:junit-platform-console:$junit_platform_version"

    common mockito(), excludeKotlin
    common kotlinStdlib()

    implementation configurations.common.dependencies
    api prefixedProject('core-api-jvm')
}

def bbcTests = task("bbcTests", group: 'Verification') {
    description = "runs all binary backward compatibility tests"
    dependsOn build
}

def bcTests = task("bcTests", group: 'Verification') {
    description = "runs all backward compatibility tests which compile against the current code"
    dependsOn build
}

['fluent-en_GB', 'infix-en_GB', /* TODO remove with 1.0.0 => */ 'cc-de_CH', 'cc-en_GB', 'cc-infix-en_GB', 'cc-en_UK', 'cc-infix-en_UK'].each { apiName ->
    task("bcBbc-$apiName", group: 'Verification') {
        description = "runs all backward compatibility and binary backward compatibility tests for $apiName"
        dependsOn build
    }

    task("bc-$apiName", group: 'Verification') {
        description = "runs all backward compatibility tests for $apiName"
        dependsOn build
    }

    task("bbc-$apiName", group: 'Verification') {
        description = "runs all binary backward compatibility tests for $apiName"
        dependsOn build
    }
}

def createBcAndBbcTasksForApis(String oldVersion, String forgive, String... apis) {

    def bc_version = task("bc-$oldVersion", group: 'Verification')
    def bbc_version = task("bbc-$oldVersion", group: 'Verification')

    apis.each { apiName ->
        createBcAndBbcTasks(apiName, oldVersion, forgive)
        bc_version.dependsOn "bc-${apiName}-$oldVersion"
        bbc_version.dependsOn "bbc-${apiName}-$oldVersion"
        def bcBbcApi = project."bcBbc-$apiName"
        bcBbcApi.dependsOn "bc-${apiName}-$oldVersion"
        bcBbcApi.dependsOn "bbc-${apiName}-$oldVersion"
        project."bc-$apiName".dependsOn "bc-${apiName}-$oldVersion"
        project."bbc-$apiName".dependsOn "bbc-${apiName}-$oldVersion"
    }
}

Task createJacocoReportTask(String apiName, String name, Task runTask) {
    project.task("jacoco-$name", type: JacocoReport) {
        sourceSets sourceSets.main
        executionData runTask

        def projects = apiName.contains('en_UK') || apiName.contains('en_GB') ?
            jacocoMulti.sourceProjects :
            jacocoMulti.sourceProjects -
                prefixedProject('translations-en_GB-jvm') -
                prefixedProject('translations-en_UK-deprecated') +
                prefixedProject('translations-de_CH-jvm') +
                prefixedProject('translations-de_CH-deprecated')

        projects.each {
            sourceSets it.sourceSets.main
        }

        reports {
            csv.enabled = false
            xml.enabled = true
            xml.destination file("${buildDir}/reports/jacoco/$name/report.xml")
            html.enabled = true
            html.destination file("${buildDir}/reports/jacoco/$name/html/")
        }
    }
}

static String getNameUnderline(String apiName, String oldVersion) {
    return "${apiName.replaceAll('-', '_')}_${oldVersion.replaceAll("\\.", '_')}"
}

File getSrcDirectory(String apiName, String oldVersion) {
    return file("${project.projectDir}/src/old/$oldVersion/atrium-api-$apiName")
}

def createBcAndBbcTasks(String apiName, String oldVersion, String forgive) {
    def nameUnderline = getNameUnderline(apiName, oldVersion)
    def name = "${apiName}-$oldVersion"
    def srcName = "src_$nameUnderline"
    def confCommon = "conf_$nameUnderline"
    def testJar = "testJar_$nameUnderline"
    def testJarSources = "testJarSources_$nameUnderline"
    def srcDirectory = getSrcDirectory(apiName, oldVersion)

    sourceSets {
        create(srcName) {
            kotlin.srcDirs = [srcDirectory]
            resources.srcDirs = [srcDirectory]
            java.srcDirs = [] // we don't have java sources
        }
    }

    configurations {
        create(confCommon)
        create(testJar)
        create(testJarSources)
    }

    def isFluentOrInfix = apiName.startsWith("fluent") || apiName.startsWith("infix")

    dependencies {
        def groupId = oldVersion == '0.6.0' ? 'ch.tutteli' : 'ch.tutteli.atrium'
        add(testJar, "$groupId:atrium-api-$apiName:$oldVersion:tests") {
            exclude group: '*'
        }
        add(testJarSources, "$groupId:atrium-api-$apiName:$oldVersion:testsources") {
            exclude group: '*'
        }

        //TODO remove with 1.0.0, keep only else branch
        if (oldVersion == '0.6.0') {
            add(confCommon, "$groupId:atrium-spec:$oldVersion") {
                exclude group: '*'
            }
            add(confCommon, prefixedProject("api-cc-en_UK")) { //required by atrium-spec
                exclude group: '*'
            }
        } else if (oldVersion == '0.7.0' || oldVersion == '0.8.0') {
            add(confCommon, "$groupId:atrium-spec:$oldVersion") {
                exclude group: '*'
            }
            add(confCommon, prefixedProject("api-cc-en_GB-jvm")) {  //required by atrium-spec
                exclude group: '*'
            }
            if (!apiName.endsWith('cc-de_CH')) {
                add(confCommon, prefixedProject("verbs-internal-jvm")) {  //required by spec in the APIs
                    exclude group: '*'
                }
            }
        } else {
            //TODO tests are now mostly in the common module. However, we cannot unzip this to the jvm project but
            // require a common module for it. Probably easiest to introduce a second bc-test which is an MPP project
//            add(testJarSources, "$groupId:atrium-api-$apiName-common:$oldVersion:testsources") {
//                exclude group: '*'
//            }
            add(confCommon, "$groupId:atrium-specs:$oldVersion") {
                exclude group: 'ch.tutteli.atrium'
            }
            add(confCommon, prefixedProject("api-fluent-en_GB-jvm")) {  //required by atrium-specs
                exclude group: '*'
            }
            add(confCommon, prefixedProject("verbs-internal-jvm")) {  //required by specs in the APIs
                exclude group: '*'
            }
        }

        //TODO keep only if branch, remove the rest with 1.0.0
        if (isFluentOrInfix) {
            add(confCommon, prefixedProject("$apiName-jvm"))
        } else if (!apiName.endsWith('en_UK')) {
            add(confCommon, prefixedProject("$apiName-robstoll-jvm"))
        } else {
            add(confCommon, prefixedProject("$apiName-robstoll"))
        }
        add(confCommon, sourceSets.main.runtimeClasspath)

        owner."${srcName}Implementation" configurations[confCommon].dependencies
    }

    def getArgs = { String scanClassPath ->
        return [
            '--scan-class-path', scanClassPath,
            '--disable-banner',
            '--fail-if-no-tests',
            '--include-engine', isFluentOrInfix ? 'spek2-deprecation' : 'spek1-deprecation',
            '--config', forgive,
            '--details', 'summary'
        ]
    }

    def bbcTask = task("bbc-$name", type: JavaExec, group: 'Verification') {
        description = "Checks if specs from $apiName $oldVersion can be run against the current version."

        inputs.files configurations[testJar]
        inputs.files configurations[confCommon]

        classpath configurations[confCommon].asPath
        classpath configurations[testJar].asPath

        main = 'org.junit.platform.console.ConsoleLauncher'
        args = getArgs(configurations[testJar].asPath)
    }

    def unzip = task("unzip-$name") {
        inputs.files configurations[testJarSources]

        outputs.dir srcDirectory
        doLast {
            configurations[testJarSources].each {
                def jar = it
                copy {
                    from zipTree(jar)
                    into srcDirectory
                }
            }
        }
    }

    project."compileSrc_${nameUnderline}Kotlin" {
        kotlinOptions.suppressWarnings = true
        dependsOn unzip
    }

    def bcTask = task("bc-$name", type: JavaExec, group: 'Verification') {
        description = "Checks if specs from $apiName $oldVersion can be compiled and run against the current version."

        inputs.files sourceSets[srcName].runtimeClasspath

        classpath sourceSets[srcName].runtimeClasspath

        main = 'org.junit.platform.console.ConsoleLauncher'
        args = getArgs(sourceSets[srcName].output.classesDirs.asPath)
    }

    apply plugin: 'jacoco'

    jacoco {
        toolVersion = "$jacoco_tool_version"
        applyTo bcTask
        applyTo bbcTask
    }

    def jacocoBbcReport = createJacocoReportTask(apiName, "bbc-$name", bbcTask)
    def jacocoBcReport = createJacocoReportTask(apiName, "bc-$name", bcTask)

    //if we want it to be part of the build
    //check.dependsOn jacocoReport
    //check.dependsOn bbcTask
    //check.dependsOn bcTask

    bbcTests.dependsOn jacocoBbcReport
    bbcTests.dependsOn bbcTask
    bcTests.dependsOn jacocoBcReport
    bcTests.dependsOn bcTask
}

def createFixSrcTask(String apiName, String oldVersion, String file, Closure modifyContent) {
    def nameUnderline070 = getNameUnderline(apiName, oldVersion)
    def taskSuffix = "$apiName-$oldVersion"
    def fixSrc = task("fixSrc-$taskSuffix-$file") {
        doLast {
            def srcFile = project.file("${getSrcDirectory(apiName, oldVersion)}/ch/tutteli/atrium/api/${apiName.replaceAll('-', '/')}/$file")
            String newContent = modifyContent(srcFile.getText('UTF-8'))
            srcFile.write(newContent, 'UTF-8')
        }
    }
    def unzip = project.tasks.getByName("unzip-$taskSuffix")
    fixSrc.mustRunAfter(unzip)
    project."compileSrc_${nameUnderline070}Kotlin".dependsOn fixSrc
}

//@formatter:off
createBcAndBbcTasksForApis('0.6.0',
    'forgive=.*ch\\.tutteli\\.atrium\\.api\\.cc\\.(de_CH|en_UK|infix\\.en_UK)\\.(' +
        '(IterableContainsNotAssertionsSpec.*:(enthaeltNicht|containsNot|notTo contain) 4.0 throws AssertionError.*)|' +
        '(IterableContainsInAnyOrderNotOrAtMostObjectsAssertionsSpec.*:(' +
            '((enthaelt|contains|to contain) 5.0 (nichtOderHoechstens|notOrAtMost) once throws AssertionError)|' +
            '((enthaelt|contains|to contain) 5.0 and 4.0 (nichtOderHoechstens|notOrAtMost) twice throws AssertionError)' +
        ').*)|' +
        '(IterableContainsInAnyOrderExactlyObjectsAssertionsSpec.*:(' +
            '((enthaelt|contains|to contain) 5.0 (genau|exactly) 3 times throws AssertionError)|' +
            '((enthaelt|contains|to contain) 5.0 and 4.0 (genau|exactly) twice throws AssertionError)|' +
            '((enthaelt|contains|to contain) 5.0 and 4.0 (genau|exactly) 3 times throws AssertionError)' +
        ').*)|' +
        '(IterableContainsInAnyOrderAtMostObjectsAssertionsSpec.*:(enthaelt|contains|to contain) 5.0 and 4.0 (hoechstens|atMost) twice throws AssertionError.*)|' +
        '(IterableContainsInAnyOrderAtLeastObjectsAssertionsSpec.*:(' +
            '((enthaelt|contains|to contain) 5.0 in any order (zumindest|atLeast) 3 times throws AssertionError)|' +
            '((enthaelt|contains|to contain) 5.0 and 4.0 in any order (zumindest|atLeast) 3 times throws AssertionError)|' +
            '((enthaelt|contains|to contain) 5.0 and 4.0 (zumindest|atLeast) once (aberHoechstens|butAtMost) twice throws AssertionError)|' +
            '((enthaelt|contains|to contain) 5.0 and 4.0 (zumindest|atLeast) 3 times (aberHoechstens|butAtMost) 4 times throws AssertionError)' +
        ').*)|' +
        '(CharSequenceContainsNotOrAtMostAssertionsSpec.*:(' +
            '((enthaelt|contains|to contain) \'o\' (nichtOderHoechstens|notOrAtMost) once throws AssertionError)|' +
            '((enthaelt|contains|to contain) \'o\' and \'l\' (nichtOderHoechstens|notOrAtMost) twice throws AssertionError)' +
        ').*)|' +
        '(CharSequenceContainsNotAssertionsSpec.*:(' +
            '((enthaeltNicht|containsNot|notTo contain) \'l\' throws AssertionError)|' +
            '((enthaeltNicht|containsNot|notTo contain) \'H\', \'l\' throws AssertionError)|' +
            '((enthaeltNicht|containsNot|notTo contain) \'l\', \'H\' once throws AssertionError)|' +
            '((enthaeltNicht|containsNot|notTo contain) (ignoriereGrossKleinschreibung|ignoringCase|ignoring case) \'H\', \'l\' throws AssertionError)' +
        ').*)|' +
        '(CharSequenceContainsExactlyAssertionsSpec.*:(' +
            '((enthaelt|contains|to contain) (ignoriereGrossKleinschreibung|ignoringCase|ignoring case) \'o\' (genau|exactly) twice throws)|' +
            '((enthaelt|contains|to contain) \'o\' (genau|exactly) 3 times throws AssertionError)|' +
            '((enthaelt|contains|to contain) \'o\' and \'l\' (genau|exactly) twice throws AssertionError)|' +
            '((enthaelt|contains|to contain) \'o\' and \'l\' (genau|exactly) 3 times throws AssertionError)' +
        ').*)|' +
        '(CharSequenceContainsContainsNotAssertionsSpec.*:(enthaelt|contains|contains Values) \'hello\' and \'robert\' throws AssertionError.*)|' +
        '(CharSequenceContainsAtMostAssertionsSpec.*:(' +
            '((enthaelt|contains|to contain) (ignoriereGrossKleinschreibung|ignoringCase|ignoring case) \'o\' (hoechstens|atMost) twice throws AssertionError )|' +
            '((enthaelt|contains|to contain) \'o\' and \'l\' (hoechstens|atMost) twice throws AssertionError)' +
        ').*)|' +
        '(CharSequenceContainsAtLeastAssertionsSpec.*:(' +
            '((enthaelt|contains|to contain) \'o\' (zumindest|atLeast) 3 times throws AssertionError)|' +
            '((enthaelt|contains|to contain) \'o\' and \'l\' (zumindest|atLeast) 3 times throws AssertionError)|' +
            '((enthaelt|contains|to contain) \'o\' and \'l\' (zumindest|atLeast) once (aberHoechstens|butAtMost) twice throws AssertionError)|' +
            '((enthaelt|contains|to contain) \'o\' and \'l\' (zumindest|atLeast) 3 times (aberHoechstens|butAtMost) 4 times throws AssertionError)' +
        ').*)|' +
        '(CharSequenceAssertionsSpec.*:(' +
            '((enthaeltStandardUebersetzungVon|containsDefaultTranslationOf|contains DefaultTranslationsOf) HELLO and WELCOME, throws AssertionError)|' +
            '((enthaeltNichtDieStandardUebersetzungVon|containsNotDefaultTranslationOf|containsNot DefaultTranslationsOf) HELLO and WELCOME, throws AssertionError)|' +
            '(.*can be used in ExplanatoryAssertionGroup and reported without failure.*fun `(' +
                'enthaeltStandardUebersetzungVon|enthaeltNichtDieStandardUebersetzungVon|' +
                'containsDefaultTranslationOf|containsNotDefaultTranslationOf|' +
                '(contains DefaultTranslationsOf)|(containsNot DefaultTranslationsOf)' +
            ')`)' +
        ').*)|' +
        '(Iterable.*Spec.*fun.*`[^`]*(' +
            'enthaelt|enthaeltNicht|enthaeltStrikt|eintraege|' +
            'contains|containsNot|containsStrictly|[Ee]ntries' +
        ')`.*(error cases|failing cases|context empty collection).*)|' +
        '(CollectionAssertionsSpec.*(hatDieGroesse|hasSize).*)|' +
        '(BigDecimalAssertionsSpec.*throws UnsupportedOperationException.*)|' +
        '(FloatingPointAssertionsSpec.*throws AssertionError.*)|' +
        '(FloatingPointWithErrorToleranceAssertionsSpec.*throws AssertionError.*)|' +
        '(ThrowableAssertionsSpec.*throws an AssertionError.*shows message and stacktrace.*)' +
    ')',
    'cc-de_CH', 'cc-en_UK', 'cc-infix-en_UK'
)
createBcAndBbcTasksForApis('0.7.0',
    'forgive=(.*ch\\.tutteli\\.atrium\\.api\\.cc\\.(de_CH|en_UK|en_GB|(infix\\.(en_UK|en_GB)))\\.(' +
        '(FloatingPointWithErrorToleranceAssertionsSpec.*throws AssertionError.*)|' +
        '(ThrowableAssertionsSpec.*throws an AssertionError.*shows message and stacktrace.*)|' +
        '(CharSequenceContainsRegexAssertionsSpec.*16 times throws AssertionError.*)|' +
        '(ComparableAssertionsSpec.*(isGreaterOrEquals|istGroesserOderGleich|isLessOrEquals|istKleinerOderGleich).*)' +
    '))|(.*ch\\.tutteli\\.atrium\\.VerbSpec.*which immediately evaluates assertions.*)',
    'cc-de_CH', 'cc-en_GB', 'cc-infix-en_GB'
)
createBcAndBbcTasksForApis('0.8.0',
    'forgive=(.*ch\\.tutteli\\.atrium\\.api\\.cc\\.(de_CH|en_GB|(infix\\.en_GB))\\.('+
        '(CharSequenceContainsRegexAssertionsSpec.*(16 times throws AssertionError|(roberto?.*once throws AssertionError)).*)|' +
        '(ComparableAssertionsSpec.*(isGreaterOrEquals|istGroesserOderGleich|isLessOrEquals|istKleinerOderGleich).*)' +
    '))|(.*ch\\.tutteli\\.atrium\\.VerbSpec.*which immediately evaluates assertions.*)',
    'cc-de_CH', 'cc-en_GB', 'cc-infix-en_GB'
)
createBcAndBbcTasksForApis('0.9.2',
    'forgive=(ch/tutteli/atrium/api/fluent/en_GB/ComparableAssertionsSpec.*(isLessThanOrEqual|isGreaterThanOrEqual).*)',
    'fluent-en_GB'
)
createBcAndBbcTasksForApis('0.10.0',
    'forgive=(ch/tutteli/atrium/api/fluent/en_GB/ComparableAssertionsSpec.*(isLessThanOrEqual|isGreaterThanOrEqual).*)',
    'fluent-en_GB'
)
createBcAndBbcTasksForApis('0.11.1',
    'forgive=(ch/tutteli/atrium/api/fluent/en_GB/ComparableAssertionsSpec.*(isLessThanOrEqual|isGreaterThanOrEqual).*)',
    'fluent-en_GB'
)
createBcAndBbcTasksForApis('0.12.0',
    'forgive=(ch/tutteli/atrium/api/fluent/en_GB/(' +
        '(PathAssertionsSpec/.*`is(Readable|Writable|RegularFile|Directory)`.*)' +
    '))|(ch/tutteli/atrium/api/infix/en_GB/(' +
        '(Fun0AssertionsJvmSpec/.*)|' +
        '(PathAssertionsSpec/.*`toBe (readable|writable|aRegularFile|aDirectory)`.*)' +
    '))|(ch/tutteli/atrium/api/(fluent|infix)/en_GB/ComparableAssertionsSpec.*(isLessThanOrEqual|isGreaterThanOrEqual).*)',
    'fluent-en_GB','infix-en_GB'
)
//@formatter:on


['cc-de_CH', 'cc-en_GB', 'cc-infix-en_GB'].each { apiName ->
    createFixSrcTask(apiName, '0.7.0', 'ArrayAsIterableAssertionsSpec.kt') { String content ->
        return content.replaceAll(/Assert<Array<Int>>::asIterable.name/, '"asIterable"')
    }
    createFixSrcTask(apiName, '0.7.0', 'IterableContainsInOrderOnlyGroupedEntriesAssertionsSpec.kt') { String content ->
        return content.replaceAll(/ ((?:Entry|Eintrag)\(groups\[0]\)|(?:Entries|Eintraege)\(groups\[0], \*groups.drop\(1\)\.toTypedArray\(\)\))/, ' $1 as GroupWithoutNullableEntries<Assert<Double>.() -> Unit>')
    }
}
createFixSrcTask('cc-infix-en_GB', '0.7.0', 'BigDecimalAssertionsSpec.kt') { String content ->
    return content.replaceAll(/Assert<BigDecimal>::toBe.name/, '"toBe"').replaceAll(/Assert<BigDecimal>::notToBe.name/, '"notToBe"')
}
createFixSrcTask('cc-infix-en_GB', '0.7.0', 'CharSequenceAssertionsSpec.kt') { String content ->
    return content.replaceAll(/"\$\{Assert<CharSequence>::notToBe.name}/, '"notToBe')
}
createFixSrcTask('cc-infix-en_GB', '0.7.0', 'IterableContainsInOrderOnlyValuesAssertionsSpec.kt') { String content ->
    return content.replaceAll(/NullableValues<Double>/, 'NullableValues<Double?>')
}
['fluent-en_GB', 'infix-en_GB'].each { apiName ->
    createFixSrcTask(apiName, '0.12.0', 'ChronoLocalDateTimeAssertionSpec.kt') { String content ->
        return content.replaceAll(/ fun1/, ' fun1<ChronoLocalDateTime<*>, ChronoLocalDateTime<*>>')
    }
}
