package net.glowstone.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import java.util.logging.Level;
import javax.net.ssl.HttpsURLConnection;
import net.glowstone.GlowServer;

/**
 * Simple library manager which downloads external dependencies.
 */
public final class LibraryManager {

    /**
     * The Maven repository to download from.
     */
    final String repository;

    /**
     * The directory to store downloads in.
     */
    final File directory;

    private final ExecutorService downloaderService = Executors.newCachedThreadPool();

    public LibraryManager() {
        // todo: allow configuration of repository, libraries, and directory
        repository = "https://repo.glowstone.net/service/local/repositories/central/content/";
        directory = new File("lib");
    }

    public void run() {
        if (!directory.isDirectory() && !directory.mkdirs()) {
            GlowServer.logger.log(Level.SEVERE, "Could not create libraries directory: " + directory);
        }

        downloaderService.execute(new LibraryDownloader("org.xerial", "sqlite-jdbc", "3.21.0", ""));
        downloaderService.execute(new LibraryDownloader("mysql", "mysql-connector-java", "5.1.44", ""));
        downloaderService.execute(new LibraryDownloader("org.apache.logging.log4j", "log4j-api", "2.8.1", ""));
        downloaderService.execute(new LibraryDownloader("org.apache.logging.log4j", "log4j-core", "2.8.1", ""));
        downloaderService.shutdown();
        try {
            if (!downloaderService.awaitTermination(1, TimeUnit.MINUTES)) {
                downloaderService.shutdownNow();
            }
        } catch (InterruptedException e) {
            GlowServer.logger.log(Level.SEVERE, "Library Manager thread interrupted: ", e);
        }
    }

    private class LibraryDownloader implements Runnable {

        private final String group;
        private final String library;
        private final String version;
        private String checksum;

        LibraryDownloader(String group, String library, String version, String checksum) {
            this.group = group;
            this.library = library;
            this.version = version;
            this.checksum = checksum;
        }

        @Override
        public void run() {
            // check if we already have it
            File file = new File(directory, getLibrary());
            if (!checksum(file, checksum)) {
                // download it
                GlowServer.logger.info("Downloading " + library + ' ' + version + "...");
                try {
                    URL downloadUrl = new URL(repository + group.replace('.', '/') + '/' + library + '/' + version + '/' + library + '-' + version + ".jar");
                    HttpsURLConnection connection = (HttpsURLConnection) downloadUrl.openConnection();
                    connection.setRequestProperty("User-Agent", "Mozilla/5.0");

                    try (ReadableByteChannel input = Channels.newChannel(connection.getInputStream()); FileOutputStream output = new FileOutputStream(file)) {
                        output.getChannel().transferFrom(input, 0, Long.MAX_VALUE);
                        GlowServer.logger.info("Downloaded " + library + ' ' + version + '.');
                    }
                } catch (IOException e) {
                    GlowServer.logger.log(Level.WARNING, "Failed to download: " + library + ' ' + version, e);
                    file.delete();
                    return;
                }
            }

            // hack it onto the classpath
            try {
                String[] javaVersion = System.getProperty("java.version").split("-")[0].split("\\.");
                if (Integer.parseInt(javaVersion[0]) >= 9) {
                    ClassPathAgent.addJarFile(new JarFile(file));
                } else {
                    Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                    method.setAccessible(true);
                    method.invoke(ClassLoader.getSystemClassLoader(), file.toURI().toURL());
                }
            } catch (ReflectiveOperationException | IOException e) {
                GlowServer.logger.log(Level.WARNING, "Failed to add to classpath: " + library + " " + version, e);
            }
        }

        String getLibrary() {
            return library + '-' + version + ".jar";
        }

        boolean checksum(File file, String checksum) {
            // TODO: actually check checksum
            return file.exists();
        }
    }
}

@@ -903,24 +903,69 @@
 eadPool();%0A%0A
+    /**%0A     * Creates the instance.%0A     */%0A
     public L
@@ -1194,24 +1194,72 @@
 b%22);%0A    %7D%0A%0A
+    /**%0A     * Downloads the libraries.%0A     */%0A
     public v
@@ -1354,32 +1354,53 @@
 lowServer.logger
+%0A                    
 .log(Level.SEVER
@@ -1520,24 +1520,41 @@
 yDownloader(
+%0A                
 %22org.xerial%22
@@ -1642,16 +1642,33 @@
 nloader(
+%0A                
 %22mysql%22,
@@ -1756,32 +1756,49 @@
 braryDownloader(
+%0A                
 %22org.apache.logg
@@ -1893,16 +1893,33 @@
 nloader(
+%0A                
 %22org.apa
@@ -3141,16 +3141,45 @@
 new URL(
+%0A                            
 reposito
@@ -3234,24 +3234,60 @@
 /' + version
+%0A                                   
  + '/' + lib
@@ -3402,16 +3402,45 @@
 nloadUrl
+%0A                            
 .openCon
@@ -3592,16 +3592,45 @@
 Channels
+%0A                            
 .newChan
@@ -3662,16 +3662,44 @@
 ream());
+%0A                           
  FileOut
@@ -4038,32 +4038,60 @@
 g(Level.WARNING,
+%0A                           
  %22Failed to down
@@ -4372,16 +4372,41 @@
 (%22-%22)%5B0%5D
+%0A                        
 .split(%22
@@ -4941,16 +4941,40 @@
 WARNING,
+%0A                       
  %22Failed

Fix CheckStyle issues in util/LibraryManager.java (#714)