/*
 * 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 groovy.json.JsonOutput
import org.apache.tools.ant.filters.ConcatFilter

enum PackageType {

  deb {
    // Debian packages allow either most recent or minimum Java version in dependencies because
    // Java 21 was not available on Debian releases despite it being preferred, although was available for Ubuntu.
    String jreDependency(GoVersions goVersions) {
      goVersions.preferredJavaVersions().collect { "openjdk-${it}-jre-headless" }.join(' | ')
    }

    String jreBinaryLocator(GoVersions goVersions) {
      // Find the most recent version that was installed
      """ls ${goVersions.preferredJavaVersions().collect { "/usr/lib/jvm/java-${it}-openjdk-\$(dpkg --print-architecture)/bin/java" }.join(' \\\n   ') } \\
           2>/dev/null | tail -1"""
    }

    List<String> fpmArgs(Injected injected, File buildRoot, InstallerType installerType, GoVersions goVersions) {
      List<String> fpmArgs = []
      fpmArgs += ['-t', 'deb']
      fpmArgs += ['--deb-no-default-config-files']

      // for basic stuff like `ls`, `id`
      fpmArgs += ['--depends', 'coreutils']

      // for `ps`
      fpmArgs += ['--depends', 'procps']

      fpmArgs += ['--deb-recommends', jreDependency(goVersions)]

      // HACK: for debian packages :(, since manifests cannot contain fine grained ownership
      File dirPermissions = File.createTempFile("dirPermissions-", ".json")
      File filePermissions = File.createTempFile("filePermissions-", ".json")

      fpmArgs += ['--template-value', "dir_permissions=${dirPermissions}"]
      fpmArgs += ['--template-value', "file_permissions=${filePermissions}"]

      dirPermissions.write(JsonOutput.toJson(installerType.directories))
      filePermissions.write(JsonOutput.toJson(installerType.configFiles))
      return fpmArgs
    }
  },
  rpm {
    String jreDependency(GoVersions goVersions) {
      "(${goVersions.preferredJavaVersions().collect { "java-${it}-openjdk-headless" }.join(' or ')})"
    }

    String jreBinaryLocator(GoVersions goVersions) {
      // Find the most recent version that was installed
      """ls ${goVersions.preferredJavaVersions().collect { "/etc/alternatives/jre_${it}/bin/java" }.join(' \\\n   ') } \\
           2>/dev/null | tail -1"""
    }

    List<String> fpmArgs(Injected injected, File buildRoot, InstallerType installerType, GoVersions goVersions) {
      List<String> fpmArgs = []

      fpmArgs += ['-t', 'rpm']
      fpmArgs += ['--rpm-os', 'linux'] // allows running fpm on osx

      // for basic stuff like `ls`, `id`
      fpmArgs += ['--depends', 'coreutils']

      // for `ps`
      fpmArgs += ['--depends', 'procps']

      fpmArgs += ['--rpm-tag', "Recommends: ${jreDependency(goVersions)}"]

      fpmArgs += ['--rpm-defattrfile', '0444']
      fpmArgs += ['--rpm-defattrdir', '0755']

      installerType.directories.each {
        dirName, permissions ->
          if (permissions.ownedByPackage) {
            fpmArgs += ['--rpm-attr', "${Integer.toOctalString(permissions.mode)},${permissions.owner},${permissions.group}:${dirName}"]
          }
      }

      injected.objectFactory.fileTree().from(buildRoot).files.forEach({ File eachFile ->
        if (eachFile.canExecute()) {
          def relativePath = buildRoot.toURI().relativize(eachFile.toURI()).getPath()
          fpmArgs += ['--rpm-attr', "0755,root,root:/${relativePath}"]
        }
      })

      installerType.configFiles.each {
        fileName, permissions ->
          // set ownership and mode on the rpm manifest
          fpmArgs += ['--rpm-attr', "${Integer.toOctalString(permissions.mode)},${permissions.owner},${permissions.group}:${fileName}"]
      }

      return fpmArgs

    }
  }

  /**
   * @return POSIX shell script that will find a path to the optimal JRE java binary for the relevant package
   */
  abstract jreBinaryLocator(GoVersions goVersions)
  abstract List<String> fpmArgs(Injected injected, File buildRoot, InstallerType installerType, GoVersions goVersions)
}

def configureLinuxPackage(Task packageTask, InstallerType installerType, PackageType packageType, File buildRoot, Configuration configuration, File destDir) {
  packageTask.with {
    dependsOn configuration, extractDeltaPack
    inputs.files(configuration)
    inputs.files(extractDeltaPack.destinationDir)

    def licenseSource = "${rootProject.projectDir}/LICENSE"
    inputs.file(licenseSource)

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

    def injected = project.objects.newInstance(Injected)
    doFirst {
      injected.fileOps.delete { delete(buildRoot) }
      destDir.mkdirs()

      injected.fileOps.copy {
        from(configuration.singleFile) {
          rename "(.*)", installerType.jarFileName
        }
        into file("${buildRoot.path}/usr/share/${installerType.baseName}/lib")
      }

      installerType.directories.each { dirName, permissions ->
        file("${buildRoot.path}/${dirName}").mkdirs()
      }

      injected.fileOps.copy {
        from(licenseSource)
        into "${buildRoot.path}/usr/share/doc/${installerType.baseName}"
      }

      injected.fileOps.copy {
        into "${buildRoot.path}/usr/share/${installerType.baseName}/bin"
        filePermissions {
          unix(0755)
        }

        from("${extractDeltaPack.destinationDir}/src/bin/App.sh.in") {
          filter({ String eachLine ->
            eachLine = eachLine
              .replaceAll('@app.name@', installerType.baseName)
              .replaceAll('@app.long.name@', installerType.baseName)
              .replaceAll('@app.description@', installerType.baseName)

            if (eachLine == 'WRAPPER_CMD="./wrapper"') {
              eachLine = 'WRAPPER_CMD="../wrapper/wrapper"'
            }

            if (eachLine == 'WRAPPER_CONF="../conf/wrapper.conf"') {
              eachLine = "WRAPPER_CONF=\"/usr/share/${installerType.baseName}/wrapper-config/wrapper.conf\""
            }

            if (eachLine == 'PIDDIR="."') {
              eachLine = "PIDDIR=\"/var/lib/${installerType.baseName}/run\""
            }

            if (eachLine =~ /^#PASS_THROUGH=/ && installerType.allowPassthrough) {
              eachLine = 'PASS_THROUGH=true'
            }

            if (eachLine == '#RUN_AS_USER=') {
              eachLine = 'RUN_AS_USER=go'
            }

            return eachLine
          })
          rename 'App.sh.in', installerType.baseName
        }
      }

      injected.fileOps.copy {
        into "${buildRoot}/usr/share/${installerType.baseName}/wrapper-config"
        from("${extractDeltaPack.destinationDir}/src/conf/wrapper.conf.in") {
          filter({ String eachLine ->
            eachLine = eachLine
              .replaceAll('@app.name@', installerType.baseName)
              .replaceAll('@app.long.name@', installerType.baseName)
              .replaceAll('@app.description@', installerType.baseName)

//            uncomment the lines below to enable debugging of installers
//            if (eachLine == '# wrapper.debug=TRUE') {
//              eachLine = 'wrapper.debug=TRUE'
//            }
//            if (eachLine == '#@include.debug') {
//              eachLine = '@include.debug'
//            }
//            if (eachLine == '#wrapper.license.debug=TRUE') {
//              eachLine = 'wrapper.license.debug=TRUE'
//            }

            if (eachLine == 'wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp') {
              eachLine = 'wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperJarApp'
            }

            if (eachLine == '@encoding=UTF-8') {
              def newLines = [
                '@encoding=UTF-8',
                "wrapper.working.dir=/var/lib/${installerType.baseName}",
                'wrapper.console_input=DISABLED',
                'wrapper.console.flush=TRUE'
              ]

              (installerType.additionalEnvVars + installerType.additionalLinuxEnvVars).forEach { k, v ->
                newLines.add("set.default.${k}=${v}")
              }

              eachLine = newLines.join('\n')
            }

            def allJvmArgs = installerType.jvmArgs + installerType.linuxJvmArgs

            if (eachLine == 'wrapper.java.additional.1=' && !allJvmArgs.isEmpty()) {
              def newLines = []

              allJvmArgs.eachWithIndex { String eachArg, int index ->
                newLines << "wrapper.java.additional.${index + 1}=${eachArg}"
              }


              eachLine = newLines.join('\n')
            }

            if (eachLine == 'wrapper.jarfile=../lib/wrapper.jar') {
              eachLine = "wrapper.jarfile=/usr/share/${installerType.baseName}/wrapper/wrapper.jar"
            }

            if (eachLine == '#wrapper.java.classpath.1=') {
              eachLine = "wrapper.java.classpath.1=/usr/share/${installerType.baseName}/lib/${installerType.jarFileName}"
            }

            if (eachLine == 'wrapper.java.library.path.1=../lib') {
              eachLine = "wrapper.java.library.path.1=/usr/share/${installerType.baseName}/wrapper"
            }

            if (eachLine == 'wrapper.app.parameter.1=<YourMainClass>') {
              eachLine = "wrapper.app.parameter.1=/usr/share/${installerType.baseName}/lib/${installerType.jarFileName}"
            }

            if (eachLine == 'wrapper.logfile=../logs/wrapper.log') {
              eachLine = "wrapper.logfile=/var/log/${installerType.baseName}/${installerType.logFileName}"
            }

            return eachLine
          })

          filter(ConcatFilter, append: file("include/wrapper-license-linux-${installerType.baseName}.conf"))
          filter(ConcatFilter, append: file("include/additional-properties.conf"))

          rename 'wrapper.conf.in', 'wrapper.conf'
        }

        // the actual default config file
        from("include/wrapper-properties-${installerType.baseName}.conf") {
          rename ".*", "wrapper-properties.conf"
          filter(ConcatFilter, append: file("include/wrapper-properties.${installerType.baseName}.conf.example"))
        }
      }

      injected.fileOps.copy {
        into "${buildRoot}/usr/share/${installerType.baseName}/wrapper"
        from("${extractDeltaPack.destinationDir}/bin") {
          include 'wrapper-linux-x86-64*'
          include 'wrapper-linux-arm-64*'
        }

        from("${extractDeltaPack.destinationDir}/lib") {
          include 'wrapper.jar'
          include 'libwrapper-linux-x86-64*'
          include 'libwrapper-linux-arm-64*'
        }
      }
    }

    def templatePaths = [
      beforeInstall: project.file('linux/shared/before-install.sh.erb').path,
      beforeUpgrade: project.file("linux/${packageType}/before-upgrade.sh.erb").path,
      afterUpgrade: project.file("linux/${packageType}/after-upgrade.sh.erb").path,
      afterInstall: project.file("linux/${packageType}/after-install.sh.erb").path,
      beforeRemove: project.file("linux/${packageType}/before-remove.sh.erb").path,
      postTrans: project.file("linux/rpm/post-tran.sh.erb").path,
    ]
    def installScriptsDir = project.file('linux').path

    doLast {
      injected.execOps.exec {
        List<String> cmd = []
        cmd += ['fpm']

//        cmd += ['--debug']
//        cmd += ['-e']
//        cmd += ['--debug-workspace']
        cmd += ['--conflicts', installerType.baseName.replaceFirst(/^go-/, 'cruise-')]
        cmd += ['--replaces', installerType.baseName.replaceFirst(/^go-/, 'cruise-')]
        cmd += ['--force']
        cmd += ['-s', 'dir']
        cmd += ['-C', buildRoot.path]
        cmd += ['--name', installerType.baseName]
        cmd += ['--description', installerType.packageDescription]
        cmd += ['--version', goVersions.goVersion]
        cmd += ['--iteration', goVersions.distVersion]
        cmd += ['--license', 'Apache-2.0']
        cmd += ['--vendor', 'Thoughtworks, Inc.']
        cmd += ['--category', 'Development/Build Tools']
        cmd += ['--architecture', 'all']
        cmd += ['--maintainer', 'Thoughtworks, Inc.']
        cmd += ['--url', 'https://gocd.org']
        cmd += ['--before-install', templatePaths.beforeInstall]
        cmd += ['--before-upgrade', templatePaths.beforeUpgrade]
        cmd += ['--after-upgrade', templatePaths.afterUpgrade]
        cmd += ['--after-install', templatePaths.afterInstall]
        cmd += ['--before-remove', templatePaths.beforeRemove]
        cmd += ['--rpm-posttrans', templatePaths.postTrans]
        cmd += ['--template-value', "install_scripts_dir=${installScriptsDir}"]
        cmd += ['--template-scripts']

        installerType.directories.each { dirName, permissions ->
          if (permissions.ownedByPackage) {
            cmd += ['--directories', dirName]
          }
        }

        installerType.configFiles.each { fileName, permissions ->
          if (permissions.confFile) {
            cmd += ['--config-files', fileName.replaceAll(/^\//, '')]
          }
        }

        cmd += packageType.fpmArgs(injected, buildRoot, installerType, goVersions)

        commandLine = cmd

        environment += ['JRE_BINARY_LOCATOR': packageType.jreBinaryLocator(goVersions)]

        workingDir = destDir

        standardOutput = System.out
        errorOutput = System.err
      }
    }
  }

  packageTask.finalizedBy"${packageTask.name}Metadata"
}

def copyMultipleAgentsReadme(Task packageTask, File buildRoot, InstallerType installerType) {
  def injected = project.objects.newInstance(Injected)
  packageTask.doFirst {
    injected.fileOps.copy {
      from "go-agent/release/README-running-multiple-agents.md"
      into "${buildRoot.path}/usr/share/doc/${installerType.baseName}"
    }
  }
}

tasks.register('agentPackageRpm') { thisTask ->
  def installerType = InstallerType.agent
  def packageType = PackageType.rpm
  def buildRoot = layout.buildDirectory.file("${installerType.baseName}/${packageType.toString()}/BUILD_ROOT").get().asFile
  def destDir = layout.buildDirectory.dir("distributions/${packageType}").get().asFile
  outputs.dir(destDir)

  copyMultipleAgentsReadme(thisTask, buildRoot, installerType)
  configureLinuxPackage(thisTask, installerType, packageType, buildRoot, configurations.agentBootstrapperJar, destDir)
}


tasks.register('agentPackageDeb') { thisTask ->
  def installerType = InstallerType.agent
  def packageType = PackageType.deb
  def buildRoot = layout.buildDirectory.file("${installerType.baseName}/${packageType.toString()}/BUILD_ROOT").get().asFile
  def destDir = layout.buildDirectory.dir("distributions/${packageType}").get().asFile
  outputs.dir(destDir)

  copyMultipleAgentsReadme(thisTask, buildRoot, installerType)
  configureLinuxPackage(thisTask, installerType, packageType, buildRoot, configurations.agentBootstrapperJar, destDir)
}

tasks.register('serverPackageRpm') { thisTask ->
  def installerType = InstallerType.server
  def packageType = PackageType.rpm
  def buildRoot = layout.buildDirectory.file("${installerType.baseName}/${packageType.toString()}/BUILD_ROOT").get().asFile
  def destDir = layout.buildDirectory.dir("distributions/${packageType}").get().asFile
  outputs.dir(destDir)

  configureLinuxPackage(thisTask, installerType, packageType, buildRoot, configurations.serverJar, destDir)
}

tasks.register('serverPackageDeb') { thisTask ->
  def installerType = InstallerType.server
  def packageType = PackageType.deb
  def buildRoot = layout.buildDirectory.file("${installerType.baseName}/${packageType.toString()}/BUILD_ROOT").get().asFile
  def destDir = layout.buildDirectory.dir("distributions/${packageType}").get().asFile
  outputs.dir(destDir)

  configureLinuxPackage(thisTask, installerType, packageType, buildRoot, configurations.serverJar, destDir)
}

['agent', 'server'].each { installerType ->
  ['Deb', 'Rpm'].each { packageType ->
    def packageTaskName = "${installerType}Package${packageType}"
    tasks.register("${packageTaskName}Metadata", InstallerMetadataTask) {
      architecture = Architecture.all
      packageTask = project.tasks.named(packageTaskName)
      type = InstallerType."${installerType}"
    }
  }
}

assemble.dependsOn(":installers:agentPackageRpm", ":installers:serverPackageRpm", ":installers:agentPackageDeb", ":installers:serverPackageDeb")
