package pbl2011.model;


import java.util.ArrayList;
import java.util.List;

import javax.swing.event.UndoableEditEvent;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.UndoManager;

/**
 * UNDO・REDO履歴を保持するクラス.
 * 
 * 履歴管理を行う場合に使用するクラス<br>
 * インスタンスを生成したクラスに対して1つの管理系として機能する
 * 
 * @author isom
 *
 */
public class UndoRedoModel {


	private List<ClassNode> classNodeList = new ArrayList<ClassNode>();
	private List<AssociationArc> associationArcList = new ArrayList<AssociationArc>();
	private List<NodeArcSet> nodearcSetList = new ArrayList<NodeArcSet>();
	protected UndoManager undoManager = new UndoManager();

	/**
	 * コンストラクタ.
	 */
	public UndoRedoModel() {

	}
	
	/**
	 * 初期化する
	 * 
	 */
	public void initializeUndoRedoModel() {
		classNodeList = new ArrayList<ClassNode>();
		associationArcList = new ArrayList<AssociationArc>();
		nodearcSetList = new ArrayList<NodeArcSet>();
		undoManager = new UndoManager();
	}
	
	/**
	 * ClassNode型のListを返す.
	 * 
	 * @return ClassNode型リスト
	 * @see ClassNode
	 */
	public List<ClassNode> getClassNodeList() {
		return classNodeList;
	}
	
	/**
	 * AssociationArc型のListを返す.
	 * 
	 * @return AssociationArc型リスト
	 * @see AssociationArc
	 */
	public List<AssociationArc> getAssociationArcList() {
		return associationArcList;
	}
	
	/**
	 * NodeArcSet型のListを返す.
	 * 
	 * @return NodeArcSet型リスト
	 * @see NodeArcSet
	 */
	public List<NodeArcSet> getNodeArcSetList() {
		return nodearcSetList;
	}
	
	/**
	 * 直前のClassNodeオブジェクトを返す.
	 * 
	 * @return ClassNode型オブジェクト
	 * @see ClassNode
	 */
	public ClassNode getNodeLatest() {
		return classNodeList.get(classNodeList.size() - 1);
	}
	
	/**
	 * 直前のAssociationArcオブジェクトを返す.
	 * 
	 * @return AssociationArc型オブジェクト
	 * @see AssociationArc
	 */
	public AssociationArc getArcLatest() {
		return associationArcList.get(associationArcList.size() - 1);
	}
	
	/**
	 * 直前のNodeArcオブジェクトを返す.
	 * 
	 * @return NodeArcSet型オブジェクト
	 * @see AssociationArc
	 */
	public NodeArcSet getNodeArcSetLatest() {
		return nodearcSetList.get(nodearcSetList.size() - 1);
	}
	
	/**
	 * NodeArcSet型を履歴スタックに保存する.
	 * 
	 * <p>
	 * NodeArcSetに含まれるすべてのオブジェクト(ClassNode, AssociationArc)
	 * において、直近の保持内容と同一である場合は保存を行わない。<br>
	 * 保持しているデータについて何らかの変更があるものだけを保存する。
	 * </p>
	 * @param nas 保存を試すNodeArcSet型のオブジェクト
	 * @return 保存した場合はtrueを、保存しなかった場合はfalseを返す
	 */
	public boolean tryPush(NodeArcSet nas) {
		NodeArcSet nodearcSet = new NodeArcSet();
		ClassNode cloned_cn;
		AssociationArc cloned_aa;
		
		if (nas == null) {
			return false;
		}
		
		//作成されたオブジェクトがある場合はフラグをセットしておく
		//undo時にオブジェクトを削除するために必要
		if (nas.isCreate()) {
			nodearcSet.setCreateFlg(true);
		}

		//NodeArcSetオブジェクトにあるClassNodeListに対して、変更内容を調べて
		//変更がある場合は履歴に保存
		for (ClassNode cn : nas.classNodeList) {
			if (cn == null) {
				continue;
			}
			cloned_cn = cn.clone();
	
			if (nodearcSet.isCreate()) {
				cloned_cn.undoredoModel.classNodeList.clear();
			}
			
			if (cloned_cn.undoredoModel.classNodeList.size() > 0 &&
					cloned_cn.equals(cn.undoredoModel.getNodeLatest())) {
			} else {
				if (cloned_cn.undoredoModel.classNodeList.size() != 0 || nodearcSet.isCreate()) {
					nodearcSet.add(cloned_cn);
				}
				cloned_cn.undoredoModel.classNodeList.add(cloned_cn);
				cloned_cn.undoredoModel.undoManager.undoableEditHappened(new UndoableEditEvent(
						this, new ClassUndoRedoAbleModel(cloned_cn, cloned_cn.undoredoModel.getClassNodeList())));
			}
		}
		
		//NodeArcSetオブジェクトにあるAssociationArcListに対して、変更内容を調べて
		//変更がある場合は履歴に保存
		for (AssociationArc aa: nas.associationArcList) {
			if (aa == null) {
				continue;
			}
			cloned_aa = aa.clone();
			
			if (nodearcSet.isCreate()) {
				cloned_aa.undoredoModel.associationArcList.clear();
			}
			
			if (cloned_aa.undoredoModel.associationArcList.size() > 0 &&
					cloned_aa.equals(cloned_aa.undoredoModel.getArcLatest())) {
			} else {
				if (cloned_aa.undoredoModel.associationArcList.size() != 0 || nodearcSet.isCreate()) {
					nodearcSet.add(cloned_aa);
		
			}
			cloned_aa.undoredoModel.associationArcList.add(cloned_aa);
			cloned_aa.undoredoModel.undoManager.undoableEditHappened(new UndoableEditEvent(
						this, new ArcUndoRedoAbleModel(cloned_aa, cloned_aa.undoredoModel.getAssociationArcList())));
			}
		}

		//ClassNode, AssociationArcのいずれかに変更をともなうオブジェクトが存在する場合は
		//NodeArcSetを履歴に保存する
		if (!nodearcSet.isEmpty()) {
			nodearcSetList.add(nodearcSet);
			undoManager.undoableEditHappened(new UndoableEditEvent(
					this, new UndoRedoAbleModel(nodearcSet, nodearcSetList)));
			return true;
		}
		
		return false;
	}

	/**
	 * Undo可能かどうかを判定.
	 * 
	 * <p>
	 * Undoができる場合はtrue, Undoができない場合はfalseを返す
	 * </p>
	 * 
	 * @return boolean
	 */
	public boolean canUndo() {
		return undoManager.canUndo();
	}
	
	/**
	 * Redo可能かどうかを判定.
	 * 
	 * <p>
	 * Redoができる場合はtrue, Redoができない場合はfalseを返す
	 * </p>
	 * 
	 * @return boolean
	 */
	public boolean canRedo() {
		return undoManager.canRedo();
	}
	
	/**
	 * Undoを実行する.
	 */
	public void undoAction() {
		try {
			undoManager.undo();
		} catch(CannotRedoException cre) {
			cre.printStackTrace();
		}
	}
	
	/**
	 * Redoを実行する.
	 */
	public void redoAction() {
		try {
			undoManager.redo();
		} catch(CannotRedoException cre) {
			cre.printStackTrace();
		}
	}	
	
	/**
	 * クラスノードのUndo・Redoを保持するクラス.
	 * 
	 * @author isom
	 *
	 */
	class ClassUndoRedoAbleModel extends AbstractUndoableEdit {
		protected List<ClassNode> classNodeList;
		protected ClassNode classNode;
		
		/**
		 * コンストラクタ.
		 * 
		 * @param cn ClassNode型オブジェクト
		 * @param cnl ClasNode型をもつリスト
		 */
		public ClassUndoRedoAbleModel(ClassNode cn, List<ClassNode> cnl) {
			classNodeList = cnl;
			classNode = cn;
		}
		
		/**
		 * 操作名を表す.
		 * 
		 * @return 操作名
		 */
		@Override
		public String getPresentationName() {
			return "Class Undo/Redo";
		}
		
		/**
		 * undoを実行する.
		 */
		@Override
		public void undo() {
			super.undo();
			classNodeList.remove(classNodeList.size() - 1);
		}
		
		/**
		 * redoを実行する.
		 */
		@Override
		public void redo() {
			super.redo();
			classNodeList.add(classNode);
		}
	}

	/**
	 * 弧のUndo・Redoを保持するクラス.
	 * 
	 * @author isom
	 *
	 */
	class ArcUndoRedoAbleModel extends AbstractUndoableEdit {
		protected List<AssociationArc> associationArcList;
		protected AssociationArc associationArc;
		
		/**
		 * コンストラクタ.
		 * 
		 * @param aa AssociationArc型オブジェクト
		 * @param aal AssociationArc型をもつリスト
		 */
		public ArcUndoRedoAbleModel(AssociationArc aa, List<AssociationArc> aal) {
			associationArcList = aal;
			associationArc = aa;
		}
		
		/**
		 * 操作名を表す.
		 * 
		 * @return 操作名
		 */
		@Override
		public String getPresentationName() {
			return "Arc Undo/Redo";
		}

		/**
		 * undoを実行する.
		 */
		@Override
		public void undo() {
			super.undo();
			associationArcList.remove(associationArcList.size() - 1);
		}
		
		/**
		 * redoを実行する.
		 */
		@Override
		public void redo() {
			super.redo();
			associationArcList.add(associationArc);
		}
	}

	/**
	 * クラスノード、弧セットのUndo・Redoを保持するクラス.
	 * 
	 * @author isom
	 *
	 */
	class UndoRedoAbleModel extends AbstractUndoableEdit {
		protected List<NodeArcSet> nodearcSetList;
		protected NodeArcSet nodearcSet;
		
		/**
		 * コンストラクタ.
		 * 
		 * @param nas NodeArcSet型オブジェクト
		 * @param nasl NodeArc型をもつリスト
		 */
		public UndoRedoAbleModel(NodeArcSet nas, List<NodeArcSet> nasl) {
			nodearcSetList = nasl;
			nodearcSet = nas;
		}
		
		/**
		 * 操作名を表す.
		 * 
		 * @return 操作名
		 */
		@Override
		public String getPresentationName() {
			return "Class and Arc Undo/Redo";
		}
		
		/**
		 * undoを実行する.
		 */
		@Override
		public void undo() {
			super.undo();
			nodearcSetList.remove(nodearcSetList.size() - 1);
		}
		
		/**
		 * redoを実行する.
		 */
		@Override
		public void redo() {
			super.redo();
			nodearcSetList.add(nodearcSet);
		}
	}
}


