package com.pictc.utils;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class ScanClasses {
	
	private static Log log=LogFactory.getLog(ScanClasses.class);
	
	 /** 过滤器 **/
	private static SanFilter filter =new SanDefaultFilter();
 
	public static final String CLASS_SUFFIX = ".class";
	
	private static final String FILE_PROCOTOL="file";
	
	private static final String JAR_PROCOTOL="jar";
	
	private ScanClasses() {
		super();
	}
	 
	public static Set<Class<?>> scan(Set<String> packages) {
		return scan(packages, filter);
	}
	
	public static Set<Class<?>> scan(Set<String> packages,SanFilter filter) {
		return scan(packages,null,filter);
	}
	 
	public static Set<Class<?>> scan(Set<String> packages,Set<Class<? extends Annotation>> annotations) {
		return scan(packages,annotations,filter);
	}
	
	public static Set<Class<?>> scan(Set<String> packages,Set<Class<? extends Annotation>> annotations,SanFilter filter) {
		Set<Class<?>> classes=CollectionUtils.newHashSet();
		Iterator<String> iterator=packages.iterator();
		while (iterator.hasNext()) {
			scan(classes,iterator.next(),annotations,filter);
		}
		return classes;
	}
	 
	public static Set<Class<?>> scan(String[] packages) {
		return scan(packages, filter);
	}
	
	public static Set<Class<?>> scan(String[] packages,SanFilter filter) {
		return scan(packages,null,filter);
	}	
	
	public static Set<Class<?>> scan(String[] packages,Set<Class<? extends Annotation>> annotations) {
		return scan(packages,annotations,filter);
	}		
	
	public static Set<Class<?>> scan(String[] packages,Set<Class<? extends Annotation>> annotations,SanFilter filter) {
		Set<Class<?>> classes=CollectionUtils.newHashSet();
		for (int i = 0; i < packages.length; i++) {
			scan(classes,packages[i],annotations,filter);
		}
		return classes;
		
	}		 

	 

	private static void scan(Set<Class<?>> classes,String scan_package,Set<Class<? extends Annotation>> annotations,SanFilter filter){
		  String current_package=getCurrentPackage(scan_package);	 
		  // 获取包的名字 并进行替换
		  String packageDirName = current_package.replace('.', '/');
		  // 定义一个枚举的集合 并进行循环来处理这个目录下的things
		  Enumeration<URL> dirs = null;
		  	try {
				dirs = ClassUtils.getDefaultClassLoader().getResources(packageDirName);
				while(dirs.hasMoreElements()) {
					URL url = dirs.nextElement();
					if(log.isDebugEnabled()) {
						log.debug("url协议--->"+url.getProtocol());
					}
					if(FILE_PROCOTOL.equals(url.getProtocol())) {
						 File file = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
						 if(!file.exists() || !file.isDirectory()) {
							  continue;
						 }
						 if(file.isDirectory()) {
							  File[] files=file.listFiles();
							  for (int i = 0; i < files.length; i++) {
								  scanFile(scan_package,current_package,files[i],annotations,classes,filter);
							  }
						 }else {
							 scanFile(scan_package, current_package, file,annotations,classes,filter);
						 }
					}else if(JAR_PROCOTOL.equals(url.getProtocol())) {
						 scanJar(scan_package, current_package, ((JarURLConnection)url.openConnection()).getJarFile(),annotations,classes,filter);
					}
				}
			} catch (IOException e) {
				log.error("扫描claess异常",e);
			} 
	}
	
	
	private static String getCurrentPackage(String scan_package) {
		if(scan_package.contains(".*")) {
			return scan_package.substring(0,scan_package.indexOf(".*"));
		}
		return scan_package;
	}

	/**
	 * 获取下次扫描的包路径
	 * */
	private static String getNextPackage(String current_package,String file_name) { 
		String nextPackage=null;
		 if(StringUtils.isEmpty(current_package)) {
			 nextPackage= getClassName(file_name);
		 }else {
			 nextPackage=current_package+"."+getClassName(file_name);
		 }
		return nextPackage;
	}
	 
	private static void scanFile(String scan_package,String current_package,File file,Set<Class<? extends Annotation>> annotations,Set<Class<?>> classes,SanFilter filter) {
		if(filter==null || (filter!=null && filter.accept(current_package,scan_package))){
			String file_name=file.getName();
			  if(file.isDirectory()) {
				  File[] files=file.listFiles();
				  for (int i = 0; i < files.length; i++) {
					  scanFile(scan_package,getNextPackage(current_package,file_name),files[i],annotations,classes,filter);
				  }
			  }else {
				 if(filter==null || (filter!=null && filter.accept(file_name))) {
					 parseClass(scan_package, current_package,getNextPackage(current_package,file_name),annotations,classes,filter);
				 }
			}
		}
	 }
	
	
	 private static void  parseClass(String scan_package,String current_package,String className,Set<Class<? extends Annotation>> annotations,Set<Class<?>> classes,SanFilter filter) {
		 Class<?> cls= ClassUtils.loadClass(className);
		 if(annotations!=null && !annotations.isEmpty()) {
			 for (Class<? extends Annotation> annotation : annotations) {
				 if(cls.getAnnotation(annotation)!=null) {
					 classes.add(cls);
					 return;
				 }
			 }
		 }else if(filter!=null && filter.accept(cls)){
			 classes.add(cls);
		 }
	 }	
	
	 
	 private static void scanJar(String scan_package,String current_package,JarFile jar,Set<Class<? extends Annotation>> annotations,Set<Class<?>> classes,SanFilter filter) {
		 	Enumeration<JarEntry> entrys= jar.entries();
			while(entrys.hasMoreElements()) {
				JarEntry entry=entrys.nextElement();
				if(!entry.isDirectory()) {
					String cpackage = getJarPackage(entry);
					if(filter==null || filter.accept(cpackage,scan_package)) {
						String name = entry.getName();
						if(filter==null || filter.accept(name)) {
							parseClass(scan_package,cpackage,getJarClassName(entry),annotations,classes,filter);
						}
					}
				}
			}
	 }
	 
	 private static String getJarPackage(JarEntry entry) {
		String name = entry.getName(); 
		if(name.lastIndexOf("/")!=-1) {
			name = name.substring(0, name.lastIndexOf("/"));
		}
		return name.replace("/", ".");
	 }
	 
	 private static String getJarClassName(JarEntry entry) {
		return entry.getName().substring(0, entry.getName().length()-6).replaceAll("/", "."); 
	 }
	 
	 private static String getClassName(String name) {
		return name.split("\\.")[0]; 
	 } 
	 
}
