/*
 * Copyright 2019 the original author or authors.
 *
 * 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 me.lucko.jarrelocator.JarRelocator
import me.lucko.jarrelocator.Relocation
import java.util.function.Predicate
import java.util.jar.JarFile
import java.util.stream.Collectors

import org.gradle.api.artifacts.transform.TransformParameters

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath("me.lucko:jar-relocator:1.3")
    }
}

// tag::artifact-transform-relocate[]
@CacheableTransform                                                          // <1>
abstract class ClassRelocator implements TransformAction<Parameters> {
    interface Parameters extends TransformParameters {                       // <2>
        @CompileClasspath                                                    // <3>
        ConfigurableFileCollection getExternalClasspath()
        @Input
        Property<String> getExcludedPackage()
    }

    @Classpath                                                               // <4>
    @InputArtifact
    abstract Provider<FileSystemLocation> getPrimaryInput()

    @CompileClasspath
    @InputArtifactDependencies                                               // <5>
    abstract FileCollection getDependencies()

    @Override
    void transform(TransformOutputs outputs) {
        def primaryInputFile = primaryInput.get().asFile
        if (parameters.externalClasspath.contains(primaryInput)) {           // <6>
            outputs.file(primaryInput)
        } else {
            def baseName = primaryInputFile.name.substring(0, primaryInputFile.name.length - 4)
            relocateJar(outputs.file("$baseName-relocated.jar"))
        }
    }

    private relocateJar(File output) {
        // implementation...
        def relocatedPackages = (dependencies.collectMany { readPackages(it) } + readPackages(primaryInput.get().asFile)) as Set
        def nonRelocatedPackages = parameters.externalClasspath.collectMany { readPackages(it) }
        def relocations = (relocatedPackages - nonRelocatedPackages).collect { packageName ->
            def toPackage = "relocated.$packageName"
            println("$packageName -> $toPackage")
            new Relocation(packageName, toPackage)
        }
        new JarRelocator(primaryInput.get().asFile, output, relocations).run()
    }
// end::artifact-transform-relocate[]

    private Set<String> readPackages(File file)  {
        return new JarFile(file).withCloseable { jarFile ->
            return jarFile.stream()
                .filter(Predicate.not { it.directory })
                .filter { it.name.endsWith(".class") }
                .map { entry ->
                    entry.name.substring(0, name.lastIndexOf('/')).replace('/', '.')
                }
                .collect(Collectors.toSet())
        }
    }
// tag::artifact-transform-relocate[]
}
// end::artifact-transform-relocate[]

configurations.create("externalClasspath")

def usage = Attribute.of('usage', String)
// tag::artifact-transform-registration[]
def artifactType = Attribute.of('artifactType', String)

dependencies {
    registerTransform(ClassRelocator) {
        from.attribute(artifactType, "jar")
        to.attribute(artifactType, "relocated-classes")
        parameters {
            externalClasspath.from(configurations.externalClasspath)
            excludedPackage.set("org.gradle.api")
        }
    }
}
// end::artifact-transform-registration[]


allprojects {
    dependencies {
        attributesSchema {
            attribute(usage)
        }
    }
    configurations.create("compile") {
        attributes.attribute usage, 'api'
    }
}
