import eclipsebuild.*
import eclipsebuild.testing.EclipseTestTask
import java.util.regex.*

plugins {
    id "org.ajoberstar.grgit" version "4.1.1"
}

apply plugin: eclipsebuild.BuildDefinitionPlugin

ext.toolingApiBundleVersion = getBundleVersion(toolingApiVersion)

// TODO pin down java version and fail if build runs with non-11 version

// define version mapping for the 3rd-party dependencies that are not specific to a particular Eclipse version
def eclipseVersionAgnosticDependencies = [
        'org.gradle.toolingapi'                : toolingApiBundleVersion
]

def targetPlatformIds = [
        '48',
        '49',
        '410',
        '411',
        '412',
        '413',
        '414',
        '415',
        '416',
        '417',
        '418',
        '419',
        '420',
        '421',
        '422',
        '423',
        '424',
        '425',
        '426',
        '427',
        '428',
        '429',
        '430',
        '431',
        '432',
        '433',
        '434',
        '435',
        '436',
        '437',
]

// target platform definition for all major Eclipse releases between version 4.3 and 4.8
// the default version is 48 which can be overridden through -Peclipse.version=<version>
// also the target platforms contain 1) the Eclipse SDK 2) the latest junit 3) SWTBot 2.2.1
eclipseBuild {
    defaultEclipseVersion = '434'
    // TODO make the default targetPlatformIds.last() after adding support for running builds on Java 17. e424+ builds should run on Java 17.

    final def swtPluginId = "org.eclipse.swt.${ECLIPSE_WS}.${ECLIPSE_OS}.${ECLIPSE_ARCH}"

    targetPlatformIds.each { id ->
        targetPlatform {
            eclipseVersion = id
            targetDefinition = file("target-platforms/${id}.target")
            versionMapping = readVersionMapping(file("target-platforms/${id}-version.properties"), swtPluginId) + eclipseVersionAgnosticDependencies
        }
    }

    scmRepo = "https://github.com/eclipse/buildship.git"
    commitId = currentCommitId()
}

def develocityApi = project.extensions.findByName('develocity')
if (develocityApi) {
    def eclipseVersion = Config.on(project).getTargetPlatform().getEclipseVersion()
    develocityApi.getBuildScan().tag("eclipse_${eclipseVersion}")
    if (project.hasProperty('eclipse.test.java.version')) {
        develocityApi.getBuildScan().tag("eclipseTestJdk_${project.getProperty('eclipse.test.java.version')}")
    }
}

// read the current version from an external file and add a timestamp suffix if requested by the caller
ext.baseVersion = file('version.txt').text.trim()
ext.versionQualifier = getVersionQualifier()
version = baseVersion + '.' + versionQualifier

// ensure that the assembleTargetPlatform is executed when the gradle.properties file is changed
project.assembleTargetPlatform.inputs.file file('gradle.properties')

subprojects {
    // set the calculated version on all projects in the hierarchy
    version = rootProject.version

    plugins.withType(JavaPlugin) {
        def config = Config.on(project)
        if (project.name.endsWith(".compat") || config.targetPlatform.eclipseVersion in ['43', '44', '45', '46', '47', '48', '49', '410', '411', '412', '413', '414', '415', '416']) {
            java {
                toolchain {
                    languageVersion = JavaLanguageVersion.of(8)
                }
            }
        } else if (config.targetPlatform.eclipseVersion in ['417', '418', '419', '420', '421', '422', '423', '424']) {
            java {
                toolchain {
                    languageVersion = JavaLanguageVersion.of(11)
                }
            }
        } else if (config.targetPlatform.eclipseVersion in ['425', '426', '427', '428', '429', '430', '431', '432', '433', '434']) {
            java {
                toolchain {
                    languageVersion = JavaLanguageVersion.of(17)
                }
            }
        } else {
            java {
                toolchain {
                    languageVersion = JavaLanguageVersion.of(21)
                }
            }
        }

        tasks.matching { it instanceof JavaCompile || it instanceof GroovyCompile }.all {
            // enable all warnings except for different sourceCompatibility and targetCompatibility value
            options.compilerArgs << '-Xlint:all'
            options.compilerArgs << '-Xlint:-options'
        }
    }

    // use common bundled testing depenendencies for all test plugins
    plugins.withType(eclipsebuild.TestBundlePlugin) {
        dependencies {
            implementation "org.codehaus.groovy:groovy-all:$groovyLibVersion"
            bundled "org.codehaus.groovy:groovy-all:$groovyLibVersion"
            bundled "org.objenesis:objenesis:$objenesisLibVersion"
            bundled("org.spockframework:spock-core:$spockLibVersion") {
                exclude group:"org.hamcrest", module: "hamcrest"
            }
            bundled "cglib:cglib-nodep:$cglibLibVersion"
            bundled "org.slf4j:slf4j-simple:$slf4jLibVersion"
            compileOnly("com.google.errorprone:error_prone_annotations:2.25.0") // Groovy would warn about unknown annotations in Guava classes without this dependency
        }
        if (project.hasProperty('eclipse.test.java.version')) {
            tasks.withType(EclipseTestTask) {
                javaLauncher = javaToolchains.launcherFor {
                    languageVersion = JavaLanguageVersion.of(project.getProperty('eclipse.test.java.version') as Integer)
                }
            }
        }
    }

    // apply Checkstyle plugin, mainly to ensure license/copyright and javadoc is present
    apply plugin: 'checkstyle'

    // share checkstyle config across all sub-projects
    def checkstyleConfigDir = "$rootDir/gradle/config/checkstyle"
    tasks.withType(Checkstyle).all {
        javaLauncher = javaToolchains.launcherFor {
            languageVersion = JavaLanguageVersion.of(17)
        }
        configFile = "$checkstyleConfigDir/checkstyle.xml" as File
        configProperties = ['checkstyleConfigDir': checkstyleConfigDir]
        inputs.file "$checkstyleConfigDir/suppressions.xml" as File
        reports {
            html.stylesheet = project.rootProject.resources.text.fromFile('gradle/config/checkstyle/stylesheet.xsl')
        }
    }

    tasks.withType(EclipseTestTask).all {
        def java8 = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(8) }
        def java11 = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(11) }
        jvmArgs "-Djdk8.location=${java8.get().metadata.installationPath.asFile}"
        jvmArgs "-Djdk11.location=${java11.get().metadata.installationPath.asFile}"

        def config = Config.on(project)
        if (config.targetPlatform.eclipseVersion in ['48', '49', '410', '411', '412', '413', '414', '415', '416', '417', '418', '419', '420', '421', '422', '423', '424', '425', '426', '427', '428']) {
           enabled = false  
        }
    }

    // configure the repositories where the external dependencies can be found
    repositories {
        maven {
            name = 'mavenized-target-platform'
            url = "${eclipsebuild.Config.on(project).mavenizedTargetPlatformDir}"
        }

        mavenCentral()

        maven {
            name = 'gradle-snapshots'
            url = gradleSnapshotsRepositoryUrl
        }

        maven {
            name = 'gradle-releases'
            url = gradleReleasesRepositoryUrl
        }

        maven {
            name = 'gradle-remote'
            url = gradleRemoteRepositoryUrl
        }
    }
}

// tag the HEAD of the current branch and push the new tag
task tag {
    doLast {
        def githubAccessKey = findProperty("githubAccessKey")
        if (!githubAccessKey) {
            throw new IllegalStateException("Cannot tag commit: define GitHub access key with -PgithubAccessKey=<access_key> project property")
        } else {
            // set access token
            System.setProperty("org.ajoberstar.grgit.auth.username", githubAccessKey)
            // tag current state
            grgit.tag.add {
                def tagVersion = file('version.txt').text.trim()
                name = "REL_$tagVersion"
                message = "Create tag REL_$tagVersion"
            }
            // push changes
            grgit.push(tags: true)
        }
    }
}

// increment the service segment in the version number and push it to master
task incrementVersion {
    doLast {
        def githubAccessKey = findProperty("githubAccessKey")
        if (!githubAccessKey) {
            throw new IllegalStateException("Cannot increment version: define GitHub access key with -PgithubAccessKey=<access_key> project property")
        } else {
            // update version file
            def oldVersion = file('version.txt').text.trim()
            def newVersion = ""
            def matcher = version =~ "^(\\d+)\\.(\\d+).(\\d+)"
            if (matcher.find()) {
                def serviceSegment = Integer.parseInt(matcher.group(3)) + 1
                newVersion = matcher.group(1) + "." + matcher.group(2) + "." + serviceSegment
                file('version.txt').text = newVersion
                Pattern bundleVersionPattern = Pattern.compile('(?<=Bundle-Version: )\\d+\\.\\d+\\.\\d+(?=\\.qualifier)')
                file('.').eachFileRecurse { file ->
                    if (file.name == 'MANIFEST.MF') {
                        replacePatternsInFile(file, [(bundleVersionPattern): newVersion])
                    }
                }
            } else {
                throw new IllegalStateException("Unparseable version: $oldVersion.")
            }

            // set access token
            System.setProperty("org.ajoberstar.grgit.auth.username", githubAccessKey)
            
            // commit and push changes
            grgit.commit {
                message = "Increment version $oldVersion -> $newVersion"
                all = true
            }
            grgit.push()
        }
    }
}

void replacePatternsInFile(File file, Map<Pattern, String> patternAndReplacement) {
    String fileText = file.text
    patternAndReplacement.each { Pattern pattern, String replacement ->
        fileText = pattern.matcher(fileText).replaceAll(replacement)
    }
    file.text = fileText
}

String getVersionQualifier() {
    def config = BuildshipConfig.on(project)

    // determine suffix for snapshot and milestone builds
    String suffix = getVersionSuffix(config)

    // use full timestamp on CI vs. date-only for local builds
    if (project.hasProperty('qualifier')) {
        'v' + project.getProperty('qualifier')
    } else if (project.hasProperty('build.invoker') && project.property('build.invoker') == 'ci') {
        // note that for Eclipse plugin versions, the '-' and '.' character are invalid in front of the build id
        'v' + new Date().format('yyyyMMdd-kkmm', TimeZone.getTimeZone('GMT')) + suffix
    } else {
        'v' + new Date().format('yyyyMMdd', TimeZone.getTimeZone('GMT')) + suffix
    }
}

private getVersionSuffix(BuildshipConfig config) {
    if (config.isRelease()) {
        return ''
    }
    if (config.isMilestone()) {
        return '-m'
    }
    if (config.isSnapshot()) {
        return '-s'
    }
    throw new IllegalStateException("BuildshipConfig must either be a release, milestone, or snapshot.")
}

String getBundleVersion(String version) {
    def matcher = version =~ /(\d+)\.(\d+)(?:-.*|\.(\d+)(?:-.*)?)?/
    if (!matcher.matches()) {
        throw new IllegalArgumentException("Invalid bundle version: $version")
    }

    def major = matcher.group(1)
    def minor = matcher.group(2)
    def service = matcher.group(3) ?: '0'
    "$major.$minor.$service"
}

def currentCommitId() {
    def gitVersion = providers.exec {
        commandLine('git', 'rev-parse', '--verify', 'HEAD')
    }.standardOutput.asText.get().trim()
}

def readVersionMapping(File propertiesFile, String swtPluginId) {
    if (!propertiesFile.exists()) {
        return [:]
    }
    Properties properties = new Properties()
    propertiesFile.withInputStream {
        properties.load(it)
    }
    Map result = [:]
    properties.each { k, v ->
        if (k == '$swtPluginId') {
            result[swtPluginId] = v
        } else {
            result[(k)] = v
        }
    }
    return result
}
