package net.northking.cctp.element.core.support;

import net.northking.cctp.element.core.ResourceLoader;
import net.northking.cctp.element.util.AntPathMatcher;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * PathMatchingResourceResolver
 *
 * @author fengxiang.zhang
 * @since 1.0
 */
public class PathMatchingResourceResolver implements ResourceLoader {

    private static Logger logger = LoggerFactory.getLogger(PathMatchingResourceResolver.class);
    private final AntPathMatcher pathMatcher = new AntPathMatcher();
    private ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    private String packageScanPattern = "**";

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public ClassLoader getClassLoader() {
        return classLoader;
    }

    @Override
    public Collection<String> filterResources(String basePackage) {
        Set<String> resources = null;
        try {
            resources = resolve(basePackage);
        } catch (Exception e) {
            logger.error("Get location {} resources error : {}", basePackage, e);
        }
        return resources;
    }

    private Set<String> resolve(String locationPattern) throws Exception {
        locationPattern = StringUtils.replace(locationPattern, ".", "/");
        if (!locationPattern.endsWith("**")) {
            locationPattern += "/**";
        }
        this.packageScanPattern = locationPattern;
        Set<String> resources = new HashSet<>();
        Enumeration<URL> root = getRootResources(locationPattern);
        while (root.hasMoreElements()) {
            findResources(resources, root.nextElement());
        }
        return resources;
    }

    private Enumeration<URL> getRootResources(String packageScanPattern) {
        String root = pathMatcher.getRoot(packageScanPattern);
        try {
            return classLoader.getResources(root);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void findResources(Set<String> resources, URL url) throws Exception {
        if (url.getProtocol().startsWith("jar")) {
            JarURLConnection connection =
                    (JarURLConnection) url.openConnection();
            File jar = new File(URLDecoder.decode(connection.getJarFileURL().getFile(), "UTF-8"));
            JarInputStream is = null;
            try{
                is = new JarInputStream(new FileInputStream(jar));
                JarEntry entry;
                while ((entry = is.getNextJarEntry()) != null) {
                    if (entry.getName().endsWith(".class")) {
                        if (!isExcluded(entry.getName())) {
                            resources.add(StringUtils.replace(entry.getName(), "/", "."));
                        }
                    }
                }
            }catch (Exception e){
                logger.error("",e);
            }finally {
                if (is != null) {
                    is.close();
                }
            }
        } else if (url.getProtocol().startsWith("file")) {
            File urlFile = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
            if (urlFile.isDirectory()) {
                for (String className : getChildrenFrom(pathMatcher.getRoot(packageScanPattern), urlFile)) {
                    if (!isExcluded(className)) {
                        resources.add(StringUtils.replace(className, "/", "."));
                    }
                }
            }
        } else {
            throw new Exception("Unsupported URL type " + url);
        }
    }

    private Set<String> getChildrenFrom(String root, File file) throws Exception {
        Set<String> clazz = new HashSet<>();
        for (File f : file.listFiles()) {
            String name = f.getName();
            if (f.isFile()) {
                if (name.endsWith(".class"))
                    clazz.add(root + f.getName());
            } else
                clazz.addAll(getChildrenFrom(root + f.getName() + "/", f));
        }
        return clazz;
    }

    private boolean isExcluded(String className) throws Exception {
        return className.startsWith("java/")
               || className.startsWith("javax/")
               || (className.indexOf("$") != -1)
               || !pathMatcher.match(packageScanPattern, className);
    }

}