package org.jboss.windup.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.furnace.util.Streams;
import org.jboss.windup.util.exception.WindupException;


/**
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 *  @author <a href="http://ondra.zizka.cz/">Ondrej Zizka, zizka@seznam.cz</a>
 */
public class ZipUtil
{
    private static final Logger log = Logger.getLogger(ZipUtil.class.getName());

    private static Set<String> supportedExtensions;

    /**
     * Unzip a classpath resource using the given {@link Class} as the resource root path.
     */
    public static void unzipFromClassResource(Class<?> clazz, String resourcePath, File extractToPath) throws IOException
    {
        File inputFile = File.createTempFile("windup-resource-to-unzip-", ".zip");
        try
        {
            try (final InputStream stream = clazz.getResourceAsStream(resourcePath))
            {
                FileUtils.copyInputStreamToFile(stream, inputFile);
            }
            extractToPath.mkdirs();
            ZipUtil.unzipToFolder(inputFile, extractToPath);
        }
        finally
        {
            inputFile.delete();
        }
    }

    /**
     * Unzip the given {@link File} to the specified directory.
     */
    public static void unzipToFolder(File inputFile, File outputDir) throws IOException
    {
        if (inputFile == null)
            throw new IllegalArgumentException("Argument inputFile is null.");
        if (outputDir == null)
            throw new IllegalArgumentException("Argument outputDir is null.");

        try (ZipFile zipFile = new ZipFile(inputFile))
        {
            Enumeration<? extends ZipEntry> entryEnum = zipFile.entries();
            while (entryEnum.hasMoreElements())
            {
                ZipEntry entry = entryEnum.nextElement();
                String entryName = entry.getName();
                File destFile = new File(outputDir, entryName);
                if (!entry.isDirectory())
                {
                    File parentDir = destFile.getParentFile();
                    if (!parentDir.isDirectory() && !parentDir.mkdirs())
                    {
                        throw new WindupException("Unable to create directory: " + parentDir.getAbsolutePath());
                    }
                    try (InputStream zipInputStream = zipFile.getInputStream(entry))
                    {
                        try (FileOutputStream outputStream = new FileOutputStream(destFile))
                        {
                            Streams.write(zipInputStream, outputStream);
                        }
                    }
                }
            }
        }
    }

    public static String getEndsWithZipRegularExpression()
    {
        Set<String> zipExtensions = getZipExtensions();
        final String regex;
        if (zipExtensions.size() == 1)
        {
            regex = ".+\\." + zipExtensions.iterator().next() + "$";
        }
        else
        {
            StringBuilder builder = new StringBuilder();
            builder.append("\\b(");
            for (String value : zipExtensions)
            {
                builder.append("|");
                builder.append(value);
            }
            builder.append(")\\b");
            regex = ".+\\." + builder.toString() + "$";
        }
        return regex;
    }

    public static boolean endsWithZipExtension(String path)
    {
        for (String extension : getZipExtensions())
        {
            if (StringUtils.endsWith(path, "." + extension))
            {
                return true;
            }
        }
        return false;
    }

    public static Set<String> getZipExtensions()
    {
        if (supportedExtensions == null)
        {
            Set<String> extensions = new HashSet<>();
            extensions.add("war");
            extensions.add("ear");
            extensions.add("jar");
            extensions.add("sar");
            extensions.add("rar");
            supportedExtensions = extensions;
        }

        return supportedExtensions;
    }


    public static List<String> scanZipFile(Path zipFilePath, boolean relativeOnly)
    {
        try
        {
            try (final InputStream is = new FileInputStream(zipFilePath.toFile()))
            {
                return scanZipFile(zipFilePath.normalize().toString(), is, relativeOnly);
            }
        }
        catch (IOException e)
        {
            System.err.println("Could not read file: " + zipFilePath + " due to: " + e.getMessage());
            return Collections.emptyList();
        }
    }


    public static List<String> scanZipFile(String parentPath, InputStream is, boolean relativeOnly)
    {
        try
        {
            ZipInputStream zis = new ZipInputStream(is);
            ZipEntry entry;
            List<String> results = new ArrayList<>();
            while ((entry = zis.getNextEntry()) != null)
            {
                String fullPath = parentPath + "/" + entry.getName();
                results.add(relativeOnly ? entry.getName() : fullPath);
                if (!entry.isDirectory() && ZipUtil.endsWithZipExtension(entry.getName()))
                {
                    results.addAll(scanZipFile(fullPath, zis, relativeOnly));
                }
            }
            return results;
        }
        catch (IOException e)
        {
            System.err.println("Could not read file: " + parentPath + " due to: " + e.getMessage());
            return Collections.emptyList();
        }
    }

}
