def checkBranchConditions(parameter="default") {
  switch(parameter) {
    case "default":
      switch(env.BRANCH_NAME) {
        case "master":
        case ~/release.*$/:
        case ~/^dev(elop)*$/:
          return true
        default:
          return false
      }
    case "yes":
      return true
    case "no":
      return false
    default:
      return false
  }
}

def setPythonDepsPipelineVars(projectPath) {
  // uses common environment variables to set parameter map for pythonPackagePipeline method
  def params = [:]
  // Common variables to parameters
  params.awsProfile              = env.AWS_PROFILE
  //ProjectSpecific parameters
  params.workDir                 = "${env.WORKSPACE}/${projectPath}"
  return params
}

def setPythonPipelineVars(projectPath, pythonPackageUploadMode) {
  // uses common environment variables to set parameter map for pythonPackagePipeline method
  def params = [:]
  // Common variables to parameters
  params.awsProfile              = env.AWS_PROFILE
  params.pythonPackageUploadMode = pythonPackageUploadMode
  //ProjectSpecific parameters
  params.projectName             = projectPath.split('/')[-1]
  params.workDir                 = "${env.WORKSPACE}/${projectPath}"

  return params
}

def pythonDepsPipeline(params) {
  withEnv(["AWS_PROFILE=${params.awsProfile}"]) {
    stage("Build Python dependencies") {
      echo sh(script: "cd ${params.workDir} && make docker/clean", returnStdout: true).trim()
      echo sh(script: "cd ${params.workDir} && make docker/deps", returnStdout: true).trim()
    }
  }
}

def pythonLinterPipeline(params) {
  logJenkinsParams(params)
  withEnv(["AWS_PROFILE=${params.awsProfile}"]) {
    stage("Run fmtcheck, lint and typecheck") {
      echo sh(script: "cd ${params.workDir} && make docker/fmtcheck", returnStdout: true).trim()
      echo sh(script: "cd ${params.workDir} && make docker/lint", returnStdout: true).trim()
      echo sh(script: "cd ${params.workDir} && make docker/typecheck", returnStdout: true).trim()
    }
  }
}

def pythonTestPipeline(params) {
  try {
    logJenkinsParams(params)
    withEnv(["AWS_PROFILE=${params.awsProfile}"]) {
      stage("Run tests") {
        echo sh(script: "cd ${params.workDir} && make docker/clean", returnStdout: true).trim()
        echo sh(script: "cd ${params.workDir} && make docker/test", returnStdout: true).trim()
      }
    }
  }
  finally {
    logJenkinsParams(params)
    junit "**/${params.projectName}/junitreport.xml"
  }
}

def logJenkinsParams(jenkinsParams) {
  for ( param in jenkinsParams ) {
    echo "key = ${param.key}, value = ${param.value}"
  }
}

def pythonPackagePipeline(params) {
  nexus.generateAuthorizedPypirc(params.workDir + '/.pypirc')
  withEnv(["AWS_PROFILE=${params.awsProfile}"]) {
    stage('Build and upload python package') {
      echo "Workdir is ${params.workDir}"
      echo sh(script: "cd ${params.workDir} && make docker/clean", returnStdout: true).trim()
      echo sh(script: "cd ${params.workDir} && make docker/upload", returnStdout: true).trim()
    }
  }
}

def setDockerPipelineParams(imageUploadMode, skipReleaseCreation, projectPath, projectGroup, ecr, ecrRepoSubfolder='', octoProjectName='', projectName='') {
  // uses common environment variables to set parameter map for dockerPipeline method

  def params = [:]
  // Jenkins build parameters
  params.imageUploadMode     = imageUploadMode
  params.skipReleaseCreation = skipReleaseCreation

  // Common variables to parameters
  params.awsProfile     = env.AWS_PROFILE
  params.octopusChannel = env.OCTOPUS_CHANNEL
  params.deployEnv      = env.DEPLOY_ENV

  // Parameters for octopus deploy and ECR
  params.projectName    = projectName ?: projectPath.split('/')[-1]
  params.octopusProject = octoProjectName ?: "${projectGroup}.${params.projectName.replace('_','-')}"
  params.workDir        = "${env.WORKSPACE}/${projectPath}"
  if (ecrRepoSubfolder == '') {
    params.ecrRepository = "${projectGroup}/${params.projectName.replace('_','-')}"
  } else {
    params.ecrRepository = "${projectGroup}/${ecrRepoSubfolder}/${params.projectName.replace('_','-')}"
  }
  params.registry = "${ecr}/${params.ecrRepository}"
  params.version  = octopus.getVersion(env.BRANCH_NAME,params.workDir)

  return params
}

def dockerPipeline(params) {
  nexus.generateAuthorizedPypirc(params.workDir + '/.pypirc')
  stage('check version') {
    if (params.imageUploadMode == 'error' && octopus.ecrImageTagExists(params.version,params.ecrRepository,params.awsProfile)) {
      error "Image with ${params.version} version tag already exists. To ignore this error set IMAGE_UPLOAD_MODE parameter to overwrite or skip_build"
    }
  }
  withEnv(["REGISTRY=${params.registry}", "VERSION=${params.version}", "AWS_PROFILE=${params.awsProfile}"]){
    stage('Build and push to ECR') {
      if (params.imageUploadMode == 'overwrite' || !octopus.ecrImageTagExists(params.version,params.ecrRepository,params.awsProfile)) {
        echo sh(script: "cd ${params.workDir} && make docker/clean AWS_PROFILE=gdb-delphi-dev", returnStdout: true).trim()
        echo sh(script: "cd ${params.workDir} && make docker/image/build AWS_PROFILE=gdb-delphi-dev", returnStdout: true).trim()
        echo sh(script: "cd ${params.workDir} && make docker/image/push", returnStdout: true).trim()
        echo octopus.createPackageBuildInformation(params.ecrRepository, params.version)
      } else {
        echo "Step skipped due to conditions."
      }
    }
  }
  stage('Create a release in octopus') {
    if (params.skipReleaseCreation == 'no' || !octopus.releaseExists(params.octopusProject,params.version)) {
      echo octopus.createRelease(params.octopusProject,params.version,params.octopusChannel)
    } else {
      echo "Step skipped due to conditions."
    }
  }
  stage('Deploy release in octopus') {
    if (params.deployEnv != 'skip_deployment') {
      echo octopus.deployRelease(params.octopusProject,params.deployEnv,params.version,params.octopusChannel)
    } else {
      echo "Step skipped due to conditions."
    }
  }
}

boolean isPathInChangeSet(path) {
  def changeLogSets = currentBuild.changeSets
  def changedFilesPathList = []
  def isChanged = false
  for (int i = 0; i < changeLogSets.size(); i++) {
    def entries = changeLogSets[i].items
    for (int j = 0; j < entries.length; j++) {
      def entry = entries[j]
      def files = new ArrayList(entry.affectedFiles)
      for (int k = 0; k < files.size(); k++) {
        changedFilesPathList.add(files[k].path)
      }
    }
  }
  changedFilesPathList.each {
    if (it.startsWith(path)) { isChanged = true }
  }
  return isChanged
}

def generateStages(jenkinsParams, stagesParamList) {
  // if parameters are 'default', sets flags true for develop/release/master branches
  // if parameters are explicitly set to yes or no, sets flag accordingly.
  def testAll            = checkBranchConditions(jenkinsParams.TEST_ALL)
  def buildPythonPackage = checkBranchConditions(jenkinsParams.BUILD_PYTHON_PACKAGE)
  def buildPythonDeps    = checkBranchConditions(jenkinsParams.PYTHON_DEPS_BUILD_UPLOAD_MODE)
  def triggerDeploy      = checkBranchConditions(jenkinsParams.TRIGGER_DEPLOY)
  def runLinter          = jenkinsParams.RUN_LINTER ? checkBranchConditions(jenkinsParams.RUN_LINTER) : false
  def stagesMap          = [:]

  stagesParamList.each {
    def stageParams    = [:]
    stageParams.testAll            = testAll
    stageParams.buildPythonPackage = buildPythonPackage
    stageParams.buildPythonDeps    = buildPythonDeps
    stageParams.triggerDeploy      = triggerDeploy
    stageParams.name               = it.path.split('/')[-1]
    stageParams.type               = it.type
    stageParams.path               = it.path
    stageParams.runLinter          = runLinter
    stagesMap.put(stageParams.name, getStage(jenkinsParams,stageParams))
  }
  return stagesMap
}

def stageFlags(forceBuild, stageParams) {
  // Map that defines what parts of pipeline will be triggered.
  // It can be test only, test + the rest of pipeline, or skipping the stage at all
  // Depends on FORCE_BUILD parameter, TEST_ALL parameter and
  // on whether there were changes in files for current component
  def flagsMap = [:]
  flagsMap.testPipelineFlag = false
  flagsMap.mainPipelineFlag = false
  flagsMap.pythonDepsPipelineFlag = false
  flagsMap.lintPipelineFlag = false
  switch(forceBuild) {
    case 'ALL':
    case stageParams.name:
      echo "all or project"
      flagsMap.testPipelineFlag  = true
      flagsMap.mainPipelineFlag = true
      flagsMap.pythonDepsPipelineFlag = true
      flagsMap.lintPipelineFlag = true
      break
    case 'Please make a choice':
      echo "default choice"
      if (isPathInChangeSet(stageParams.path)) {
        flagsMap.testPipelineFlag  = true
        flagsMap.mainPipelineFlag = true
        flagsMap.pythonDepsPipelineFlag = true
        flagsMap.lintPipelineFlag = true
      } else if (stageParams.testAll) {
        flagsMap.testPipelineFlag  = true
        flagsMap.mainPipelineFlag = false
        flagsMap.pythonDepsPipelineFlag = true
        flagsMap.lintPipelineFlag = true
      } else {
        flagsMap.testPipelineFlag  = false
        flagsMap.mainPipelineFlag = false
        flagsMap.pythonDepsPipelineFlag = false
        flagsMap.lintPipelineFlag = false
      }
      break
    default:
      echo "Other project"
      if (stageParams.testAll) {
        flagsMap.testPipelineFlag  = true
        flagsMap.lintPipelineFlag = true
      } else {
        flagsMap.testPipelineFlag = false
        flagsMap.lintPipelineFlag = false
      }
      flagsMap.mainPipelineFlag = false
      break
  }
  return flagsMap
}

def getStage(jenkinsParams, stageParams) {
  return {
    println(stageParams)
    stage(stageParams.name) {
      nexus.generateAuthorizedPypirc(stageParams.path + '/.pypirc')
      if (stageParams.runLinter && stageFlags(jenkinsParams.FORCE_BUILD, stageParams).pythonDepsPipelineFlag) {
        pythonLinterPipeline(
          setPythonPipelineVars(stageParams.path,jenkinsParams.PYTHON_PACKAGE_UPLOAD_MODE)
        )
      }
      // build python dependencies
      if (stageParams.buildPythonDeps && stageFlags(jenkinsParams.FORCE_BUILD, stageParams).pythonDepsPipelineFlag) {
        pythonDepsPipeline(
          setPythonDepsPipelineVars(stageParams.path)
        )
      }
      if (stageFlags(jenkinsParams.FORCE_BUILD, stageParams).testPipelineFlag) {
        // run tests
        pythonTestPipeline(
          setPythonPipelineVars(stageParams.path,jenkinsParams.PYTHON_PACKAGE_UPLOAD_MODE)
        )
        if (stageParams.buildPythonPackage && stageFlags(jenkinsParams.FORCE_BUILD, stageParams).mainPipelineFlag) {
          // build python package
          pythonPackagePipeline(
            setPythonPipelineVars(stageParams.path,jenkinsParams.PYTHON_PACKAGE_UPLOAD_MODE)
          )
        }
        if (stageParams.triggerDeploy && stageFlags(jenkinsParams.FORCE_BUILD, stageParams).mainPipelineFlag) {
          //nexus.generateCleanPypirc(stageParams.path + '/.pypirc')
          switch(stageParams.type) {
            case "lambda":
              // build docker image with zipped lambda
              dockerPipeline(setDockerPipelineParams(
                jenkinsParams.IMAGE_UPLOAD_MODE,
                jenkinsParams.SKIP_RELEASE_CREATION,
                stageParams.path, env.PROJECT_GROUP, env.ECR, "lambda")
              )
              break
            case "task":
            // build docker image with installed python application
              dockerPipeline(setDockerPipelineParams(
                jenkinsParams.IMAGE_UPLOAD_MODE,
                jenkinsParams.SKIP_RELEASE_CREATION,
                stageParams.path, env.PROJECT_GROUP, env.ECR)
              )
              break
          }
        }
      } else {
        echo "Step skipped due to conditions."
      }
    }
  }
}
