/*
 * Copyright (C) 2009 by Instytut Podstaw Informatyki Polskiej
 * Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 * Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 *
 * This file is part of WSDDE.
 *
 * WSDDE is free software: it may be distributed and/or modified under
 * the terms of the GNU General Public License version 3 as published
 * by the Free Software Foundation and appearing in the file doc/gpl.txt
 * included in the packaging of this file.
 *
 * A commercial license is available from IPI PAN (contact
 * Michal.Ciesiolka@ipipan.waw.pl or ipi@ipipan.waw.pl for more
 * information).  Licensees holding a valid commercial license from IPI
 * PAN may use this file in accordance with that license.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */

package wsdde.generator;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import java.io.Serializable;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.CodeSource;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader.Array;

import wsdde.corpus.KWIC;
import wsdde.corpus.WSDCorpus;
import wsdde.general.Utils;
import wsdde.general.XML;

/**
 * 
 * @author Rafał Młodzki
 *
 */
public abstract class FeatureGenerator implements Serializable {

	
	
	
	private static final long serialVersionUID = 6162623914640045887L;
	
	/**
	 * Parameters of the generator and theirs values.
	 */
	
	public HashMap<String, String> parameters;
	
	
	
	
	
	
	/**
	 * The Constructor from XML. 
	 */
	public FeatureGenerator(Element featureGeneratorElement) {
		parameters = new HashMap<String, String>();
		NodeList params = featureGeneratorElement.getElementsByTagName("param");
		for(int i = 0; i < params.getLength(); i++){
			Element parameter = (Element)params.item(i);
			parameters.put(parameter.getAttribute("name"), parameter.getAttribute("value"));
		}
		prepareParams();
	}

	/**
	 * constructor from parameters given in the hashtable
	 * @param parameters
	 */
	public FeatureGenerator(HashMap<String, String> parameters) {
		this.parameters = parameters;
		prepareParams();
	}
	
	/**
	 * it can do type-casting of strings in parameters table to localvariables (for convienice and efficiency) 
	 */
	
	public abstract void prepareParams();// {}
	
	
	public HashMap<String, String> changeParam() {
		return parameters;
	}
	
	public FeatureGenerator() {}
	
	/**
	 * 
	 * @return Returns a short desciption of the generator.
	 */
	public static String description() {
		return "impement it in child class!";
	}
	
	/**
	 * 
	 * @return Returns a specification of the xml used by this generator;
	 * if meta true, then specification of the meta description is returned
	 */
	public static String getXSD(String fgName,boolean meta) {
		String name = fgName+"_"+(meta?"meta":"desc")+".xsd";	
		InputStream is = FeatureGenerator.class.getResourceAsStream(name);
		return Utils.convertStreamToString(is);
		
		
	}
	
	
	public static HashMap<String, String> getChangedParams(String featureGeneratorName, HashMap<String,String> params) {
		return null;
	}
	
	public String getParam(String paramName){
		return parameters.get(paramName);
	}
	
	public void setParam(String paramName, String paramValue){
		parameters.put(paramName, paramValue);
	}
	
	public Vector<String> paramList() {
		Vector<String> v= new Vector<String>();
		Set<String> s = parameters.keySet();
		for (String string : s) {
			v.add(string);
		}
		return v;
	}
	
	/**
	 * The most important method in the class. It generates features for the given corpus.
	 * @param contexts - corpus to be featurized.
	 */
	public abstract void generate(final WSDCorpus contexts);
	
	/**
	 * Id must be alphanumeric.
	 * @return an unique id for each generator class;
	 */
	public String id() {return "implement in child class";}
	
	/*
	 * To add a feature in generate(WSDCorpus) for the given context this method should be used 
	 */
	public void feature(WSDCorpus wsdcorp, KWIC kwic, String feature) {
		StringBuffer sb = new StringBuffer();
		sb.append(feature);
		sb.append('_');
		sb.append(id());
		kwic.counter.increase(sb.toString());
		wsdcorp.whichGenerator.put(sb.toString(),this);
	}
	
	/**
	 * It means if features generator by this feature generator object are binariy features or not.
	 * If not specified, binary is a deafult setting. 
	 * @return
	 */
	public boolean isBinary() {
		if (parameters.containsKey("binary")) {
			return (Integer.parseInt(getParam("binary")))==1;
		} else {
			return true;
		}
	}

	
	/**
	 * A factory method. A generator object based on given xml is created. 
	 * @param feature_generator_element
	 * @return
	 */
	public static FeatureGenerator getGenerator(Element feature_generator_element) {
		String class_name = feature_generator_element.getTagName();//feature_generator_element.getAttribute("name");
		try {
			Class<?> FG_class = Class.forName(class_name);
			Constructor<?> con = FG_class.getConstructor(Element.class);
			Object obj = con.newInstance(new Object[]{feature_generator_element});
			return (FeatureGenerator)obj;	
		} catch (Exception e) {
			e.printStackTrace();
		}		
		return null;
	}
	
	public static FeatureGenerator getGenerator(String fgName,HashMap<String,String> parameters) {
		try {
			Class<?> FG_class = Class.forName(fgName);
			Constructor<?> con = FG_class.getConstructor(HashMap.class);
			Object obj = con.newInstance(new Object[]{parameters});
			return (FeatureGenerator)obj;	
		} catch (Exception e) {
			e.printStackTrace();
		}		
		return null;
	}
	
	
	/**
	 * helper function;
	 * lists all classess in a given package
	 * @param packageName
	 * @return
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	private static String[] getClasses(String packageName)
    throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        assert classLoader != null;
        String path = packageName.replace('.', '/');
        Enumeration<URL> resources = classLoader.getResources(path);
        List<File> dirs = new ArrayList<File>();
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            dirs.add(new File(resource.getFile().replace("%20", " ")));
        }
        ArrayList<Class> classes = new ArrayList<Class>();
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName));
        }
        ArrayList<String> strings = new ArrayList<String>();
        for (Class class1 : classes) {
			strings.add(class1.getCanonicalName());
		}
        return strings.toArray(new String[strings.size()]);
    }
	
	private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
		List<Class> classes = new ArrayList<Class>();
	    if (!directory.exists()) {
	        return classes;
	    }
	    File[] files = directory.listFiles();
	    for (File file : files) {
	        if (file.isDirectory()) {
	            //assert !file.getName().contains(".");
	            classes.addAll(findClasses(file, packageName + "." + file.getName()));
	        } else if (file.getName().endsWith(".class")) {
	            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
	        }
	    }
	    return classes;
	    
	}
	
	
	private static String[] getClassesFromJar() {
		try {
			
			CodeSource src = FeatureGenerator.class.getProtectionDomain().getCodeSource();
			List<String> list = new ArrayList<String>();
			
			if( src != null ) {
				
				URL jar = src.getLocation();
			    ZipInputStream zip = new ZipInputStream( jar.openStream());
			    ZipEntry ze = null;
		
			    while( ( ze = zip.getNextEntry() ) != null ) {
			        String entryName = ze.getName();
			        if (entryName.endsWith(".class")) {
			        	entryName = entryName.replace('/', '.').substring(0,entryName.lastIndexOf('.'));
			        	
			        	//System.out.println(entryName);
			        	list.add(entryName);
			        }
			        
			        //list.add(entryName);
			    }
		
			 }
			 return  list.toArray( new String[ list.size() ] );
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}	
	
	/**
	 * get concatenated XSD schemas from all features generators (with descriptions as comments)
	 * @return
	 */
	public static String getGeneratorsXSDs(boolean meta) {
		StringBuffer sb = new StringBuffer();
		String[] cs;
		try {
			cs = getClassesFromJar(); //if in jar
			Vector<String> v = new Vector<String>(Arrays.asList(cs));
			cs = getClasses("wsdde.generator"); //else (if in directories)
			v.addAll(Arrays.asList(cs));
			for (String classt : v) {
				
				Class class1 = Class.forName(classt);
				if (class1.getSuperclass().getCanonicalName().equals("wsdde.generator.FeatureGenerator")) {
					Class<?> FG_class = Class.forName(class1.getCanonicalName());
					Method m = FG_class.getDeclaredMethod("description", null);
				    String o = (String)m.invoke(null, null);
				    sb.append("<!--");
				    sb.append(o);
				    sb.append("-->");


					
					Constructor<?> con = FG_class.getConstructor();
					FeatureGenerator obj = (FeatureGenerator)con.newInstance();
					

				    
				    m = FG_class.getDeclaredMethod("id", null);
		    		String id = (String)m.invoke(obj, null);

		    		
		    		
		    		m = FG_class.getSuperclass().getDeclaredMethod("getXSD", new Class[]{String.class,boolean.class});
				    o = (String)m.invoke(null, new Object[]{id,meta});
					sb.append(o);
				}
			}
		
		}	catch (Exception e) {
			
			e.printStackTrace();
		}
		

		return sb.toString();
	}
	

	
	//	public static String getGeneratorsDesc() {
//		StringBuffer sb = new StringBuffer();
//		Class[] cs;
//		try {
//			cs = getClasses("wsdde.generator"); 
//			for (Class class1 : cs) {
//				if (class1.getSuperclass().getCanonicalName().equals("wsdde.generator.FeatureGenerator")) {
//					Class<?> FG_class = Class.forName(class1.getCanonicalName());
//					Method m = FG_class.getDeclaredMethod("description", null);
//				    String o = (String)m.invoke(null, null);
//				    sb.append("FEATURE GENERATOR NAME: ");
//				    sb.append(class1.getCanonicalName());
//					sb.append("\n");
//					sb.append("DESCRIPTION: ");
//				    sb.append(o);
//				    sb.append("\n");
//				    sb.append("*****************");
//				    sb.append("\n");
//				}
//			}
//		
//		}	catch (Exception e) {
//			
//			e.printStackTrace();
//		}
//		
//
//		return sb.toString();
//	}

	
	public static Vector<HashMap<String, String>> getParametersOption(String fGName, HashMap<String, String> params, int quantity){
		Vector<HashMap<String,String>> v = new Vector<HashMap<String,String>>();
		FeatureGenerator fg = getGenerator(fGName, params);
		for (int i = 0; i < quantity; i++) {
			v.add(fg.changeParam());
		} 
		return v;
	}
	
	public static void main(String[] args){
		System.out.println(getGeneratorsXSDs(true));
		
	}
}
