package br.uem.din.architectureEvolution.representation;

import static br.uem.din.architectureEvolution.helpers.ElementTypeNames.*;

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

import br.uem.din.architectureEvolution.helpers.ModelElementHelper;

import com.sdmetrics.model.MetaModelElement;
import com.sdmetrics.model.ModelElement;

public class Class extends Element {
	
	private final HashMap<String, Method> methods = new HashMap<String, Method>();
	private final HashMap<String, Attribute> attributes = new HashMap<String, Attribute>();
	
	public Class(ModelElement element, Architecture architecture) throws Exception{
		this.setModelElement(element, architecture);		
	}
	
	@Override
	protected void loadFields() throws Exception {		
		loadMethods();
		loadAttributes();
	}

	private void loadAttributes() throws Exception {
		List<ModelElement> elements = ModelElementHelper.getOwnedElementsOfType(getModelElement(), ATTRIBUTE);
		for (ModelElement element : elements) {
			getAttributes().put(element.getXMIID(), new Attribute(element, getArchitecture()));
		}
	}

	private void loadMethods() throws Exception {
		List<ModelElement> elements = ModelElementHelper.getOwnedElementsOfType(getModelElement(), METHOD);
		for (ModelElement element : elements) {
			getMethods().put(element.getXMIID(), new Method(element, getArchitecture()));
		}
	}

	public HashMap<String, Method> getMethods() {
		return methods;
	}

	public HashMap<String, Attribute> getAttributes() {
		return attributes;
	}
	
	public void removeMethod(Method method) {
		getMethods().values().remove(method);
		getModelElement().removeRelation(MetaModelElement.CONTEXT, method.getModelElement());
	}
	
	public void removeAttribute(Attribute attribute) {
		getAttributes().values().remove(attribute);
		getModelElement().removeRelation(MetaModelElement.CONTEXT, attribute.getModelElement());
	}
	
	public Method createMethod(String methodName, String returnType) throws Exception {
		ModelElement element = new ModelElement(getArchitecture().getMetaModel().getType(METHOD));
		ModelElement parameterElement = new ModelElement(getArchitecture().getMetaModel().getType(PARAMETER));
		ModelElement parameterType = createOrReturnPrimitiveType(returnType); 
		parameterElement.setRefAttribute("parametertype", parameterType);
		parameterElement.setName("return");
		element.addRelation(MetaModelElement.CONTEXT, parameterElement);
		String xmiID = getNextMethodXMIID();
		element.setXMIID(xmiID);
		element.setName(methodName);
		Method method = new Method(element, getArchitecture());
		getMethods().put(xmiID, method);
		getModelElement().addRelation(MetaModelElement.CONTEXT, method.getModelElement());
		return method;
	}

	private String getNextMethodXMIID() {
		String xmiID = getModelElement().getXMIID() + "m";
		int number = 0;
		String nextXmiID = "";
		do {
			number++;
			nextXmiID = xmiID + number;
		} while(getMethods().containsKey(nextXmiID));
		return nextXmiID;
	}
	
	public Attribute createAttribute(String attributeName, String typeName) throws Exception {
		ModelElement element = new ModelElement(getArchitecture().getMetaModel().getType(ATTRIBUTE));
		ModelElement attributetype = createOrReturnPrimitiveType(typeName);
		element.setRefAttribute("attributetype", attributetype);
		
		String xmiID = getNextAttributeXMIID();
		element.setXMIID(xmiID);
		element.setName(attributeName);
		Attribute attribute = new Attribute(element, getArchitecture());
		getAttributes().put(xmiID, attribute);
		getModelElement().addRelation(MetaModelElement.CONTEXT, attribute.getModelElement());
		return attribute;
	}

	private ModelElement createOrReturnPrimitiveType(String typeName) {
		//TODO don't duplicate
		MetaModelElement typeMetaModel = getArchitecture().getMetaModel().getType("datatype");
		ModelElement type = new ModelElement(typeMetaModel);
		type.setName(typeName);
		return type;
	}

	private String getNextAttributeXMIID() {
		String xmiID = getModelElement().getXMIID() + "a";
		int number = 0;
		String nextXmiID = "";
		do {
			number++;
			nextXmiID = xmiID + number;
		} while(getAttributes().containsKey(nextXmiID));
		return nextXmiID;
	}

	public void moveMethodToClass(Method method, Class classToMove) {
		if (!getMethods().values().contains(method)) return;
		
		removeMethod(method);
		classToMove.addExternalMethod(method);
	}

	private void addExternalMethod(Method method) {
		methods.put(method.getModelElement().getXMIID(), method);
		getModelElement().addRelation(MetaModelElement.CONTEXT, method.getModelElement());
	}
	
	public void moveAttributeToClass(Attribute attribute, Class classToMove) {
		if (!getAttributes().values().contains(attribute)) return;
		
		removeAttribute(attribute);
		classToMove.addExternalAttribute(attribute);
	}

	private void addExternalAttribute(Attribute attribute) {
		attributes.put(attribute.getModelElement().getXMIID(), attribute);
		getModelElement().addRelation(MetaModelElement.CONTEXT, attribute.getModelElement());
	}
	
	public Collection<InterClassRelationship> getRelationships() {
		Collection<InterClassRelationship> relationships = new ArrayList<InterClassRelationship>();
		
		for (InterClassRelationship relationship : getArchitecture().getInterClassRelationships()) {
			if (relationship instanceof GeneralizationInterClassRelationship){
				GeneralizationInterClassRelationship generalization = (GeneralizationInterClassRelationship) relationship;
				if (generalization.getChild().equals(this) || generalization.getParent().equals(this))
					relationships.add(generalization);
			}
			
			if (relationship instanceof DependencyInterClassRelationship){
				DependencyInterClassRelationship dependency = (DependencyInterClassRelationship) relationship;
				if (dependency.getClient().equals(this) || dependency.getSupplier().equals(this))
					relationships.add(dependency);
			}
			
			if (relationship instanceof AssociationInterClassRelationship){
				AssociationInterClassRelationship association = (AssociationInterClassRelationship) relationship;
				for (AssociationEnd associationEnd : association.getParticipants()) {
					if (associationEnd.getCLSClass().equals(this)){
						relationships.add(association);
						break;
					}
				}
			}
		}
		return relationships;
	}
}
