/*
 * Copyright Thoughtworks, Inc.
 *
 * 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 com.thoughtworks.go.build.*
import org.apache.tools.ant.filters.FixCrLfFilter

def destFile(String url) {
  new File(gradle.gradleUserHomeDir, "download-cache/${TextUtils.md5Hex(url)}/${new File(URI.create(url).toURL().path).name}")
}

tasks.register('downloadWindowsJreChecksum', DownloadFile) {
  def goVersions = project.goVersions as GoVersions
  src goVersions.packagedJavaVersion.toSha256SumURLFor(OperatingSystem.windows, Architecture.x64)
  dest destFile(src.toString())
}

tasks.register('downloadWindowsJre', DownloadFile) {
  dependsOn downloadWindowsJreChecksum
  def goVersions = project.goVersions as GoVersions
  src goVersions.packagedJavaVersion.toDownloadURLFor(OperatingSystem.windows, Architecture.x64)
  dest destFile(src.toString())
  checksum = { downloadWindowsJreChecksum.outputs.files.singleFile.getText("utf-8").trim().split(" ").first() }
}

def configureWindowsFilesystem(Task windowsInstallerTask, InstallerType installerType, Zip genericZipTask, File buildRoot) {
  windowsInstallerTask.with {
    group = project.name
    description = "Build the ${installerType.baseName} windows (exe) installer"

    dependsOn downloadWindowsJre
    dependsOn genericZipTask

    def goVersions = project.goVersions as GoVersions
    inputs.property('goVersions', goVersions)

    def installerBaseDirName = "${installerType.baseName}-${goVersions.goVersion}"

    def injected = project.objects.newInstance(Injected)
    doFirst {
      injected.fileOps.delete { delete(buildRoot) }
      new File("${layout.buildDirectory.get()}/distributions/win").mkdirs()

      injected.fileOps.copy {
        into buildRoot

        def genericZipTree = injected.archiveOps.zipTree(genericZipTask.archiveFile)
        // dont include the wrapper.conf, and tanuki wrappers for OSes other than windows
        from(genericZipTree) {
          exclude "${installerBaseDirName}/wrapper-config/wrapper.conf"
          exclude "${installerBaseDirName}/wrapper/wrapper-*"
          exclude "${installerBaseDirName}/wrapper/libwrapper-*"
          exclude "${installerBaseDirName}/bin/*"
        }

        from(genericZipTree) {
          include "${installerBaseDirName}/wrapper/*.dll"
          include "${installerBaseDirName}/wrapper/*.exe"
          include "${installerBaseDirName}/bin/*.bat"
        }

        from("include/wrapper-properties.${installerType.baseName}.conf.example") {
          into "${installerBaseDirName}/wrapper-config"
          rename ".*", "wrapper-properties.conf.example"
        }

        // include the wrapper.conf, but replace the java command
        from(genericZipTree) {
          include "${installerBaseDirName}/wrapper-config/wrapper.conf"
          filter { String eachLine ->
            if (eachLine == 'wrapper.java.command=java') {
              eachLine = 'wrapper.java.command=jre/bin/java'
            }
            eachLine
          }

          filter { String eachLine ->
            eachLine = eachLine.replaceAll("=${installerType.baseName}".toString(), "=${installerType.windowsAndOSXServiceName}".toString())
            return eachLine
          }

          filter(FixCrLfFilter.class, eol: FixCrLfFilter.CrLf.newInstance("dos"))
        }

        from(injected.archiveOps.zipTree(downloadWindowsJre.outputs.files.singleFile)) {
          eachFile { FileCopyDetails fcd ->
            List<String> segments = fcd.relativePath.segments
            segments.set(1, "jre")
            fcd.relativePath = new RelativePath(!fcd.isDirectory(), (segments as String[]))
          }
          includeEmptyDirs = false
          // relative to parent copy task's `into`
          into installerBaseDirName
        }
      }
    }
  }
}

def configureNSIS(Task packageTask, InstallerType installerType, File buildRoot, File destDir) {
  packageTask.with {
    def goVersions = project.goVersions as GoVersions
    def outputFile = project.file("${destDir.path}/${installerType.baseName}-${goVersions.fullVersion}-jre-64bit-setup.exe")

    // pad enough zeros to be able to convert the version string using simple numbers, instead of version numbers
    def registryVersionString = "${goVersions.goVersion.split(/\./).collect { it.padLeft(2, '0') }.join()}${goVersions.distVersion.padLeft(5, '0')}"

    def installerContents = project.file("${buildRoot.path}/${installerType.baseName}-${goVersions.goVersion}")
    def unInstallManifestFile = project.file("${buildRoot.path}/uninstall-manifest.nsi")
    def nsisPluginsDir = project.file("windows/nsis-plugins")
    def icon = project.file('windows/gocd.ico')
    def nsiPath = project.file("windows/${installerType.baseName}.nsi").path
    def licenseSourceFile = rootProject.file("LICENSE")

    def injected = project.objects.newInstance(Injected)

    // We only have a custom NSIS build with logging enabled for Linux on CI
    // See https://github.com/gocd/nsis-rpm/blob/master/mingw-nsis.spec#L2
    def usingCustomNsisBinaryWithLogging = OperatingSystemHelper.CURRENT_OS.isLinux()

    doLast {
      destDir.mkdirs()

      unInstallManifestFile.withWriter { out ->
        injected.objectFactory.fileTree().from(installerContents).forEach { File eachFile ->
          out.println("Delete \"\$INSTDIR\\${installerContents.relativePath(eachFile).replaceAll("/", "\\\\")}\"")
        }
      }

      List<String> cmdArgs = [
        "-DADDITIONAL_PLUGINS_DIR=${nsisPluginsDir}",
        "-DCOMPONENT_FULL_VERSION=${goVersions.fullVersion}",
        "-DCOMPONENT_NAME=${installerType.baseName.replace("go-", "").capitalize()}",
        "-DCOMPONENT_REGISTRY_VERSION=${registryVersionString}",
        "-DCOMPONENT_VERSION=${goVersions.goVersion}",
        "-DCUSTOM_PAGES=${installerType.baseName}-custom-page.nsi",
        "-DGOCD_ICON=${icon}",
        "-DGOCD_LICENSE_FILE=${licenseSourceFile}",
        "-DINSTALLER_CONTENTS=${installerContents.path}",
        "-DOUTPUT_FILE=${outputFile.path}",
        "-DUNINSTALL_MANIFEST_FILE=${unInstallManifestFile.path}",
        "-DWINDOWS_PRODUCT_VERSION=${goVersions.goVersion}.${goVersions.distVersion}",
      ]

      if (usingCustomNsisBinaryWithLogging) {
        cmdArgs += ["-DLOGGING_ENABLED=true"]
      }

      cmdArgs += ["-V4"] // verbose

      injected.execOps.exec { ExecSpec execSpec ->
        commandLine = (["makensis"] + cmdArgs + [nsiPath])

        standardOutput = System.out
        errorOutput = System.err

        println "[${execSpec.workingDir}]\$ ${execSpec.executable} ${execSpec.args.join(' ')}"
      }
    }
  }
}

tasks.register('agentWindows64bitExe') { thisTask ->
  def installerType = InstallerType.agent
  def buildRoot = project.file("${layout.buildDirectory.get()}/${installerType.baseName}/win/BUILD_ROOT")
  def destDir = project.file("${layout.buildDirectory.get()}/distributions/win")
  outputs.dir(destDir)

  configureWindowsFilesystem(thisTask, installerType, agentGenericZip, buildRoot)
  configureNSIS(thisTask, installerType, buildRoot, destDir)

  finalizedBy "${thisTask.name}Metadata"
}

tasks.register('serverWindows64bitExe') { thisTask ->
  def installerType = InstallerType.server
  def buildRoot = project.file("${layout.buildDirectory.get()}/${installerType.baseName}/win/BUILD_ROOT")
  def destDir = project.file("${layout.buildDirectory.get()}/distributions/win")
  outputs.dir(destDir)

  configureWindowsFilesystem(thisTask, installerType, serverGenericZip, buildRoot)
  configureNSIS(thisTask, installerType, buildRoot, destDir)

  finalizedBy "${thisTask.name}Metadata"
}

['agent', 'server'].each { installerType ->
  def packageTaskName = "${installerType}Windows64bitExe"
  tasks.register("${packageTaskName}Metadata", InstallerMetadataTask) {
    architecture = Architecture.x64
    packagedJavaVersion = (project.goVersions as GoVersions).packagedJavaVersion
    packageTask = project.tasks.named(packageTaskName)
    type = InstallerType."${installerType}"
  }
}

assemble.dependsOn(":installers:agentWindows64bitExe", ":installers:serverWindows64bitExe")
