package emn.fr.ascola.extractor;

import java.util.HashMap;
import java.util.Vector;

import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jface.dialogs.MessageDialog;

/**
 * Table for storing informations about the types.
 * @author jroyer
  */
public class TypesTable extends  HashMap<String, Information>{
	private static final long serialVersionUID = 1L;

	/**
	 * Constructor.
	 * @param vt
	 */
	public TypesTable(Vector<IType> vt) {
		super();
		for (int i = 0; i < vt.size(); i++) {
			this.put(vt.get(i).getFullyQualifiedName(), new Information());
		}
	}
	
	/**
	 * Check the existence of such a type.
	 * @param fn
	 * @return
	 */
	public boolean isOfInterest(String fn) {
		return this.containsKey(fn);
	}
	
	/**
	 * Check if it is flagged as a data type.
	 *  Require this.isOfInterest()
	 * @param fn
	 * @return
	 */
	public boolean isDataType(String fn) {
		return this.get(fn).isDataType();
	}

	/**
	 * Check if the type name is resolved
	 * Require this.isOfInterest()
	 * @param fn
	 * @return
	 */
	public boolean isResolved(String fn) {
		return (this.get(fn).isResolved());
	}
	
	/**
	 * Test fields existence.
	 * @param fn
	 * @return
	 */
	public boolean hasFields(String fn) {
		return this.get(fn).hasFields();
	}
	
	/**
	 * Set that name is a data type.
	 * @param bn binary name
	 */
	public void setDataType(String bn) {
		if (this.containsKey(bn)) {
			Information inf = this.get(bn);
			inf.setDataType(true);
			this.put(bn, inf);
		}
	}
	
	/**
	 * Set that name is a component type.
	 * @param bn binary name
	 */
	public void setComponentType(String bn) {
		if (this.containsKey(bn)) {
			Information inf = this.get(bn);
			inf.setComponentType(true);
			this.put(bn, inf);
		}
	}
	
	/**
	 * Set as THE root name. 
	 * @param bn
	 */
	public void setRoot(String bn) {
		if (this.containsKey(bn)) {
			Information inf = this.get(bn);
			inf.setRoot();
			this.put(bn, inf);
		}
	}
	
	/**
	 * Add a new entry and its information.
	 * @param fn
	 * @param it
	 */
	public void addType(String fn, Information it) {
		this.put(fn, it);
	}
	
	/**
	 * Set the information type for a name.
	 * @param bn
	 * @param it
	 */
	public void setType(String bn, MyIType it) {
		if (this.containsKey(bn)) {
			Information inf = this.get(bn);
			inf.setType(it);
			this.put(bn, inf);
		} else {
			MessageDialog.openError(ASTActionDelegate.shell, "Error" , " TypesTable.setType inconnu  " + bn);
		}
	}
	
	/**
	 * test if sub inherits directly or not from sup
	 * IF sup is external we consider it is true
	 * @param sub full name of type in the table
	 * @param sup full name of any type
	 * @return
	 */
	public boolean inheritsFromData(String sub, String sup) {
		boolean result = false;
		if (!sub.equals(sup) && this.isOfInterest(sub)) {
			if (!this.isOfInterest(sup)) {
				result = true;
			} else {
				// TODO attention ibind != null
				String thesup = this.get(sub).getIbind().getSupername();
				result = thesup.equals(sup) || this.inheritsFromData(thesup, sup);
			}
		}
		return result;
	}
	
	/**
	 * For each entry flagged DATA then direct subclasses are also
	 * flagged as DATA.
	 * If it is an interfaces subinterfaces are flagged too
	 */
	public void propagateDATA() {
		for (String key : this.keySet()) {
			if (this.get(key).isDataType()) {
				MyIType ity = this.get(key).getIbind();
				// for subclasses
				for (String subc : ity.getSubclasses()) {
					this.get(subc).setDataType(true);
				}
				// if an interface it has sub-interfaces
				if (!ity.isNature()) {
					for (String subi : ity.getSubinterfaces()) {
						this.get(subi).setDataType(true);
					}
				}
			}
		}
	}
	
	/**
	 * Compute the list of component which have a public fields
	 * TODO check it here not an interface !
	 * @return
	 */
	public Vector<String> checkPublicFields() {
		Vector<String> res = new Vector<String> ();
		for (String key : this.keySet()) {
			Information info = this.get(key);
			MyIType ity = info.getIbind();
			if (ity.isNature()) {
				if (info.isComponentType() && ity.hasPublicFields()) {
					res.add(key);
				}
			}
		}
		return res;
	}
	
	/**
	 * Compute the component type which have a cyclic structure.
	 * Look only at component type and catch all the connected components
	 * @return
	 */
	public Vector<String> checkCycle() {
		// set initial flag for DATA type
		for (String key : this.keySet()) {
			if (!this.get(key).isComponentType()) {
				this.get(key).setViewed(true);
			}
		}
		// look the component type not already viewed
		// to explore all the connected component of the graph structure
		Vector<String> res = new Vector<String> ();
		for (String key : this.keySet()) {
			if (!this.get(key).isViewed()) {
				//System.out.println(" relance pour " + key);
				res.addAll(this.findCycle(key, new Vector<String> ()));
			}
		}
		return res;
	}
	
	/**
	 * Recursive function to find cyclic component type.
	 * @param node
	 * @param history
	 * @return
	 */
	public Vector<String> findCycle(String node, Vector<String> history) {
		Vector<String> res = new Vector<String> ();
		//System.out.println(" visit " + node + " h = " + history);
		Information info = this.get(node);
		// from structure construction these are component types
		if (history.contains(node)) {
				res.add(node);
			info.setCycle(true);
		} else { 
			// look at the fields and explore
			for (IVariableBinding f : info.getIbind().getStructure()) {
				String fname = f.getType().getBinaryName();
				Information inff = this.get(fname);
				// already visited
				if (inff.isViewed()) {
					if (inff.hasCycle()) {
						// forget multiple occurences
						if (!res.contains(node)) {
							res.add(node);
						}
						info.setCycle(true);
					}
				} else {
					history.add(node);
					// forget multiple occurences
					for (String st : this.findCycle(fname, history)) {
						if (!res.contains(st)) {
							res.add(st);
						}
					}
				}	
			}
		}
		// flag node as viewed
		info.setViewed(true);
		return res;
	}
	
	/**
	 * Setting default null information.
	 * @param bn
	 */
	public void setType(String bn) {
			this.put(bn, new Information());
	}
	
	/**
	 * Get the structure stored in the table.
	 * @param fullname
	 * @return
	 */
	public Fields getStructure(String fullname) {
		return this.get(fullname).getIbind().getStructure();
	}
	
	/**
	 * String description.
	 */
	public String toString() {
		String result = "";
		String sep = "%------------------------------\n";
		for (String key : this.keySet()) {
			result += sep + key  + " ==> " + this.get(key) + "\n\n";
		}
		return result;
	}
	
	/**
	 * Compute the number of component type.
	 * @return
	 */
	public int getComponentNumber () {
		int result = 0;
		for (String typ : this.keySet()) {
			if (this.get(typ).isComponentType()) {
				result++;
			}
		}
		return result;
	}
	
	/**
	 * Compute the number of data type.
	 * @return
	 */
	public int getDatatypeNumber () {
		int result = 0;
		for (String typ : this.keySet()) {
			if (this.get(typ).isDataType()) {
				result++;
			}
		}
		return result;
	}
	
	/**
	 * Compute the list of component type which have a cycle structure.
	 * @return
	 */
	public Vector<String> getCycles () {
		Vector<String> result = new Vector<String>();
		for (String typ : this.keySet()) {
			if (this.get(typ).isComponentType()) {
				if (this.get(typ).hasCycle()) {
					result.add(typ);
				}
			}
		}
		return result;
	}
	
	/**
	 * Compute the list of undefined types.
	 * @return
	 */
	public Vector<String> getUndefined () {
		Vector<String> result = new Vector<String>();
		for (String typ : this.keySet()) {
			if (this.get(typ).isUndefined()) {
					result.add(typ);
			}
		}
		return result;
	}
	
	/**
	 * Set the undefined interfaces to component types.
	 */
	public void setUndefinedInterfaces() {
		for (String typ : this.keySet()) {
			Information ity = this.get(typ);
			if (ity.isUndefined()) {
				if (!ity.getIbind().isNature()) {
					ity.setComponentType(true);
				} else {
					System.err.println("TypesTable.setUndefinedInterfaces: problem with " + typ + " : " + ity);
				}
			}
		}
	}

}
