package com.mkobit.jenkins.pipelines

import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import com.squareup.javapoet.WildcardTypeName
import java.io.File
import java.io.IOException
import java.nio.file.Path
import java.nio.file.Paths
import java.util.Objects
import javax.lang.model.element.Modifier

private const val codegenPackage = "com.mkobit.jenkins.pipelines.codegen"

private val generated: ClassName = ClassName.get(codegenPackage, "JenkinsGradlePluginSharedLibraryGenerated")
private val file: ClassName = ClassName.get(File::class.java)
private val string: ClassName = ClassName.get(String::class.java)

private val filePath: ClassName = ClassName.get("hudson", "FilePath")
private val hudsonRun: ClassName = ClassName.get("hudson.model", "Run")
private val taskListener: ClassName = ClassName.get("hudson.model", "TaskListener")

private val generatedAnnotationSpec: AnnotationSpec = AnnotationSpec.builder(generated).build()

/**
 * Marker annotation of code generated by the Shared Library plugin
 */
internal fun internalGeneratedAnnotation(): JavaFile {
  val generatedType = TypeSpec.annotationBuilder(generated)
    .addJavadoc("Marker annotation of code generated by Jenkins Gradle Shared Library plugin.")
    .build()

  return JavaFile.builder(codegenPackage, generatedType)
    .build()
}

/**
 * `org.jenkinsci.plugins.workflow.libs.LibraryRetriever` retriever used for retrieving local source code.
 */
internal fun localLibraryAdder(): JavaFile {
  val javaxNonNull = AnnotationSpec.builder(
    ClassName.get("javax.annotation", "Nonnull")
  ).build()

  val nameParam = ParameterSpec.builder(string.annotated(javaxNonNull), "name")
    .addModifiers(Modifier.FINAL)
    .build()
  val versionParam = ParameterSpec.builder(string.annotated(javaxNonNull), "version")
    .addModifiers(Modifier.FINAL)
    .build()
  val changelogParam = ParameterSpec.builder(TypeName.BOOLEAN, "changelog")
    .addModifiers(Modifier.FINAL)
    .build()
  val targetParam = ParameterSpec.builder(filePath.annotated(javaxNonNull), "target")
    .addModifiers(Modifier.FINAL)
    .build()
  val runParam = ParameterSpec.builder(ParameterizedTypeName.get(hudsonRun, WildcardTypeName.subtypeOf(Object::class.java), WildcardTypeName.subtypeOf(Object::class.java)), "run", Modifier.FINAL)
    .addAnnotation(javaxNonNull)
    .build()
  val listenerParam = ParameterSpec.builder(taskListener.annotated(javaxNonNull), "listener")
    .addModifiers(Modifier.FINAL)
    .build()

  val typeSpec = TypeSpec.classBuilder("LocalLibraryRetriever")
    .addJavadoc("Retriever used for retrieving local pipeline shared library implementation.")
    .addAnnotation(generatedAnnotationSpec)
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addField(
      FieldSpec.builder(file, "localDirectory", Modifier.FINAL, Modifier.PRIVATE)
        .build()
    ).superclass(ClassName.get("org.jenkinsci.plugins.workflow.libs", "LibraryRetriever"))
    .addMethod(
      MethodSpec.constructorBuilder()
        .addModifiers(Modifier.PUBLIC)
        .addStatement("this(\$T.get(\$T.getProperty(\"user.dir\")))", Paths::class.java, System::class.java)
        .build()
    ).addMethod(
      MethodSpec.constructorBuilder()
        .addModifiers(Modifier.PUBLIC)
        .addParameter(Path::class.java, "path", Modifier.FINAL)
        .addStatement("localDirectory = \$T.requireNonNull(path).toFile()", Objects::class.java)
        .build()
    ).addMethod(
      MethodSpec.methodBuilder("retrieve")
        .addModifiers(Modifier.FINAL, Modifier.PUBLIC)
        .addAnnotation(Override::class.java)
        .returns(TypeName.VOID)
        .addParameter(nameParam)
        .addParameter(versionParam)
        .addParameter(changelogParam)
        .addParameter(targetParam)
        .addParameter(runParam)
        .addParameter(listenerParam)
        .addException(Exception::class.java)
        .addStatement("doRetrieve(target, listener)")
        .build()
    ).addMethod(
      MethodSpec.methodBuilder("retrieve")
        .addModifiers(Modifier.FINAL, Modifier.PUBLIC)
        .returns(TypeName.VOID)
        .addAnnotation(Override::class.java)
        .addParameter(nameParam)
        .addParameter(versionParam)
        .addParameter(targetParam)
        .addParameter(runParam)
        .addParameter(listenerParam)
        .addException(Exception::class.java)
        .addStatement("doRetrieve(target, listener)")
        .build()
    ).addMethod(
      MethodSpec.methodBuilder("doRetrieve")
        .returns(TypeName.VOID)
        .addModifiers(Modifier.PRIVATE)
        .addParameter(targetParam)
        .addParameter(listenerParam)
        .addException(IOException::class.java)
        .addException(InterruptedException::class.java)
        .addStatement("final \$T localFilePath = new \$T(localDirectory)", filePath, filePath)
        .addStatement("listener.getLogger().format(\$S, localDirectory, target, \$T.lineSeparator())", "Copying from local path %s to workspace path %s%s", ClassName.get(System::class.java))
        .addComment("Exclusion filter copied from SCMSourceRetriever")
        .addStatement("localFilePath.copyRecursiveTo(${'$'}S, null, target)", "src/**/*.groovy,vars/*.groovy,vars/*.txt,resources/")
        .build()
    ).build()

  return JavaFile.builder(codegenPackage, typeSpec)
    .build()
}
