/*******************************************************************************
 * Copyright (c) 2000, 2010 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.gef.examples.logicdesigner.model.commands;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.eclipse.draw2d.geometry.Rectangle;

import org.eclipse.gef.commands.Command;

import org.eclipse.gef.examples.logicdesigner.LogicMessages;
import org.eclipse.gef.examples.logicdesigner.model.AndGate;
import org.eclipse.gef.examples.logicdesigner.model.Circuit;
import org.eclipse.gef.examples.logicdesigner.model.GroundOutput;
import org.eclipse.gef.examples.logicdesigner.model.LED;
import org.eclipse.gef.examples.logicdesigner.model.LiveOutput;
import org.eclipse.gef.examples.logicdesigner.model.LogicDiagram;
import org.eclipse.gef.examples.logicdesigner.model.LogicFlowContainer;
import org.eclipse.gef.examples.logicdesigner.model.LogicGuide;
import org.eclipse.gef.examples.logicdesigner.model.LogicLabel;
import org.eclipse.gef.examples.logicdesigner.model.LogicSubpart;
import org.eclipse.gef.examples.logicdesigner.model.OrGate;
import org.eclipse.gef.examples.logicdesigner.model.Wire;
import org.eclipse.gef.examples.logicdesigner.model.WireBendpoint;
import org.eclipse.gef.examples.logicdesigner.model.XORGate;

public class CloneCommand extends Command {

	private List parts, newTopLevelParts, newConnections;
	private LogicDiagram parent;
	private Map bounds, indices, connectionPartMap;
	private ChangeGuideCommand vGuideCommand, hGuideCommand;
	private LogicGuide hGuide, vGuide;
	private int hAlignment, vAlignment;

	public CloneCommand() {
		super(LogicMessages.CloneCommand_Label);
		parts = new LinkedList();
	}

	public void addPart(LogicSubpart part, Rectangle newBounds) {
		parts.add(part);
		if (bounds == null) {
			bounds = new HashMap();
		}
		bounds.put(part, newBounds);
	}

	public void addPart(LogicSubpart part, int index) {
		parts.add(part);
		if (indices == null) {
			indices = new HashMap();
		}
		indices.put(part, new Integer(index));
	}

	protected void clonePart(LogicSubpart oldPart, LogicDiagram newParent,
			Rectangle newBounds, List newConnections, Map connectionPartMap,
			int index) {
		LogicSubpart newPart = null;

		if (oldPart instanceof AndGate) {
			newPart = new AndGate();
		} else if (oldPart instanceof Circuit) {
			newPart = new Circuit();
		} else if (oldPart instanceof GroundOutput) {
			newPart = new GroundOutput();
		} else if (oldPart instanceof LED) {
			newPart = new LED();
			newPart.setPropertyValue(LED.P_VALUE,
					oldPart.getPropertyValue(LED.P_VALUE));
		} else if (oldPart instanceof LiveOutput) {
			newPart = new LiveOutput();
		} else if (oldPart instanceof LogicLabel) {
			newPart = new LogicLabel();
			((LogicLabel) newPart).setLabelContents(((LogicLabel) oldPart)
					.getLabelContents());
		} else if (oldPart instanceof OrGate) {
			newPart = new OrGate();
		} else if (oldPart instanceof LogicFlowContainer) {
			newPart = new LogicFlowContainer();
		} else if (oldPart instanceof XORGate) {
			newPart = new XORGate();
		}

		if (oldPart instanceof LogicDiagram) {
			Iterator i = ((LogicDiagram) oldPart).getChildren().iterator();
			while (i.hasNext()) {
				// for children they will not need new bounds
				clonePart((LogicSubpart) i.next(), (LogicDiagram) newPart,
						null, newConnections, connectionPartMap, -1);
			}
		}

		Iterator i = oldPart.getTargetConnections().iterator();
		while (i.hasNext()) {
			Wire connection = (Wire) i.next();
			Wire newConnection = new Wire();
			newConnection.setValue(connection.getValue());
			newConnection.setTarget(newPart);
			newConnection.setTargetTerminal(connection.getTargetTerminal());
			newConnection.setSourceTerminal(connection.getSourceTerminal());
			newConnection.setSource(connection.getSource());

			Iterator b = connection.getBendpoints().iterator();
			Vector newBendPoints = new Vector();

			while (b.hasNext()) {
				WireBendpoint bendPoint = (WireBendpoint) b.next();
				WireBendpoint newBendPoint = new WireBendpoint();
				newBendPoint.setRelativeDimensions(
						bendPoint.getFirstRelativeDimension(),
						bendPoint.getSecondRelativeDimension());
				newBendPoint.setWeight(bendPoint.getWeight());
				newBendPoints.add(newBendPoint);
			}

			newConnection.setBendpoints(newBendPoints);
			newConnections.add(newConnection);
		}

		if (index < 0) {
			newParent.addChild(newPart);
		} else {
			newParent.addChild(newPart, index);
		}

		newPart.setSize(oldPart.getSize());

		if (newBounds != null) {
			newPart.setLocation(newBounds.getTopLeft());
		} else {
			newPart.setLocation(oldPart.getLocation());
		}

		// keep track of the new parts so we can delete them in undo
		// keep track of the oldpart -> newpart map so that we can properly
		// attach
		// all connections.
		if (newParent == parent)
			newTopLevelParts.add(newPart);
		connectionPartMap.put(oldPart, newPart);
	}

	public void execute() {
		connectionPartMap = new HashMap();
		newConnections = new LinkedList();
		newTopLevelParts = new LinkedList();

		Iterator i = parts.iterator();

		LogicSubpart part = null;
		while (i.hasNext()) {
			part = (LogicSubpart) i.next();
			if (bounds != null && bounds.containsKey(part)) {
				clonePart(part, parent, (Rectangle) bounds.get(part),
						newConnections, connectionPartMap, -1);
			} else if (indices != null && indices.containsKey(part)) {
				clonePart(part, parent, null, newConnections,
						connectionPartMap,
						((Integer) indices.get(part)).intValue());
			} else {
				clonePart(part, parent, null, newConnections,
						connectionPartMap, -1);
			}
		}

		// go through and set the source of each connection to the proper
		// source.
		Iterator c = newConnections.iterator();

		while (c.hasNext()) {
			Wire conn = (Wire) c.next();
			LogicSubpart source = conn.getSource();
			if (connectionPartMap.containsKey(source)) {
				conn.setSource((LogicSubpart) connectionPartMap.get(source));
				conn.attachSource();
				conn.attachTarget();
			}
		}

		if (hGuide != null) {
			hGuideCommand = new ChangeGuideCommand(
					(LogicSubpart) connectionPartMap.get(parts.get(0)), true);
			hGuideCommand.setNewGuide(hGuide, hAlignment);
			hGuideCommand.execute();
		}

		if (vGuide != null) {
			vGuideCommand = new ChangeGuideCommand(
					(LogicSubpart) connectionPartMap.get(parts.get(0)), false);
			vGuideCommand.setNewGuide(vGuide, vAlignment);
			vGuideCommand.execute();
		}
	}

	public void setParent(LogicDiagram parent) {
		this.parent = parent;
	}

	public void redo() {
		for (Iterator iter = newTopLevelParts.iterator(); iter.hasNext();)
			parent.addChild((LogicSubpart) iter.next());
		for (Iterator iter = newConnections.iterator(); iter.hasNext();) {
			Wire conn = (Wire) iter.next();
			LogicSubpart source = conn.getSource();
			if (connectionPartMap.containsKey(source)) {
				conn.setSource((LogicSubpart) connectionPartMap.get(source));
				conn.attachSource();
				conn.attachTarget();
			}
		}
		if (hGuideCommand != null)
			hGuideCommand.redo();
		if (vGuideCommand != null)
			vGuideCommand.redo();
	}

	public void setGuide(LogicGuide guide, int alignment, boolean isHorizontal) {
		if (isHorizontal) {
			hGuide = guide;
			hAlignment = alignment;
		} else {
			vGuide = guide;
			vAlignment = alignment;
		}
	}

	public void undo() {
		if (hGuideCommand != null)
			hGuideCommand.undo();
		if (vGuideCommand != null)
			vGuideCommand.undo();
		for (Iterator iter = newTopLevelParts.iterator(); iter.hasNext();)
			parent.removeChild((LogicSubpart) iter.next());
	}

}
