// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.nativ;

import java.util.Iterator;
import java.net.URL;
import java.util.Properties;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.io.FilenameFilter;
import java.io.File;
import java.util.logging.Logger;

public class JLineNativeLoader
{
    private static final Logger logger;
    private static boolean loaded;
    private static String nativeLibraryPath;
    private static String nativeLibrarySourceUrl;
    
    public static synchronized boolean initialize() {
        if (!JLineNativeLoader.loaded) {
            final Thread cleanup = new Thread(JLineNativeLoader::cleanup, "cleanup");
            cleanup.setPriority(1);
            cleanup.setDaemon(true);
            cleanup.start();
        }
        try {
            loadJLineNativeLibrary();
        }
        catch (final Exception e) {
            throw new RuntimeException("Unable to load jline native library: " + e.getMessage(), e);
        }
        return JLineNativeLoader.loaded;
    }
    
    public static String getNativeLibraryPath() {
        return JLineNativeLoader.nativeLibraryPath;
    }
    
    public static String getNativeLibrarySourceUrl() {
        return JLineNativeLoader.nativeLibrarySourceUrl;
    }
    
    private static File getTempDir() {
        return new File(System.getProperty("jline.tmpdir", System.getProperty("java.io.tmpdir")));
    }
    
    static void cleanup() {
        final String tempFolder = getTempDir().getAbsolutePath();
        final File dir = new File(tempFolder);
        final File[] nativeLibFiles = dir.listFiles(new FilenameFilter() {
            private final String searchPattern = "jlinenative-" + JLineNativeLoader.getVersion();
            
            @Override
            public boolean accept(final File dir, final String name) {
                return name.startsWith(this.searchPattern) && !name.endsWith(".lck");
            }
        });
        if (nativeLibFiles != null) {
            for (final File nativeLibFile : nativeLibFiles) {
                final File lckFile = new File(nativeLibFile.getAbsolutePath() + ".lck");
                if (!lckFile.exists()) {
                    try {
                        nativeLibFile.delete();
                    }
                    catch (final SecurityException e) {
                        JLineNativeLoader.logger.log(Level.INFO, "Failed to delete old native lib" + e.getMessage(), e);
                    }
                }
            }
        }
    }
    
    private static int readNBytes(final InputStream in, final byte[] b) throws IOException {
        int n = 0;
        int count;
        for (int len = b.length; n < len; n += count) {
            count = in.read(b, n, len - n);
            if (count <= 0) {
                break;
            }
        }
        return n;
    }
    
    private static String contentsEquals(final InputStream in1, final InputStream in2) throws IOException {
        final byte[] buffer1 = new byte[8192];
        final byte[] buffer2 = new byte[8192];
        do {
            final int numRead1 = readNBytes(in1, buffer1);
            final int numRead2 = readNBytes(in2, buffer2);
            if (numRead1 > 0) {
                if (numRead2 <= 0) {
                    return "EOF on second stream but not first";
                }
                if (numRead2 != numRead1) {
                    return "Read size different (" + numRead1 + " vs " + numRead2 + ")";
                }
                continue;
            }
            else {
                if (numRead2 > 0) {
                    return "EOF on first stream but not second";
                }
                return null;
            }
        } while (Arrays.equals(buffer1, buffer2));
        return "Content differs";
    }
    
    private static boolean extractAndLoadLibraryFile(final String libFolderForCurrentOS, final String libraryFileName, final String targetFolder) {
        final String nativeLibraryFilePath = libFolderForCurrentOS + "/" + libraryFileName;
        final String uuid = randomUUID();
        final String extractedLibFileName = String.format("jlinenative-%s-%s-%s", getVersion(), uuid, libraryFileName);
        final String extractedLckFileName = extractedLibFileName + ".lck";
        final File extractedLibFile = new File(targetFolder, extractedLibFileName);
        final File extractedLckFile = new File(targetFolder, extractedLckFileName);
        try {
            try (final InputStream in = JLineNativeLoader.class.getResourceAsStream(nativeLibraryFilePath)) {
                if (!extractedLckFile.exists()) {
                    new FileOutputStream(extractedLckFile).close();
                }
                try (final OutputStream out = new FileOutputStream(extractedLibFile)) {
                    copy(in, out);
                }
            }
            finally {
                extractedLibFile.deleteOnExit();
                extractedLckFile.deleteOnExit();
            }
            extractedLibFile.setReadable(true);
            extractedLibFile.setWritable(true);
            extractedLibFile.setExecutable(true);
            try (final InputStream nativeIn = JLineNativeLoader.class.getResourceAsStream(nativeLibraryFilePath);
                 final InputStream extractedLibIn = new FileInputStream(extractedLibFile)) {
                final String eq = contentsEquals(nativeIn, extractedLibIn);
                if (eq != null) {
                    throw new RuntimeException(String.format("Failed to write a native library file at %s because %s", extractedLibFile, eq));
                }
            }
            if (loadNativeLibrary(extractedLibFile)) {
                JLineNativeLoader.nativeLibrarySourceUrl = JLineNativeLoader.class.getResource(nativeLibraryFilePath).toExternalForm();
                return true;
            }
        }
        catch (final IOException e) {
            log(Level.WARNING, "Unable to load JLine's native library", e);
        }
        return false;
    }
    
    private static String randomUUID() {
        return Long.toHexString(new Random().nextLong());
    }
    
    private static void copy(final InputStream in, final OutputStream out) throws IOException {
        final byte[] buf = new byte[8192];
        int n;
        while ((n = in.read(buf)) > 0) {
            out.write(buf, 0, n);
        }
    }
    
    private static boolean loadNativeLibrary(final File libPath) {
        if (libPath.exists()) {
            try {
                final String path = libPath.getAbsolutePath();
                System.load(path);
                JLineNativeLoader.nativeLibraryPath = path;
                return true;
            }
            catch (final UnsatisfiedLinkError e) {
                log(Level.WARNING, "Failed to load native library:" + libPath.getName() + ". osinfo: " + OSInfo.getNativeLibFolderPathForCurrentOS(), e);
                return false;
            }
        }
        return false;
    }
    
    private static void loadJLineNativeLibrary() throws Exception {
        if (JLineNativeLoader.loaded) {
            return;
        }
        final List<String> triedPaths = new ArrayList<String>();
        String jlineNativeLibraryPath = System.getProperty("library.jline.path");
        String jlineNativeLibraryName = System.getProperty("library.jline.name");
        if (jlineNativeLibraryName == null) {
            jlineNativeLibraryName = System.mapLibraryName("jlinenative");
            assert jlineNativeLibraryName != null;
            if (jlineNativeLibraryName.endsWith(".dylib")) {
                jlineNativeLibraryName = jlineNativeLibraryName.replace(".dylib", ".jnilib");
            }
        }
        if (jlineNativeLibraryPath != null) {
            final String withOs = jlineNativeLibraryPath + "/" + OSInfo.getNativeLibFolderPathForCurrentOS();
            if (loadNativeLibrary(new File(withOs, jlineNativeLibraryName))) {
                JLineNativeLoader.loaded = true;
                return;
            }
            triedPaths.add(withOs);
            if (loadNativeLibrary(new File(jlineNativeLibraryPath, jlineNativeLibraryName))) {
                JLineNativeLoader.loaded = true;
                return;
            }
            triedPaths.add(jlineNativeLibraryPath);
        }
        final String packagePath = JLineNativeLoader.class.getPackage().getName().replace('.', '/');
        jlineNativeLibraryPath = String.format("/%s/%s", packagePath, OSInfo.getNativeLibFolderPathForCurrentOS());
        final boolean hasNativeLib = hasResource(jlineNativeLibraryPath + "/" + jlineNativeLibraryName);
        if (hasNativeLib) {
            final String tempFolder = getTempDir().getAbsolutePath();
            if (extractAndLoadLibraryFile(jlineNativeLibraryPath, jlineNativeLibraryName, tempFolder)) {
                JLineNativeLoader.loaded = true;
                return;
            }
            triedPaths.add(jlineNativeLibraryPath);
        }
        final String javaLibraryPath = System.getProperty("java.library.path", "");
        for (final String ldPath : javaLibraryPath.split(File.pathSeparator)) {
            if (!ldPath.isEmpty()) {
                if (loadNativeLibrary(new File(ldPath, jlineNativeLibraryName))) {
                    JLineNativeLoader.loaded = true;
                    return;
                }
                triedPaths.add(ldPath);
            }
        }
        throw new Exception(String.format("No native library found for os.name=%s, os.arch=%s, paths=[%s]", OSInfo.getOSName(), OSInfo.getArchName(), join(triedPaths, File.pathSeparator)));
    }
    
    private static boolean hasResource(final String path) {
        return JLineNativeLoader.class.getResource(path) != null;
    }
    
    public static int getMajorVersion() {
        final String[] c = getVersion().split("\\.");
        return (c.length > 0) ? Integer.parseInt(c[0]) : 1;
    }
    
    public static int getMinorVersion() {
        final String[] c = getVersion().split("\\.");
        return (c.length > 1) ? Integer.parseInt(c[1]) : 0;
    }
    
    public static String getVersion() {
        final URL versionFile = JLineNativeLoader.class.getResource("/META-INF/maven/org.jline/jline-native/pom.properties");
        String version = "unknown";
        try {
            if (versionFile != null) {
                final Properties versionData = new Properties();
                versionData.load(versionFile.openStream());
                version = versionData.getProperty("version", version);
                version = version.trim().replaceAll("[^0-9.]", "");
            }
        }
        catch (final IOException e) {
            log(Level.WARNING, "Unable to load jline-native version", e);
        }
        return version;
    }
    
    private static String join(final List<String> list, final String separator) {
        final StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (final String item : list) {
            if (first) {
                first = false;
            }
            else {
                sb.append(separator);
            }
            sb.append(item);
        }
        return sb.toString();
    }
    
    private static void log(final Level level, final String message, final Throwable t) {
        if (JLineNativeLoader.logger.isLoggable(level)) {
            if (JLineNativeLoader.logger.isLoggable(Level.FINE)) {
                JLineNativeLoader.logger.log(level, message, t);
            }
            else {
                JLineNativeLoader.logger.log(level, message + " (caused by: " + t + ", enable debug logging for stacktrace)");
            }
        }
    }
    
    static {
        logger = Logger.getLogger("org.jline");
        JLineNativeLoader.loaded = false;
    }
}
