package br.uem.din.architectureEvolution.caseStudy;

import java.util.Collection;

import br.uem.din.architectureEvolution.representation.Architecture;
import br.uem.din.architectureEvolution.representation.Component;
import br.uem.din.architectureEvolution.representation.Concern;
import br.uem.din.architectureEvolution.representation.Element;
import br.uem.din.architectureEvolution.representation.Interface;
import br.uem.din.architectureEvolution.representation.Operation;

public abstract class PLAMutation {
	
	protected Concern concern;
	private Architecture architecture;
	private Component component;
	
	abstract <T extends Element> boolean canMutate(T element); 

	public PLAMutation(Architecture architecture, String concernName) throws Exception {
		this.architecture = architecture;
		concern = architecture.getOrCreateConcernByName(concernName);
		component = architecture.createComponent(concernName + "Component");
		component.addConcern(concernName);
	}

	public Architecture execute() throws Exception {
		for (Interface interface_ : architecture.getInterfaces().values().toArray(new Interface[0]))
			if (canMutate(interface_)) 
				moveImplementedInterfaceToComponent(interface_);
		
		for (Interface interface_ : architecture.getInterfaces().values().toArray(new Interface[0])) 
			moveOperationsWithConcern(interface_);
		
		return architecture;
	}

	private void moveOperationsWithConcern(Interface interface_) throws Exception {
		for (Operation operation : interface_.getOperations().toArray(new Operation[0])) {
			if (canMutate(operation)){
				Interface targetInterface = getRandomImplementedInterface();
				interface_.moveOperationToInterface(operation, targetInterface);
				for (Component dependent : interface_.getDependents()) {
					architecture.addRequiredInterfaceToComponent(targetInterface, dependent);
				}
			}
		}
		
		if (interface_.getOperations().size() == 0){
			Collection<Component> implementors = interface_.getImplementors();
			for (Component component : implementors) 
				if (component.getImplementedInterfaces().size() == 1)
					architecture.removeComponent(component);
			
			architecture.removeInterface(interface_);
		}
	}

	private Interface getRandomImplementedInterface() throws Exception {
		for (Interface interface_ : component.getImplementedInterfaces()) 
			return interface_;
		
		Interface concernInterface = component.getArchitecture().createInterface(concern.getName() + "Interface");
		concernInterface.addConcern(concern.getName());
		component.getArchitecture().addImplementedInterfaceToComponent(concernInterface, component);
		
		return concernInterface;
	}

	private void moveImplementedInterfaceToComponent(Interface interface_) throws Exception {
		for (Component implementor : interface_.getImplementors()) {
			architecture.removeImplementedInterfaceFromComponent(interface_, implementor);
			architecture.addRequiredInterfaceToComponent(interface_, implementor);
		}
		architecture.addImplementedInterfaceToComponent(interface_, component);
	}
}
