import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
import org.gradle.api.file.FileTreeElement
import shadow.org.apache.tools.zip.ZipOutputStream
import shadow.org.apache.tools.zip.ZipEntry

plugins {
    id "java"
    id "maven-publish"

    id "com.github.johnrengelman.shadow"
    id "ru.vyarus.animalsniffer"
}

description = "gRPC: Netty Shaded"

sourceSets { testShadow {} }

dependencies {
    implementation project(':grpc-netty')
    runtimeOnly libraries.netty.tcnative,
            libraries.netty.tcnative.classes
    runtimeOnly (libraries.netty.tcnative) {
        artifact {
            classifier = "linux-x86_64"
        }
    }
    runtimeOnly (libraries.netty.tcnative) {
        artifact {
            classifier = "linux-aarch_64"
        }
    }
    runtimeOnly (libraries.netty.tcnative) {
        artifact {
            classifier = "osx-x86_64"
        }
    }
    runtimeOnly (libraries.netty.tcnative) {
        artifact {
            classifier = "osx-aarch_64"
        }
    }
    runtimeOnly (libraries.netty.tcnative) {
        artifact {
            classifier = "windows-x86_64"
        }
    }
    runtimeOnly (libraries.netty.transport.epoll) {
        artifact {
            classifier = "linux-x86_64"
        }
    }
    runtimeOnly (libraries.netty.transport.epoll) {
        artifact {
            classifier = "linux-aarch_64"
        }
    }
    testShadowImplementation files(shadowJar),
            project(':grpc-testing-proto'),
            project(':grpc-testing'),
            libraries.truth
    shadow project(':grpc-netty').configurations.runtimeClasspath.allDependencies.matching {
        it.group != 'io.netty'
    }
    signature libraries.signature.java
    signature libraries.signature.android
}

tasks.named("jar").configure {
    // Must use a different archiveClassifier to avoid conflicting with shadowJar
    archiveClassifier = 'original'
}

tasks.named("shadowJar").configure {
    archiveClassifier = null
    dependencies {
        include(project(':grpc-netty'))
        include(dependency('io.netty:'))
    }
    exclude 'META-INF/maven/**'
    relocate 'io.grpc.netty', 'io.grpc.netty.shaded.io.grpc.netty'
    relocate 'io.netty', 'io.grpc.netty.shaded.io.netty'
    // We have to be careful with these replacements as they must not match any
    // string in NativeLibraryLoader, else they cause corruption. Note that
    // this includes concatenation of string literals and constants.
    relocate 'META-INF/native/libnetty', 'META-INF/native/libio_grpc_netty_shaded_netty'
    relocate 'META-INF/native/netty', 'META-INF/native/io_grpc_netty_shaded_netty'
    transform(NettyResourceTransformer.class)
    mergeServiceFiles()
}

publishing {
    publications {
        maven(MavenPublication) {
            project.shadow.component(it)

            // Empty jars are not published via withJavadocJar() and withSourcesJar()
            artifact javadocJar
            artifact sourcesJar

            // Avoid confusing error message "class file for
            // io.grpc.internal.AbstractServerImplBuilder not found"
            // (https://github.com/grpc/grpc-java/issues/5881). This can be
            // removed after https://github.com/grpc/grpc-java/issues/7211 is
            // resolved.
            pom.withXml {
                asNode().dependencies.'*'.findAll() { dep ->
                    dep.artifactId.text() == 'grpc-core'
                }.each() { core ->
                    core.scope*.value = "compile"
                }
            }

            // shadow.component() is run after the main build.gradle's withXml
            pom.withXml {
                asNode().dependencies.'*'.findAll() { dep ->
                    dep.artifactId.text() in ['grpc-api', 'grpc-core']
                }.each() { core ->
                    core.version*.value = "[" + core.version.text() + "]"
                }
            }
        }
    }
}

tasks.register("testShadow", Test) {
    testClassesDirs = sourceSets.testShadow.output.classesDirs
    classpath = sourceSets.testShadow.runtimeClasspath
}
tasks.named("compileTestShadowJava").configure {
    options.compilerArgs = compileTestJava.options.compilerArgs
    options.encoding = compileTestJava.options.encoding
}

tasks.named("test").configure {
    dependsOn tasks.named("testShadow")
}

/**
 * A Transformer which updates the Netty JAR META-INF/ resources to accurately
 * reference shaded class names.
 */
class NettyResourceTransformer implements Transformer {

    // A map of resource file paths to be modified
    private Map<String, String> resources = [:]

    @Override
    boolean canTransformResource(FileTreeElement fileTreeElement) {
        fileTreeElement.name.startsWith("META-INF/native-image/io.netty")
    }

    @Override
    void transform(TransformerContext context) {
        String updatedPath = context.path.replace("io.netty", "io.grpc.netty.shaded.io.netty")
        String updatedContent = context.is.getText().replace("io.netty", "io.grpc.netty.shaded.io.netty")
        resources.put(updatedPath, updatedContent)
    }

    @Override
    boolean hasTransformedResource() {
        resources.size() > 0
    }

    @Override
    void modifyOutputStream(ZipOutputStream outputStream, boolean preserveFileTimestamps) {
        for (resourceEntry in resources) {
            ZipEntry entry = new ZipEntry(resourceEntry.key)
            entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time)

            outputStream.putNextEntry(entry)
            outputStream.write(resourceEntry.value.getBytes())
            outputStream.closeEntry()
        }
    }
}
