package br.uem.din.architectureEvolution.representation;

import static br.uem.din.architectureEvolution.helpers.ElementTypeNames.CLASS;
import static br.uem.din.architectureEvolution.helpers.ElementTypeNames.COMPONENT;
import static br.uem.din.architectureEvolution.helpers.ElementTypeNames.INTERFACE;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import br.uem.din.architectureEvolution.helpers.ModelHelper;
import br.uem.din.architectureEvolution.helpers.ModelHelperFactory;
import br.uem.din.architectureEvolution.helpers.SDMetricsToEMFParser;
import br.uem.din.architectureEvolution.helpers.StereotypeHelper;

import com.rits.cloning.Cloner;
import com.sdmetrics.model.MetaModel;
import com.sdmetrics.model.Model;
import com.sdmetrics.model.ModelElement;

public class Architecture {

	private final ModelHelper modelHelper = ModelHelperFactory.getModelHelper();
	
	private final Model model;
	
	private final List<InterClassRelationship> interClassRelationships = new ArrayList<InterClassRelationship>();
	private final List<InterElementRelationship> interElementRelationships = new ArrayList<InterElementRelationship>();
	private final HashMap<String, Component> components = new HashMap<String, Component>();
	private final HashMap<String, Interface> interfaces = new HashMap<String, Interface>();
	private final HashMap<String, Variability> variability = new HashMap<String, Variability>();
	private final HashMap<String, Concern> concerns = new HashMap<String, Concern>();

	private final String xmiFilePath;

	public Architecture(String xmiFilePath) throws Exception {
		this.xmiFilePath = xmiFilePath;
		this.model = modelHelper.getModel(xmiFilePath);
		
		loadComponents();
		loadInterfaces();
		loadRelationships();
		loadVariability();
	}

	private void loadComponents() throws Exception {
		List<ModelElement> elements = modelHelper.getAllComponents(model);
		
		for (ModelElement element : elements) {
			components.put(element.getXMIID(), new Component(element, this));
		}
	}

	private void loadRelationships() throws Exception {
		loadGeneralizations();
		loadAbstractions();
		loadAssociations();
		loadDependencies();
	}

	private void loadDependencies() throws Exception {
		List<ModelElement> elements = modelHelper.getAllDependencies(model);
		
		for (ModelElement element : elements) {			
			if (element.getRefAttribute("depclient").getType().getName().equals(CLASS))
				interClassRelationships.add(new DependencyInterClassRelationship(element, this));
			else{
				interElementRelationships.add(new DependencyComponentInterfaceRelationship(element, this));
			}
		}
	}

	private void loadAssociations() throws Exception {
		List<ModelElement> elements = modelHelper.getAllAssociations(model);
		
		for (ModelElement element : elements) {
			interClassRelationships.add(new AssociationInterClassRelationship(element, this));
		}
	}

	private void loadAbstractions() throws Exception {
		List<ModelElement> elements = modelHelper.getAllAbstractions(model);
		
		for (ModelElement element : elements) {
			interElementRelationships.add(new AbstractionInterElementRelationship(element, this));
		}
	}

	private void loadGeneralizations() throws Exception {
		List<ModelElement> elements = modelHelper.getAllGeneralizations(model);
		
		for (ModelElement element : elements) {
			interClassRelationships.add(new GeneralizationInterClassRelationship(element, this));
		}
	}
	
	private void loadInterfaces() throws Exception {
		List<ModelElement> elements = modelHelper.getAllInterfaces(model);
		
		for (ModelElement element : elements) {
			interfaces.put(element.getXMIID(), new Interface(element, this));
		}
	}
	
	private void loadVariability() {
		for (ModelElement comment : modelHelper.getAllComments(model)) {
			if (StereotypeHelper.isVariability(comment)){
				getVariability().put(comment.getXMIID(), new Variability(comment, this));
			}
		}
	}

	public Component findComponentByXMIID(String xmiid) {
		return components.get(xmiid);
	}

	public Class findClassByXMIID(String xmiid) {
		for (Component component : components.values()) {
			 Class class_ = component.getClassByXMIID(xmiid);
			 if (class_ != null)
				 return class_;
		}
		return null;
	}

	public Interface findInterfaceByXMIID(String xmiid) {
		return interfaces.get(xmiid);
	}

	public Element findElementByName(String name) {
		Element element = findInterfaceByName(name);
		
		if (element == null)
			element = findComponentByName(name);
		
		if (element == null)
			element = findClassByName(name);
		
		return element;
	}

	public Class findClassByName(String name) {
		for (Component	component : components.values()) {
			Class class_ = component.findClassByName(name);
			if (class_ != null)
				return class_;
		}
		return null;
	}

	public Component findComponentByName(String name) {
		for (Component component : components.values()) {
			if (component.getName().equals(name))
				return component;
		}
		return null;
	}

	public Interface findInterfaceByName(String name) {
		for (Interface interface_ : interfaces.values()) {
			if (interface_.getName().equals(name))
				return interface_;
		}
		return null;
	}
	
	public HashMap<String, Class> getClasses(){
		HashMap<String, Class> classes = new HashMap<String, Class>(); 
		for (Component component : components.values()) {
			classes.putAll(component.getClasses());
		}
		return classes;
	}
	
	public HashMap<String, Component> getComponents(){
		return components;
	}
	
	public HashMap<String, Interface> getInterfaces() {
		return interfaces;
	}
	
	public Collection<Concern> getConcerns() {
		return concerns.values();
	}
	
	public Concern getOrCreateConcernByName(String name){
		Concern concern = concerns.get(name.toLowerCase());
		if (concern == null){
			concern = new Concern(name);
			concerns.put(name.toLowerCase(), concern);
		}
		return concern;
	}
	

	public Concern getOrCreateFeatureByName(String name) {
		Concern concern = concerns.get(name.toLowerCase());
		if (concern == null){
			concern = new Feature(name);
			concerns.put(name.toLowerCase(), concern);
		}
		return concern;
	}
	
	public List<InterClassRelationship> getInterClassRelationships() {
		return interClassRelationships;
	}
	
	public List<InterElementRelationship> getInterElementRelationships() {
		return interElementRelationships;
	}
	
	public Collection<AbstractionInterElementRelationship> getAbstractionInterElementRelationships() {
		Collection<AbstractionInterElementRelationship> abstractions = new ArrayList<AbstractionInterElementRelationship>();
		for (InterElementRelationship relationship : getInterElementRelationships()) {
			if (relationship instanceof AbstractionInterElementRelationship)
				abstractions.add((AbstractionInterElementRelationship) relationship);
		}
		return abstractions;
	}
	
	public Collection<DependencyComponentInterfaceRelationship> getDependencyComponentInterfaceRelationships() {
		Collection<DependencyComponentInterfaceRelationship> dependencies = new ArrayList<DependencyComponentInterfaceRelationship>();
		for (InterElementRelationship relationship : getInterElementRelationships()) {
			if (relationship instanceof DependencyComponentInterfaceRelationship)
				dependencies.add((DependencyComponentInterfaceRelationship) relationship);
		}
		return dependencies;
	}
	
	public void removeInterClassRelationship(InterClassRelationship relationship){		
		interClassRelationships.remove(relationship);
		model.removeElement(relationship.getModelElement());
	}
	
	public void removeInterElementComponentRelationship(InterElementRelationship relationship){		
		interElementRelationships.remove(relationship);
		model.removeElement(relationship.getModelElement());
	}

	public void addInterElementRelationship(InterElementRelationship relationship) {
		interElementRelationships.add(relationship);
		model.addElement(relationship.getModelElement());
	}
	
	public void addInterClassRelationship(InterClassRelationship relationship) {
		interClassRelationships.add(relationship);
		model.addElement(relationship.getModelElement());
	}
	
	public Component createComponent(String componentName) throws Exception {
		ModelElement element = new ModelElement(getMetaModel().getType(COMPONENT));
		String xmiID = getNextOwnedComponentXMIID();
		element.setName(componentName);
		element.setXMIID(xmiID);
		Component component = new Component(element, this);
		components.put(xmiID, component);
		return component;
	}
	
	public void removeComponent(Component component) {
		components.values().remove(component);
		model.removeElement(component.getModelElement());
	}
	
	private String getNextOwnedComponentXMIID() {
		String xmiID = "createdComponent";
		int number = 0;
		String nextXmiID = "";
		do {
			number++;
			nextXmiID = xmiID + number;
		} while(components.containsKey(nextXmiID));
		return nextXmiID;
	}
	
	public Interface createInterface(String interfaceName) throws Exception {
		ModelElement element = new ModelElement(getMetaModel().getType(INTERFACE));
		String xmiID = getNextOwnedInterfaceXMIID();
		element.setName(interfaceName);
		element.setXMIID(xmiID);
		Interface interface_ = new Interface(element, this);
		interfaces.put(xmiID, interface_);
		return interface_;
	}
	
	public void removeInterface(Interface interface_) throws Exception {
		Collection<Component> implementors = interface_.getImplementors();
		for (Component implementor : implementors) 
			removeImplementedInterfaceFromComponent(interface_, implementor);
			
		Collection<Component> dependents = interface_.getDependents();
		for (Component dependent : dependents) 
			removeRequiredInterfaceFromComponent(interface_, dependent);
		
		interfaces.values().remove(interface_);
		model.removeElement(interface_.getModelElement());
	}
	
	public void removeImplementedInterfaceFromComponent(Interface interface_, Component component) throws Exception {
		if (!component.removeImplementedInterface(interface_)) return;

		for (AbstractionInterElementRelationship relationship : getAbstractionInterElementRelationships()) {
			if (relationship.getParent().equals(interface_) && relationship.getChild().equals(component)){
				removeInterElementComponentRelationship(relationship);
				return;
			}
		}
	}
	
	public void addImplementedInterfaceToComponent(Interface interface_, Component component) throws Exception {
		if (component.getImplementedInterfaces().contains(interface_)) return;
		
		interElementRelationships.add(new AbstractionInterElementRelationship(interface_, component, this));
	}
	
	public void removeRequiredInterfaceFromComponent(Interface interface_, Component component) throws Exception {
		if (!component.removeRequiredInterface(interface_)) return;

		for (DependencyComponentInterfaceRelationship relationship : getDependencyComponentInterfaceRelationships()) {
			if (relationship.getInterface().equals(interface_) && relationship.getComponent().equals(component)){
				removeInterElementComponentRelationship(relationship);
				return;
			}
		}
	}
	
	public void addRequiredInterfaceToComponent(Interface interface_, Component component) throws Exception {
		if (component.getRequiredInterfaces().contains(interface_)) return;
		
		interElementRelationships.add(new DependencyComponentInterfaceRelationship(interface_, component, this));
	}
	
	private String getNextOwnedInterfaceXMIID() {
		String xmiID = "createdInterface";
		int number = 0;
		String nextXmiID = "";
		do {
			number++;
			nextXmiID = xmiID + number;
		} while(interfaces.containsKey(nextXmiID));
		return nextXmiID;
	}
	
	public MetaModel getMetaModel() {
		return model.getMetaModel();
	}

	public Architecture deepClone() {
		return new Cloner().deepClone(this);
	}
	
	public void save() {
		SDMetricsToEMFParser parser = new SDMetricsToEMFParser(this);
		parser.parseAndSave();
	}
	
	public void save(String pathToSave, int number) {
		SDMetricsToEMFParser parser = new SDMetricsToEMFParser(this, pathToSave, number);
		parser.parseAndSave();
	}

	public String getXMIFileName() {
		return new File(xmiFilePath).getName().split("\\.")[0];
	}

	public HashMap<String, Variability> getVariability() {
		return variability;
	}
}