package io.mycat.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * used for mycat's catlet class loader ,catlet's class file is stored in Mycat_home/catlet dir
 *
 * @author wuzhih
 */
public class DynaClassLoader {

    private static final Logger LOGGER = LoggerFactory.getLogger(DynaClassLoader.class);
    /**
     * key- class full name
     */
    private static Map<String, DynaClass> loadedDynaClassMap = new ConcurrentHashMap<String, DynaClass>();
    private final String extClassHome;
    private final MyDynaClassLoader myClassLoader;
    private final long classCheckMilis;

    public DynaClassLoader(String extClassHome, int classCheckSeconds) {
        super();
        this.extClassHome = extClassHome;
        classCheckMilis = classCheckSeconds * 1000L;
        myClassLoader = new MyDynaClassLoader();
        LOGGER.info("dyna class load from " + extClassHome
                + ",and auto check for class file modified every "
                + classCheckSeconds + " seconds");
    }

    /**
     * 加载某个类的字节码
     */
    private static byte[] loadFile(String path) throws IOException {
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(path));
            byte[] readed = new byte[1024 * 4];
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int count = 0;
            while ((count = in.read(readed)) != -1) {
                out.write(readed, 0, count);
            }
            return out.toByteArray();
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    public Object getInstanceofClass(String className) throws Exception {
        DynaClass dynaClass = loadedDynaClassMap.get(className);
        boolean needReload = (dynaClass == null || (dynaClass
                .needReloadClass(classCheckMilis)
                && checkChanged(dynaClass)));
        Class<?> newClass = null;
        if (needReload) {
            newClass = myClassLoader.loadClass(className);
            dynaClass = loadedDynaClassMap.get(className);
        } else {
            newClass = dynaClass.realClass;
        }

        if (dynaClass != null) {
            Object val = dynaClass.classObj;
            if (val == null) {
                val = dynaClass.realClass.newInstance();
                dynaClass.classObj = val;

            }
            return val;
        } else {
            return newClass.newInstance();
        }
    }

    private boolean checkChanged(DynaClass dynaClass) throws IOException {
        boolean isChanged = false;
        File f = new File(dynaClass.filePath);
        if (f.exists()) {
            long newTime = f.lastModified();
            long oldTime = dynaClass.lastModified;
            if (oldTime != newTime) {
                // need reload
                dynaClass.lastModified = newTime;
                dynaClass.classObj = null;
                dynaClass.realClass = null;
                isChanged = true;
            }
        }
        return isChanged;
    }

    public void clearUnUsedClass() {
        long deadTime = System.currentTimeMillis() - 30 * 60 * 1000L;
        Iterator<Map.Entry<String, DynaClass>> itor = loadedDynaClassMap
                .entrySet().iterator();
        while (itor.hasNext()) {
            Map.Entry<String, DynaClass> entry = itor.next();
            DynaClass dyCls = entry.getValue();
            if (dyCls.lastModified < deadTime) {
                LOGGER.info("reset unused catlet " + entry.getKey());
                dyCls.clear();
                itor.remove();
            }
        }
    }

    class MyDynaClassLoader extends ClassLoader {

        public MyDynaClassLoader() {
        }

        public MyDynaClassLoader(ClassLoader parentLoader) {
            super(parentLoader);
        }

        /**
         * 加载某个类
         */
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (name.startsWith("java") || name.startsWith("sun")
                    || name.startsWith("io.mycat")) {
                return super.loadClass(name);
            }
            DynaClass dynaClass = loadedDynaClassMap.get(name);
            if (dynaClass != null) {
                if (dynaClass.realClass != null) {
                    return dynaClass.realClass;
                }
            } else {
                try {
                    dynaClass = searchFile(extClassHome, name);
                } catch (Exception e) {
                    LOGGER.error("SearchFileError", e);
                }
            }

            if (dynaClass == null) {
                return super.loadClass(name);
            } else {
                LOGGER.info("load class from file " + dynaClass.filePath);
                Class<?> cNew = null;
                if (dynaClass.isJar) {
                    cNew = dynaClass.realClass;
                } else {
                    byte[] content;
                    try {
                        content = loadFile(dynaClass.filePath);
                    } catch (IOException e) {
                        throw new ClassNotFoundException(e.toString());
                    }
                    cNew = super.defineClass(name, content, 0, content.length);
                    dynaClass.realClass = cNew;
                }
                dynaClass.classObj = null;
                loadedDynaClassMap.put(name, dynaClass);
                return cNew;
            }

        }

        private DynaClass searchFile(String classpath, String fileName) throws Exception {
            DynaClass dynCls = null;
            String path = fileName.replace('.', File.separatorChar) + ".class";
            System.out.println("class " + classpath + " file " + path);
            File f = new File(classpath, path);
            if (f.isFile()) {
                String theName = f.getPath();
                System.out.println("found " + theName);

                dynCls = new DynaClass(f.getPath());
                dynCls.lastModified = f.lastModified();
                return dynCls;
            } else {
                path = fileName.replace('.', File.separatorChar) + ".jar";
                //classpath="D:\\code\\mycat\\Mycat-Server\\catlet\\";
                System.out.println("jar " + classpath + " file " + path);
                f = new File(classpath, path);
                if (f.isFile()) {
                    try {
                        dynCls = new DynaClass(f.getPath());
                        dynCls.lastModified = f.lastModified();
                        dynCls.realClass = JarLoader.loadJar(classpath + "/" + path, fileName);
                        dynCls.isJar = true;
                        return dynCls;
                    } catch (Exception err) {
                        return null;
                    }

                }
                return null;
            }

        }

    }
}

class DynaClass {

    public final String filePath;
    public volatile long lastModified;
    public Class<?> realClass;
    public Object classObj;
    public boolean isJar = false;

    public DynaClass(String filePath) {
        super();
        this.filePath = filePath;
    }

    public boolean needReloadClass(long classCheckMilis) {
        return lastModified + classCheckMilis < System.currentTimeMillis();
    }

    public void clear() {
        this.realClass = null;
        this.classObj = null;

    }
}
