package emn.fr.ascola.extractor;

import java.util.Vector;

import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;

/**
 * Auxiliary class for additional services of ITypeBinding.
 * We consider SimpleType
 * TODO extends for generic and so on
  * @author jroyer
 *
 */
public class MyIType  {
	
	/**
	 * Resolved type.
	 */
	private ITypeBinding ibind;
	
	/**
	 * The proper structure.
	 */
	private Fields structure;
	
	/**
	 * Full super class name.
	 */
	private String supername;
	
	/**
	 * Store the list of direct subclasses.
	 */
	private Vector<String> subclasses;
	
	/**
	 * Store the vector of direct sub interfaces.
	 */
	private Vector<String> subinterfaces;
	
	/**
	 * Class or interface.
	 * True if a class.
	 */
	private boolean nature;
	
	/**
	 * Defaut constructor.
	 */
	public MyIType() {
		this.supername = "Java.lang.Object";
		this.subclasses = new Vector<String>();
		this.subinterfaces = new Vector<String>();
	}
	
	/**
	 * Get full name.
	 * @return
	 */
	public String getFullName() {
		// binary name 
		return this.ibind.getBinaryName();
	}
	
	/**
	 * Add the fields in the structure and the  TypeInfo.
	 * Recursive method with side effects 
	 */
	public void storeStructure() {
		//System.out.println(" storeStructure " + this.ibind.getBinaryName());
		// Only for SimpleType
		// get the declared fields of the class
		IVariableBinding[] ivbs = this.ibind.getDeclaredFields();
		// collect the  fields of the current type and resolved if not yet declared 
		Fields champs = new Fields();
		for (int i = 0; i < ivbs.length; i++) {
			// get the binding variables
			IVariableBinding field = ivbs[i];
			ITypeBinding ibd = field.getType();
			String fullname = ibd.getBinaryName();
			if (ASTActionDelegate.table.isOfInterest(fullname) &&
					!ASTActionDelegate.table.isDataType(fullname)) {
				// add structure information
				champs.add(field);
				//  recursive resolution and store
				// not for interface
				if (!ASTActionDelegate.table.isResolved(fullname)
						&& ibd.isClass()) {
					// set component type indicator
					ASTActionDelegate.table.setComponentType(fullname);
					//  add the resolved type in the table
					MyIType mit = ASTActionDelegate.table.get(fullname).getIbind();
					mit.setIbind(ibd);
					// to stop infinite loop
					ASTActionDelegate.table.setType(fullname, mit);
					// compute the structure
					mit.storeStructure();
					ASTActionDelegate.table.setType(fullname, mit);
				}	
			}	
		}
		// analyse inheritance 
		ITypeBinding sup = this.ibind.getSuperclass();
		if (sup != null) {
			String supname = sup.getBinaryName();
			// test if class and type of interest
			if (sup.isClass() && ASTActionDelegate.table.isOfInterest(supname)
					&& !ASTActionDelegate.table.isDataType(supname)) {
				//System.out.println(" Inherits from " + this.ibind.getBinaryName() + " to " + supname + " struc ");
				// recursive resolution and store
				if (!ASTActionDelegate.table.isResolved(supname)
						|| !ASTActionDelegate.table.hasFields(supname)) {
					// set component type indicator 
					ASTActionDelegate.table.setComponentType(supname);
					// add the resolved type in the table
					MyIType mit = ASTActionDelegate.table.get(supname).getIbind();
					mit.setIbind(sup);
					// to stop infinite loop
					ASTActionDelegate.table.setType(supname, mit);
					// compute the structure
					mit.storeStructure();
					ASTActionDelegate.table.setType(supname, mit);
				}
				// do not consider inherited privates since not really inherited
				// simple merge of locals and inherited fields
				this.structure = champs.merge(ASTActionDelegate.table.getStructure(supname));					
			} else {
				this.structure = champs;
			}
		} else {
			this.structure = champs;
		}
	}

	/**
	 * String representation.
	 */
	public String toString() {
		String result = "";
		String other = " CLASS? " + this.isNature() + "\n SUPERCLASS " + this.supername 
					+ "\n SUBCLASSES " + this.subclasses + "\n SUBINTERFACES " + this.subinterfaces ;
		if (this.isResolved()) {
			result += this.getFullName() + other + "\n THESTRUCTURE: " + this.structure;
		} else {
			result += "NOT RESOLVED " + other;
		}
		return result + "\n";
	}
	
	/**
	 * Check if it has a public or default-package field.
	 * @return
	 */
	public boolean hasPublicFields() {
		return this.structure.hasPublicFields();
	}
	
	/**
	 * Set the structure.
	 * @param struc
	 */
	public void setStructure(Fields struc) {
		this.structure = struc;
	}

	/**
	 * Get the structure.
	 * @return
	 */
	public Fields getStructure() {
		return structure;
	}

	/**
	 * Test if structure exists.
	 * @return
	 */
	public boolean hasFields() {
		return this.structure != null;
	}

	public String getSupername() {
		return supername;
	}

	public void setSupername(String supername) {
		this.supername = supername;
	}

	/**
	 * Test if typebinding is resolved.
	 * @return
	 */
	public boolean isResolved() {
		return this.ibind != null;
	}

	public ITypeBinding getIbind() {
		return ibind;
	}

	public void setIbind(ITypeBinding ibind) {
		this.ibind = ibind;
	}

	/**
	 * Getter for subclasses.
	 * @return
	 */
	public Vector<String> getSubclasses() {
		return subclasses;
	}

	/**
	 * Add a subclass
	 * @param subclasses
	 */
	public void addSubclass(String subclass) {
		if (!this.subclasses.contains(subclass)) {
			this.subclasses.add(subclass);
		}
	}

	/**
	 * Get sup interfaces
	 * @return
	 */
	public Vector<String> getSubinterfaces() {
		return subinterfaces;
	}

	/**
	 * Add a new sub interfaces in the list
	 * @param sub
	 */
	public void addSubinterface(String sub) {
		if (!this.subinterfaces.contains(sub)) {
			this.subinterfaces.add(sub);
		}
	}

	/**
	 * Is a class ?
	 * @return
	 */
	public boolean isNature() {
		return nature;
	}

	/**
	 * Set the nature.
	 * @param nature
	 */
	public void setNature(boolean nature) {
		this.nature = nature;
	}
}
