package com.github.ulisesbocchio.jar.resources;


    import com.google.common.base.Preconditions;

    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;

    import org.apache.commons.io.FileUtils;
    import org.apache.commons.vfs2.FileObject;
    import org.apache.commons.vfs2.VFS;
    import org.springframework.core.io.Resource;
    import org.springframework.util.ResourceUtils;

    import java.io.File;
    import java.util.Arrays;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import java.util.stream.IntStream;

/**
 * @author Ulises Bocchio
 */
@Slf4j
public class JarUtils {

    private static Pattern EXCLAMATION_PATH = Pattern.compile("/([^/!]*!)/");

    @SneakyThrows
    public static File getFile(Resource resource, String extractPath) {
        return ResourceUtils.isJarURL(resource.getURL()) ? getFromJar(resource, extractPath) : resource.getFile();
    }

    @SneakyThrows
    private static File getFromJar(Resource resource, String extractPath) {
        Preconditions.checkArgument(extractPath != null, "Extract Path cannot be null");
        FileObject file = VFS.getManager().resolveFile(maybeFixUri(resource));
        File extractDir;
        extractDir = new File(extractPath);
        if (!extractDir.exists() || !extractDir.isDirectory()) {
            FileUtils.forceMkdir(extractDir);
            log.debug("TEMP EXTRACT DIR CREATED {}", extractDir.getAbsolutePath());
        }
        return copyToDir(file, extractDir);
    }

    @SneakyThrows
    private static String maybeFixUri(Resource resource) {
        String uri = resource.getURI().toString();
        uri = maybeFixUriPrefix(uri);
        uri = maybeFixExclamationPath(uri);
        return uri;
    }

    private static String maybeFixExclamationPath(String uri) {
        String fixedUri = uri;
        Matcher matcher = EXCLAMATION_PATH.matcher(uri);
        while(matcher.find()) {
            String match = matcher.group(1);
            if(!match.endsWith(".jar!")) {
                fixedUri = fixedUri.replaceFirst(match, match.substring(0, match.length() - 1));
            }
        }
        return fixedUri;
    }

    private static String maybeFixUriPrefix(String uri) {
        int numOfJarsInResource = numbOfJars(uri);
        String jarPrefix = jarPrefix(numOfJarsInResource);
        String fixedUri = jarPrefix + uri.substring(4);
        return fixedUri;
    }

    private static String jarPrefix(int n) {
        return IntStream.range(0, n)
                .mapToObj(num -> "jar:")
                .reduce((r, l) -> r + l)
                .orElse("jar:");
    }

    private static int numbOfJars(String uri) {
        Matcher matcher = Pattern.compile("\\.jar!").matcher(uri);
        int matches = 0;
        while (matcher.find()) {
            matches++;
        }
        return matches;
    }

    private static File copyToDir(FileObject jarredFile, File destination) {
        return copyToDir(jarredFile, destination, true);
    }

    @SneakyThrows
    private static File copyToDir(FileObject jarredFile, File destination, boolean retryIfImaginary) {
        switch (jarredFile.getType()) {
            case FILE:
                return copyFileToDir(jarredFile, destination);
            case FOLDER:
                return copyDirToDir(jarredFile, destination);
            case IMAGINARY:
                if (retryIfImaginary) {
                    log.debug("Imaginary file found, retrying extraction");
                    VFS.getManager().getFilesCache().removeFile(jarredFile.getFileSystem(), jarredFile.getName());
                    FileObject newJarredFile = VFS.getManager().resolveFile(jarredFile.getName().getURI());
                    return copyToDir(newJarredFile, destination, false);
                } else {
                    log.debug("Imaginary file found after retry, abandoning retry");
                }

            default:
                throw new IllegalStateException("File Type not supported: " + jarredFile.getType());
        }
    }

    @SneakyThrows
    private static File copyDirToDir(FileObject jarredDir, File destination) {
        File tempDir = new File(destination, jarredDir.getName().getBaseName());
        createDir(tempDir);
        Arrays.stream(jarredDir.getChildren())
                .forEach(fileObject -> copyToDir(fileObject, tempDir));
        return tempDir;
    }

    @SneakyThrows
    private static File copyFileToDir(FileObject jarredFile, File destination) {
        File tempFile = new File(destination, jarredFile.getName().getBaseName());
        createFile(tempFile);
        log.info("TEMP FILE CREATED {}", tempFile.getAbsolutePath());
        FileUtils.copyInputStreamToFile(jarredFile.getContent().getInputStream(), tempFile);
        return tempFile;
    }

    private static void createDir(File file) {
        if (!file.exists() && !file.mkdir()) {
            throw new IllegalStateException(String.format("Could not create temp directory: %s", file.getAbsolutePath()));
        }
    }

    @SneakyThrows
    private static void createFile(File file) {
        if (file.exists()) {
            FileUtils.forceDelete(file);
        }
        if (!file.createNewFile()) {
            throw new IllegalStateException(String.format("Could not create temp jarredFile: %s", file.getAbsolutePath()));
        }
    }
}