package tk.eclipse.plugin.jspeditor.editors;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import jp.aonir.fuzzyxml.FuzzyXMLDocument;
import jp.aonir.fuzzyxml.FuzzyXMLElement;
import jp.aonir.fuzzyxml.FuzzyXMLNode;
import jp.aonir.fuzzyxml.FuzzyXMLParser;
import jp.aonir.fuzzyxml.XPath;

import org.eclipse.jface.preference.IPreferenceStore;

import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.HTMLUtil;
import tk.eclipse.plugin.htmleditor.gefutils.IJarVisitor;
import tk.eclipse.plugin.htmleditor.gefutils.JarAcceptor;

/**
 * Provides one static method to get TLD files.
 */
public class TLDLoader {
	
	private static HashMap<String, byte[]> cache = new HashMap<String, byte[]>();
	
	/**
	 * Returns the TLD file as <code>InputStream</code>.
	 * <p>
	 * If the URL starts <code>http://</code>, this method tries to
	 * get the TLD file from the remote server.
	 * Otherwise, tries to get from the local path using given URI as
	 * the relative path from the base directory.
	 * 
	 * @param basedir the base directory
	 * @param uri the URI of the TLD file
	 * @return <code>InputStream</code> of the TLD file
	 */
	public static InputStream get(File basedir,String uri) throws Exception {
		if(cache.get(uri)==null){
			// Default internal TLDs
			Map innerTLD = HTMLPlugin.getInnerTLD();
			if(innerTLD.get(uri)!=null){
				InputStream in = TLDLoader.class.getResourceAsStream((String)innerTLD.get(uri));
				byte[] bytes = HTMLUtil.readStream(in);
				cache.put(uri,bytes);
				return new ByteArrayInputStream(bytes);
			}
			// Contributed TLDs
			ITLDLocator[] locators = HTMLPlugin.getDefault().getTLDLocatorContributions();
			for ( int i = 0; i < locators.length; i++) {
				InputStream in = locators[i].locateTLD(uri);
				if ( in != null) {
					byte[] bytes = HTMLUtil.readStream(in);
					cache.put(uri,bytes);
					return new ByteArrayInputStream(bytes);
				}
			}
			// from PreferenceStore
			Map pref = getPreferenceTLD();
			if(pref.get(uri)!=null){
				InputStream in = new FileInputStream(new File((String)pref.get(uri)));
				byte[] bytes = HTMLUtil.readStream(in);
				cache.put(uri,bytes);
				return new ByteArrayInputStream(bytes);
			}
			// Check web.xml
			byte[] bytes = getTLDFromWebXML(basedir,uri);
			if(bytes!=null){
				cache.put(uri,bytes);
				return new ByteArrayInputStream(bytes);
			}
			if(uri.startsWith("http://") || uri.startsWith("https://")){
				// Search META-INF in jar files
				bytes = getTLDFromJars(basedir,uri);
				if(bytes!=null){
					cache.put(uri,bytes);
					return new ByteArrayInputStream(bytes);
				}
				// from the URL
				URL url = new URL(uri);
				InputStream in = url.openStream();
				cache.put(uri,HTMLUtil.readStream(in));
			} else {
				// from the local file
				File file = new File(basedir,uri);
				InputStream in = new FileInputStream(file);
				cache.put(uri,HTMLUtil.readStream(in));
			}
		}
		
		byte[] bytes = cache.get(uri);
		return new ByteArrayInputStream(bytes);
	}
	
	/** Load configurations from <code>IPreferenceStore</code>. */
	private static Map getPreferenceTLD(){
		HashMap<String, String> map = new HashMap<String, String>();
		
		IPreferenceStore store = HTMLPlugin.getDefault().getPreferenceStore();
		String[] uri  = store.getString(HTMLPlugin.PREF_TLD_URI).split("\n");
		String[] path = store.getString(HTMLPlugin.PREF_TLD_PATH).split("\n");
		for(int i=0;i<uri.length;i++){
			if(!uri[i].trim().equals("") && !path[i].trim().equals("")){
				map.put(uri[i].trim(),path[i].trim());
			}
		}
		
		return map;
	}
	
	/** Load from web.xml */
	private static byte[] getTLDFromWebXML(File basedir,String uri){
		File webXML = new File(basedir,"/WEB-INF/web.xml");
		
		if(webXML.exists() && webXML.isFile()){
			try {
				FuzzyXMLDocument doc = new FuzzyXMLParser(false).parse(new FileInputStream(webXML));
				FuzzyXMLNode[] nodes = XPath.selectNodes(doc.getDocumentElement(),"/web-app/taglib|/web-app/jsp-config/taglib");
				
				for(int i=0;i<nodes.length;i++){
					FuzzyXMLElement element = (FuzzyXMLElement)nodes[i];
					String taglibUri = HTMLUtil.getXPathValue(element,"/taglib-uri/child::text()");
					String taglibLoc = HTMLUtil.getXPathValue(element,"/taglib-location/child::text()");
					if(uri.equals(taglibUri)){
						if(taglibLoc!=null && taglibLoc.endsWith(".tld")){
							File file = new File(basedir,taglibLoc);
							return HTMLUtil.readStream(new FileInputStream(file));
						}
						break;
					}
				}
			} catch(Exception ex){
				HTMLPlugin.logException(ex);
			}
		}
		return null;
	}
	
	/** Load from META-INF in the jar file */
	private static byte[] getTLDFromJars(File basedir,final String uri){
		return (byte[])JarAcceptor.accept(basedir, new IJarVisitor(){
			public Object visit(JarFile file, JarEntry entry) throws Exception {
				if(entry.getName().endsWith(".tld")){
					byte[] bytes = HTMLUtil.readStream(file.getInputStream(entry));
					try {
						FuzzyXMLDocument doc = new FuzzyXMLParser(false).parse(new ByteArrayInputStream(bytes));
						String nodeURI = HTMLUtil.getXPathValue(doc.getDocumentElement(),"/taglib/uri/child::text()");
						if(nodeURI!=null && uri.equals(nodeURI)){
							return bytes;
						}
					} catch(Exception ex){
						HTMLPlugin.logException(ex);
					}
				}
				return null;
			}
		});
	}
}
