evaluationDependsOn(":node:capsule")


import com.github.dockerjava.api.model.Identifier
import net.corda.build.docker.DockerImage
import net.corda.build.docker.DockerUtils
import net.corda.build.docker.ObjectInputStreamWithCustomClassLoader

import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.stream.Collectors
import java.util.stream.Stream

apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'application'

// We need to set mainClassName before applying the shadow plugin.
mainClassName = 'net.corda.core.ConfigExporterMain'
apply plugin: 'com.github.johnrengelman.shadow'

dependencies{
    implementation project(':node')
    implementation project(':node-api')
    implementation project(':common-configuration-parsing')
    implementation project(':common-validation')

    implementation "com.typesafe:config:$typesafe_config_version"
}

shadowJar {
    baseName = 'config-exporter'
    classifier = null
    version = null
    zip64 true
    exclude '**/Log4j2Plugins.dat'
}

enum ImageVariant {
    UBUNTU_ZULU("Dockerfile", "17", "zulu-openjdk"),
    AL_CORRETTO("DockerfileAL", "17", "amazonlinux2"),
    OFFICIAL(UBUNTU_ZULU)

    String dockerFile
    String javaVersion
    String baseImageFullName

    ImageVariant(ImageVariant other) {
        this.dockerFile = other.dockerFile
        this.javaVersion = other.javaVersion
        this.baseImageFullName = other.baseImageFullName
    }

    ImageVariant(String dockerFile, String javaVersion, String baseImageFullName) {
        this.dockerFile = dockerFile
        this.javaVersion = javaVersion
        this.baseImageFullName = baseImageFullName
    }

    static final String getRepository(Project project) {
        return project.properties.getOrDefault("docker.image.repository", "corda/corda")
    }

    Set<Identifier> buildTags(Project project) {
        return ["${project.version.toString().toLowerCase()}-${baseImageFullName}"].stream().map {
            toAppend -> "${getRepository(project)}:${toAppend}".toString()
        }.map(Identifier.&fromCompoundString).collect(Collectors.toSet())
    }

    static Set<ImageVariant> toBeBuilt = Arrays.stream(values()).collect(Collectors.toSet())
}

class BuildDockerFolderTask extends DefaultTask {

    @Option(option = "image", description = "Docker image variants that will be built")
    void setVariants(List<ImageVariant> variants) {
        ImageVariant.toBeBuilt = new HashSet<>(variants)
    }

    @OptionValues("image")
    Collection<ImageVariant> getAllVariants() {
        return EnumSet.allOf(ImageVariant.class)
    }

    @Input
    Iterable<ImageVariant> getVariantsToBuild() {
        return ImageVariant.toBeBuilt
    }

    @InputFiles
    FileCollection getDockerBuildFiles() {
        return project.fileTree("${project.projectDir}/src/docker/build")
    }

    @InputFiles
    FileCollection getShellScripts() {
        return project.fileTree("${project.projectDir}/src/bash")
    }

    private File cordaJar = project.findProject(":node:capsule").tasks.buildCordaJAR.outputs.files.filter {
        it.name.contains("corda")
    }.singleFile

    private File configExporter = project.tasks.shadowJar.outputs.files.singleFile

    private FileCollection getRequiredArtifacts() {
        FileCollection res = project.tasks.shadowJar.outputs.files
        def capsuleProject = project.findProject(":node:capsule")
        def capsuleTaksOutput = capsuleProject.tasks.buildCordaJAR.outputs.files
        res += capsuleTaksOutput
        return res
    }

    @OutputFiles
    FileCollection getDockerFiles() {
        return project.objects.fileCollection().from(ImageVariant.toBeBuilt.stream().map {
            new File(dockerBuildDir, it.dockerFile)
        }.collect(Collectors.toList()))
    }

    @OutputDirectory
    final File dockerBuildDir = project.file("${project.buildDir}/docker/build")

    @TaskAction
    def run() {
        for(ImageVariant imageVariant : ImageVariant.toBeBuilt) {
            def sourceFile = project.projectDir.toPath().resolve("src/docker/${imageVariant.dockerFile}")
            def destinationFile = dockerBuildDir.toPath().resolve(imageVariant.dockerFile)
            Files.copy(sourceFile, destinationFile, StandardCopyOption.REPLACE_EXISTING)
        }
        Files.copy(cordaJar.toPath(), dockerBuildDir.toPath().resolve("corda.jar"), StandardCopyOption.REPLACE_EXISTING)
        Files.copy(configExporter.toPath(), dockerBuildDir.toPath().resolve("config-exporter.jar"), StandardCopyOption.REPLACE_EXISTING)

        ["src/bash/run-corda.sh",
         "src/config/starting-node.conf",
         "src/bash/generate-config.sh"].forEach {
            def source = project.file(it).toPath()
            Files.copy(source, dockerBuildDir.toPath().resolve(source.fileName), StandardCopyOption.REPLACE_EXISTING)
        }
    }
}


class BuildDockerImageTask extends DefaultTask {

    @Option(option = "image", description = "Docker image variants that will be built")
    void setVariants(List<ImageVariant> variants) {
        ImageVariant.toBeBuilt = new HashSet<>(variants)
    }

    @OptionValues("image")
    Collection<ImageVariant> getAllVariants() {
        return EnumSet.allOf(ImageVariant.class)
    }

    @OutputDirectory
    final File dockerBuildDir = project.file("${project.buildDir}/docker/build")

    @OutputDirectory
    final File dockerBuiltImageDir = project.file("${project.buildDir}/docker/images")

    @Input
    String getRepository() {
        return ImageVariant.getRepository(project)
    }

    @InputFiles
    FileCollection dockerFiles

    private Map<ImageVariant, DockerImage> images

    @OutputFiles
    FileCollection getImageFiles() {
        return project.objects.fileCollection().from(ImageVariant.toBeBuilt.stream().map { imageVariant ->
            dockerBuiltImageDir.toPath().resolve(imageVariant.toString()).toFile()
        }.collect(Collectors.toList()))
    }

    void from(BuildDockerFolderTask buildDockerFolderTask) {
        dockerFiles = buildDockerFolderTask.outputs.files
    }

    @Override
    Task configure(Closure closure) {
        return super.configure(closure)
    }

    @TaskAction
    def run() {
        this.@images = ImageVariant.toBeBuilt.stream().map { imageVariant ->
            new Tuple2<>(imageVariant, new DockerImage(
                    baseDir: dockerBuildDir,
                    dockerFile: imageVariant.dockerFile,
                    tags: imageVariant.buildTags(project)))
        }.collect(Collectors.toMap({it.first}, {it.second}))
        DockerUtils.buildImages(project, this.@images.values())
        images.entrySet().forEach { entry ->
            ImageVariant imageVariant = entry.key
            def destinationFile = dockerBuiltImageDir.toPath().resolve(imageVariant.toString())
            new ObjectOutputStream(Files.newOutputStream(destinationFile)).withStream {
                it.writeObject(entry.value)
            }
        }
    }
}

class PushDockerImage extends DefaultTask {
    @Option(option = "image", description = "Docker image variants that will be built")
    void setVariants(List<ImageVariant> variants) {
        ImageVariant.toBeBuilt = new HashSet<>(variants)
    }

    @OptionValues("image")
    Collection<ImageVariant> getAllVariants() {
        return EnumSet.allOf(ImageVariant.class)
    }

    private static String registryURL

    @Option(option = "registry-url", description = "Docker image registry where images will be pushed, defaults to DockerHub")
    void setRegistryURL(String registryURL) {
        PushDockerImage.registryURL = registryURL
    }

    @InputFiles
    FileCollection imageFiles

    def from(BuildDockerImageTask buildDockerImageTask) {
        imageFiles = buildDockerImageTask.outputs.files
    }

    @TaskAction
    def run() {
        def classLoader = DockerImage.class.classLoader
        Stream<DockerImage> imageStream = imageFiles.files.stream().filter{
            it.isFile()
        }.map {
            new ObjectInputStreamWithCustomClassLoader(Files.newInputStream(it.toPath()), classLoader).withStream {
                DockerImage image = it.readObject()
                if(PushDockerImage.registryURL) {
                    image.destination = PushDockerImage.registryURL
                }
                image
            }
        }
        DockerUtils.pushImages(project, imageStream)
    }
}

def buildDockerFolderTask = tasks.register("buildDockerFolder", BuildDockerFolderTask) {
    dependsOn = [ tasks.named('shadowJar') ]
}

def buildDockerImageTask = tasks.register("buildDockerImage", BuildDockerImageTask) {
    from(buildDockerFolderTask.get())
}

tasks.register("pushDockerImage", PushDockerImage) {
    from(buildDockerImageTask.get())
}
