// Copyright 2017 The Promagent 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.

package io.promagent.plugin;

import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.IOUtil;

import java.io.*;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

class AgentJar implements AutoCloseable {

    private final String jarName;
    private final JarOutputStream jarOutputStream;
    private final Set<String> content = new HashSet<>();

    enum Directory {
        SHARED_JARS("shared-jars/"), // Directory entries in JAR file must end in '/'
        PER_DEPLOYMENT_JARS("per-deployment-jars/");

        private final String name;

        Directory(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

    private AgentJar(String jarName, JarOutputStream jarOutputStream) {
        this.jarName = jarName;
        this.jarOutputStream = jarOutputStream;
    }

    static AgentJar create(File jarFile) throws MojoExecutionException {
        try {
            JarOutputStream jarOutputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(jarFile)));
            return new AgentJar(jarFile.getName(), jarOutputStream);
        } catch (IOException e) {
            throw new MojoExecutionException("Error creating " + jarFile.getName() + ": " + e.getMessage(), e);
        }
    }

    void addFile(File srcFile, String targetFileName, Directory targetDir) throws MojoExecutionException {
        String destPath = targetDir.getName() + targetFileName;
        if (content.contains(destPath)) {
            return;
        }
        makeDirsRecursively(destPath);
        content.add(destPath);
        try (InputStream in = new FileInputStream(srcFile)) {
            jarOutputStream.putNextEntry(new JarEntry(destPath));
            IOUtil.copy(in, jarOutputStream);
        } catch (IOException e) {
            throw new MojoExecutionException("Error adding " + srcFile.getName() + " to target JAR: " + e.getMessage(), e);
        }
    }

    void addFile(File srcFile, Directory targetDir) throws MojoExecutionException {
        addFile(srcFile, srcFile.getName(), targetDir);
    }

    void extractJar(File jar, ManifestTransformer manifestTransformer) throws MojoExecutionException {
        try (JarFile jarFile = new JarFile(jar)) {
            for (Enumeration<JarEntry> jarEntries = jarFile.entries(); jarEntries.hasMoreElements(); ) {
                JarEntry jarEntry = jarEntries.nextElement();
                if (manifestTransformer.canTransform(jarEntry)) {
                    jarEntry = manifestTransformer.transform(jarEntry);
                }
                if (!jarEntry.isDirectory() && !content.contains(jarEntry.getName())) {
                    content.add(jarEntry.getName());
                    makeDirsRecursively(jarEntry.getName());
                    try (InputStream in = getInputStream(jarEntry, jarFile, manifestTransformer)) {
                        jarOutputStream.putNextEntry(jarEntry);
                        IOUtil.copy(in, jarOutputStream);
                    }
                }
            }
        } catch (IOException e) {
            throw new MojoExecutionException("Error adding " + jar.getName() + " to target JAR: " + e.getMessage(), e);
        }
    }

    private InputStream getInputStream(JarEntry jarEntry, JarFile jarFile, ManifestTransformer manifestTransformer) throws IOException, MojoExecutionException {
        InputStream in = jarFile.getInputStream(jarEntry);
        if (manifestTransformer.canTransform(jarEntry)) {
            in = manifestTransformer.transform(in);
        }
        return in;
    }

    private void makeDirsRecursively(String path) throws MojoExecutionException {
        String[] parts = path.split("/+");
        String segment = "";
        for (int i=0; i<parts.length-1; i++) {
            segment += parts[i] + "/";
            if (!content.contains(segment)) {
                try {
                    jarOutputStream.putNextEntry(new JarEntry(segment));
                } catch (IOException e) {
                    throw new MojoExecutionException("Error adding directory " + segment + " to target JAR: " + e.getMessage(), e);
                }
                content.add(segment);
            }
        }
    }

    @Override
    public void close() throws MojoExecutionException {
        try {
            jarOutputStream.close();
        } catch (IOException e) {
            throw new MojoExecutionException("Error finalizing " + jarName + ": " + e.getMessage(), e);
        }
    }
}