/**
 * $Id$
 * Copyright(C) 2015-2020 kowlone - internet center, All Rights Reserved.
 */
package com.liquidnet.commons.lang.util;


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

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author <a href="kowlone2006@163.com">kowlone</a>
 * @version 1.0 2015年09月28日 16:52:00
 */
public class PackageUtil {

	private static final String PROTOCOL_FILE = "file";
	private static final String PROTOCOL_JAR = "jar";

	private static final String PREFIX_FILE = "file:";

	private static final String JAR_URL_SEPERATOR = "!/";
	private static final String CLASS_FILE = ".class";

	private final static Logger logger = LoggerFactory.getLogger(PackageUtil.class);







	@SuppressWarnings("unchecked")
	public static <T> List<T> getInstances(String packageName,Predicate<Class<?>> filter,Comparator<Class<?>> compositor){
		List<T> result = new ArrayList<>();

		Collection<Class<?>> classList = getClasses(packageName, filter);

		if (compositor != null) {
			classList.stream().sorted(compositor);
		}

		for(Class<?> clazz : classList){
			try {
				//跳过抽象类
				if(Modifier.isAbstract(clazz.getModifiers())){
					continue;
				}
				result.add((T)clazz.newInstance());
			} catch (Exception e) {
				throw new RuntimeException("create instance error :" + clazz,e);
			}
		}
		return result;
	}

	public static Collection<Class<?>> getClasses(String packageName,Predicate<Class<?>> filter) {
		List<Class<?>> list = new ArrayList<Class<?>>();
		Enumeration<URL> en = null;
		try {
			//			en = ClassLoader.getSystemClassLoader().getResources(nameToPath(packageName));
			en = Thread.currentThread().getContextClassLoader().getResources(nameToPath(packageName));
			//			en = ClassDiscover.class.getClassLoader().getResources("/" + nameToPath(packageName));
			int count = 0;
			while (en.hasMoreElements()) {
				count++;
				URL url = en.nextElement();
				if (PROTOCOL_FILE.equals(url.getProtocol())) {
					File root = new File(url.getFile());
					findInDirectory(list, root, root, packageName,filter);
				} else if (PROTOCOL_JAR.equals(url.getProtocol())) {
                    findInJar(list, getJarFile(url), packageName, filter);
                }
			}
			System.out.println("count ======== " + count);
		} catch (IOException e) {
			logger.error("无效的包名:" + packageName, e.getCause());
		}
		return list;
	}

	public static File getJarFile(URL url) {
		String file = url.getFile();
		if (file.startsWith(PREFIX_FILE)) {
            file = file.substring(PREFIX_FILE.length());
        }
		int end = file.indexOf(JAR_URL_SEPERATOR);
		if (end != (-1)) {
            file = file.substring(0, end);
        }
		return new File(file);
	}

	private static void findInDirectory(List<Class<?>> results, File rootDir, File dir, String packageName,Predicate<Class<?>> filter) {
		File[] files = dir.listFiles();
		String rootPath = rootDir.getPath();
		for (File file : files){
			if (file.isFile()) {
				String classFileName = file.getPath();
				if (classFileName.endsWith(CLASS_FILE)) {
					String className = classFileName.substring(rootPath.length() - packageName.length(),
							classFileName.length() - CLASS_FILE.length());
					try {
						Class<?> clz = Class.forName(pathToName(className));
						if(filter == null || filter.test(clz)){
							results.add(clz);
						}
					} catch (ClassNotFoundException e) {
						logger.error("无法获取类:" + className, e.getCause()); // 该错误应该不会出现
					}
				}
			} else if (file.isDirectory()) {
                findInDirectory(results, rootDir, file, packageName, filter);
            }
		}
	}

	private static void findInJar(List<Class<?>> results, File file, String packageName,Predicate<Class<?>> filter) {
		JarFile jarFile = null;
		String packagePath = nameToPath(packageName) + "/";
		try {
			jarFile = new JarFile(file);
			Enumeration<JarEntry> en = jarFile.entries();
			while (en.hasMoreElements()) {
				JarEntry je = en.nextElement();
				String name = je.getName();
				if (name.startsWith(packagePath) && name.endsWith(CLASS_FILE)) {
					String className = name.substring(0, name.length() - CLASS_FILE.length());
					try {
						Class<?> clz = Class.forName(pathToName(className));
						if(filter == null || filter.test(clz)){
							results.add(clz);
						}
					} catch (ClassNotFoundException e) {
						logger.error("无法获取类:" + className, e.getCause()); // 该错误应该不会出现
					}
				}
			}
		} catch (IOException e) {
			logger.error("无法读取 Jar 文件:" + file.getName(), e);
		} finally {
			if (jarFile != null) {
                try {
                    jarFile.close();
                } catch (IOException e) {
                }
            }
		}
	}

	/**
	 * 将类名的字符串形式转换为路径表现形式
	 *
	 * @param className
	 *            类名
	 * @return 路径的字符串
	 */
	private static String nameToPath(String className) {
		return className.replace('.', '/');
	}

	/**
	 * 将路径转换为类的字符串表现形式
	 *
	 * @param path
	 * @return 类名的字符串形式
	 */
	private static String pathToName(String path) {
		return path.replace('/', '.').replace('\\', '.');
	}


	public static Class<?> getParameterizedType(Type type) {
		Class<?> ret = null;
		if (type instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) type;
			for (Type typeArg : parameterizedType.getActualTypeArguments()) {
				if (Class.class.isAssignableFrom(typeArg.getClass())) {
					ret = (Class<?>) typeArg;
				} else {
					if (typeArg instanceof ParameterizedType) {
                        return getParameterizedType(typeArg);
                    } else {
                        ret = (Class<?>) ((ParameterizedType) typeArg).getRawType();
                    }
				}

			}
		} else {
            ret = (Class<?>) type;
        }
		return ret;
	}


}
