
import javax.swing.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.util.HashSet;
import java.util.Set;

public class Launcher extends URLClassLoader {
    private static String INDEX_ROOT = "http://www.pmilne.net/Launcher/Index/";
    private static String CACHE = System.getProperty("user.home") + "/.launcher/Cache/";
    private static String START_TOKEN = "<a href=\"";
    private static String END_TOKEN = "\"";
    private Set tried = new HashSet();
    private static boolean DEBUG1 = false;

    public Launcher(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public Launcher(ClassLoader parent) {
        this(new URL[0], parent);
    }

    private static String readFile(InputStream is) {
        try {
            StringBuffer b = new StringBuffer();
            InputStream r = new BufferedInputStream(is);
            for (int c = is.read(); c != -1; c = is.read()) {
                b.append((char) c);
            }
            r.close();
            return b.toString();
        } catch (IOException e) {
            throw new RuntimeException("Oof");

        }
    }

    private URL remoteLocationOf(String name) {
        // Dump the class name - makes things faster.
        // jars should not contain classes that are not in packages.
        // (think about this)
        name = name.substring(0, name.lastIndexOf('.'));

        String path = name.replace('.', '/');
        while (true) {
            if (DEBUG1) System.out.println("Marking: " + path);
            tried.add(path);
//            System.out.println("tried = " + tried);
            String indexLocation = INDEX_ROOT + path + ".html";
            try {
                URL url = new URL(indexLocation);
                URLConnection urlConnection = url.openConnection();
                InputStream is = urlConnection.getInputStream();
                String result = readFile(is);
                if (DEBUG1) System.out.println("result = " + result);
                int start = result.indexOf(START_TOKEN) + START_TOKEN.length();
                int end = result.indexOf(END_TOKEN, start);
                String url2 = result.substring(start, end);
                return new URL(url2);

            } catch (MalformedURLException e) {
                throw new RuntimeException("oof");
            } catch (IOException e) {
//                System.out.println("Couldn't find " + e.getMessage());
//                return null;
            }
            int lastSlash = path.lastIndexOf('/');
            if (lastSlash == -1) {
                return null;
            }
            path = path.substring(0, lastSlash);
        }
    }

    private static String substringAfterLast(String path, char c) {
        return path.substring(path.lastIndexOf(c) + 1);
    }

    // Also used by Indexer.
    public static File localLocationOf(URL url) {
        String fileName = substringAfterLast(url.getFile(), '/');
        return new File(CACHE + fileName);
    }

    // Also used by Indexer.
    public static File downloadAndInstallClass(URL remoteLocation, File localFile) {
        if (localFile.exists()) {
//            System.out.println("Found: " + localFile.getName());
            return localFile;
        }
        localFile.getParentFile().mkdirs();
        try {
            System.out.println("Dynamically downloading: " + remoteLocation);
            InputStream is = remoteLocation.openStream();
//            BufferedReader d2 = new BufferedReader(new InputStreamReader(is));
            try {
                // localFile.delete();
                localFile.createNewFile();
                OutputStream os = new BufferedOutputStream(new FileOutputStream(localFile));
                for (int c = is.read(); c != -1; c = is.read()) {
                    os.write(c);
                }
                os.close();
                return localFile;
            } catch (IOException e) {
                System.out.println(e);
                System.out.println("Error copying file: " + remoteLocation);
            }
        } catch (IOException e3) {
            System.out.println(e3);
            System.out.println("Failed to open URL: " + remoteLocation);
        }
        return null;
    }

    private static URL toURL(File f) {
        try {
            return f.toURL();
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }

//    public Class loadClass(String className) throws ClassNotFoundException {
//        if (DEBUG1) System.out.println("Attempting to load: " + className);
//        return super.loadClass(className);
//    }

    protected Class findClass(String className)
            throws ClassNotFoundException {
//            System.out.println("TimeStampClassLoader: findClass name = " + name);
//        if (DEBUG1) System.out.println("Attempting to load: " + className);
        try {
            return super.findClass(className);
        } catch (ClassNotFoundException e) {
            // Don't look for arrays. (Groovy looks for array BeanInfo).
            if (className.startsWith("[")) {
                throw e;
            }
            // Again Groovy seems to chance the classloader for unkown tokens.
            if (className.indexOf('.') == -1) {
                throw e;
            }
            String path = className.replace('.', '/');
            while (true) {
//                System.out.println("Testing " + path);
                if (tried.contains(path)) {
//                    System.out.println("Giving up on " + path);
                    throw e;
                }
                int lastSlash = path.lastIndexOf('/');
                if (lastSlash == -1) {
                    break;
                }
                path = path.substring(0, lastSlash);

            }
            if (DEBUG1) System.out.println("Launcher - attempting to locate: " + className);
            URL url = remoteLocationOf(className);
            if (url == null) {
                throw e;
            }
            File f = localLocationOf(url);
            if (!f.exists()) {
                downloadAndInstallClass(url, f);
            }
            addURL(toURL(f));
            return super.findClass(className);
        }
    }

    /**
     * First argument should be the name of
     * the class to load and run. Reminder of
     * args are passed to the main method of the
     * loaded class.
     * <p/>
     * e.g.
     * <p/>
     * java Launcher groovy.ui.Console
     * <p/>
     * Then, in Groovy console:
     * <p/>
     * f = new javax.swing.JFrame()
     * bob.Guide.edit(f)
     * de.progra.charting.test.GraphFrame.main()
     * org.bitstorm.gameoflife.StandaloneGameOfLife.main()
     * nahay.games.MegaTTT.main()
     * 
     */

    public static void main(String[] args) throws Exception {
        Launcher cl = new Launcher(Launcher.class.getClassLoader());
        Thread.currentThread().setContextClassLoader(cl);
        String className;
        String[] args2;
        if (args.length == 0) {
            className = JOptionPane.showInputDialog("Class to load: ");
            args2 = args;
        } else {
            className = args[0];
            args2 = new String[args.length - 1];
            System.arraycopy(args, 1, args2, 0, args2.length);
        }
        Class c = cl.loadClass(className);
        c.getMethod("main", new Class[]{String[].class}).invoke(null, new Object[]{args2});
    }
}
