package pbl2011.gui;

import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

import javax.swing.JColorChooser;
import javax.swing.JDialog;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import pbl2011.common.CommonConst.AssocType;
import pbl2011.common.Util;
import pbl2011.model.AssociationArc;
import pbl2011.model.ClassNode;
import pbl2011.model.NodeArcSet;
import pbl2011.model.UndoRedoModel;
import pbl2011.mvc.Controller;
import pbl2011.mvc.View;

/**
 * UMLキャンバスコントローラ
 * <p>
 *
 * @author 10745104 Y.Ishii
 *
 */
public class UMLCanvasController extends Controller implements MouseListener {

	private UMLCanvas UMLView;
	public UndoRedoModel undoredoModel;
	private JColorChooser colorChooser;

	public UMLCanvasController(View v) {
		super(v);
		UMLView = (UMLCanvas) v;
		createPopMenue();
		undoredoModel = v.getModel().undoredoModel;
		colorChooser = new JColorChooser();
	}

	@Override
	public void update() {
		NodeArcSet nodearcSet = new NodeArcSet();
		for (ClassNode cn: myModel.getAllActiveClassData()) {
			nodearcSet.add(cn);
		}
		for (AssociationArc aa: myModel.getAllActiveAssociationArc()) {
			nodearcSet.add(aa);
		}
		undoredoModel.tryPush(nodearcSet);
		myView.update();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		String cmd = e.getActionCommand();
		if ("MODIFY_CLASS_COLOR".equals(cmd)) {
			executeChangeClassColor();
		} else if ("MODIFY_PACKAGE_COLOR".equals(cmd)) {
			executeChangePackageColor();
		} else if ("DEL_CLASS".equals(cmd)) {
			executeClassDelete();
		} else if ("DEL_ARC".equals(cmd)) {
			executeArcDelete();
		} else if ("RIGHT_ANGLE".equals(cmd)) {
			executeLine(AssocLineType.RIGHT_ANGLE);
		} else if ("RIGHT_ANGLE_H".equals(cmd)) {
			executeLine(AssocLineType.RIGHT_ANGLE_H);
		} else if ("STRAIGHT_LINE".equals(cmd)) {
			executeLine(AssocLineType.STRAIGHT_LINE);
		} else if ("FREE_LINE".equals(cmd)) {
			executeLine(AssocLineType.FREE_LINE);
		} else if ("ADD_POINT".equals(cmd)) {
			executeAddPoint();
		} else if ("DEL_POINT".equals(cmd)) {
			executeDelPoint();
		}
	}

	protected ArrayList<ClassNode> executeChangeClassColor() {
		
		ClassNode cn = cuMgr.getCurrentNode();
		ArrayList<ClassNode> list = new ArrayList<ClassNode>();
		list.add(cn);
		ActionListener changeColor = new ChangeClassColorListener(this, list, colorChooser);
		JDialog dialog = JColorChooser.createDialog(UMLView, "Select Color",
				true, colorChooser, changeColor, null);
		dialog.setVisible(true);
		return list;
	}

	protected ArrayList<ClassNode> executeChangePackageColor() {
		
		ClassNode node = cuMgr.getCurrentNode();
		ArrayList<ClassNode> activeNodes = UMLView.getModel().getAllActiveClassData();
		ArrayList<ClassNode> list = new ArrayList<ClassNode>();
		for (ClassNode cn :activeNodes) {
			if ((node.getPackageName() != null) && (cn.getPackageName() != null)) {
				if (node.getPackageName().equals(cn.getPackageName())) {
					list.add(cn);
				}
			} else if (node.getPackageName() == null) {
				if (cn.getPackageName() == null) {
					list.add(cn);
				}
			}
		}
		
		ActionListener changeColor = new ChangeClassColorListener(this, list, colorChooser);
		JDialog dialog = JColorChooser.createDialog(UMLView, "Select Color",
				true, colorChooser, changeColor, null);
		dialog.setVisible(true);
		return list;
	}
	protected void executeClassDelete() {

		myModel.deleteClass();
	}

	protected void executeArcDelete() {
		NodeArcSet nodearcSet = new NodeArcSet();
		AssociationArc a = cuMgr.getCurrentArc();

		a.delFlag = true;
		nodearcSet.add(a);
		undoredoModel.tryPush(nodearcSet);
		cuMgr.setCurrentArc(null);
		myModel.changeModel();

	}

	// 線種を変更する
	protected void executeLine(AssocLineType type) {
		AssociationArc a = cuMgr.getCurrentArc();
		if (type.isFREE_LINE()) {
			a.pointList = UMLView.getArcPointList(a);
		} else {
			a.pointList = null;
		}
		a.lineStyle = type;

		NodeArcSet nodearcSet = new NodeArcSet();
		nodearcSet.add(a);
		undoredoModel.tryPush(nodearcSet);

		myModel.changeModel();
	}

	// 頂点を追加
	protected void executeAddPoint() {
		UMLView.addArcPoint(popPoint);
		myModel.changeModel();
	}

	// 頂点を削除
	protected int executeDelPoint() {
		int poz = pointClickCheck(popPoint);
		if(poz > -1) {
			UMLView.delArcPoint(poz);
			myModel.changeModel();
		}
		return poz;
	}

	@Override
	public void mouseMoved(MouseEvent e) {
	}

	@Override
	public void mouseClicked(MouseEvent e) {
	}

	MouseEditState state;

	@Override
	public void mousePressed(MouseEvent e) {
		showPopup(e);
		Point pt = Util.adjust(e.getPoint());
		int no = -1;
		ClassNode node = UMLView.selectClass(pt);
		AssociationArc arc = UMLView.selectArc(e.getPoint());

		// 関連の追加モードでクラス以外をクリックした場合選択モードに戻す。
		if (State.arcSelect != null && node == null) {
			State.changeToSelectMode();
		}

		// クラスの追加モード
		if (State.classSelect) {
			if (!UMLView.mainCanvas)
				return;
			assert Util.debug("UMLView.classSelect");
			state = new FrameMoveState(UMLView, pt);
			cuMgr.setCurrentNode(UMLView.addNewClass(pt));

			NodeArcSet nodearcSet = new NodeArcSet();
			nodearcSet.add(cuMgr.getCurrentNode());
			nodearcSet.setCreateFlg(true);
			undoredoModel.tryPush(nodearcSet);
//			State.changeToSelectMode();
			clearClassAddMode();
			
		// 関連の追加モード
		} else if (State.arcSelect != null) {
			if (!UMLView.mainCanvas)
				return;
			if (node != null) {
				assert Util.debug("UMLView.arcSelect");
				state = new ArcDrawState(UMLView, node, State.arcSelect);

				NodeArcSet nodearcSet = new NodeArcSet();
				nodearcSet.add(cuMgr.getCurrentArc());
				nodearcSet.setCreateFlg(true);
				undoredoModel.tryPush(nodearcSet);

			} else {
				cuMgr.setCurrentNode(null);
				cuMgr.setCurrentArc(null);
			}

		// 複数クラスを選択し、フレーム内であれば移動
		} else if( cuMgr.getCurrentNodeList().size() > 1 &&
					UMLView.selectClass(pt, cuMgr.getCurrentNodeList()) != null	) {
				state = new FrameMoveState(UMLView, pt);

		// クラス選択中で、ノブ・クラス内をクリックした場合
		} else if( cuMgr.getCurrentNodeList().size() == 1) {
			Point orgPt;
			if (UMLView.mainCanvas) {

				if ((orgPt = knobClickCheck1(pt)) != null) { // ノブをプレスしたらリサイズ
					state = new FrameResizeState(UMLView, orgPt, pt, 1);
				} else if ((orgPt = knobClickCheck2(pt)) != null) { // ノブをプレスしたらリサイズ
					state = new FrameResizeState(UMLView, orgPt, pt, 2);
				} else if ((orgPt = knobClickCheck3(pt)) != null) { // ノブをプレスしたらリサイズ
					state = new FrameResizeState(UMLView, orgPt, pt, 3);
				} else if (node != null) {
					cuMgr.setCurrentNode(node);
					state = new FrameMoveState(UMLView, pt);
				} else if (arc != null) {
					cuMgr.setCurrentArc(arc);
				} else {
					cuMgr.setCurrentNode(null);
					cuMgr.setCurrentArc(null);
					state = new ModelSelectState(UMLView, pt);
				}
			} else {
				if (node != null) { // クラス内をプレスしたら移動
					cuMgr.setCurrentNode(node);
					state = new FrameMoveState(UMLView, pt);
				} else {
					cuMgr.setCurrentNode(null);
					cuMgr.setCurrentArc(null);
				}
			}
		// 関連選択中で、ポイントをクリックした場合
		} else if( cuMgr.getCurrentArc() != null &&
				((no = pointClickCheck(pt)) > -1)) {
			state = new PointMoveState(UMLView, pt, no);
			no = -1;

		// クラスを選択した場合
		} else if (node != null) {
			cuMgr.setCurrentNode(node);
			state = new FrameMoveState(UMLView, pt);

		// 関連線を選択した場合
		} else if (arc != null) {
			cuMgr.setCurrentArc(arc);

		// それ以外の場合、複数選択
		} else {
			cuMgr.setCurrentNode(null);
			cuMgr.setCurrentArc(null);
			state = new ModelSelectState(UMLView, pt);
		}

		UMLView.repaint();
	}

	protected void clearClassAddMode(){
		State.changeToSelectMode();
	}
	
	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {

		if ( state instanceof FrameMoveState )
				state = null;
	}

	@Override
	public void mouseDragged(MouseEvent me) {
		if (state == null)
			return;
		state.mouseDragged(me);
	}

	@Override
	public void mouseReleased(MouseEvent me) {

		showPopup(me);
		if (state == null)
			return;

		if (cuMgr.getCurrentNode() != null) {
			NodeArcSet nodearcSet = new NodeArcSet();
			ArrayList<AssociationArc> arclist = myModel.getAllActiveAssociationArc();
			ClassNode cn = cuMgr.getCurrentNode();
			nodearcSet.add(cn);
			for (AssociationArc a: arclist) {
				if (a.classIdTarget == cn.classId || a.classIdSource == cn.classId) {
					nodearcSet.add(a);
				}
			}
			undoredoModel.tryPush(nodearcSet);
		}

		state.mouseReleased(me);
		myModel.changeModel();
	}

	JPopupMenu popClass;
	JPopupMenu popArc;

	private void createPopMenue() {
		popClass = new JPopupMenu();
		popArc = new JPopupMenu();

		JMenu colorModify = new JMenu("色の変更");
		JMenuItem colorModifyClassItem = new JMenuItem("クラス色");
		colorModifyClassItem.setActionCommand("MODIFY_CLASS_COLOR");
		colorModifyClassItem.addActionListener(this);
		JMenuItem colorModifyPackageItem = new JMenuItem("パッケージ色");
		colorModifyPackageItem.setActionCommand("MODIFY_PACKAGE_COLOR");
		colorModifyPackageItem.addActionListener(this);
		colorModify.add(colorModifyClassItem);
		colorModify.add(colorModifyPackageItem);
		
		popClass.add(colorModify);
		JMenuItem delClassItem = new JMenuItem("削除");
		delClassItem.setActionCommand("DEL_CLASS");
		delClassItem.addActionListener(this);
		popClass.add(delClassItem);

		JMenu rightAngle = new JMenu("直角にする");
		JMenuItem rightAngleV = new JMenuItem("縦優先");
		rightAngleV.setActionCommand("RIGHT_ANGLE");
		rightAngleV.addActionListener(this);
		JMenuItem rightAngleH = new JMenuItem("横優先");
		rightAngleH.setActionCommand("RIGHT_ANGLE_H");
		rightAngleH.addActionListener(this);
		rightAngle.add(rightAngleV);
		rightAngle.add(rightAngleH);

		JMenuItem straightLine = new JMenuItem("直線にする");
		straightLine.setActionCommand("STRAIGHT_LINE");
		straightLine.addActionListener(this);
		JMenuItem freeLine = new JMenuItem("自由につなぐ");
		freeLine.setActionCommand("FREE_LINE");
		freeLine.addActionListener(this);
		addPoint = new JMenuItem("頂点を追加");
		addPoint.setActionCommand("ADD_POINT");
		addPoint.addActionListener(this);
		delPoint = new JMenuItem("頂点を削除");
		delPoint.setActionCommand("DEL_POINT");
		delPoint.addActionListener(this);
		JMenuItem delArcItem = new JMenuItem("削除");
		delArcItem.setActionCommand("DEL_ARC");
		delArcItem.addActionListener(this);
		popArc.add(straightLine);
		popArc.add(rightAngle);
		popArc.add(freeLine);
		popArc.add(addPoint);
		popArc.add(delPoint);
		popArc.add(delArcItem);
	}

	JMenuItem addPoint;
	JMenuItem delPoint;
	Point popPoint;
	private void showPopup(MouseEvent e) {

		if (e.isPopupTrigger()) {
			popPoint = e.getPoint();
			if (cuMgr.getCurrentNode() != null
					&& cuMgr.getCurrentNode().equals(UMLView.selectClass(popPoint))) {
				popClass.show(e.getComponent(), e.getX(), e.getY());
			} else if (cuMgr.getCurrentArc() != null
					&& cuMgr.getCurrentArc().equals(UMLView.selectArc(popPoint))) {

				// 頂点の追加と削除は、操作が可能なときだけ活性にする。
				addPoint.setEnabled(false);
				delPoint.setEnabled(false);
				if(cuMgr.getCurrentArc().lineStyle.isFREE_LINE()) {
					addPoint.setEnabled(true);
					if(cuMgr.getCurrentArc().pointList.length > 2) {
						delPoint.setEnabled(true);
					}
				}
				popArc.show(e.getComponent(), e.getX(), e.getY());
			}
		}
	}


	private Point knobClickCheck1(Point p) {

		ClassNode current = cuMgr.getCurrentNode();
		assert Util.debug("knobClickCheck1");
		if (hitKnob(p, current.p.x, current.p.y)) {
			return new Point(current.p.x + current.width, current.p.y
					+ current.height1);
		}

		if (hitKnob(p, current.p.x, current.p.y + current.height1)) {
			return new Point(current.p.x + current.width, current.p.y);
		}

		if (hitKnob(p, current.p.x + current.width, current.p.y)) {
			return new Point(current.p.x, current.p.y + current.height1);
		}

		if (hitKnob(p, current.p.x + current.width, current.p.y
				+ current.height1)) {
			return new Point(current.p.x, current.p.y);
		}

		return null;

	}

	private Point knobClickCheck2(Point p) {
		ClassNode current = cuMgr.getCurrentNode();
		assert Util.debug("knobClickCheck2");
		if (hitKnob(p, current.p.x, current.p.y + current.height1
				+ current.height2)) {
			return new Point(current.p.x + current.width, current.p.y
					+ current.height1);
		}
		if (hitKnob(p, current.p.x + current.width, current.p.y
				+ current.height1 + current.height2)) {
			return new Point(current.p.x, current.p.y + current.height1);
		}
		return null;

	}

	private Point knobClickCheck3(Point p) {
		ClassNode current = cuMgr.getCurrentNode();
		assert Util.debug("knobClickCheck3");
		if (hitKnob(p, current.p.x, current.p.y + current.height())) {
			return new Point(current.p.x + current.width, current.p.y
					+ current.height1 + current.height2);
		}
		if (hitKnob(p, current.p.x + current.width, current.p.y
				+ current.height())) {
			return new Point(current.p.x, current.p.y + current.height1
					+ current.height2);
		}
		return null;

	}

	private boolean hitKnob(Point p, int x, int y) {
		int z = KNOB_SIZE;
		Rectangle knobRect = new Rectangle(x - z / 2, y - z / 2, z, z);

		return knobRect.contains(p);
	}

	private int pointClickCheck(Point p) {

		AssociationArc current = cuMgr.getCurrentArc();
		if (current != null && current.lineStyle.isFREE_LINE()) {
			Point[] pList = current.pointList;
			for(int i = 0; i< pList.length; i++) {
				if (hitPoint(p, pList[i].x,pList[i].y)) {
					return i;
				}
			}
		}
		return -1;
	}

	private boolean hitPoint(Point p, int x, int y) {
		int z = POINT_SIZE;
		Rectangle pointRect = new Rectangle(x - z / 2, y - z / 2, z, z);

		return pointRect.contains(p);
	}

}

/**
 * マウスを使った個々の編集状態におけるマウス操作メソッド
 */
abstract class MouseEditState {
	UMLCanvas UMLView;
	Point currPt; // ドラッブ中のマウス位置
	MouseEditState(UMLCanvas UMLView, Point currPt) {
		this.UMLView = UMLView;
		this.currPt = currPt;

	}
	abstract void mouseDragged(MouseEvent me);

	abstract void mouseReleased(MouseEvent me);
}

/**
 * UMLクラス図の範囲選択
 */
class ModelSelectState extends MouseEditState {

	public ModelSelectState(UMLCanvas view, Point currPt) {
		super(view, currPt);
		UMLView.setStartPoint(currPt);
	}

	@Override
	public void mouseDragged(MouseEvent me) {
		Point pt = me.getPoint();
		if (pt != null && !pt.equals(currPt)) {
			UMLView.setEndPoint( pt );
			UMLView.repaint();
		}
	}

	@Override
	public void mouseReleased(MouseEvent me) {
		UMLView.cuMgr.selectStatus();
	}
}

/**
 * フレームの移動状態の処理
 */
class FrameMoveState extends MouseEditState {

	public FrameMoveState(UMLCanvas UMLView, Point currPt) {
		super(UMLView,currPt);
	}

	@Override
	public void mouseDragged(MouseEvent me) {
		Point pt = Util.adjust(me.getPoint());
		if (pt != null && !pt.equals(currPt)) {

			UMLView.moveFrame(pt.x - currPt.x, pt.y - currPt.y);
			currPt = pt;
		}
	}

	@Override
	public void mouseReleased(MouseEvent me) {
	}
}

/**
 * フレームのリサイズ状態の処理
 */
class FrameResizeState extends MouseEditState {
	protected Point orgPt; // リサイズ／新規作成時の固定点
	int poz;
	int side;

	final int LEFT = 1;
	final int RIGHT = 2;

	public FrameResizeState(UMLCanvas UMLView, Point orgPt, Point currPt,
			int poz) {
		super(UMLView,currPt);
		this.orgPt = orgPt;
		this.poz = poz;
		if (orgPt.x < currPt.x) {
			this.side = RIGHT;
		} else {
			this.side = LEFT;
		}
	}

	@Override
	public void mouseDragged(MouseEvent me) {
		Point pt = Util.adjust(me.getPoint());
		UMLView.dragFrame(orgPt, currPt, pt, poz, side);

		if (pt != null && !pt.equals(currPt)) {
			currPt = pt;
		}
	}

	@Override
	public void mouseReleased(MouseEvent me) {

	}
}

/**
 * 関連線追加の処理
 */
class ArcDrawState extends MouseEditState {
	private ClassNode orgNode; // リサイズ／新規作成時の固定点
	protected AssocType kind; // 関連

	public ArcDrawState(UMLCanvas UMLView, ClassNode orgNode, AssocType kind) {
		super(UMLView,null);
		this.orgNode = orgNode;
		this.kind = kind;
		if (orgNode != null) {
			currPt = new Point(orgNode.p.x + (orgNode.width / 2),
					orgNode.p.y + ((orgNode.height()) / 2));
		}
	}

	@Override
	public void mouseDragged(MouseEvent me) {
		Point pt = Util.adjust(me.getPoint());
		UMLView.setCurrPoint(currPt, pt);

	}

	@Override
	public void mouseReleased(MouseEvent me) {
		Point pt = me.getPoint();
		ClassNode node = UMLView.selectClass(pt);
		UMLView.setCurrPoint(null, null);
		if (node != null && !node.equals(orgNode)) {
			AssociationArc aa = UMLView.addNewArc(orgNode, node, kind);
			NodeArcSet nodearcSet = new NodeArcSet();
			nodearcSet.add(aa);
			nodearcSet.setCreateFlg(true);
			UMLView.getModel().undoredoModel.tryPush(nodearcSet);

		}
	}
}


/**
 * 関連線移動の処理
 */
class PointMoveState extends MouseEditState {
	protected int no;
	public PointMoveState(UMLCanvas UMLView, Point p, int no) {
		super(UMLView, p);
		this.no = no;
	}

	@Override
	public void mouseDragged(MouseEvent me) {
		Point pt = Util.adjust(me.getPoint());
		UMLView.moveArcPoint(pt,no);
	}

	@Override
	public void mouseReleased(MouseEvent me) {
	}
}

/**
 * クラス図の色を決定した場合の処理
 */
class ChangeClassColorListener implements ActionListener {
	JColorChooser colorChooser;
	Controller controller;
	ArrayList<ClassNode> nodeList;
	ChangeClassColorListener(Controller controller, ArrayList<ClassNode> nodeList, 
			JColorChooser colorChooser) {
		this.controller = controller;
		this.nodeList = nodeList;
		this.colorChooser = colorChooser;
	}

	public void actionPerformed(ActionEvent e) {
		Color color = colorChooser.getColor();
		for (ClassNode cn: nodeList) {
			cn.colorRed = color.getRed();
			cn.colorGreen = color.getGreen();
			cn.colorBlue = color.getBlue();
		}
		controller.update();
	}
}
