package org.postgresforest.tool.commandline;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.sql.SQLException;

import org.postgresforest.tool.ForestToolCommandLine;
import org.postgresforest.tool.action.LoginAction;
import org.postgresforest.tool.bean.LoginInfo;
import org.postgresforest.tool.commandline.action.BrokenLogActions;
import org.postgresforest.tool.commandline.action.ConfigActions;
import org.postgresforest.tool.commandline.action.DbActions;
import org.postgresforest.tool.commandline.action.GscActions;
import org.postgresforest.tool.commandline.action.HashActions;
import org.postgresforest.tool.commandline.action.HelpActions;
import org.postgresforest.tool.commandline.action.InstanceActions;
import org.postgresforest.tool.commandline.action.TableActions;
import org.postgresforest.tool.commandline.action.UserActions;
import org.postgresforest.tool.db.GSCdata;
import org.postgresforest.tool.util.CommandParser;
import org.postgresforest.tool.util.MessagesCommandLine;
import org.postgresforest.tool.util.Parser;
import org.postgresforest.tool.util.ReWriter;

	/**
	 * コマンドラインクラス
	 * コマンドの受けつけ、割り振りを行う。
	 */
	public class ForestCommandLine {

	private static ForestToolCommandLine ftc = null;
	private static LoginInfo loginInfo = null;
	public static GSCdata gsc = null;
	/** Added by CWJ to permit \q command */
	static boolean done = false;
	/** 実行するコマンド文 */
	static String ExecLine = ""; //$NON-NLS-1$
	private static boolean isDbOpened = false;
	private static String selectDb = null; 
	
	/**
	 * コンストラクタ
	 * (通常ログイン時)
	 * オプション情報をログイン情報に格納する
	 * @return ForestCommandLine
	 * @param hostname ホスト名
	 * @param portno ポート番号
	 * @param username ユーザ名
	 * @param userpass ユーザパスワード
	 * @param gscname GSC名
	 */
	public ForestCommandLine(String hostname, String portno, String username,
			String userpass, String gscname) {
		loginInfo = new LoginInfo();
		loginInfo.setHostname(hostname);
		loginInfo.setPortno(portno);
		loginInfo.setUser(username);
		loginInfo.setPassword(userpass);
		loginInfo.setDbname(gscname);
		// エラー出力にResourceBundleを使うためインスタンス生成
		ftc = new ForestToolCommandLine();
	}
	

	/**
	 * コマンドライン処理
	 * ユーザからの入力を受けつける。
	 * @return void
	 * @throws ClassNotFoundException
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws SQLException
	 */
	public void commandLine() throws ClassNotFoundException, FileNotFoundException, IOException, SQLException{
		// ログイン処理
		if (!login(loginInfo)) {
			System.out.println("[Error] Can not connect to DB ");
			return;
		}
		
		// オプションによる処理の振り分け
		// コマンドファイル入力がある場合、ファイル名を設定後一括処理する 
		if (ForestTool.isInputFile()) {
			String inputfile = ForestTool.getInputFile();
			System.out.println("Using Command file input from " + inputfile);
			System.out.println("------------ Start Commands ------------");
			processCommandFile(inputfile);
			System.out.println("------------- End Commands -------------");
			System.exit(0);//以降の処理は行わない
		} 
		
		// コマンドライン
		// 通常処理
		if (true) { //Todo 振り分け条件の整備 
			/* 入力プロンプトを表示し、入力されたコマンドを逐次処理する */
			// This provides us the means of reading from stdin
			StreamTokenizer input = new StreamTokenizer(new InputStreamReader(System.in));
			input.resetSyntax();
			input.slashSlashComments(true); // allow // as a comment delimiter
			input.eolIsSignificant(false);	// treat eol's as spaces
			input.wordChars(32, 126);
			// input.whitespaceChars(59, 59);	// セミコロンをスペースには変換しない
			// バックスラッシュコマンド実行時にはセミコロンを入力する必要は無い
			// input.quoteChar(39); *** CWJ: messes up literals in query string ***
			
			// Now the main loop.
			int tt = 0, lineno = 1;
			boolean IsPronpt = true;
			
			while (tt != StreamTokenizer.TT_EOF && ! done)
			{	
				/* プロンプト表示フラグに従ってプロンプト表示 */
				if(IsPronpt){
					if (isDbOpened) {
						// DB選択モード
						System.out.println();
						System.out.print("[" + selectDb + "]> "); //$NON-NLS-1$ //$NON-NLS-2$
						System.out.flush();
						IsPronpt = false;
					} else {
						// GSC選択モード
						System.out.println();
						System.out.print("[" + loginInfo.getDbname() + "]$ "); //$NON-NLS-1$ //$NON-NLS-2$
						System.out.flush();
						IsPronpt = false;
					}
				}
				
				// Here, we trap SQLException so they don't terminate the application
				try
				{
					tt = input.nextToken();
					if (tt == StreamTokenizer.TT_WORD || tt == StreamTokenizer.TT_EOL)
					{
						/* LF改行を受け取ったときのみプロンプト表示フラグを立てる */
						if(input.ttype == StreamTokenizer.TT_EOL){
							IsPronpt = true;
						}
						else{
							processLine(input.sval);
							lineno++;
						}
					}
				}
				catch (IOException e)
				{
					System.out.println("cui.error");
					System.out.println(e.getMessage());
					ExecLine = ""; //$NON-NLS-1$
				}
			}
	}
	System.out.println("Now closing the connection");			 //$NON-NLS-1$
	}
	
	/**
	 * ログイン処理
	 * オプション情報に基づきログイン処理を行う
	 * @return void
	 * @param lInfo ログイン情報を格納したGSCdata
	 */
	private static boolean login(LoginInfo lInfo) {
		LoginAction adminLoginMng = new LoginAction();
		if(!adminLoginMng.login(loginInfo)){
			return false;
		}
		gsc = adminLoginMng.getGsc();
		return true;
	}
			

	/**
	 * 入力されたステートメントを処理する
	 * This processes a statement
	 * 
	 * @return void
	 * @param line コマンドラインより入力された文字列
	 * @throws SQLException
	 */
	private static void processLine(String line) throws SQLException
	{	
		String EndChar = "q"; //$NON-NLS-1$
		
		String SQLEnd = ";"; //$NON-NLS-1$
		
		// コメント文
		if (line.startsWith("--") || line.startsWith("#")){
			return ;
		} 
		
		if (line.startsWith("\\")) //$NON-NLS-1$
		{
			/* バックスラッシュコマンド内でセミコロンが使われていた場合、これを削除 */
			if(line.endsWith(";")){ //$NON-NLS-1$
				ExecLine = line.substring(0,line.indexOf(';')); 
			}else{
				ExecLine = line;
			}
			processSlashCommand(ExecLine);
			ExecLine = ""; //$NON-NLS-1$
			return ;
		}
		if (line.indexOf(';') < 0) {
			/* 入力文字列を連結する */
			ExecLine += " " + line; //$NON-NLS-1$
		} else {
			/* セミコロンが入力されるまで入力文字列を連結する */
			ExecLine += line.substring(0, line.indexOf(';'));
			
			// コマンドのパース・実行
			try {
				// コマンドの実行
				executeCommand(ExecLine);
			} catch (Exception e) {
				// TODO 自動生成された catch ブロック
				e.printStackTrace();
			} finally {
				ExecLine = ""; //$NON-NLS-1$
			}	
		}
	}
	
		
	
	
	/**
	 * コマンドの実行
	 * CommandParserによるパース結果に基づいて、各ロジックに処理を割り振る。
	 * @return void
	 * @param command コマンド
	 */
	private static void executeCommand(String command) {

		// コマンドのパース
		CommandParser cp = null;
		try {
			command = ReWriter.toLowerSql(command);
			cp = new CommandParser(command);
		} catch (Exception e1) {
			// TODO 自動生成された catch ブロック
			//			System.out.println(MessagesCommandLine.getString("cui.error.parser")); //$NON-NLS-1$
			//			e1.printStackTrace();
			return;
		}
		//環境構築コマンドタイプの取得
		int cpType = cp.getType();

		//DDLコマンドタイプの取得
		Parser pr = null;
		try {
			command = ReWriter.toLowerSql(command);
			pr = new Parser(command);
		} catch (Exception e) {
			// TODO 自動生成された catch ブロック
			System.out.println(MessagesCommandLine.getString("cui.error.parser")); //$NON-NLS-1$
			e.printStackTrace();
			return;
		}
		int prType = pr.getType();

		// コマンドタイプ別のパース
		if (cp.isHelpCommand()){
			HelpActions help = new HelpActions();
			help.show(cp);	
		} else if ( (cpType != CommandParser.NONE) && (prType == Parser.NONE)) {
			//DB Close状態
			if (!isDbOpened) {
				switch (cpType) {
	
					case CommandParser.NONE : //解析不可
	
					case CommandParser.CREATE_INSTANCE :
						InstanceActions addInstance = new InstanceActions();
						addInstance.create(cp, gsc);	
						break;
				 	
					case CommandParser.DROP_INSTANCE :
						InstanceActions delInstance = new InstanceActions();
						delInstance.drop(cp, gsc);
						break;
	
					case CommandParser.SHOW_INSTANCE :
						InstanceActions showInstance = new InstanceActions();
						showInstance.show(gsc);
						break;
	
					case CommandParser.SET_INSTANCE_UP :
						InstanceActions setInstanceUp = new InstanceActions();
						setInstanceUp.set(cp, gsc, GSCdata.SERVER_RUNNING);
						break;
	
					case CommandParser.SET_INSTANCE_DOWN :
						InstanceActions setInstanceDown = new InstanceActions();
						setInstanceDown.set(cp, gsc, GSCdata.SERVER_TROUBLE);
						break;
	
					case CommandParser.CREATE_GSC :
						GscActions addGsc = new GscActions();
						addGsc.create(cp, gsc);					
						break;
	
					case CommandParser.DROP_GSC :
						GscActions delGsc = new GscActions();
						delGsc.drop(cp, gsc);		
						break;
						
					case CommandParser.SHOW_GSC :
						GscActions showGsc = new GscActions();
						showGsc.show(gsc);		
						break;
						
					case CommandParser.DUMP_GSC :
						System.out.println(MessagesCommandLine.getString("cui.message.gsc.dump") ); //$NON-NLS-1$
						// To do
						break;
	
					case CommandParser.RESTORE_GSC :
						// To do
						System.out.println(MessagesCommandLine.getString("cui.message.gsc.restore")) ; //$NON-NLS-1$
						break;
	
					case CommandParser.CHECK_GSC :
						GscActions checkGsc = new GscActions();
						checkGsc.check(loginInfo);		
						break;
	
					case CommandParser.CREATE_DB :
						DbActions createDb = new DbActions();
						createDb.create(cp, gsc);
						break;
	
					case CommandParser.DROP_DB :
						DbActions dropDb = new DbActions();
						dropDb.drop(cp, gsc);
						break;
	
					case CommandParser.RESTORE_DB :
						DbActions restoreDb = new DbActions();
						restoreDb.restore(cp, gsc);
						break;
	
					case CommandParser.SHOW_DB :
						DbActions showDb = new DbActions();
						showDb.show(gsc);
						break;
	
					case CommandParser.CREATE_DBINSTANCE :
						DbActions addDb = new DbActions();
						addDb.createDbInstance(cp, gsc);
						break;
	
					case CommandParser.DROP_DBINSTANCE :
						DbActions deleteDb = new DbActions();
						deleteDb.dropDbInstance(cp, gsc);
						break;
	
					case CommandParser.CREATE_USER :
						UserActions createUser = new UserActions();
						createUser.create(cp, gsc);
						break;
	
					case CommandParser.DROP_USER :
						UserActions dropUser = new UserActions();
						dropUser.drop(cp, gsc);
						break;
	
					case CommandParser.SHOW_USER :
						UserActions showUser = new UserActions();
						showUser.show(gsc);
						break;
	
					case CommandParser.CREATE_CONFIG :
						ConfigActions createConfig = new ConfigActions();
						createConfig.create(cp, gsc);
						break;
	
					case CommandParser.DROP_CONFIG :
						ConfigActions dropConfig = new ConfigActions();
						dropConfig.drop(cp, gsc);
						break;
	
					case CommandParser.SHOW_CONFIG :
						ConfigActions showConfig = new ConfigActions();
						showConfig.show(gsc);
						break;
	
					case CommandParser.SET_CONFIG :
						ConfigActions setConfig = new ConfigActions();
						setConfig.set(cp, gsc);
						break;
	
					case CommandParser.REPLICATE_CONFIG :
						ConfigActions replicateConfig = new ConfigActions();
						replicateConfig.replicate(cp, gsc);
						break;
	
					case CommandParser.CREATE_HASH :
						HashActions createHash = new HashActions();
						createHash.create(cp, gsc);
						break;
	
					case CommandParser.DROP_HASH :
						HashActions dropHash = new HashActions();
						dropHash.drop(cp, gsc);
						break;
	
					case CommandParser.SHOW_HASH :
						HashActions showHash = new HashActions();
						showHash.show(gsc);
						break;
	
					case CommandParser.SHOW_BROKENLOG :
						BrokenLogActions showLog = new BrokenLogActions();
						showLog.show(gsc);
						break;
	
					case CommandParser.CLEAR_BROKENLOG :
						BrokenLogActions clearLog = new BrokenLogActions();
						clearLog.clear(gsc);
						break;
	
					case CommandParser.OPEN_DB :
						if (isDbOpened != true) {
							if (cp.getDB().isEmpty() != true) {
								DbActions openDb = new DbActions();
								if (openDb.open(cp, gsc)) {
									isDbOpened = true ; 
									selectDb = (String)cp.getDB().get(0);									
									System.out.println(MessagesCommandLine.getString("cui.message.db.open", new Object[] { selectDb })) ; //$NON-NLS-1$
								} else {
									// GSCに存在しないDB名が指定された場合
									String nosuchGsc = (String)cp.getDB().get(0);									
									System.out.println(MessagesCommandLine.getString("cui.error.db.open.nosuchdb", new Object[] { nosuchGsc })) ;									 //$NON-NLS-1$
								}
							} else {
								//DB名が指定されていない場合
								System.out.println(MessagesCommandLine.getString("cui.error.db.notspecify")); //$NON-NLS-1$
							}
						} else {
							//DBが既にOpen状態の場合
							System.out.println(MessagesCommandLine.getString("cui.error.db.dupricateopen", new Object[] { selectDb })) ;							 //$NON-NLS-1$
						}
						break;
					

						
					default :
						System.out.println(MessagesCommandLine.getString("cui.message.db.notopen") ); //$NON-NLS-1$
						break;
				}
			}
			//DB Open状態
			else if (isDbOpened) {
				switch (cpType) {		
					case CommandParser.CLOSE_DB :
						if (selectDb != null) {
							System.out.println(MessagesCommandLine.getString("cui.message.db.close", new Object[] { selectDb })) ; //$NON-NLS-1$
							isDbOpened = false ;
							selectDb = null;
						} else {
							System.out.println(MessagesCommandLine.getString("cui.error.db.close", new Object[] { selectDb })) ; //$NON-NLS-1$
						}
						break;
						
					case CommandParser.SHOW_TABLE :
						TableActions showTable = new TableActions();
						showTable.showTable(gsc, selectDb);
						break;
						
					case CommandParser.SHOW_TABLE_COLUMN :
						TableActions showTableColumn = new TableActions();
						showTableColumn.showTableColumn(cp, gsc, selectDb);
						break;

					case CommandParser.PART_TABLE :
						TableActions partTable = new TableActions();
						partTable.part(cp, gsc, selectDb);
						break;

					case CommandParser.PART_TABLE2 :
						TableActions partTable2 = new TableActions();
						partTable2.part2(cp, gsc, selectDb);
						break;

					case CommandParser.REPART_TABLE :
						TableActions repartTable = new TableActions();
						repartTable.repart(cp, gsc, selectDb);
						break;

					case CommandParser.UNPART_TABLE :
						TableActions unpartTable = new TableActions();
						unpartTable.unpart(cp, gsc, selectDb);
						break;

					case CommandParser.SET_PRIORITY :
						TableActions setPriority = new TableActions();
						setPriority.setPriority(cp, gsc, selectDb);
						break;

					case CommandParser.SHOW_PRIORITY :
						TableActions showPriority = new TableActions();
						showPriority.showPriority(cp, gsc, selectDb);
						break;	
						
					default :
						//DBが既にOpen状態の場合
						System.out.println(MessagesCommandLine.getString("cui.error.db.dupricateopen", new Object[] { selectDb })) ;							 //$NON-NLS-1$
						break;
				}
			}			
		} else if ((cpType == CommandParser.NONE) && (prType != Parser.NONE)) {
			if (isDbOpened) {
				if (pr.getType() == Parser.DML) {
					// DML実行(Jpsqlの呼出し)
					// To do
					System.out.println(MessagesCommandLine.getString("cui.error.dml.notsupport")); //$NON-NLS-1$
				} else if (pr.getType() != Parser.NONE) {
					//DDL実行
					DbActions execDdl = new DbActions();
					execDdl.executeDdl(gsc, command, selectDb);
				} 
			} else {
				// DB未オープン状態での、DDL/DMLコマンドの入力
				System.out.println(MessagesCommandLine.getString("cui.message.db.notopen")); //$NON-NLS-1$
			} 
		} else if (cpType == CommandParser.SHOW_HELP){
			System.out.println(MessagesCommandLine.getString("cui.help.command.show")) ; //$NON-NLS-1$
 		} else if (cp.isHelpCommand()){
			System.out.println(MessagesCommandLine.getString("cui.help.help.show")) ; //$NON-NLS-1$
		} else {
			// 未対応のコマンド 
			System.out.println(MessagesCommandLine.getString("cui.error.command.noexist"));		 //$NON-NLS-1$
		}
	}

	/**
	 * コマンドファイルを読み込み実行する
	 * 
	 * @param filename 入力されたコマンドファイル(フルパス)
	 * @throws SQLException
	 * @throws IOException
	 */
	public static void processCommandFile(String filename) throws SQLException,IOException{
		BufferedReader br;
		String line;

		try{
			br = new BufferedReader(new FileReader(filename));
			while((line = br.readLine()) != null){
				processLine(line);
			}		
		}
		catch(FileNotFoundException e){
			System.out.println(MessagesCommandLine.getString("cui.error.command.filenotfound", new Object[] {filename})); //$NON-NLS-1$
		}
	}
	
	
	/**
	 * バックスラッシュコマンド(\q,\i,\fi,\fg\,\fu,\fc,\fh\,fb,\ft,\fp)を処理する
	 * This process / commands (for now just /d)
	 * 
	 * @return void
	 * @param line コマンドラインより入力された文字列
	 * @throws SQLException
	 * 
	 */
	public static void processSlashCommand(String line) throws SQLException
	{	

		// ファイルの読み込み
		if (line.startsWith("\\i")) //$NON-NLS-1$
		{
			if (line.startsWith("\\i ")) //$NON-NLS-1$
			{
					// Display details about a table
					String file = extractSlashCmdArgument(line, 3);
					// fileの読み込み
					try {
						System.out.println("execute command"); //$NON-NLS-1$
						System.out.println("  file：" + file); //$NON-NLS-1$
						ExecLine = ""; //$NON-NLS-1$
						processCommandFile(file);
					} catch (IOException e) {
						System.out.println(MessagesCommandLine.getString("cui.error.command.io", new Object[] { file })); //$NON-NLS-1$						
						e.printStackTrace();
					}
			} else {
				// Display details about a table
				System.out.println(MessagesCommandLine.getString("cui.help.back.i")); //$NON-NLS-1$
			}
		} else	if (line.startsWith("\\f")) { //$NON-NLS-1$
			// PostgresForest CommandLineTool用のバックスラッシュコマンド
			if (line.startsWith("\\fi")) { //$NON-NLS-1$
				// インスタンスの表示
				InstanceActions showInstance = new InstanceActions();
				showInstance.show(gsc);				
			} else if (line.startsWith("\\fg")) { //$NON-NLS-1$
				// GSCの表示
				GscActions showGsc = new GscActions();
				showGsc.show(gsc);					
			} else if (line.startsWith("\\fd")) { //$NON-NLS-1$
				// DBの表示
				DbActions showDb = new DbActions();
				showDb.show(gsc);					
			} else if (line.startsWith("\\fu")) { //$NON-NLS-1$
				// ユーザの表示
				UserActions showUser = new UserActions();
				showUser.show(gsc);						
			} else if (line.startsWith("\\fc")) { //$NON-NLS-1$
				// コンフィグの表示
				ConfigActions showConfig = new ConfigActions();
				showConfig.show(gsc);			
			} else if (line.startsWith("\\fh")) { //$NON-NLS-1$
				// ハッシュの表示
				HashActions showHash = new HashActions();
				showHash.show(gsc);					
			} else if (line.startsWith("\\fb")) { //$NON-NLS-1$
				// ログの表示
				BrokenLogActions showLog = new BrokenLogActions();
				showLog.show(gsc);				
			} else if (line.startsWith("\\ft")) { //$NON-NLS-1$
				// テーブルの表示
				if (isDbOpened) {
					if (line.startsWith("\\ft ")) { //$NON-NLS-1$
						// テーブル名の取得
						String table = extractSlashCmdArgument(line, 4);
						if (!table.equals("")) { //$NON-NLS-1$
							// テーブル情報の表示
							TableActions showTable = new TableActions();
							showTable.showTableColumn(table, gsc, selectDb);
							return;
						}
					}
					TableActions showTable = new TableActions();
					showTable.showTable(gsc, selectDb);							
				} else {
					System.out.println(MessagesCommandLine.getString("cui.message.db.notopen"));					 //$NON-NLS-1$
				}
			} else if (line.startsWith("\\fp")) { //$NON-NLS-1$
				// 優先度の表示
				if (isDbOpened) {
					if (line.startsWith("\\fp ")) { //$NON-NLS-1$
						// テーブル名の取得
						String table = extractSlashCmdArgument(line, 4);
						if (!table.equals("")) { //$NON-NLS-1$
							// 優先度の表示
							TableActions showPriority = new TableActions();
							showPriority.showPriority(table, gsc, selectDb);
							return;
						}
					}
					System.out.println(MessagesCommandLine.getString("cui.error.help.back.fp"));					 //$NON-NLS-1$
					System.out.println(MessagesCommandLine.getString("cui.help.back.f")); //$NON-NLS-1$
				} else {
					System.out.println(MessagesCommandLine.getString("cui.message.db.notopen"));					 //$NON-NLS-1$
				}
			} else {
				System.out.println(MessagesCommandLine.getString("cui.error.back.f")); //$NON-NLS-1$
				System.out.println(MessagesCommandLine.getString("cui.help.back.f")); //$NON-NLS-1$
			}
		} else if (line.equals("\\q")) // Added by CWJ to permit \q command //$NON-NLS-1$
			done = true;
		else {
//			System.out.println("Unsupported \\ command: " + line);
			System.out.println(MessagesCommandLine.getString("cui.error.back.f")); //$NON-NLS-1$
			System.out.println(MessagesCommandLine.getString("cui.help.back.f")); //$NON-NLS-1$
		}
	}


	public static LoginInfo getLoginInfo() {
		return loginInfo;
	}
	
	/**
	 * バックスラッシュコマンドのパラメータを抽出
	 * コマンドの後のスペースを無視して、パラメータのみを抽出する
	 * @param str スペースを含んだ文字列
	 * @param index バックスラッシュコマンドの文字数+1
	 * @return バックスラッシュコマンドのパラメータ
	 */
	private static String extractSlashCmdArgument(String str, int index) {
		String ret = ""; //$NON-NLS-1$
		String subStr = str.substring(index);
		StreamTokenizer tokenizer = CommandParser.initTokenizer(subStr);
		try {
			int token = tokenizer.nextToken();
			if(tokenizer.sval != null) {
				ret = tokenizer.sval;				
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return ret;
	}

}

