package pbl2011.mvc;

import java.awt.Font;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.swing.JOptionPane;

import pbl2011.common.BuiltInClass;
import pbl2011.common.CommonConst;
import pbl2011.common.FileUtil;
import pbl2011.common.JavaCodeGenerator;
import pbl2011.common.Util;
import pbl2011.model.AssociationArc;
import pbl2011.model.Attribute;
import pbl2011.model.ClassModel;
import pbl2011.model.ClassNode;
import pbl2011.model.Method;
import pbl2011.model.NodeArcSet;
import pbl2011.model.PackageManager;
import pbl2011.model.Parameter;
import pbl2011.model.UndoRedoModel;

/**
 * <p>
 * モデルクラス
 * <p>
 * MVCモデルのMを担当するクラス。
 * 
 * @author 10745104 Y.Ishii
 */
public class Model implements CommonConst {

	/** UndoRedoModel **/
	public UndoRedoModel undoredoModel;
	
	/** レジストリ */
	private Set<Observer> registry;

	/**
	 * <p>
	 * レジストリに追加する。
	 * 
	 * @param o
	 */
	public void attach(Observer o) {

		if (registry == null) {
			registry = new HashSet<Observer>();
		}
		registry.add(o);
	}

	/**
	 * <p>
	 * レジストリから削除する。
	 * 
	 * @param o
	 */
	public void detach(Observer o) {

		if (registry == null) {
			registry = new HashSet<Observer>();
		}
		registry.remove(o);
	}

	/** 変更フラグ */
	private boolean isModified = false;

	public boolean isModified() {
		return this.isModified;
	}

	/**
	 * <p>
	 * 更新伝播メカニズム
	 */
	protected void notifyAllObj() {

		isModified = true;

		// すべてのviewを更新

		for (Observer o : registry) {
			o.update();
		}

	}

	/** クラス情報 */
	private List<ClassNode> classList;

	/** 関連情報 */
	private List<AssociationArc> arcList;

	/**
	 * <p>
	 * コンストラクタ
	 * 
	 */
	public Model() {
		this.classList = Collections.synchronizedList(new ArrayList<ClassNode>());
		this.classList.addAll(getBuiltinClassNodeList());

		this.arcList = Collections.synchronizedList(new ArrayList<AssociationArc>());

		this.undoredoModel = new UndoRedoModel();

	}
	
	protected List<ClassNode> getBuiltinClassNodeList(){
		return BuiltInClass.getBuiltinClassNodeList();
	}

	/** 操作中オブジェクト管理クラス */
	CurrentManager cuMgr;

	public CurrentManager getCurrentManager() {
		if (cuMgr == null) {
			cuMgr = new CurrentManager(this);
		}
		return cuMgr;
	}

	/**
	 * <p>
	 * クラス情報クリア
	 * <p>
	 * 全てのクラス情報をクリアする。<br>
	 * 更新後に、更新伝播メカニズムの呼び出しを行う。
	 */
	public void clearData() {

		cuMgr.clear();
		PackageManager.clear();
		classList.clear();
		this.classList.addAll(getBuiltinClassNodeList());
		arcList.clear();
		undoredoModel.initializeUndoRedoModel();
		maxClassId = 0;
		notifyAllObj();
	}

	/**
	 * <p>
	 * クラス情報削除
	 * <p>
	 * 全てのクラス情報をクリアする。<br>
	 * 更新後に、更新伝播メカニズムの呼び出しを行う。
	 */
	public void delClassData(ClassNode newClass) {

		classList.remove(newClass);
		notifyAllObj();
	}

	/**
	 * <p>
	 * クラス情報追加
	 */
	public void addClass(ArrayList<ClassNode> newClasses) {
		classList.addAll(newClasses);
		notifyAllObj();
	}

	/**
	 * <p>
	 * クラス情報追加
	 */
	public void addClass(ClassNode newClass) {
		classList.add(newClass);
		notifyAllObj();
	}

	/**
	 * <p>
	 * クラス情報変更
	 */
	public void changeModel() {

		// TODO
		notifyAllObj();
	}

	/**
	 * <p>
	 * クラス情報取得
	 * 
	 * @return クラス情報リスト
	 */
	public ArrayList<ClassNode> getAllActiveClassData() {

		ArrayList<ClassNode> list = new ArrayList<ClassNode>();
		for (ClassNode n : classList) {
			if (!n.delFlag && !n.isBuiltInClass && !(n.p == null)) {
				list.add(n);
			}
		}
		return list;
	}

	/**
	 * <p>
	 * クラス情報取得(定義済みも含めて取得)
	 *
	 * @return クラス情報リスト
	 */
	public ArrayList<ClassNode> getAllClassData() {

		ArrayList<ClassNode> list = new ArrayList<ClassNode>();
		for (ClassNode n : classList) {
			if (!n.delFlag) {
				list.add(n);
			}
		}
		return list;
	}

	/**
	 * <p>
	 * クラス情報取得
	 * 
	 * @return クラス情報
	 */
	public ClassNode getClassData(int classId) {

		for (ClassNode n : classList) {
			if (n.classId == classId) {
				return n;
			}
		}
		return null;
	}

	/**
	 * <p>
	 * クラス名よりクラスID取得
	 * 
	 * @return クラスID
	 */
	public int getClassIdByClassName(String className) {

		for (ClassNode n : classList) {
			if (n.className.equals(className)) {
				return n.classId;
			}
		}
		return 0;
	}

	/**
	 * <p>
	 * 指定したクラスIDをもつクラス(ノード)を削除する
	 */
	public void removeClassById(ClassNode cn) {
		for (int i = 0; i < classList.size(); i++) {
			if (cn.classId == classList.get(i).classId){
				
				classList.remove(i);
				return;
			}
		}
	}
	
	/**
	 * <p>
	 * 指定した弧IDを持つ弧を削除する
	 */
	public void removeArcById(AssociationArc aa) {
		for (int i = 0; i < arcList.size(); i++) {
			if (aa.assocId == arcList.get(i).assocId) {

				arcList.remove(i);
				return;
			}
		}
	}

	/**
	 * <p>
	 * 指定したクラスIDからクラス情報を更新する
	 */
	public void setClassDataById(ClassNode cn) {
		for (int i=0; i < classList.size(); i++) {
			if ( cn.classId == classList.get(i).classId) {

				classList.set(i, cn.clone());
				return;
			}
		}
	}
	
	/**
	 * <p>
	 * 指定した弧IDから弧情報を更新する
	 */
	public void setArcDataById(AssociationArc aa) {

		for (int i = 0; i < arcList.size(); i++) {
			if (aa.assocId == arcList.get(i).assocId) {
				arcList.set(i, aa.clone());
				return;
			}
		}
	}

	/**
	 * <p>
	 * 関連情報取得
	 * 
	 * @return 関連情報リスト
	 */
	public ArrayList<AssociationArc> getAllActiveAssociationArc() {

		ArrayList<AssociationArc> list = new ArrayList<AssociationArc>();
		for (AssociationArc a : arcList) {
			if (!a.delFlag) {
				list.add(a);
			}
		}
		return list;
	}

	/**
	 * <p>
	 * 関連情報取得
	 *
	 * @return クラス情報
	 */
	public AssociationArc getAssocData(int assocId) {

		for (AssociationArc a : arcList) {
			if (a.assocId == assocId) {
				return a;
			}
		}
		return null;
	}
	/**
	 * <p>
	 * 関連情報追加
	 */
	public void addAssociation(ArrayList<AssociationArc> newAssociation) {
		arcList.addAll(newAssociation);
		notifyAllObj();
	}

	/**
	 * <p>
	 * 関連情報追加
	 */
	public void addAssociation(AssociationArc newAssociation) {
		arcList.add(newAssociation);
		notifyAllObj();
	}

	/**
	 * <p>
	 * ターゲット関連情報取得
	 * 
	 * @return 関連情報リスト
	 */
	public List<AssociationArc> getTargetAssocList(ClassNode inClass) {

		List<AssociationArc> list = new ArrayList<AssociationArc>();
		if (inClass == null)
			return list;
		for (AssociationArc a : arcList) {
			if (inClass.classId == a.classIdTarget) {
				list.add(a);
			}
		}
		return list;
	}

	/**
	 * <p>
	 * ソース関連情報取得
	 * 
	 * @return 関連情報リスト
	 */
	public List<AssociationArc> getSourceAssocList(ClassNode outClass) {
		List<AssociationArc> list = new ArrayList<AssociationArc>();
		if (outClass == null)
			return list;
		for (AssociationArc a : arcList) {
			if (outClass.classId == a.classIdSource) {
				list.add(a);
			}
		}
		return list;
	}

	/**
	 * <p>
	 * ファイル読込
	 */
	public boolean read(BufferedReader br, File file) {

		clearData();

		ArrayList<ClassNode> list = new ArrayList<ClassNode>();
		Map<Integer, String> map = new HashMap<Integer, String>();
		ArrayList<AssociationArc> arclist = new ArrayList<AssociationArc>();
		ClassNode node = null;
		Attribute attribute = null;
		Method method = null;
		Parameter parameter = null;
		AssociationArc association = null;
		String line = "";			
		try {
			// ファイルの内容をメモリ上に読込む
			List<String> lines = new ArrayList<String>();
			while ((line = br.readLine()) != null) {
				lines.add(line);
			}
			// br.close();
			
			// ユーザが定義したクラス情報を取得
			Map<String, Integer> userDefClassMap = FileUtil.getUserDefClass(lines);
			// ファイル内で利用されている型情報のすべてを取得
			Set<String> allTypeSet = FileUtil.getAllTypeSet(lines);
			// 未定義の型情報が存在した場合にクラスリストに設定する
			int maxClassId = FileUtil.getMaxClassId(userDefClassMap);
			for (String className : allTypeSet) {
				if (getClassIdByClassName(className) == 0 && !userDefClassMap.containsKey(className)) {
					classList.add(createClassNode(++maxClassId, className));
					assert Util.debug("##########  未定義の型を設定  ########## className=" + className + ",classId=" + maxClassId);
				}
			}

			// 最終行まで読み込む
			for (String lineForClassLoad : lines) {
				if (FileUtil.isClass(lineForClassLoad)) {
					node = createClassNode(lineForClassLoad);
					list.add(node);
				} else if (FileUtil.isPackage(lineForClassLoad)) {
					String[] tokens = lineForClassLoad.split(DELIMITER);
					map.put(Integer.valueOf(tokens[1]), tokens[2]);
				} else if (FileUtil.isAttrib(lineForClassLoad)) {
					attribute = makeAttribute(lineForClassLoad, userDefClassMap);
					node.attributeList.add(attribute);
				} else if (FileUtil.isMethod(lineForClassLoad)) {
					method = makeMethod(lineForClassLoad, userDefClassMap);
					node.methodList.add(method);
				} else if (FileUtil.isParam(lineForClassLoad)) {
					parameter = makeParameter(lineForClassLoad, userDefClassMap);
					Method m = node.methodList.get(node.methodList.size() - 1);
					m.parameterList.add(parameter);
				} else if (FileUtil.isArc(lineForClassLoad)) {
					association = new AssociationArc(lineForClassLoad);
					// 関連IDが設定されていない場合には採番する。
					if(association.assocId < 0) {
						association.assocId = getNewAssocId();
					}
					arclist.add(association);
				}
			}
			PackageManager.add(map);
			addClass(list);
			addAssociation(arclist);
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;

	}
	
	protected ClassNode createClassNode(String line){
		return new ClassModel(line);
	}
	
	protected ClassNode createClassNode(int classId, String className){
		return new ClassModel(classId, className);
	}

	/**
	 * 属性インスタンスを生成する
	 * @param line
	 * @param userDefinitionClassMap
	 * @return 属性インスタンス
	 */
	public Attribute makeAttribute(String line, Map<String, Integer> userDefinitionClassMap) {
		String[] tokens = line.split(DELIMITER);

		int i = 1;
		String attributeName = tokens[i++];
		String typeName = tokens[i++];
		String typeId = getTypeIdByTypeName(typeName, userDefinitionClassMap);
		assert typeId != null : line;
		VisibilityType visibility = VisibilityType.aliasOf(tokens[i++]);
		
		return new Attribute(attributeName, typeId, visibility);
	}

	/**
	 * 操作インスタンスを生成する
	 * @param line
	 * @param userDefinitionClassMap
	 * @return 操作インスタンス
	 */
	public Method makeMethod(String line, Map<String, Integer> userDefinitionClassMap) {
		String[] tokens = line.split(DELIMITER);

		int i = 1;
		String methodName = tokens[i++];
		String typeName = tokens[i++];
		if (typeName == null || NULL.equals(typeName)) { // コンストラクタの場合は戻り値がnullになる
			typeName = "";
		}
		String typeId = getTypeIdByTypeName(typeName, userDefinitionClassMap);
		assert typeId != null : line;
		VisibilityType visibility = VisibilityType.aliasOf(tokens[i++]);
		
		return new Method(methodName, typeId, visibility);
	}

	/**
	 * 引数インスタンスを生成する
	 * @param line
	 * @param userDefinitionClassMap
	 * @return 引数インスタンス
	 */
	public Parameter makeParameter(String line, Map<String, Integer> userDefinitionClassMap) {
		String[] tokens = line.split(DELIMITER);

		int i = 1;
		String parameterName = tokens[i++];
		String dataTypeName = tokens[i++];
		String typeId = getTypeIdByTypeName(dataTypeName, userDefinitionClassMap);
		assert typeId != null : line;
		return new Parameter(parameterName, typeId);
	}

	/**
	 * 型名から型IDに変換する
	 * ※完全にロードが完了した場合に利用すること
	 * @param typeName
	 * @return
	 */
	public String getTypeIdByTypeName(String typeName) {
		if (Util.hasSymbol(typeName)) {
			String tmpTypeName = typeName.replace(SPACE, "");
			List<String> list = Util.getTypeList(tmpTypeName);
			for (String s : list) {
				addNoDefClass(s);
				tmpTypeName = tmpTypeName.replace(s, String.valueOf(getClassIdByClassName(s)));
			}
			return tmpTypeName;
		} else {
			addNoDefClass(typeName);
			return String.valueOf(getClassIdByClassName(typeName));
		}
	}

	/**
	 * 引数で取得した型名称がclassListになかった場合はclassListに登録する
	 * @param typeName 型名称
	 */
	private void addNoDefClass(String typeName) {
		if (getClassIdByClassName(typeName) == 0) {
			classList.add(createClassNode(getNextClassId(), typeName));
			assert Util.debug("##########  未定義の型を設定  ########## typeName=" + typeName + ",classId=" + maxClassId);			
		}
	}
	/**
	 * 型名から型IDに変換する
	 * ※ファイル取り込み時に利用する
	 * @param typeName
	 * @return
	 */
	public String getTypeIdByTypeName(String typeName, Map<String, Integer> userDefinitionClassMap) {
		if (Util.hasSymbol(typeName)) {
			String tmpTypeName = typeName.replace(SPACE, "");
			List<String> list = Util.getTypeList(tmpTypeName);
			for (String s : list) {
				if (getClassIdByClassName(s) != 0) {
					tmpTypeName = tmpTypeName.replace(s, String.valueOf(getClassIdByClassName(s)));
				} else {
					tmpTypeName = tmpTypeName.replace(s, String.valueOf(userDefinitionClassMap.get(s)));
				}
			}
			return tmpTypeName;
		} else {
			if (getClassIdByClassName(typeName) != 0) {
				return String.valueOf(getClassIdByClassName(typeName));
			} else {
				return String.valueOf(userDefinitionClassMap.get(typeName));
			}
		}
	}

	/**
	 * 型IDから型名に変換する
	 * @param typeId
	 * @return
	 */
	public String getTypeNameByTypeId(String typeId) {
		if (Util.hasSymbol(typeId)) {
			String tmpTypeName = typeId.replace(SPACE, "");
			List<String> list = Util.getTypeList(tmpTypeName);
			for (String id : list) {
				tmpTypeName = tmpTypeName.replace(id, getClassData(Integer.valueOf(id)).className);
			}
			return tmpTypeName;
		} else {
			return getClassData(Integer.valueOf(typeId)).className;
		}
	}

	/**
	 * 型IDから型に変換する
	 * @param typeId
	 * @return
	 */
	public ClassNode getTypeByTypeId(String typeId) {
		if (Util.hasSymbol(typeId)) {
			return getClassData(Util.removeSymbol(typeId));
		} else {
			return getClassData(Integer.valueOf(typeId));
		}
	}

	/**
	 * <p>
	 * ファイル保存
	 */
	public boolean save(BufferedWriter bw, File file) {
		assert Util.debug("executeSave");

		try {
			writeLine(bw, PackageManager.write());
			
			for (ClassNode nd : getAllActiveClassData()) {
				int id = nd.classId;
				if (!nd.isBuiltInClass) {
					writeLine(bw, nd.toString());
				}

				for (Attribute a : nd.attributeList) {
					writeLine(bw, replaceIdToName(a.toString(id)));
				}

				for (Method m : nd.methodList) {
					writeLine(bw, replaceIdToName(m.toString(id)));

					for (Parameter p : m.parameterList) {
						writeLine(bw, replaceIdToName(p.toString(id)));
					}
				}
			}
			bw.newLine();

			for (AssociationArc arc : getAllActiveAssociationArc()) {
				writeLine(bw, arc.toString());
			}

		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}

		return true;

	}

	private void writeLine(BufferedWriter out, String str) throws IOException {
		out.write(str);
		out.newLine();
	}

	private int maxClassId = 0;

	/**
	 * <p>
	 * クラスID採番
	 */
	public int getNextClassId() {
		if (maxClassId < 1) {
			calcMaxClassId();
		}
		return ++maxClassId;
	}

	private void calcMaxClassId() {
		for (ClassNode n : classList) {
			if (!n.isBuiltInClass  && maxClassId < n.classId) {
				maxClassId = n.classId;
			}
		}
	}

	private int maxAssocId = 0;
	/**
	 * <p>
	 * 関連ID採番
	 */
	public int getNewAssocId() {
		if (maxAssocId < 1) {
			calcMaxAssocId();
		}
		return ++maxAssocId;
	}

	private void calcMaxAssocId() {
		for (AssociationArc a : arcList) {
			if (maxAssocId < a.assocId) {
				maxAssocId = a.assocId;
			}
		}
	}


	/**
	 * 型のクラスIDをクラス名に変換する
	 * @param line
	 * @return 型のクラスIDをクラス名に変換されたline
	 */
	private String replaceIdToName(String line) {
		String[] tokens = line.split(DELIMITER);

		String typeId = tokens[2];
		String typeName = getTypeNameByTypeId(typeId);
		return line.replace(typeId, typeName);
	}

	// 関連変更
	public void changeAssociationArc(String string, AssociationArc a,
			String role, Boolean navi, String multi) {

		if ("t".equals(string)) {
			a.changeT(role, navi, multi);
		} else if ("s".equals(string)) {
			a.changeS(role, navi, multi);
		}
		notifyAllObj();

	}
	// 関連変更（協同編集連携用追加）
	public void changeAssociationArc(String string, AssociationArc a,
			String role, Boolean navi, String multi, int sc) {
		changeAssociationArc(string, a, role, navi, multi);
	}


	// クラス削除
	public void deleteClass() {
		NodeArcSet nodearcSet = new NodeArcSet();

		ArrayList<ClassNode> list = cuMgr.getCurrentNodeList();
		for (ClassNode cn : list) {
			cn.delFlag = true;
			nodearcSet.add(cn);

			ArrayList<AssociationArc> arclist = getAllActiveAssociationArc();
			for (AssociationArc a : arclist) {
				if (a.classIdTarget == cn.classId || a.classIdSource == cn.classId) {
					a.delFlag = true;
					nodearcSet.add(a);
				}
			}
		}
		undoredoModel.tryPush(nodearcSet);
		cuMgr.clear();
		notifyAllObj();
	}

	/**
	 * 戻り値型リストを返却する
	 * @param classList
	 * @return
	 */
	public String[] getReturnTypeList() {
		return toStringArray(getAllTypeList());
	}

	/**
	 * 型リストを返却する
	 * ※voidは除く
	 * @param classList
	 * @return
	 */
	public String[] getTypeList() {
		List<String> allTypeList = getAllTypeList();
		allTypeList.remove(DEFAULT_RETURN_TYPE_NAME); // voidを除く
		allTypeList.remove(CONSTRACTOR_RETURN_TYPE_NAME);
		return toStringArray(allTypeList);
	}

	private List<String> getAllTypeList() {
		List<String> typeList = new ArrayList<String>();
		
		for (ClassNode classNode : getAllClassData()) {
			typeList.add(classNode.className);
		}
		
		return typeList;
	}

	private String[] toStringArray(List<String> stringList) {
		String[] stringArray = new String[stringList.size()];
		for (int i = 0; i < stringList.size() ; i++) {
			stringArray[i] = stringList.get(i);
		}
		return stringArray;
	}

	/**
	 * Javaソースコードを生成する
	 * @throws IOException
	 */
	public void outputJavaCode(File outputDir) throws IOException {
		
		// 対象となるクラスの取得
		ArrayList<ClassNode> activeClassNodeList = getAllActiveClassData();
		if (activeClassNodeList.size() < 1) {
			JOptionPane.showMessageDialog(null, "出力対象となるクラスが存在しません。");
		}
		
		// ソースコード出力処理
		JavaCodeGenerator.generate(outputDir, classList);
		
	}

	/**
	 * クラスの詳細情報を更新する
	 */
	public void updateClass(ClassNode clone) {
		clone.autoAdjust();
		ClassNode cn = getClassData(clone.classId);
		int i = classList.indexOf(cn);
		classList.remove(i);
		classList.add(i, clone);
		notifyAllObj();

	}
	
	/**
	 * 多重継承チェック
	 */
	public void checkMultiInherit() {
		Map<Integer, Integer> extendsMap = new HashMap<Integer, Integer>();
		Set<ClassNode> multiInheritList = new HashSet<ClassNode>();
		for (AssociationArc arc :arcList) {
			if (arc.getKind().isEXTENDS() && !arc.delFlag && !getClassData(arc.classIdTarget).classType.isINTERFACE()) {
				if (extendsMap.containsKey(arc.classIdSource)) {
					multiInheritList.add(getClassData(arc.classIdSource));
				}
				extendsMap.put(arc.classIdSource, arc.classIdTarget);
			}
		}
		if (multiInheritList.size() < 1) {
			JOptionPane.showMessageDialog(null, "多重継承しているクラスはありません。");
		} else {
			StringBuffer sb = new StringBuffer(System.getProperty("line.separator"));
			for (ClassNode c : multiInheritList) {
				sb.append(c.className + System.getProperty("line.separator"));
			}
			JOptionPane.showMessageDialog(null, "以下のクラスが多重継承しています。" + sb.toString());			
		}
	}
}
