apply plugin: eclipsebuild.UpdateSitePlugin
apply plugin: 'org.hidetake.ssh'

buildscript {
    repositories {
        gradlePluginPortal()
    }
    dependencies {
        classpath 'org.hidetake:gradle-ssh-plugin:2.10.1'
    }
}

updateSite {
    extraResources = files('epl-v20.html', 'notice.html', 'p2.index')
    signing = { File unsignedFolder, File signedFolder ->
        if (gradle.taskGraph.hasTask(uploadUpdateSite)) {
            signWithEclipseService(signedFolder, unsignedFolder)
        } else {
            signLocally(unsignedFolder, signedFolder)
        }
    }
    mutateArtifactsXml = { Node artifactsXml ->
        addExtraPropertiesToArtifactsXml(artifactsXml)
    }
}

remotes {
    eclipseDotOrg {
        host = ECLIPSE_ORG_FTP_HOST
        user =  ECLIPSE_ORG_FTP_USER
        password = ECLIPSE_ORG_FTP_PASSWORD
        knownHosts = project.rootProject.file('gradle/ssh/known_hosts')
        fileTransfer = 'scp'
    }
}

dependencies {
    localPlugin project(':org.eclipse.buildship.compat')
    localPlugin project(':org.eclipse.buildship.core')
    localPlugin project(':org.eclipse.buildship.ui')
    localPlugin project(':org.eclipse.buildship.branding')
    localFeature project(':org.eclipse.buildship.feature')
    localPlugin project(':org.eclipse.buildship.oomph')
    localPlugin project(':org.eclipse.buildship.oomph.edit')
    localFeature project(':org.eclipse.buildship.oomph.feature')
    if (findProperty('latest') == 'true') {
        localPlugin project(':org.eclipse.buildship.gradleprop.provider')
        localFeature project(':org.eclipse.buildship.gradleprop.feature')
        localPlugin project(':org.eclipse.buildship.kotlindsl.provider')
        localFeature project(':org.eclipse.buildship.kotlindsl.feature')
        localPlugin project(':org.eclipse.buildship.dcl.provider')
        localFeature project(':org.eclipse.buildship.dcl.feature')
    }

    // include 3rd party dependencies in the update site
    externalPlugin withEclipseBundle('org.gradle.toolingapi')

    // official 3rd party dependencies from Orbit which are already signed and normalized with pack200
    signedExternalPlugin withEclipseBundle('slf4j.api')
    signedExternalPlugin withEclipseBundle('com.google.gson')
    signedExternalPlugin withEclipseBundle('com.google.guava')
}

task uploadUpdateSite(dependsOn : createP2Repository) {
    description = "Uploads the generated update site to the eclipse.org server."

    onlyIf { !['36', '37'].contains(eclipsebuild.Config.on(project).targetPlatform.eclipseVersion) }

    doLast {
        // folder structure
        // releases                    (main folder - composite update site if release repository)
        //  |--2.x                     (version folder - always a composite update site)
        //     |--2.0.0.v20150413-1512 (repository folder - simple update site)

        // define the folder structure
        def releaseTypeFolder = "${ECLIPSE_ORG_FTP_UPDATE_SITES_PATH}/e${eclipsebuild.Config.on(project).targetPlatform.eclipseVersion}/${releaseTypeFolder(project)}"
        def versionStreamFolder = "${releaseTypeFolder}/${releaseStream()}"
        def repositoryFolder = "${versionStreamFolder}/${project.version}"

        // create composite update site for the root of the release repository and for the versions folder
        def config = eclipsebuild.BuildshipConfig.on(project)
        def compositeFolders = config.isRelease() ? [releaseTypeFolder, versionStreamFolder] : [versionStreamFolder]

        // upload the update site and update the composite descriptors
        def directoriesInCompositeUpdateSite = eclipsebuild.BuildshipConfig.on(project).isSnapshot() ? 'ls -td -- */ | head -1' : 'ls -td -- */'
        ssh.run {
            session(remotes.eclipseDotOrg) {
                // create the leaf directory if not exist
                execute "mkdir -p $repositoryFolder"

                // copy the update site from the build directory to the remote location
                put from: files { file("$buildDir/repository").listFiles() }, into: repositoryFolder
            }
        }

        def repoName = "Buildship"
        def timestamp = (int) (System.currentTimeMillis() / 1000)

        // update metadata for composite repositories 
        compositeFolders.each { compositeFolder ->

            // Reference all subfolders in the repository xml files
            // we have to rely on the output of the `ls` command as we only have access to a restricted shell
            // which allows as to only list/create/delete files and directories.
            def remoteFileStructure = ssh.run {
                session(remotes.eclipseDotOrg) {
                    execute "ls -l $compositeFolder"
                }
            }
            def remoteDirs = remoteFileStructure.readLines().findAll { it.startsWith('d') }.collect{ it.split(' +').last() }
            def remoteDirsXml = remoteDirs.collect { "    <child location='$it/'/>"  }.join(System.getProperty('line.separator'))
            def compositeArtifactsFile = file("${buildDir.path}/composite-repository/compositeArtifacts.xml")
            def compositeContentFile = file("${buildDir.path}/composite-repository/compositeContent.xml")
            def p2indexFile = file("${buildDir.path}/composite-repository/p2.index")
            compositeArtifactsFile.parentFile.mkdirs()

            compositeArtifactsFile.text = """<?xml version='1.0' encoding='UTF-8'?>
<?compositeArtifactRepository version='1.0.0'?>
<repository name='$repoName' type='org.eclipse.equinox.internal.p2.artifact.repository.CompositeArtifactRepository' version='1.0.0'>
   <properties size='3'>
    <property name='p2.timestamp' value='$timestamp'/>
    <property name='p2.compressed' value='true'/>
    <property name='p2.atomic.composite.loading' value='true'/>
  </properties>
  <children size='${remoteDirs.size()}'>
$remoteDirsXml
  </children>
</repository>
"""

            compositeContentFile.text = """<?xml version='1.0' encoding='UTF-8'?>
<?compositeMetadataRepository version='1.0.0'?>
<repository name='$repoName' type='org.eclipse.equinox.internal.p2.metadata.repository.CompositeMetadataRepository' version='1.0.0'>
   <properties size='3'>
    <property name='p2.timestamp' value='$timestamp'/>
    <property name='p2.compressed' value='true'/>
    <property name='p2.atomic.composite.loading' value='true'/>
  </properties>
  <children size='${remoteDirs.size()}'>
$remoteDirsXml
  </children>
</repository>
"""

            p2indexFile.text = """version=1
metadata.repository.factory.order=compositeContent.xml,\\!
artifact.repository.factory.order=compositeArtifacts.xml,\\!
"""
            project.exec {
                workingDir "$buildDir/composite-repository"
                commandLine "${System.getProperty('java.home')}/bin/jar", 'cvMf', 'compositeArtifacts.jar', 'compositeArtifacts.xml'
            }

            project.exec {
                workingDir "$buildDir/composite-repository"
                commandLine "${System.getProperty('java.home')}/bin/jar", 'cvMf', 'compositeContent.jar', 'compositeContent.xml'
            }

            ssh.run {
                session(remotes.eclipseDotOrg) {
                    // remove old metadata xmls if any
                    execute "rm -rf $compositeFolder/compositeArtifacts.xml"
                    execute "rm -rf $compositeFolder/compositeContent.xml"
                    
                    // update composite repo metadate
                    uploadFileWithScp(new File(compositeArtifactsFile.parentFile, 'compositeArtifacts.jar'), compositeFolder)
                    uploadFileWithScp(new File(compositeContentFile.parentFile, 'compositeContent.jar'), compositeFolder)
                    uploadFileWithScp(p2indexFile, compositeFolder)
                }
            }           
        }
        
        if ((config.isRelease() || config.isSnapshot()) && isLatestRelease()) {
            ssh.run {
                session(remotes.eclipseDotOrg) {
                    def latestFolder = "${ECLIPSE_ORG_FTP_UPDATE_SITES_PATH}/latest${config.isSnapshot() ? '-snapshot' : ''}"
                    // wipe the previous release and replace it with the new one
                    execute "rm -rf $latestFolder"
                    execute "mkdir -p $latestFolder"
                    file("$buildDir/repository").listFiles().each { uploadDirWithScp(it, latestFolder) }
                }
            }
        }
    }
}

// utility methods
def releaseTypeFolder(Project project) {
    def config = eclipsebuild.BuildshipConfig.on(project)
    if(config.isRelease()) {
        "releases"
    } else if(config.isMilestone()){
        "milestones"
    } else if(config.isSnapshot()) {
        "snapshots"
    } else {
        throw new IllegalStateException("BuildshipConfig must either be a release, milestone, or snapshot.")
    }
}

def releaseStream() {
    // crop the major segment from the version number
    def matcher = version =~ /(\d+)\.\d+\.\d+\..*/
    "${matcher[0][1]}.x"
}

private void signWithEclipseService(signedFolder, unsignedFolder) {
    file(unsignedFolder).eachFileRecurse(groovy.io.FileType.FILES) { File source ->
        if(source.name.endsWith('.jar')) {
            String sourceRelativePath = source.path.replace(unsignedFolder.path + System.getProperty('file.separator'), '')
            File target = new File(signedFolder, sourceRelativePath)
            target.parentFile.mkdirs()
            def cmd = "${file('sign-jar.sh').absolutePath} $ECLIPSE_ORG_FTP_HOST $ECLIPSE_ORG_FTP_USER $ECLIPSE_ORG_FTP_PASSWORD ${project.rootProject.file('gradle/ssh/known_hosts').absolutePath} ${source.absolutePath} ${target.absolutePath}"
            def sout = new StringBuilder()
            def serr = new StringBuilder()
            def process = cmd.execute()
            process.consumeProcessOutput(sout, serr)
            def exitCode = process.waitFor()
            System.out.println(sout)
            System.err.println(serr)
            if (exitCode != 0) {
                throw new RuntimeException("Signing failed for " + source.name)
            }
        }
    }
}

private void uploadFileWithScp(File source, String targetPath) {
    def sourcePath = source.absolutePath
    def cmd = "sshpass -p $ECLIPSE_ORG_FTP_PASSWORD scp -o UserKnownHostsFile=${project.rootProject.file('gradle/ssh/known_hosts')} $source $ECLIPSE_ORG_FTP_USER@$ECLIPSE_ORG_FTP_HOST:${targetPath}/"
    def process = cmd.execute()
    process.waitFor()
}

private void uploadDirWithScp(File source, String targetPath) {
    def sourcePath = source.absolutePath
    def cmd = "sshpass -p $ECLIPSE_ORG_FTP_PASSWORD scp -r -o UserKnownHostsFile=${project.rootProject.file('gradle/ssh/known_hosts')} $source $ECLIPSE_ORG_FTP_USER@$ECLIPSE_ORG_FTP_HOST:${targetPath}/"
    def process = cmd.execute()
    process.waitFor()
}

private void signLocally(File unsignedFolder, File signedFolder) {
  def unsignedPluginsDir = new File(unsignedFolder, "plugins")
  def unsignedFeaturesDir = new File(unsignedFolder, "features")
  def signedPluginsDir = new File(signedFolder, "plugins")
  def signedFeaturesDir = new File(signedFolder, "features")

  signedPluginsDir.mkdirs()
  signedFeaturesDir.mkdirs()

  File targetDir = signedPluginsDir
  def signBundle = {
    project.logger.info("Sign '${it.absolutePath}'")
    project.ant.signjar(
        verbose: 'false',
        destDir: targetDir,
        alias: 'EclipsePlugins',
        jar: it,
        keystore: project.findProject(':').file('gradle/config/signing/DevKeystore.ks'),
        storepass: 'tooling',
        keypass: 'tooling',
        sigalg: 'SHA1withDSA',
        digestalg: 'SHA1',
        preservelastmodified: 'true')
  }

  unsignedPluginsDir.listFiles().each signBundle
  targetDir = signedFeaturesDir
  unsignedFeaturesDir.listFiles().each signBundle
}

private void addExtraPropertiesToArtifactsXml(Node artifactsXml) {
    // modify the artifacts.xml file only when it is deployed to the eclipse.org servers
    if (ECLIPSE_ORG_MIRROR_PATH) {
        // https://wiki.eclipse.org/Equinox/p2/p2.mirrorsURL
        // add p2.mirrorsUrl to the properties section
        def mainPropertiesNode = artifactsXml.depthFirst().find { it.parent()?.name() == 'repository' && it.name() == 'properties' }
        new Node(mainPropertiesNode, 'property', ['name': 'p2.mirrorsURL', 'value': calculateP2MirrorsUrl(project)] )

        // add download.stats property to each installable unit
        // https://wiki.eclipse.org/Equinox_p2_download_stats#Enabling_stats_in_your_repository
        new Node(mainPropertiesNode, 'property', ['name': 'p2.statsURI', 'value': calculateStatsUrl(project)])
        mainPropertiesNode.@size = mainPropertiesNode.children().size()

        // add <property name="download.stats" value="feature-id"/> node to the features
        artifactsXml.depthFirst().findAll {
            it.parent()?.parent()?.name() == 'artifacts' &&
            it.parent()?.name() == 'artifact' &&
            it.parent()?.@classifier == 'org.eclipse.update.feature' &&
            it.name() == 'properties'
        }.each {
            new Node(it, 'property', ['name': 'download.stats', 'value': it.parent().@id ] )
            it.@size = it.children().size()
        }
    }
}

def calculateP2MirrorsUrl(Project project) {
    def repositoryPath = "${ECLIPSE_ORG_MIRROR_PATH}/e${eclipsebuild.Config.on(project).targetPlatform.eclipseVersion}/${releaseTypeFolder(project)}/${releaseStream()}/${project.version}"
    "http://www.eclipse.org/downloads/download.php?file=${repositoryPath}&format=xml"
}

def calculateStatsUrl(Project project) {
    "http://download.eclipse.org/stats/buildship/e${eclipsebuild.Config.on(project).targetPlatform.eclipseVersion}/${releaseTypeFolder(project)}/${baseVersion}"
}

def isLatestRelease() {
    def x = project.rootProject.eclipseBuild.targetPlatforms.keySet().collect {
        if (!it.startsWith('4')) {
            throw new RuntimeException("Build script only prepared for 4.x versions. Update the pattern matching below!")
        }
        def group = it =~ /4(\d+)/
        group[0][1] as Integer
    }.max()
    "4${x}" == eclipsebuild.Config.on(project).targetPlatform.eclipseVersion
}
