package ash.reverse.uml;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.PrintStream;
import java.awt.Font;
import static java.awt.Font.*;
import ash.reverse.parser.JavaParser;
import ash.reverse.struct.FileRecord;

/**
 * UMLクラス図生成ツール：Javaソースコードを解析してUMLクラス図エディタの
 * 入力となるデータを生成する。
 * <p>
 * 使用法１：コマンドラインから実行する
 * <pre>
 *	java -cp build reverse.uml.UMLTool -c1234ab -prd -f<font> <src-dir>...
 *	</pre>
 * <p>
 * 使用法２：プログラムから呼び出す
 * <pre>
 * UMLTool uml = new UMLTool();
 * uml.setFont(new Font(SERIF,PLAIN,14));
 * uml.setParams(4, true, true); uml.setParams("c4ab");	// クラス図の場合
 * uml.setParams("prd");		// パッケージ図の場合
 * uml.parse("src/pbl2011/mvc");
 * uml.parse("src/pbl2011/gui");
 * uml.generateUML(System.out);
 *	</pre>
 * @author C.Akiguchi
 */
public class UMLTool {
	public static void main(String... args) {
		new UMLTool(args);
	}

	/**
	 * コマンドラインから実行する場合のコンストラクタ。
	 * このコンストラクタの中ですべての処理を完結している。
	 * @param args コマンドラインのパラメータ
	 */
	private UMLTool(String... args) {
		int i = 0;
		for(String arg : args) {
			if(arg.startsWith("-f")) {
				setFont(arg.substring(2));
			} else if(arg.startsWith("-")) {
				setParams(arg.substring(1));
			} else {
				parse(arg);
			}
		}

		// UMLクラス図エディタのファイルフォーマットでクラス情報を出力する。
		generateUML(System.out);
	}

	// -f 'BI20%MS Gothic'
	static final String FONTDEF = "([PBI]+)(\\d+)(@(.+))$";
	//                             @1      @2      @4
	static private Pattern fontDefPat = Pattern.compile(FONTDEF);

	/**
	 * フォント情報からフォントを作りレイアウト計算で使用するフォントに設定する。
	 * @param fontInfo フォント情報('BI20%MS Gothic')
	 */
	private void setFont(String fontInfo) {
		Matcher m = fontDefPat.matcher(fontInfo);
		if(m.find()) {
			String name = m.group(3);
			int size = Integer.parseInt(m.group(2));
			int style = 0;
			for(char c : m.group(1).toCharArray()) {
				switch(c) {
				case 'P': style |= PLAIN; break;
				case 'B': style |= BOLD; break;
				case 'I': style |= ITALIC; break;
				}
			}
			font = new Font(name, style, size);
		}
	}

	/**
	 * プログラムから呼び出す場合に使用するコンストラクタ
	 */
	public UMLTool() {}

	/**
	 * レイアウト計算で使用するフォントに設定する。
	 * @param font レイアウト計算で使用するフォント
	 */
	public void setFont(Font font) {
		this.font = font;
	}

	/**
	 * クラス図生成に必要なパラメータを設定する。
	 * @param params オプション文字列(-c1234abprd")
	 * c:クラス図  1: public  2: protected  3: package  4: private
	 * a:引数の型参照依存関係の生成  b:メソッド本体の型参照依存関係の生成
	 * p:パッケージ図生成  r:外部パッケージ参照  d:定義クラス表示
	 */
	public void setParams(String params) {
		for(char c : params.toCharArray()) {
			switch(c) {
			case 'c': cflag = true; break;	// クラス図生成
			case 'p': pflag = true; break;	// パッケージ図生成
			case '1': case '2': case '3': case '4':	// 可視性の選択レベル
				selectLevel = c - '0'; break;
			case 'a': aflag = true; break;	// 引数の依存関係
			case 'b': bflag = true; break;	// メソッド本体の依存関係
			case 'r': rflag = true; break;	// 外部パッケージ参照
			case 'd': dflag = true; break;	// 定義クラス表示
			}
		}
	}

	/**
	 * クラス図生成に必要なパラメータを設定する。
	 * @param selectLevel 可視性の選択レベル<p>
	 * 						1: public  2: protected  3: package  4: private
	 * @param aflag 引数の型参照依存関係の生成
	 * @param bflag メソッド本体の型参照依存関係の生成
	 */
	public void setParams(int selectLevel, boolean aflag, boolean bflag) {
		this.selectLevel = selectLevel;
		this.aflag = aflag;
		this.bflag = bflag;
	}

	/**
	 * UMLクラス図情報を生成する。
	 * @param out UMLクラス図情報の出力先
	 */
	public void generateUML(PrintStream out) {
		if(cflag)
			ClsGenerator.generate(out, fileList, font, selectLevel,aflag,bflag);
		if(pflag)
			PkgGenerator.generate(out, fileList, font, rflag, dflag);
	}

	private boolean cflag = false;	// クラス図生成
	private boolean pflag = false;	// パッケージ図生成
	private int selectLevel = 0;	// 可視性の選択レベル
	// 0: no member  1: public  2: protected  3: package  4: private
	private boolean aflag = false;	// 引数の型参照依存関係の生成
	private boolean bflag = false;	// メソッド本体の型参照依存関係の生成
	private boolean rflag = false;	// 外部パッケージ参照
	private boolean dflag = false;	// 定義クラス表示
	private Font font = null;		// レイアウト計算で使用するフォント

	// ファイル情報をパッケージ記録として管理するリスト
	private List<FileRecord> fileList = new ArrayList<FileRecord>();

	/**
	 * 指定されたディレクトリの配下のJavaソースファイルを解析し、
	 * クラス図情報を抽出する。
	 * @param dirpath Javaソースファイルを含むディレクトリ
	 */	
	public void parse(String dirpath) {
		File dir = new File(dirpath);
		if(dir != null) {
			parseSub(dir);
		} else {
			printErr(dirpath + ": パス名を確認して下さい。");
		}
	}

	private void parseSub(File path) {
		if(path.isDirectory()) {
			File[] files = path.listFiles();
			if(files != null) {
				for(File f : files) parseSub(f);
			} else {
				printErr(path.getName() + ": アクセス許可がありません。");
			}
		} else if(path.getName().endsWith(".java")) {
			BufferedReader reader = null;
			try {
				reader = new BufferedReader(new FileReader(path));
				parseJavaCode(reader, path);
			} catch(IOException e) {
				printErr(path.getName() + ": ファイルの解析ができません。");
			} finally {
				if(path != null && reader != null) 
					try { reader.close(); } catch(IOException e) {}
			}
		}
	}

	void printErr(Object msg) { System.err.println(msg); }

	/**
	 * Javaソースファイルのリーダからソーステキストを読み込み、
	 * クラス図生成に必要な情報を抽出する。
	 * @param reader Javaソースファイルのリーダ
	 */
	protected void parseJavaCode(BufferedReader reader, File path)
		throws IOException {
		JavaParser parser = new JavaParser(reader, path.getName(), true);
		FileRecord frec = parser.parse();
		fileList.add(frec);
	}
}
