package jp.hasc.hasctool.ui.bdeditor2.model.commands;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.hasc.hasctool.core.blockdiagram.model.AbstractBlock;
import jp.hasc.hasctool.core.blockdiagram.model.BeanBlock;
import jp.hasc.hasctool.core.blockdiagram.model.BlockDiagram;
import jp.hasc.hasctool.core.blockdiagram.model.Connection;
import jp.hasc.hasctool.core.blockdiagram.model.PortReference;
import jp.hasc.hasctool.core.blockdiagram.model.Shape;
import jp.hasc.hasctool.ui.bdeditor2.BlockDiagramEditor;
import jp.hasc.hasctool.ui.bdeditor2.model.BlockDiagramElement;
import jp.hasc.hasctool.ui.bdeditor2.model.BlockElement;
import jp.hasc.hasctool.ui.bdeditor2.model.ConnectionElement;
import jp.hasc.hasctool.ui.bdeditor2.model.ModelElement;
import jp.hasc.hasctool.ui.bdeditor2.model.ShapeElement;

import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;

/**
 * @author iwasaki
 */
public class BlockDiagramPasteCommand extends Command {
	/** logger for this class */
	private final static org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory
			.getLog(BlockDiagramPasteCommand.class);
	
	private BlockDiagramEditor editor_;
	private BlockDiagram srcbd_;
	private boolean executed_=false;
	
	public BlockDiagramPasteCommand(BlockDiagramEditor editor,
			BlockDiagram contents) {
		super();
		editor_ = editor;
		srcbd_ = BlockDiagramEditor.cloneByXStream(contents);
	}
	
	@Override
	public boolean canExecute() {
		return executed_==false;
	}
	
	@Override
	public boolean canUndo() {
		return executed_==true;
	}
	
	@Override
	public void execute() {
		redo();
	}
	
	@Override
	public void redo() {
		LOG.debug("paste");
		executed_=true;
		
		//
		HashMap<String, String> renameMap = new HashMap<String, String>();
		addedModelElements_ = new ArrayList<ModelElement>();
		
		//
		BlockDiagramElement destbde = editor_.getModel();
		Rectangle destbounds = BlockDiagramEditor.getContentsBounds(destbde.getBlockDiagram());
		Rectangle srcbounds = BlockDiagramEditor.getContentsBounds(srcbd_);
		jp.hasc.hasctool.core.blockdiagram.model.Point offset = 
			BlockDiagramElement.toModelPosition(new Point(
					BlockDiagramElement.GRID_SIZE-srcbounds.x, 
					BlockDiagramElement.GRID_SIZE+destbounds.bottom()-srcbounds.y),true);
		
		// blocks
		for(AbstractBlock ab:srcbd_.getBlocks()) {
			if (ab instanceof BeanBlock) {
				BeanBlock bb = (BeanBlock) ab;
				// name
				String srcname=bb.getName();
				String destname=getDestBlockName(destbde, srcname);
				renameMap.put(srcname, destname);
				// put
				bb.setName(destname);
				jp.hasc.hasctool.core.blockdiagram.model.Point op = bb.getViewPosition();
				if (op==null) op=BlockDiagramEditor.ZERO_POINT;
				bb.setViewPosition(new jp.hasc.hasctool.core.blockdiagram.model.Point(
						op.getX()+offset.getX(), op.getY()+offset.getY()));
				BlockElement newShape=new BlockElement();
				newShape.init(destbde, bb);
				destbde.addBlock(newShape);
				addedModelElements_.add(newShape);
			}else{
				LOG.warn("Unsupported class:"+ab);
			}
		}
		
		// shaoes
		for(Shape s:srcbd_.getShapes()) {
			// put
			jp.hasc.hasctool.core.blockdiagram.model.Point op = s.getViewPosition();
			if (op==null) op=BlockDiagramEditor.ZERO_POINT;
			s.setViewPosition(new jp.hasc.hasctool.core.blockdiagram.model.Point(
					op.getX()+offset.getX(), op.getY()+offset.getY()));
			ShapeElement newShape=ShapeElement.newInstance(destbde, s);
			destbde.addShape(newShape);
			addedModelElements_.add(newShape);
		}
		
		// connections
		for(Connection c:srcbd_.getConnections()) {
			// rename
			String obname = renameMap.get(c.getOutputPortReference().getBlockName());
			String ibname = renameMap.get(c.getInputPortReference().getBlockName());
			if (obname==null || ibname==null) continue;
			c.setOutputPortReference(new PortReference(obname, c.getOutputPortReference().getPortName()));
			c.setInputPortReference(new PortReference(ibname, c.getInputPortReference().getPortName()));
			// put
			BlockElement obe = destbde.getBlockObjMap().get(obname);
			BlockElement ibe = destbde.getBlockObjMap().get(ibname);
			if (obe==null || ibe==null) continue;
			ConnectionElement newShape=new ConnectionElement();
			newShape.init(destbde, obe, ibe, c); // ここでreconnectされる
			addedModelElements_.add(newShape);
		}
		
		// select
		editor_.selectModelElements(addedModelElements_);
	}
	
	private static Pattern PAT_NAME=Pattern.compile("(.+)(\\d+)");

	private ArrayList<ModelElement> addedModelElements_;
	
	private String getDestBlockName(BlockDiagramElement destbde, String srcname) {
		Map<String, BlockElement> srcnames = destbde.getBlockObjMap();
		if (!srcnames.containsKey(srcname)) {
			return srcname;
		}
		String basename;
		int index;
		Matcher m = PAT_NAME.matcher(srcname);
		if (m.matches()) {
			basename=m.group(1);
			index=Integer.parseInt(m.group(2))+1;
		}else{
			basename=srcname;
			index=2;
		}
		while(true) {
			String name=basename+index;
			if (!srcnames.containsKey(name)) return name;
			++index;
		}
	}
	
	@Override
	public void undo() {
		LOG.debug("undo paste");
		executed_=false;
		//
		if (addedModelElements_==null) return;
		BlockDiagramElement destbde = editor_.getModel();
		for(ModelElement e:addedModelElements_) {
			if (e instanceof ConnectionElement) {
				ConnectionElement ce = (ConnectionElement) e;
				ce.disconnect();
			}else if (e instanceof BlockElement) {
				destbde.removeBlock((BlockElement)e);
			}else if (e instanceof ShapeElement) {
				destbde.removeShape((ShapeElement)e);
			}
		}
		addedModelElements_=null;
	}
	
}
