/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.text.SimpleDateFormat
import java.util.jar.Attributes
import org.gradle.api.artifacts.repositories.WebdavResolver

import org.gradle.build.samples.WrapperProjectCreator

/**
 * For building Gradle you usually don't need to specify any properties. Only certain functionality of the Gradle requires
 * setting certain properties. Those properties can be set in the gradle.properties file in the the gradle user home. The
 * following properties can be set:
 *
 * Uploading distributions to Gradle's release and snapshot repository at codehaus: codehausUserName, codehausUserPassword
 * Uploading the userguide and the javadoc to Gradle's website: websiteFtpUserName, websiteFtpUserPassword
 * Using the build to create a new distribution and install it on the local machine: gradle_installPath
 */

version = new Version(project)

defaultTasks "clean", "assemble"

apply plugin: 'java-base'

startScriptsDir = new File("$buildDir/startScripts")

archivesBaseName = 'gradle'

libraries = [
        ant: 'org.apache.ant:ant:1.8.1@jar',
        ant_junit: 'org.apache.ant:ant-junit:1.8.1@jar',
        ant_launcher: 'org.apache.ant:ant-launcher:1.8.1@jar',
        ant_nodeps: 'org.apache.ant:ant-nodeps:1.8.1@jar',
        ant_antlr: 'org.apache.ant:ant-antlr:1.8.1@jar',
        antlr: 'antlr:antlr:2.7.7@jar',
        asm_all: 'asm:asm-all:3.2@jar',
        commons_cli: 'commons-cli:commons-cli:1.2@jar',
        commons_io: 'commons-io:commons-io:1.4@jar',
        commons_lang: 'commons-lang:commons-lang:2.5@jar',
        dom4j: 'dom4j:dom4j:1.6.1@jar',
        google_collections: 'com.google.collections:google-collections:1.0@jar',
        groovy: 'org.codehaus.groovy:groovy-all:1.7.3@jar',
        ivy: 'org.apache.ivy:ivy:2.2.0-rc1@jar',
        jaxen: 'jaxen:jaxen:1.1@jar',
        jopt_simple: 'net.sf.jopt-simple:jopt-simple:3.2@jar',
        slf4j_api: 'org.slf4j:slf4j-api:1.6.1@jar',
        jcl_to_slf4j: 'org.slf4j:jcl-over-slf4j:1.6.1@jar',
        jul_to_slf4j: 'org.slf4j:jul-to-slf4j:1.6.1@jar',
        log4j_to_slf4j: 'org.slf4j:log4j-over-slf4j:1.6.1@jar',
        logback_classic: 'ch.qos.logback:logback-classic:0.9.24@jar',
        logback_core: 'ch.qos.logback:logback-core:0.9.24@jar',
        junit: 'junit:junit:4.8.1',
        xmlunit: 'xmlunit:xmlunit:1.3',
]

libraries.spock = ['org.spockframework:spock-core:0.4-groovy-1.7@jar', libraries.groovy, 'cglib:cglib-nodep:2.2', 'org.objenesis:objenesis:1.2']
libraries.groovy_depends = [libraries.groovy, libraries.commons_cli]
libraries.jetty_depends = ["org.mortbay.jetty:jetty:6.1.22@jar", "org.mortbay.jetty:jetty-util:6.1.22@jar",
        "javax.servlet:servlet-api:2.5@jar"]

allprojects {
    group = 'org.gradle'

    plugins.withType(JavaPlugin).whenPluginAdded {
        sourceCompatibility = 1.5
        targetCompatibility = 1.5
    }

    repositories {
        mavenRepo(urls: 'http://gradle.artifactoryonline.com/gradle/libs')
    }

    version = this.version
}

def FIRST_LEVEL_JMOCK = ['org.hamcrest:hamcrest-core:1.1@jar', 'org.hamcrest:hamcrest-library:1.1@jar', 'org.jmock:jmock-junit4:2.5.1@jar']

configure(groovyProjects()) {
    apply plugin: 'groovy'
    apply plugin: 'code-quality'
    apply plugin: 'eclipse'

    archivesBaseName = "gradle-${name.replaceAll("\\p{Upper}") { "-${it.toLowerCase()}" } }"
    dependencies {
        testCompile libraries.junit, FIRST_LEVEL_JMOCK, libraries.spock
        testCompile module("org.jmock:jmock:2.5.1") {
            dependencies('org.jmock:jmock-legacy:2.5.1@jar', 'org.objenesis:objenesis:1.0', 'cglib:cglib-nodep:2.1_3')
            dependencies(FIRST_LEVEL_JMOCK as Object[])
        }
        // todo - these should not be here
        testCompile 'org.jmock:jmock-legacy:2.5.1@jar', 'org.objenesis:objenesis:1.0', 'cglib:cglib-nodep:2.1_3'
    }

    checkstyleConfigDir = "$rootDir/config/checkstyle"
    checkstyleConfigFileName = new File(checkstyleConfigDir, "checkstyle.xml")
    codeNarcConfigFileName = "$rootDir/config/codenarc.xml"
    checkstyleProperties.checkstyleConfigDir = checkstyleConfigDir

    sourceSets.allObjects { sourceSet ->
        task "${sourceSet.getTaskName('checkstyle', 'groovy')}"(type: Checkstyle) {
            configFile = new File(checkstyleConfigDir, "checkstyle-groovy.xml")
            source sourceSet.allGroovy
            classpath = sourceSet.compileClasspath
            resultFile = new File(checkstyleResultsDir, "${sourceSet.name}.xml")
        }
    }

    test {
        maxParallelForks = guessMaxForks()
    }

    tasks.withType(Jar).each { jar ->
        jar.manifest.mainAttributes([
                (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
                (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
        ])
    }
}

allprojects {
    apply plugin: 'idea'
    apply plugin: 'eclipse'
    def config = configurations.findByName('testRuntime')
    if (!config) {
        return
    }
    ideDir = file('ide')
    task ide(type: Sync) {
        into "$ideDir/lib"
        from { config.copyRecursive {dep -> !(dep instanceof ProjectDependency)}.files }
    }
    ideaModule {
        gradleCacheVariable = 'GRADLE_CACHE'
        outputDir = "$rootProject.projectDir/intellij/out" as File
        testOutputDir = "$rootProject.projectDir/intellij/testOut" as File
    }
}

ideaModule {
    excludeDirs += file('intTestHomeDir')
    excludeDirs += file('buildSrc/build')
    excludeDirs += file('buildSrc/.gradle')
}

ideaProject {
    wildcards += ['?*.gradle']
    javaVersion = '1.6'
}

configurations {
    dists
    runtime {
        visible = false
    }
    plugins {
        visible = false
    }
    testRuntime {
        extendsFrom runtime
        extendsFrom plugins
    }
}

dependencies {
    runtime runtimeProjects()
    plugins pluginProjects()
}

evaluationDependsOn(':docs')
evaluationDependsOn(':core')

clean.dependsOn subprojects.collect { "$it.path:clean" }

task check(overwrite: true, dependsOn: groovyProjects()*.check)
task test(overwrite: true, dependsOn: groovyProjects()*.test)

zipRootFolder = "$archivesBaseName-${-> version}"

binDistImage = copySpec {
    from 'src/toplevel'
    from project(':docs').distDocs.destFile
    into('bin') {
        from startScriptsDir
        fileMode = 0755
    }
    into('lib') {
        from configurations.runtime
        into('plugins') {
            from configurations.plugins - configurations.runtime
        }
    }
}

allDistImage = copySpec {
    with binDistImage
    into('src') {
        from groovyProjects().collect {project -> project.sourceSets.main.allSource }
    }
    into('docs') {
        from project(':docs').docsDir
    }
    into('samples') {
        from project(':docs').samplesDir
    }
}

task allZip(type: Zip, dependsOn: ['launcher:startScripts', 'docs:docs', 'docs:samples']) {
    classifier = 'all'
    into(zipRootFolder) {
        with allDistImage
    }
}

task binZip(type: Zip, dependsOn: ['launcher:startScripts', 'docs:distDocs']) {
    classifier = 'bin'
    into(zipRootFolder) {
        with binDistImage
    }
}

task srcZip(type: Zip) {
    classifier = 'src'
    into(zipRootFolder) {
        from('gradlew') {
            fileMode = 0755
        }
        from(projectDir) {
            def spec = delegate
            ['buildSrc', 'subprojects/*'].each {
                spec.include "$it/*.gradle"
                spec.include "$it/src/"
            }
            include 'config/'
            include 'src/'
            include '*.gradle'
            include 'gradle.properties'
            include 'wrapper/'
            include 'gradlew.bat'
        }
    }
}

artifacts {
    tasks.withType(Zip).each {
        dists it
    }
}

task intTestImage(type: Sync) {
    dependsOn allZip.taskDependencies
    with allDistImage
    integTestGradleHome = file("$buildDir/integ test")
    into integTestGradleHome
    doLast { task ->
        ant.chmod(dir: "$integTestGradleHome/bin", perm: "ugo+rx", includes: "**/*")
        WrapperProjectCreator.createProject(new File(integTestGradleHome, 'samples'), binZip.archivePath.parentFile, version.toString())
    }
}

task integTest(type: Test, dependsOn: [intTestImage, binZip, allZip, srcZip, ':docs:userguideDocbook']) {
    integTestUserDir = file('intTestHomeDir')
    systemProperties['integTest.srcDir'] = file('src').absolutePath
    systemProperties['integTest.userGuideInfoDir'] = project(':docs').docbookSrc
    systemProperties['integTest.userGuideOutputDir'] = new File(project(':docs').samplesSrcDir, "userguideOutput").absolutePath
    systemProperties['integTest.gradleUserHomeDir'] = integTestUserDir.absolutePath
    include 'org/gradle/integtests/**/*IntegrationTest.*'
    forkEvery = 15
    maxParallelForks = guessMaxForks()

    testClassesDir = project(':core').sourceSets.integTest.classesDir
    classpath = project(':core').sourceSets.integTest.runtimeClasspath + configurations.testRuntime
    testResultsDir = file('build/test-results')
    testReportDir = file('build/reports/tests')
    testSrcDirs = []
    doFirst {
        if (isDevBuild()) {
            exclude 'org/gradle/integtests/DistributionIntegrationTest.*'
        }
        systemProperties['integTest.gradleHomeDir'] = intTestImage.integTestGradleHome.absolutePath
        def forkArgs
        if (noForkIntegTests()) {
            systemProperties['org.gradle.integtest.nofork'] = "true"
            jvmArgs '-Xmx512m', '-XX:MaxPermSize=256m', '-XX:+HeapDumpOnOutOfMemoryError'
        } else {
            jvmArgs '-Xmx512m', '-XX:+HeapDumpOnOutOfMemoryError'
        }
    }
}

boolean noForkIntegTests() {
    if (project.hasProperty('forkIntegTests')) {
        return !Boolean.valueOf(forkIntegTests)
    }
    return isDevBuild() && !OperatingSystem.current().isWindows()
}

private def isDevBuild() {
    gradle.taskGraph.hasTask(':developerBuild')
}

def guessMaxForks() {
    int processors = Runtime.runtime.availableProcessors()
    return Math.max(2, (int) (processors / 2))
}

task testedDists(dependsOn: [assemble, check, integTest])

task nightlyBuild(dependsOn: [clean, testedDists, ':docs:uploadDocs'])

task install(type: Install, description: 'Installs the minimal distribution into directory $gradle_installPath') {
    dependsOn binZip.taskDependencies
    with binDistImage
    installDirProperyName = 'gradle_installPath'
}

task installAll(type: Install, description: 'Installs the full distribution into directory $gradle_installPath') {
    dependsOn allZip.taskDependencies
    with allDistImage
    installDirProperyName = 'gradle_installPath'
}

uploadDists {
    dependsOn testedDists
    uploadDescriptor = false
    doFirst {
        it.repositories.add(new WebdavResolver()) {
            name = 'gradleReleases'
            user = codehausUserName
            userPassword = codehausUserPassword
            addArtifactPattern("${version.distributionUrl}/[artifact]-[revision](-[classifier]).[ext]" as String)
        }
    }
}

gradle.taskGraph.whenReady {graph ->
    if (graph.hasTask(uploadDists)) {
        // check properties defined and fail early
        codehausUserName
        codehausUserPassword
    }
}

task developerBuild(dependsOn: [clean, testedDists])
task ciBuild(dependsOn: [clean, testedDists])

task release(dependsOn: [uploadDists, ':docs:uploadDocs']) << {
    // Update the version properties in gradle.properties
//    Properties props = new Properties()
//    File propsFile = file('gradle.properties')
//    propsFile.withReader { props.load(it) }
//    props.previousVersion = props.nextVersion
//    def nextVersion = props.nextVersion.split('\\.')
//    nextVersion[1] = (nextVersion[1] as Integer) +1
//    props.nextVersion = nextVersion.join('.')
//    propsFile.withWriter { props.store(it, 'properties') }
// todo rewrite release functionality for Git. Until then we have to do tagging and branching manually.
}

task wrapper(type: Wrapper, dependsOn: binZip)
wrapper.doFirst {task ->
    task.configure {
        gradleVersion = version
        jarPath = 'wrapper'
    }
}

task rebuildWrapper(dependsOn: [clean, wrapper])

def groovyProjects() {
    subprojects.findAll { project -> project.name != 'docs' }
}

def runtimeProjects() {
    groovyProjects() - pluginProjects()
}

def pluginProjects() {
    ['plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven', 'eclipse', 'idea', 'announce', 'scala'].collect {
        project(it)
    }
}

class Version {
    String versionNumber
    Date buildTime
    Boolean release = null

    def Version(project) {
        this.versionNumber = project.nextVersion
        File timestampFile = new File(project.buildDir, 'timestamp.txt')
        if (!timestampFile.isFile()) {
            timestampFile.parentFile.mkdirs()
            timestampFile.createNewFile()
        } else {
            boolean uptodate = true
            def modified = timestampFile.lastModified()
            project.project(':core').fileTree('src/main').visit {fte ->
                if (fte.file.isFile() && fte.lastModified > modified) {
                    uptodate = false
                    fte.stopVisiting()
                }
            }
            if (!uptodate) {
                timestampFile.setLastModified(new Date().time)
            }
        }
        buildTime = new Date(timestampFile.lastModified())

        project.gradle.taskGraph.whenReady {graph ->
            if (!graph.hasTask(':release')) {
                this.versionNumber += "-" + getTimestamp()
                release = false
            } else {
                release = true
            }
        }
    }

    String toString() {
        versionNumber
    }

    String getTimestamp() {
        new SimpleDateFormat('yyyyMMddHHmmssZ').format(buildTime)
    }

    boolean isRelease() {
        if (release == null) {
            throw new GradleException("Can't determine whether this is a release build before the task graph is populated")
        }
        return release
    }

    String getDistributionUrl() {
        if (release) {
            'https://dav.codehaus.org/dist/gradle'
        } else {
            'https://dav.codehaus.org/snapshots.dist/gradle'
        }
    }
}

class Install extends Sync {

    String installDirProperyName
    File installDir

    def Install() {
        addPropertyCheck()
        doLast {
            ant.chmod(file: "$installDir/bin/gradle", perm: 'ugo+x')
        }
    }

    private void addPropertyCheck() {
        project.gradle.taskGraph.whenReady {graph ->
            if (graph.hasTask(path)) {
                // Do this early to ensure that the properties we need have been set, and fail early
                if (!project.hasProperty(installDirProperyName)) {
                    throw new RuntimeException("You can't install without setting the $installDirProperyName property.")
                }
                installDir = project.file(this.project."$installDirProperyName")
                if (installDir.file) {
                    throw new RuntimeException("Install directory $installDir does not look like a Gradle installation. Cannot delete it to install.")
                }
                if (installDir.directory) {
                    File libDir = new File(installDir, "lib")
                    if (!libDir.directory || !libDir.list().findAll { it.matches('gradle.*\\.jar')}) {
                        throw new RuntimeException("Install directory $installDir does not look like a Gradle installation. Cannot delete it to install.")
                    }
                }
                into installDir
            }
        }
    }
}
