package com.axis.innovators.box.plugins;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 自定义类加载器，装载CorePlugins
 * @author tzdwindows 7
 */
public class BoxClassLoader extends URLClassLoader {
    private static final List<IClassTransformer> CLASS_TRANSFORMS = new CopyOnWriteArrayList<>();
    private static final List<String> CLASS_BLACKLIST = new ArrayList<>();
    private static final List<String> CLASS_LOADING_LIST = Collections.synchronizedList(new ArrayList<>());
    private static final List<Class<?>> CLASS_LOADING_LIST_OBJECT = new CopyOnWriteArrayList<>();

    static {
        Collections.addAll(CLASS_BLACKLIST,
                "java.", "javax.", "sun.", "com.sun.", "jdk.",
                "org.xml.", "org.w3c.", "org.apache.",
                "javax.management.", "javax.swing."
                , "javafx.","org.jnativehook.","com.dustinredmond."
        );
    }

    public BoxClassLoader(ClassLoader parent) {
        super(new URL[]{}, parent);
        try {
            PluginLoader.loadCorePlugin();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void addURL(URL url) {
        super.addURL(url);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> loadedClass = findLoadedClass(name);
            if (loadedClass != null) {
                return loadedClass;
            }

            if (isBlacklisted(name)) {
                return getParent().loadClass(name);
            }

            try {
                Class<?> c = findClass(name);
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            } catch (ClassNotFoundException e) {
                return super.loadClass(name, resolve);
            }
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            if (CLASS_LOADING_LIST.contains(name)) {
                throw new ClassCircularityError(name + " circular loading detected");
            }

            CLASS_LOADING_LIST.add(name);
            try {
                byte[] clazzByte = getClassBytes(name);
                for (IClassTransformer transformer : CLASS_TRANSFORMS) {
                    byte[] transformed = transformer.transform(name, transformer.getClass().getName(), clazzByte);
                    if (transformed != null) clazzByte = transformed;
                }

                Class<?> clazz = defineClass(name, clazzByte, 0, clazzByte.length);
                CLASS_LOADING_LIST_OBJECT.add(clazz);
                return clazz;
            } catch (IOException e) {
                throw new ClassNotFoundException("Class byte loading failed", e);
            } finally {
                CLASS_LOADING_LIST.remove(name);
            }
        }
    }

    private byte[] getClassBytes(String className) throws IOException, ClassNotFoundException {
        String path = className.replace('.', '/') + ".class";
        try (InputStream is = getResourceAsStream(path)) {
            if (is == null) throw new ClassNotFoundException(className);
            return is.readAllBytes();
        }
    }

    private boolean isBlacklisted(String className) {
        return CLASS_BLACKLIST.stream().anyMatch(className::startsWith);
    }

    public static void addClassTransformer(IClassTransformer transformer) {
        CLASS_TRANSFORMS.add(transformer);
    }

    public static List<IClassTransformer> getClassTransforms() {
        return CLASS_TRANSFORMS;
    }

    public static List<Class<?>> getClassList() {
        return CLASS_LOADING_LIST_OBJECT;
    }
}