/**
 * Licensed to LGPL v3.
 */
package com.endofhope.neurasthenia.webcontainer.servlet;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.Servlet;
import javax.servlet.ServletException;


import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import com.endofhope.neurasthenia.LifeCycle;
import com.endofhope.neurasthenia.webcontainer.WebContextManager;
/**
 * 
 * @author endofhope
 *
 */
public class ServletManager implements LifeCycle{
	
	private static Logger logger = Logger.getLogger("servlet");

	private ServletContextImpl servletContextImpl;
	private URLClassLoader urlCL;
	private Map<String, ServletInfo> servletInfoMap;
	private Map<String, String> urlNameMap;
	protected ServletManager(ServletContextImpl servletContextImpl){
		this.servletContextImpl = servletContextImpl;
		servletCache = new HashMap<String, Servlet>();
		init();
	}
	class ServletInfo{
		private String servletName;
		private String servletClass;
		private Map<String, String> initParamMap;
		private ServletInfo(String servletName, String servletClass){
			this.servletName = servletName;
			this.servletClass = servletClass;
			initParamMap = new HashMap<String, String>();
		}
		protected String getServletName(){
			return servletName;
		}
		private String getServletClass(){
			return servletClass;
		}
		private Map<String, String> getInitParamMap(){
			return initParamMap;
		}
	}
	
	protected String getServletName(String servletPath){
		return urlNameMap.get(servletPath);
	}
	public String getServletClass(String servletName){
		return servletInfoMap.get(servletName).getServletClass();
	}

	private static final String RESOURCE_SERVLET = "__RESOURCE_SERVLET";
	private static final String JSP_SERVLET = "__JSP_SERVLET";

	public Servlet getServlet(String servletPath){
		String servletName = null;
		if(servletPath != null 
				&&(servletPath.endsWith(".jsp")
						|| servletPath.endsWith(".JSP")
						|| servletPath.endsWith(".jspx")
						|| servletPath.endsWith(".JSPX"))){
			servletName = ServletManager.JSP_SERVLET;
		}else{
			servletName = getServletName(servletPath);
		}
		if(servletName == null){
			servletName = ServletManager.RESOURCE_SERVLET;
		}
		return servletCache.get(servletName);
	}
	
	protected Map<String, Servlet> getServletMap(){
		return servletCache;
	}
	
	private Map<String, Servlet> servletCache;
	
	@SuppressWarnings("unchecked")
	private void init(){
		
		List<URL> urlList = new ArrayList<URL>();
		String webinfPath = servletContextImpl.getRealContextPath() + File.separator + "WEB-INF" + File.separator; 
		File classes = new File(webinfPath + "classes");
		File lib = new File(webinfPath + "lib");

		WebContextManager webContextManager = servletContextImpl.getWebContextManager();
		String commonClasspath = webContextManager.getCommonClassPath();
		if(commonClasspath != null && commonClasspath.length() > 1){
			StringTokenizer st = new StringTokenizer(commonClasspath.trim());
			while(st.hasMoreTokens()){
				String oneCommonClasspath = st.nextToken();
				File commonClasspathFile = new File(oneCommonClasspath);
				if(commonClasspathFile.exists()){
					try {
						urlList.add(commonClasspathFile.toURI().toURL());
					} catch (MalformedURLException e) {
						logger.log(Level.WARNING, "common classpath invalid", e);
					}
				}
			}
		}
//		StringBuilder sb = new StringBuilder();
		if(classes.exists()){
//			sb.append(classes.toString());
			try {
				urlList.add(classes.toURI().toURL());
			} catch (MalformedURLException e) {
				logger.log(Level.WARNING, "WEB-INF/classes invalid", e);
			}
		}
		if(lib.exists()){
			try {
				FileFilter ff = new FileFilter(){
					@Override
					public boolean accept(File pathname) {
						boolean result = false;
						if(pathname.getName().endsWith(".jar")){
							result = true;
						}
						return result;
					}
				};
				File[] jarList = lib.listFiles(ff);
				for(File file : jarList){
					urlList.add(file.toURI().toURL());
//					sb.append(":").append(file.getAbsolutePath());
				}
			} catch (MalformedURLException e) {
				logger.log(Level.WARNING, "WEB-INF/lib invalid", e);
			}
		}
		URL[] urls = new URL[urlList.size()];
		for(int i=0; i<urls.length; i++){
			urls[i] = urlList.get(i);
		}
		urlCL = new URLClassLoader(urls);
		
		servletInfoMap = new HashMap<String, ServletInfo>();
		servletInfoMap.put(
				ServletManager.RESOURCE_SERVLET, 
				new ServletInfo(
						ServletManager.RESOURCE_SERVLET, 
						"com.endofhope.neurasthenia.webcontainer.servlet.ResourceServlet"));
		ServletInfo jspServletInfo = new ServletInfo(
				ServletManager.JSP_SERVLET,
				"org.apache.jasper.servlet.JspServlet");
		servletInfoMap.put(ServletManager.JSP_SERVLET, jspServletInfo);	
		
		Thread.currentThread().setContextClassLoader(urlCL);
//		servletContextImpl.setAttribute("org.apache.catalina.jsp_classpath", sb.toString());
		
		SAXBuilder saxb = new SAXBuilder();
		try {
			Document doc = saxb.build(new File(webinfPath + "web.xml"));
			Element webAppElement = doc.getRootElement();
			
			List<Element> contextParamElementList = webAppElement.getChildren("context-param");
			for(Element contextParamElement : contextParamElementList){
				servletContextImpl.getInitParamMap().put(
						contextParamElement.getChildTextTrim("param-name"), 
						contextParamElement.getChildTextTrim("param-value"));
			}
			
			List<Element> servletElementList = webAppElement.getChildren("servlet");
			for(Element servletElement : servletElementList){
				Element servletNameElement = servletElement.getChild("servlet-name");
				Element servletClassElement = servletElement.getChild("servlet-class");
				String servletName = servletNameElement.getTextTrim();
				String servletClass = servletClassElement.getTextTrim();
				ServletInfo servletInfo = new ServletInfo(servletName, servletClass); 
				List<Element> initParamElementList = servletElement.getChildren("init-param");
				for(Element initParamElement : initParamElementList){
					servletInfo.getInitParamMap().put(
							initParamElement.getChildTextTrim("param-name"), 
							initParamElement.getChildTextTrim("param-value"));
				}
				servletInfoMap.put(servletName, servletInfo);
			}
			
			urlNameMap = new HashMap<String, String>();
			List<Element> servletMappingList = webAppElement.getChildren("servlet-mapping");
			for(Element servletMappingElement : servletMappingList){
				Element servletNameElement = servletMappingElement.getChild("servlet-name");
				Element urlPatternElement = servletMappingElement.getChild("url-pattern");
				String servletName = servletNameElement.getTextTrim();
				String urlPattern = urlPatternElement.getTextTrim();
				urlNameMap.put(urlPattern, servletName);
			}
		} catch (JDOMException e) {
			logger.log(Level.WARNING, "WEB-INF/web.xml is invalid", e);
		} catch (IOException e) {
			logger.log(Level.WARNING, "WEB-INF/web.xml is invalid", e);
		}		
	}
	
	@Override
	public void boot(){
		running = true;
		Set<String> nameSet = servletInfoMap.keySet();
		Iterator<String> nameIter = nameSet.iterator();
		Class<?> servletClass = null;
		while(nameIter.hasNext()){
			String servletName = nameIter.next();
			
			ServletConfigImpl servletConfigImpl = new ServletConfigImpl(servletName, servletContextImpl);
			ServletInfo servletInfo = servletInfoMap.get(servletName);
			String servletClassName = servletInfo.getServletClass();
			Map<String, String> initParamMap = servletInfo.getInitParamMap();
			Set<String> keySet = initParamMap.keySet();
			Iterator<String> keyIter = keySet.iterator();
			while(keyIter.hasNext()){
				String key = keyIter.next();
				servletConfigImpl.addServletConfigInitParam(key, initParamMap.get(key));
			}
			try {
				servletClass = urlCL.loadClass(servletClassName);
				Servlet cachedServlet = (Servlet)servletClass.newInstance();
//				if(ServletManager.JSP_SERVLET.equals(servletName)){
//					servletConfigImpl.addServletConfigInitParam("engineOptionsClass"
//							, "com.endofhope.neurasthenia.webcontainer.servlet.JasperEngineOption");
//				}
				cachedServlet.init(servletConfigImpl);
				servletCache.put(servletName, cachedServlet);
				logger.log(Level.FINE, "servelt {0}, {1} initialized", 
						new Object[]{servletName, cachedServlet});
			} catch (ClassNotFoundException e) {
				logger.log(Level.WARNING, "servlet class ["+servletName+"] not found", e);
			} catch (InstantiationException e) {
				logger.log(Level.WARNING, "servlet class ["+servletName+"] is not valid servlet", e);
			} catch (IllegalAccessException e) {
				logger.log(Level.WARNING, "servlet class ["+servletName+"] can not accessible", e);
			} catch (ServletException e) {
				logger.log(Level.WARNING, "servlet ["+servletName+"] exception", e);
			}
		}
	}

	@Override
	public void down() {
		// TODO Auto-generated method stub
		running = false;
	}
	private volatile boolean running;
	@Override
	public boolean isRunning(){
		return running;
	}
}
