import java.util.regex.Matcher

/**
 * Pipeline to release a stable version of Silverpeas from a given build version. The first step is
 * to figure out the version to release and from which SCM branch the release has to be performed.
 * The pipeline identifies the release of either a patch version from the current stable branch or a
 * major/minor version from the master branch. It takes also in charge the release of a patch
 * version from a previous stable branch other than the current one.
 *
 * In order to get the build version, the pipeline requires the Jenkins job aimed at constructing
 * such build version to be named 'Silverpeas_[PROJECT_TYPE]_AutoDeploy' with [PROJECT_TYPE] being
 * either 'Master' for the main major or minor version of Silverpeas in development, or 'Stable' for
 * the next patch version in development of the last stable version of Silverpeas, or a branch name
 * for older stable branch in which patch versions are yet ongoing.
 *
 * Once the release is successfully done, the pipeline triggers extra jobs that must exist:
 * 'Silverpeas IzPack Installer'          to publish an IzPack installer of the new version of
 *                                        Silverpeas.
 * 'Silverpeas Dev Image'                 to publish a Docker image of a dev environment for working
 *                                        on the new stable branch of Silverpeas.
 * 'Silverpeas Projects Build Image'      to publish a Docker image for building a Silverpeas
 *                                        project.
 * 'Silverpeas Test Image'                to publish a Docker image of the new version of Silverpeas
 *                                        for testing purpose.
 * 'Silverpeas Prod Image'                to publish a production-ready Docker image of the new
 *                                        version of Silverpeas.
 * 'Silverpeas_Documentation_Publishing'  to publish the documentation of the new version of
 *                                        Silverpeas.
 *
 * For doing, this pipeline expects the following environment variables to be set:
 * STABLE_BRANCH     the SCM branch in which the current stable version of the project is currently
 *                   maintained
 *
 * This pipeline requires the following parameters:
 * BUILD_VERSION the build version of Silverpeas from which the release has to be done.
 *
 * The build is performed within a dedicated Docker image in order to ensure the reproducibility of
 * the builds and to containerize them from the host OS.
 */

// versions of the Docker image to use with the different SCM branch names of the projects
def imageVersions = [
        '6.1.x'            : '6.1',
        '6.2.x'            : '6.2.1',
        '6.3.x'            : '6.3.3',
        '6.4.x'            : '6.4',
        'master'           : 'latest'
]

String imageVersion = getDockerImageVersion(imageVersions)

// projects making Silverpeas to release
def projects = [
                core        : 'Silverpeas-Core',
                components  : 'Silverpeas-Components',
                looks       : 'Silverpeas-Looks',
                setup       : 'Silverpeas-Setup',
                distribution: 'Silverpeas-Distribution',
                assembly    : 'Silverpeas-Assembly',
                mobile      : 'silverpeasmobile']

pipeline {

  agent {
    docker {
      image "silverpeas/silverbuild:${imageVersion}"
      args '''
          -v $HOME/.m2/settings.xml:/home/silverbuild/.m2/settings.xml 
          -v $HOME/.m2/settings-security.xml:/home/silverbuild/.m2/settings-security.xml 
          -v $HOME/.gitconfig:/home/silverbuild/.gitconfig 
          -v $HOME/.ssh:/home/silverbuild/.ssh 
          -v $HOME/.gnupg:/home/silverbuild/.gnupg
          '''
    }
  }

  parameters {
    string(
        description: 'The build version of Silverpeas from which the release has to be done',
        name: 'BUILD_VERSION'
    )
  }

  environment {
    gitBaseRepo = 'https://github.com/Silverpeas/'
    gitCredential = 'cacc0467-7c85-41d1-bf4e-eaa470dd5e59'
    mavenRepo = 'https://nexus3.silverpeas.org/repository/silverpeas'
    branch = getBranch()
    isPatchVersion = isPatchVersion()
    wildflyVersion = ''
    jackrabbitVersion = null
    nextVersion = ''
    pomParentVersion = ''
    buildToRelease = null
    artifact = 'target/release.yaml'
  }

  stages {

    stage('Prepare the release') {
      steps {
        script {
          sh 'rm -rf target && mkdir target'
          String type = getBranchType()
          copyArtifacts projectName: "Silverpeas_${type}_AutoDeploy", flatten: true,
              selector: specific(params.BUILD_VERSION)
          buildToRelease = readYaml file: 'build.yaml'
          nextVersion = getNextVersion(buildToRelease.release)
          sh 'rm -f build.yaml'

          copyArtifacts projectName: "Silverpeas_Mobile_${type}_AutoDeploy", flatten: true,
              selector: specific(params.BUILD_VERSION)
          def mobileToRelease = readYaml file: 'build.yaml'
          buildToRelease.commits['silverpeasmobile'] = mobileToRelease.commit
          sh 'rm -f build.yaml'

          if (branch != 'master') {
            sh "curl -fsSL -o pom.xml https://raw.githubusercontent.com/Silverpeas/Silverpeas-Assembly/${branch}/pom.xml"
            def pom = readMavenPom()
            jackrabbitVersion = pom.properties['jackrabbit.version']
            sh 'rm -f pom.xml'
          }
        }
      }
    }

    stage('Check the build version can be used for release') {
      when {
        expression { !buildToRelease.tested || !buildToRelease.QA }
      }
      steps {
        error("The build ${buildToRelease.version} cannot be used for the release of ${buildToRelease.release} as it hasn't been tested or no QA has been applied on it!")
      }
    }

    stage('Check Jackrabbit version') {
      when {
        expression {
          branch != 'master' && jackrabbitVersion != null &&
                  (jackrabbitVersion.contains('build') || jackrabbitVersion.contains('SNAPSHOT'))
        }
      }
      steps {
        error("The Jackrabbit JCA dependency must be at a stable version for this project to be released. Current version is ${jackrabbitVersion}")
      }
    }

    stage ('Check Silverpeas Projects Definition') {
      steps {
        script {
          String parentVersion = buildToRelease.parent
          buildToRelease.releaseParent = parentVersion != null && branch == 'master' &&
              (parentVersion.contains('build') || parentVersion.contains('SNAPSHOT'))
          sh "curl -fsSL -o pom.xml ${mavenRepo}/org/silverpeas/silverpeas-project/${parentVersion}/silverpeas-project-${parentVersion}.pom"
          def pom = readMavenPom()
          wildflyVersion = pom.properties['wildfly.version']
          sh 'rm -f pom.xml'
        }
      }
    }

    stage('Echo release parameters') {
      steps {
        echo """
          Release processing parameters:
          Version to release          ${buildToRelease.release}
          From build version          ${params.BUILD_VERSION}
          Patch version               ${isPatchVersion}
          Wildfly version             ${wildflyVersion}
          POM parent version          ${buildToRelease.parent}
          Should release POM parent?  ${buildToRelease.releaseParent}
          Next version                ${nextVersion}
          """
      }
    }

    stage('Release Silverpeas Projects Definition') {
      when {
        expression { buildToRelease.releaseParent }
      }
      steps {
        build job: 'Silverpeas_Project_Definition_Release', propagate: true, wait: true
        copyArtifacts projectName: 'Silverpeas_Project_Definition_AutoDeploy', flatten: true
        script {
          def parentBuild = readYaml file: 'build.yaml'
          buildToRelease.parent = parentBuild.release
        }
        sh 'rm -f build.yaml'
      }
    }

    stage('Release Silverpeas Core') {
      steps {
        dir(projects.core) {
          git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.core)
          releaseProject project: projects.core,
              commit: buildToRelease.commits[projects.core],
              version: buildToRelease.release,
              parent: buildToRelease.parent,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Prepare next version of Silverpeas Core') {
      steps {
        dir(projects.core) {
          prepareNextVersion project: projects.core,
              commit: buildToRelease.commits[projects.core],
              version: buildToRelease.release,
              nextVersion: nextVersion,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Release Silverpeas Components') {
      steps {
        dir(projects.components) {
          git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.components)
          releaseProject project: projects.components,
              commit: buildToRelease.commits[projects.components],
              version: buildToRelease.release,
              parent: buildToRelease.parent,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Prepare next version of Silverpeas Components') {
      steps {
        dir(projects.components) {
          prepareNextVersion project: projects.components,
              commit: buildToRelease.commits[projects.components],
              version: buildToRelease.release,
              nextVersion: nextVersion,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Release Silverpeas Assembly') {
      steps {
        dir (projects.assembly) {
          git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.assembly)
          releaseProject project: projects.assembly,
              commit: buildToRelease.commits[projects.assembly],
              version: buildToRelease.release,
              parent: buildToRelease.parent,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Prepare next version of Silverpeas Assembly') {
      steps {
        dir (projects.assembly) {
          prepareNextVersion project: projects.assembly,
              commit: buildToRelease.commits[projects.assembly],
              version: buildToRelease.release,
              nextVersion: nextVersion,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Release Silverpeas Setup') {
      steps {
        dir(projects.setup) {
          git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.setup)
          releaseProject project: projects.setup,
              commit: buildToRelease.commits[projects.setup],
              version: buildToRelease.release,
              parent: buildToRelease.parent,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Prepare next version of Silverpeas Setup') {
      steps {
        dir(projects.setup) {
          prepareNextVersion project: projects.setup,
              commit: buildToRelease.commits[projects.setup],
              version: buildToRelease.release,
              nextVersion: nextVersion,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Release Silverpeas Distribution') {
      steps {
        dir(projects.distribution) {
          git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.distribution)
          releaseProject project: projects.distribution,
              commit: buildToRelease.commits[projects.distribution],
              version: buildToRelease.release,
              parent: buildToRelease.parent,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Prepare next version of Silverpeas Distribution') {
      steps {
        dir(projects.distribution) {
          prepareNextVersion project: projects.distribution,
              commit: buildToRelease.commits[projects.distribution],
              version: buildToRelease.release,
              nextVersion: nextVersion,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Release Silverpeas Looks') {
      steps {
        dir(projects.looks) {
          git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.looks)
          releaseProject project: projects.looks,
              commit: buildToRelease.commits[projects.looks],
              version: buildToRelease.release,
              parent: buildToRelease.parent,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Prepare next version of Silverpeas Looks') {
      steps {
        dir(projects.looks) {
          prepareNextVersion project: projects.looks,
              commit: buildToRelease.commits[projects.looks],
              version: buildToRelease.release,
              nextVersion: nextVersion,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Release Silverpeas Mobile') {
      steps {
        dir(projects.mobile) {
          git credentialsId: gitCredential, branch: branch, url: (gitBaseRepo + projects.mobile)
          releaseProject project: projects.mobile,
              commit: buildToRelease.commits[projects.mobile],
              version: buildToRelease.release,
              parent: buildToRelease.parent,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Prepare next version of Silverpeas Mobile') {
      steps {
        dir(projects.mobile) {
          prepareNextVersion project: projects.mobile,
              commit: buildToRelease.commits[projects.mobile],
              version: buildToRelease.release,
              nextVersion: nextVersion,
              isPatch: isPatchVersion
        }
      }
    }

    stage('Publish Docker Image for development environment') {
      when {
        expression { !isPatchVersion }
      }
      steps {
        build job: 'Silverpeas Dev Image', parameters: [
            string(name: 'IMAGE_VERSION', value: buildToRelease.release),
            string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", ""))
        ], wait: false
      }
    }

    stage('Publish Docker image for Jenkins job') {
      when {
        expression { !isPatchVersion }
      }
      steps {
        build job: 'Silverpeas Projects Build Image', parameters: [
            string(name: 'IMAGE_VERSION', value: buildToRelease.release),
            string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", ""))
        ], wait: false
      }
    }

    stage('Publish Docker Image of Silverpeas for trial purpose') {
      steps {
        build job: 'Silverpeas Test Image', parameters: [
            string(name: 'SILVERPEAS_VERSION', value: buildToRelease.release),
            string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", ""))
        ], wait: false
      }
    }

    stage('Prepare Docker Image of Silverpeas for production') {
      steps {
        build job: 'Silverpeas Prod Image', parameters: [
            string(name: 'SILVERPEAS_VERSION', value: buildToRelease.release),
            string(name: 'WILDFLY_VERSION', value: wildflyVersion.replace(".Final", ""))
        ], wait: false
      }
    }

    stage('Publish Silverpeas Documentation') {
      steps {
        echo 'Trigger the documentation generation and publishing job'
        build job: 'Silverpeas_Documentation_Publishing', parameters: [
            string(name: 'SILVERPEAS_VERSION', value: buildToRelease.release)
        ], quietPeriod: 20, wait: false
      }
    }

    stage('Create YAML release report') {
      steps {
        script {
          writeYaml file: artifact, data: ['release': buildToRelease.release,
                                           'parent': buildToRelease.parent,
                                           'wildfly': wildflyVersion,
                                           'build': params.BUILD_VERSION,
                                           'branch': branch]
        }
      }
    }
  }
  post {
    success {
      script {
        currentBuild.displayName = buildToRelease.release
      }
      archiveArtifacts artifacts: artifact, fingerprint: true
      script {
        if (branch == 'master') {
          emailext(
              subject: "Silverpeas ${buildToRelease.release} has been released!",
              body: """
                Enjoy this new version of Silverpeas, nevertheless don't forget to update in Jenkins
                the following environment variables in order the usual jobs in Jenkins continues to 
                work properly:
                STABLE_BRANCH to '${buildToRelease.release}.x'
                IMAGE_FOR_STABLE to '${buildToRelease}'
                """,
              to: 'miguel.moquillon@silverpeas.org,' +
                      'david.lesimple@silverpeas.com,sebastien.vuillet@silverpeas.com,' +
                      'silveryocha@chastagnier.com'
          )
        }
      }
    }
    always {
      step([$class                  : 'Mailer',
            notifyEveryUnstableBuild: true,
            recipients              : "david.lesimple@silverpeas.org, sebastien.vuillet@silverpeas.org, aurore.allibe@silverpeas.org, miguel.moquillon@silverpeas.org, silveryocha@chastagnier.com",
            sendToIndividuals       : true])
    }
  }
}

void withGit( closure) {
  withCredentials([usernamePassword(credentialsId: gitCredential,
      usernameVariable: 'GIT_AUTH_USR', passwordVariable: 'GIT_AUTH_PSW')]) {
    sh 'git config --local credential.helper "!f() { echo username=\\$GIT_AUTH_USR; echo password=\\$GIT_AUTH_PSW; }; f"'
    closure.call()
  }
}

void releaseProject(args) {
  // we first change the Maven repo definition to use the repo dedicated to releases
  // then we release the project and finally we tag it
  sh """
    git checkout ${args.commit}
    git checkout -b release-${args.version}
    """
  if (fileExists('pom.xml')) {
    publishVersionOfMavenProject(args)
  } else {
    publishVersionOfGradleProject(args)
  }
  withGit {
    sh """
      git commit -am "Release of ${args.version} from ${params.BUILD_VERSION} (commit ${args.commit})"
      git tag ${args.version}
      git push origin --tags
      """
  }
}

void publishVersionOfMavenProject(args) {
  def pom = readMavenPom()
  if (pom.parent && args.parent && pom.parent.version != args.parent) {
    sh """
      mvn -U versions:update-parent -DgenerateBackupPoms=false -DparentVersion="[${args.parent}]"
      git commit -am "Update parent POM to version ${args.parent}"
      """
  }
  if (fileExists('Jenkinsfile') && args.isPatch == 'false') {
    sh """
      sed -i -e "s/silverpeas\\/silverbuild'/silverpeas\\/silverbuild:${args.version}'/g" Jenkinsfile
      """
  }
  sh """
    sed -i -e "s/<core.version>[0-9a-zA-Z.-]\\+/<core.version>${args.version}/g" pom.xml
    sed -i -e "s/<silverpeas.version>[0-9a-zA-Z.-]\\+/<silverpeas.version>${args.version}/g" pom.xml
    sed -i -e "s/<tag>[a-zA-Z0-9.\\-]\\+/<tag>${args.version}/g" pom.xml
    mvn -U versions:set -DgenerateBackupPoms=false -DnewVersion=${args.version}
    mvn clean deploy -Dmaven.test.skip=true -Pdeployment -Prelease-sign-artifacts
    """
}

void publishVersionOfGradleProject(args) {
  if (fileExists("Jenkinsfile") && args.isPatch == 'false') {
    sh """
      sed -i -e "s/silverpeas\\/silverbuild'/silverpeas\\/silverbuild:${args.version}'/g" Jenkinsfile
      """
  }
  sh """
    sed -i -e "s/version = '.\\+'/version = '${args.version}'/g" build.gradle
    ./gradlew clean build publish
    """
}

void prepareNextVersion(args) {
  if (args.isPatch == 'false') {
    echo "Create bug-fix branch ${args.version}.x"
    createMinorBranch(args)
  }

  sh "git checkout ${branch}"
  if (fileExists('pom.xml')) {
    buildSnapshotVersionOfMavenProject(args.nextVersion)
  } else {
    buildSnapshotVersionOfGradleProject(args.nextVersion)
  }
  withGit {
    sh """
      git commit -am "${args.version} has been released from build ${params.BUILD_VERSION} (${args.commit}).\\
Prepare for development iteration of next version ${args.nextVersion}"
      git push origin ${branch}
      """
  }
}

void createMinorBranch(args) {
  sh "git checkout -b ${args.version}.x"
  if (fileExists('pom.xml')) {
    buildSnapshotVersionOfMavenProject("${args.version}.1")
  } else if (fileExists('build.gradle')) {
    buildSnapshotVersionOfGradleProject("${args.version}.1")
  }
  withGit {
    String anyModif = sh(script: 'git status -s', returnStdout: true).trim()
    if (!anyModif.isEmpty()) {
      sh "git commit -am \"Prepare branch ${args.version}.x for bug fixes\""
    }
    sh "git push origin HEAD:${args.version}.x"
  }
}

void buildSnapshotVersionOfMavenProject(version) {
  sh """
    sed -i -e "s/<core.version>[0-9a-zA-Z.-]\\+/<core.version>${version}-SNAPSHOT/g" pom.xml
    sed -i -e "s/<silverpeas.version>[0-9a-zA-Z.-]\\+/<silverpeas.version>${version}-SNAPSHOT/g" pom.xml
    sed -i -e "s/<next.release>[0-9.]\\+/<next.release>${version}/g" pom.xml
    sed -i -e "s/<tag>[a-zA-Z0-9.\\-]\\+/<tag>HEAD/g" pom.xml
    mvn -U versions:set -DgenerateBackupPoms=false -DnewVersion=${version}-SNAPSHOT
    mvn clean install -Dmaven.test.skip=true -Djava.awt.headless=true
    """
}

void buildSnapshotVersionOfGradleProject(version) {
  sh """
    sed -i -e "s/version = '.\\+'/version = '${version}-SNAPSHOT'/g" build.gradle
    ./gradlew clean build publish
    """
}

String getNextVersion(version) {
  Matcher m = version =~ /^([0-9.]+)-rc(\d+)/
  String nextVersion
  if (m.matches()) {
    nextVersion = "${m.group(1)}-rc${(m.group(2) as Integer) + 1}"
  } else {
    String[] parts = version.split('\\.')
    nextVersion = parts.length < 3 ? "${parts[0]}.${(parts[1] as Integer) + 1}" : "${parts[0]}.${parts[1]}.${(parts[2] as Integer) + 1}"
  }
  return nextVersion
}

String getBranchType() {
  String type
  switch (branch) {
    case env.STABLE_BRANCH:
      type = 'Stable'
      break
    case 'master':
      type = 'Master'
      break
    default:
      type = branch
      break
  }
  return type
}

String getBranch() {
  Matcher matcher = params.BUILD_VERSION =~ '^(\\d+.\\d+)\\..*$'
  matcher ? "${matcher[0][1]}.x" : 'master'
}

boolean isPatchVersion() {
  Matcher m = params.BUILD_VERSION =~ /^\d\.\d\.\d-build\d+$/
  return m.matches()
}

String getDockerImageVersion(imageVersions) {
  String currentBranch = getBranch()
  Matcher matcher = currentBranch =~ '^(\\d+.\\d+\\..*)$'
  matcher ? imageVersions["${matcher[0][1]}"] : imageVersions[currentBranch]
}