/**
 * Copyright (c) 2013 committers of YAKINDU and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * Contributors:
 * 	committers of YAKINDU - initial API and implementation
 * 
 */
package org.yakindu.sct.refactoring.refactor.impl;

import java.util.List;

import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.core.services.ViewService;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.notation.Bounds;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationFactory;
import org.eclipse.gmf.runtime.notation.View;
import org.yakindu.sct.model.sgraph.Region;
import org.yakindu.sct.model.sgraph.SGraphFactory;
import org.yakindu.sct.model.sgraph.State;
import org.yakindu.sct.model.sgraph.Vertex;
import org.yakindu.sct.refactoring.refactor.AbstractRefactoring;
import org.yakindu.sct.ui.editor.DiagramActivator;
import org.yakindu.sct.ui.editor.editparts.StateEditPart;
import org.yakindu.sct.ui.editor.providers.SemanticHints;

import com.google.common.collect.Lists;
/**
 * Groups a set of states into one composite state. All transitions to and from this set of state are preserved, i.e.
 * the new composite state itself will have no incoming or outgoing transitions.
 * <br><br>
 * Context:
 * <ul>
 * <li>A set of states</li>
 * </ul>
 * Preconditions:
 * <ul>
 * <li>All states are in the same region.</li>
 * </ul>
 * @author thomas kutz - Initial contribution and API
 * 
 */
public class GroupStatesIntoCompositeRefactoring extends
		AbstractRefactoring<GraphicalEditPart> {

	private View parentRegionView;
	private PreferencesHint preferencesHint = DiagramActivator.DIAGRAM_PREFERENCES_HINT;

	private final int PADDING = 55;
	private State compositeState;
	private Region innerRegion;
	
	protected List<State> contextStates;

	@Override
	protected void internalExecute() {
		doSemanticalRefactoring();
		doGraphicalRefactoring();
	}

	@Override
	public boolean isExecutable() {
		return super.isExecutable() && allStatesHaveSameParentRegion();
	}

	protected void doSemanticalRefactoring() {
		compositeState = createCompositeState();
		
		innerRegion = SGraphFactory.eINSTANCE.createRegion();
		innerRegion.setName("inner region"); // TODO check for uniqueness?
		compositeState.getRegions().add(innerRegion);
		
		for (State state : contextStates) {
			innerRegion.getVertices().add(state);
		}
	}

	protected void doGraphicalRefactoring() {
		Node compositeStateView = createNodeForCompositeState(compositeState);
		Node innerRegionNode = ViewService.createNode(
				getStateFigureCompartmentView(compositeStateView), innerRegion,
				SemanticHints.REGION, preferencesHint);
		
		View regionCompartment = ViewUtil.getChildBySemanticHint(innerRegionNode, SemanticHints.REGION_COMPARTMENT);
		moveSelectedStateNodesTo(regionCompartment, (Bounds)compositeStateView.getLayoutConstraint());
	}

	protected State createCompositeState() {
		State compositeState = SGraphFactory.eINSTANCE.createState();
		compositeState.setName(getNameForCompositeState());
		getParentRegion().getVertices().add(compositeState);
		return compositeState;
	}

	protected Node createNodeForCompositeState(State compositeState) {
		Node compositeStateNode = ViewService.createNode(parentRegionView,
				compositeState, SemanticHints.STATE, preferencesHint);
		setCompositeStateLayoutConstraint(compositeStateNode);
		return compositeStateNode;
	}

	protected void setContextStates() {
		contextStates = Lists.newArrayList();
		List<GraphicalEditPart> contextObjects = getContextObjects();
		for (GraphicalEditPart editPart : contextObjects) {
			EObject element = editPart.resolveSemanticElement();
			contextStates.add((State) element);
		}
	}
	
	@Override
	public void setContextObjects(List<GraphicalEditPart> contextObject) {
		super.setContextObjects(contextObject);
		setContextStates();
	}
	
	protected void moveSelectedStateNodesTo(View containerView, Bounds compositeBounds) {

		for (GraphicalEditPart editPart : getContextObjects()) {
			Node stateNode = (Node)editPart.getNotationView();
			ViewUtil.insertChildView(containerView, stateNode, ViewUtil.APPEND, true);
			Bounds newBounds = NotationFactory.eINSTANCE.createBounds();
			Bounds oldBounds = (Bounds)stateNode.getLayoutConstraint();
			newBounds.setX(oldBounds.getX() - compositeBounds.getX() - 7); //FIXME use bounds of region view
			newBounds.setY(oldBounds.getY() - compositeBounds.getY() - 34); //FIXME use bounds of region view
			
			((Node)editPart.getNotationView()).setLayoutConstraint(newBounds);
		}
	}
	
	/**
	 * Iterates through all {@link StateEditPart}s of the current selection and
	 * computes layout constraints for the composite node.
	 * 
	 * @param compositeStateNode
	 *            node of the composite state
	 */
	protected void setCompositeStateLayoutConstraint(Node compositeStateNode) {

		Rectangle newbounds = null;
		
		for (GraphicalEditPart editPart : getContextObjects()) {
			Rectangle childBounds = editPart.getFigure().getBounds();
			if (newbounds == null)
				newbounds = childBounds.getCopy();
			
			newbounds.union(childBounds);
		}
		newbounds.expand(new Insets(PADDING, PADDING, PADDING, PADDING));

		Bounds bounds = NotationFactory.eINSTANCE.createBounds();
		bounds.setX(newbounds.x);
		bounds.setY(newbounds.y);
		bounds.setWidth(newbounds.width);
		bounds.setHeight(newbounds.height);
		compositeStateNode.setLayoutConstraint(bounds);
	}

	protected View getStateFigureCompartmentView(Node compositeStateView) {
		return ViewUtil.getChildBySemanticHint(compositeStateView,
				SemanticHints.STATE_FIGURE_COMPARTMENT);
	}

	protected String getNameForCompositeState() {
		StringBuilder nameBuilder = new StringBuilder("Composite");
		for (State state : contextStates) {
			nameBuilder.append("_");
			nameBuilder.append(state.getName());
		}
		makeNameUnique(nameBuilder);	
		return nameBuilder.toString();
	}

	protected void makeNameUnique(StringBuilder nameBuilder) {
		int index = 2;
		List<String> existingStateNames = Lists.newArrayList();
		EList<Vertex> vertices = getParentRegion().getVertices();
		for (Vertex vertex : vertices) {
			existingStateNames.add(vertex.getName());
		}
		while (existingStateNames.contains(nameBuilder.toString())) {
			nameBuilder.append(index++);
		}
	}

	protected Region getParentRegion() {
		return contextStates.get(0).getParentRegion();
	}

	protected boolean allStatesHaveSameParentRegion() {
		parentRegionView = null;
		for (IGraphicalEditPart editPart : getContextObjects()) {
			if (parentRegionView == null) {
				parentRegionView = (Node) ((Node)editPart.getNotationView()).eContainer();
			} else {
				Node nextParentRegion = (Node) ((Node)editPart.getNotationView()).eContainer();
				if (!nextParentRegion.equals(parentRegionView)) {
					return false;
				}
			}
		}
		return true;
	}

	@Override
	protected String getCommandLabel() {
		return "Group states into composite state";
	}
	
	@Override
	protected Resource getResource() {
		return getContextObject().resolveSemanticElement().eResource();
	}

}
