package org.postgresforest.tool.cli.action;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;

import org.postgresforest.tool.Logger;
import org.postgresforest.tool.lib.Database;
import org.postgresforest.tool.lib.ForestToolException;
import org.postgresforest.tool.lib.Table;
import org.postgresforest.tool.util.CommandParser;
import org.postgresforest.tool.util.MessagesCommandLine;
import org.postgresforest.tool.util.Misc;
import org.postgresforest.tool.util.Table2StringUtil;

/**
 * テーブルへのコマンドラインツール用のアクションを提供するクラス
 */
public class TableActions {

	private static final String[] SHOW_TABLE_HEADER = {"TABLENAME","TABLETYPE","PARTITION_FUNC","STATUS" }; //$NON-NLS-1$
	private static final String[] SHOW_COLUMN_HEADER = {"PARTITION_KEY","COLUMNNAME","DATATYPE" }; //$NON-NLS-1$

	private static final int REST_MODE = 1; //$NON-NLS-1$
	private static final int NOREST_MODE = 0; //$NON-NLS-1$
	private static final String DEFAULT_HASH = "default"; //$NON-NLS-1$
	private static final String HASH_OPTION_NAME = "hashname"; //$NON-NLS-1$

	protected static DecimalFormat m_dcFmt = new DecimalFormat("00"); //$NON-NLS-1$
	/**
	 * パーティション化1設定
	 * @param cp コマンド解析結果
	 * @param database データベース情報
	 */
	public static void part(CommandParser cp, Database database) {
		
		
		// テーブル名設定
		String tableName = cp.getTables();
		if(tableName == null || tableName.equals("")){
			//引数が不正
			// FIXME:
			Logger.error("invalid argument");
			return;
		}

		String hashName = null;
		ArrayList paramNames = cp.getParamName();
		ArrayList paramValues = cp.getParamValue();
		for (int i=0; i<paramNames.size(); i++) {
			String paramName = (String)paramNames.get(i);
			if (paramName.equalsIgnoreCase(HASH_OPTION_NAME) ) {
				// パーティション関数の設定
				hashName = (String)paramValues.get(i);
				if (hashName.equals(DEFAULT_HASH)) {
					hashName = null;
				}
			} else {
				Logger.error(MessagesCommandLine.getString("cui.error.commandparser.param.invalid"));
				return;
			}
		}

		int partCount =  cp.getPartCnt();
		// パーティション数の妥当性チェック
		int serverSize = database.getInstanceIds().length;
		if (partCount < 2 || partCount > serverSize) {
			Logger.error(MessagesCommandLine.getString("cui.error.part.partno.invalid",
													   new Object[]{new Integer(serverSize)}) );
			return ;
		}

		
		// パーティション属性の設定
		ArrayList partColumns = cp.getKeyCol();
		String[] partColumnsName  = (String[])partColumns.toArray(new String[0]);
		
		
		Table table = database.getTable(tableName);
		if(table == null){
			//テーブル情報の取得失敗
			Logger.error(MessagesCommandLine.getString("cui.error.table.nosuchtable",
													   new Object[]{tableName}));
			return;
		}

		
		// テーブル情報の取得
		
		//テーブルのパーティション化
		try {
			if (table.part(partColumnsName, partCount, hashName)) {
				Logger.println(MessagesCommandLine.getString("cui.message.table.part",
															 new Object[]{tableName}) );
				return;
			}
		}
		catch (Exception e)
		{
			Logger.error("part() failed.");
			Logger.error(e.getMessage());
			Logger.trace(e);
			return;
		}
		
		Logger.error(MessagesCommandLine.getString("cui.error.table.part", new Object[]{tableName}) );
	}
	
	/**
	 * パーティション化2設定
	 * @param cp コマンド解析結果
	 * @param database データベース情報
	 */
	public static void part2(CommandParser cp, Database database) {

		ArrayList setServerList = cp.getServer();
		
		int[] serverIds = database.getInstanceIds();

		//サーバIDのチェック
		for (Iterator iter = setServerList.iterator(); iter.hasNext();) {
			String severID = (String) iter.next();
			int setid = Integer.parseInt(severID);
			if( ! Misc.contains(setid,serverIds) ){
				//指定されたサーバIDは存在しません
				//FIXME:
				return;
			}
		}

		
		// テーブル名設定
		String tableName = cp.getTables();
		if(tableName == null || tableName.equals("")){
			//引数が不正
			// FIXME:
			return;
		}

		String hashName = null;
		ArrayList paramNames = cp.getParamName();
		ArrayList paramValues = cp.getParamValue();
		for (int i=0; i<paramNames.size(); i++) {
			String paramName = (String)paramNames.get(i);
			if (paramName.equalsIgnoreCase(HASH_OPTION_NAME) ) {
				// パーティション関数の設定
				hashName = (String)paramValues.get(i);
				if (hashName.equals(DEFAULT_HASH)) {
					hashName = null;
				}
			} else {
				Logger.error(MessagesCommandLine.getString("cui.error.commandparser.param.invalid"));
				return;
			}
		}

		int partCount = cp.getPartCnt();
		// パーティション数の妥当性チェック
		int serverSize = database.getInstanceIds().length;
		if (partCount < 2 || partCount > serverSize) {
			Logger.error(MessagesCommandLine.getString("cui.error.part.partno.invalid",
													   new Object[]{new Integer(serverSize)}) );
			return ;
		}

		
		// パーティション属性の設定
		ArrayList partColumns = cp.getKeyCol();
		String[] partColumnsName  = (String[])partColumns.toArray(new String[0]);
		
		
		Table table = database.getTable(tableName);
		if(table == null){
			//テーブル情報の取得失敗
			Logger.error(MessagesCommandLine.getString("cui.error.table.nosuchtable",
													   new Object[]{tableName}));
			return;
		}

		
		// テーブル情報の取得
		
		//テーブルのパーティション化
		try {
			if ( ! table.part(partColumnsName, partCount, hashName) ) {
				Logger.error(MessagesCommandLine.getString("cui.error.table.part", new Object[]{tableName}) );
				return;
			}
		}
		catch (Exception e)
		{
			Logger.error("part() failed.");
			Logger.error(e.getMessage());
			Logger.trace(e);
			return;
		}
		
		
		// パーティション2化テーブルのパーティションテーブル配置設定の取得
		HashMap part2NoListMap = cp.getPart2NoListMap();
		
		
		//指定されていないパーティションを各インスタンスから削除する
		for (int i = 0; i < serverIds.length; i++) {
			
			int serverId = serverIds[i];
			ArrayList partNoList = (ArrayList)part2NoListMap.get(String.valueOf(serverId));
			
			if( partNoList == null){
				
				//サーバIDが指定されていなかったらすべてのパーティションを削除
				for (int k = 0 ; k < partCount; k++) {
						
					if ( ! table.dropPart( k, serverId) ) {
						Logger.error(MessagesCommandLine.getString("cui.error.table.part2",
																   new Object[]{tableName}));
						return;
					}							
				}					
				
			}else{
				
				//指定されていないパーティションを削除

				
				for (int k = 0 ; k < partCount; k++) {
					if( ! partNoList.contains( String.valueOf(k) ) ){
						
						if ( ! table.dropPart( k, serverId) ) {
							Logger.error(MessagesCommandLine.getString("cui.error.table.part2",
																	   new Object[]{tableName}));
							return;
						}							
					}
				}					
				
			}
			
		}
		
		Logger.println(MessagesCommandLine.getString("cui.message.table.part2", new Object[]{tableName}) );
	}

	/**
	 * パーティション化解除
	 * @param cp コマンド解析結果
	 * @param database データベース情報
	 */
	public static void unpart(CommandParser cp, Database database) {
		// テーブル名設定
		String tableName = cp.getTables();		
		if(tableName == null || tableName.equals("")){
			//引数が不正
			// FIXME:
			Logger.error("invalid argument");
			return;
		}

		
		Table table = database.getTable(tableName);
		if(table == null){
			//テーブル情報の取得失敗
			Logger.error(MessagesCommandLine.getString("cui.error.table.nosuchtable", new Object[]{tableName}) );
			return;
		}


		try {
			if( ! table.unpart() ){
				Logger.error(MessagesCommandLine.getString("cui.error.table.unpart", new Object[]{tableName}));
				return;
			}
		} catch (Exception e) {
			Logger.error(MessagesCommandLine.getString("cui.error.table.unpart", new Object[]{tableName}));
			Logger.error(e.getMessage());
			Logger.trace(e);
			return;
		}
		Logger.println(MessagesCommandLine.getString("cui.message.table.unpart", new Object[]{tableName}));
	}


	/**
	 * テーブル一覧表示
	 * @param gsc GSCヘの接続情報
	 * @param database データベース情報
	 */
	public static void showTable(Database database) {

		String[] tables;

		//テーブル名のリストを取得
		try {
			tables = database.getTableNames();
			
		} catch (ForestToolException e) {
			Logger.error(MessagesCommandLine.getString("cui.error.table.show"));
			Logger.error(e.getMessage());
			Logger.trace(e);
			return;
		}

		//DB情報表示
		Logger.println(MessagesCommandLine.getString("cui.message.table.show"));

		Table2StringUtil table2StringUtil = new Table2StringUtil( SHOW_TABLE_HEADER );

		
		//IDからインスタンス情報取得し表示
		
		for (int i = 0; i < tables.length; i++) {
			String tableName = tables[i];
			//テーブル名からテーブル情報取得
			Table table = database.getTable(tableName);
			if(table == null){
				//テーブル情報の取得失敗
				Logger.error(MessagesCommandLine.getString("cui.error.table.show"));
				return;
			}

			ArrayList row = new ArrayList();
			row.add(tableName );
			int type = table.getTableType();
			row.add(convertTableType(type));
			
			String hashname = table.getHashName();
			if (hashname != null ) {
				row.add(hashname);
			}
			else if (type == Table.TYPE_PARTITION_1 || type == Table.TYPE_PARTITION_2) {
				row.add("DEFAULT");
			}
			else {
				row.add("");
			}

			row.add(convertTableStatus(table.getStatus()));
						
			table2StringUtil.addRow(row);
		}
				
		table2StringUtil.print();
	}

	/**
	 * テーブルカラム情報表示
	 * @param tableName テーブル名
	 * @param database データベース情報
	 */
	public static void showTableColumn(CommandParser cp, Database database) {
		String tableName = cp.getTables();
		if(tableName == null || tableName.equals("")){
			//引数が不正
			// FIXME:
			Logger.error("invalid argument");
			return;
		}

		showTableColumn(tableName, database);
	}
	/**
	 * テーブルカラム情報表示
	 * @param tableName テーブル名
	 * @param database データベース情報
	 */
	public static void showTableColumn(String tableName, Database database) {
		

		Table table = database.getTable(tableName);
		if(table == null){
			//テーブル情報の取得失敗
			Logger.error(MessagesCommandLine.getString("cui.error.table.nosuchtable", new Object[]{tableName}) );
			return;
		}

		
		//Column名リスト表示
		String[] columnNames = null;
		ArrayList partitonKeyList = null;

		try {
			columnNames = table.getColumnNames();
			partitonKeyList = new ArrayList(Arrays.asList(table.getPartitionKeys())) ;
		}
		catch (Exception e)
		{
			Logger.error("getColumnNames() failed.");
			Logger.error(e.getMessage());
			Logger.trace(e);
			return;
		}

		//Column情報表示
		Logger.println(MessagesCommandLine.getString("cui.message.tablecolumn.show", new Object[]{tableName}) );
		
		Table2StringUtil table2StringUtil = new Table2StringUtil( SHOW_COLUMN_HEADER );

		
		try {
			//Column名から情報取得し表示
			for (int i = 0; i < columnNames.length; i++) {
				String columnName = columnNames[i];
				//テーブル名からテーブル情報取得
				
				ArrayList row = new ArrayList();
				row.add(convertPartKey(columnName, partitonKeyList ) ); //$NON-NLS-1$
				row.add(columnName); //$NON-NLS-1$
				row.add(table.getColumnType(columnName) ); //$NON-NLS-1$
							
				table2StringUtil.addRow(row);
					
			}
			table2StringUtil.print();
		} catch (ForestToolException e) {
			table2StringUtil.print();

			Logger.error(e.getMessage());
			Logger.trace(e);
		}
				

		
	}
	/**
	 * 優先度設定
	 * @param cp コマンド解析結果
	 * @param database データベース情報
	 */
	public static void setPriority(CommandParser cp, Database database) {

		String tableName = cp.getTables();
		if(tableName == null || tableName.equals("")){
			//引数が不正
			// FIXME:
			Logger.error("Invalid argument.");
			return;
		}

		Table table = database.getTable(tableName);
		if (table == null) {
			//テーブル情報の取得失敗
			Logger.error(MessagesCommandLine.getString("cui.error.table.nosuchtable", new Object[]{tableName}));
			return;
		}

		int partNo = cp.getPartNo();
		ArrayList serverList = cp.getServer();

		for (int pri=0 ; pri<serverList.size() ; pri++)
		{
			int serverid = Integer.parseInt((String)serverList.get(pri));
			if( ! table.setPriority(serverid, partNo, pri) ){
				Logger.error(MessagesCommandLine.getString("cui.error.priority.set", new Object[]{tableName}));
				return;
			}

			Logger.debug("setPriority: serverid=" + serverid + ",partition=" + partNo + ",priority=" + pri);
		}
		Logger.println(MessagesCommandLine.getString("cui.message.priority.set", new Object[]{tableName}));
		
	}

	/**
	 * 優先度表示(show priority)
	 * @param cp コマンド解析結果
	 * @param database データベース情報
	 */
	public static void showPriority(CommandParser cp, Database database) {
		String tableName = cp.getTables();
		if(tableName == null || tableName.equals("")){
			//引数が不正
			// FIXME:
			Logger.error("invalid argument");
			return;
		}

		showPriority(tableName, database);
	}

	/**
	 * 優先度表示(\fp)
	 * @param tableName テーブル名
	 * @param database データベース情報
	 */
	public static void showPriority(String tableName, Database database) {
		
		Table table = database.getTable(tableName);
		if (table == null) {
			//テーブル情報の取得失敗
			Logger.error(MessagesCommandLine.getString("cui.error.table.nosuchtable",
													   new Object[]{tableName}));
			return;
		}

		int partCount = table.getPartCount();

		if ( partCount==1 )
		{
			Logger.println("Table `" + tableName + "' is not a partitioned table.");
			return;
		}

		/*
		 * テーブル表示用ヘッダを作成する（サーバID + パーティション数分）
		 */
		ArrayList columnList = new ArrayList();
		columnList.add( 0,"SERVERID");
		for (int i = 0; i < table.getPartCount(); i++) {
			columnList.add(tableName + "_" + m_dcFmt.format(i));
		}
		
		Logger.println(MessagesCommandLine.getString("cui.message.priority.show", new Object[]{tableName}) );
		
		Table2StringUtil table2StringUtil = new Table2StringUtil( columnList );

		try {
			/*
			 * 各インスタンスの処理
			 */
			int[] ids = table.getInstanceIds();
			
			for (int j=0 ; j<ids.length ; j++)
			{
				ArrayList row = new ArrayList();

				int serverId = ids[j];
				row.add(new Integer(serverId));

				/*
				 * 各パーティションの優先度を取得
				 */
				for (int i = 1; i < columnList.size(); i++ )
				{
					int priority = table.getPriority(serverId, i-1);

					if (priority < 0 )
						row.add("");
					else
						row.add( new Integer(priority) );

					//					Logger.println();
				}

				table2StringUtil.addRow(row);
			}
		
			table2StringUtil.print();

		} catch (Exception e) {
			Logger.error(MessagesCommandLine.getString("cui.error.priority.show", new Object[]{tableName}) );
			Logger.error(e.getMessage());
			Logger.trace(e);
		}
	}

	
	/*
	 * FIXME: need to implement
	 *
	private boolean validatePriority(TableInfo tableInfo) {
		int m_partCount = tableInfo.getPartCount();
		ArrayList m_servers = tableInfo.getServer();
		boolean result = true;
		
		//優先順チェック	
		for (int partNo = 0; partNo < m_partCount; partNo++) {
			ArrayList priorityNoList = new ArrayList();
			//優先順配列を作成
			for (Iterator iterator = m_servers.iterator(); iterator.hasNext();) {		
				ServerPriorityInfo svrPriorityInfo =
					(ServerPriorityInfo) iterator.next();

				PriorityInfo priorityInfo =	svrPriorityInfo.getPartition(partNo);
				if(priorityInfo != null){
					priorityNoList.add(priorityInfo.getPriority());
				}
			}

			//優先順配列をチェック
			for (int priority = 0; priority < priorityNoList.size(); priority++) {
				boolean check = false;
				boolean duplicate = false;
				for (int i = 0; i < priorityNoList.size(); i++) {
					Integer priorityNo = (Integer)priorityNoList.get(i);
					if (priority == priorityNo.intValue()) {
						if(check){
							duplicate = true;
							break;
						}
						check = true;
					}
				}
				if (duplicate) {
				Logger.error(MessagesCommandLine.getString("errors.duplicate", m_dcFmt.format(partNo)));
					result = false;
					break;
				}
				if (!check) {
					String[] param = {m_dcFmt.format(partNo),Integer.toString(priority)};
					Logger.error(MessagesCommandLine.getString("errors.priority", param));
					result = false;
					break;
				}
			}
		}
		
		
		return result;
	}
	*/

	
	private static String convertTableType(int type) {
		String ret = "";
		if (type == Table.TYPE_REPLICATED ) {
			ret = MessagesCommandLine.getString("form.partition.type0");
		} else if (type == Table.TYPE_PARTITION_1){
			ret = MessagesCommandLine.getString("form.partition.type1");
		} else if (type == Table.TYPE_PARTITION_2){
			ret = MessagesCommandLine.getString("form.partition.type2");
		} else {
			// FIXME:
			ret = "UNKNOWN VALUE";
		}
		return ret;
	}
	
	
	private static String convertPartKey(String column , ArrayList partitionKeyList ) {
		String ret = " ";
		if ( partitionKeyList.contains(column) ){
			ret = " * ";
		} else {
			ret = "";
		}
		return ret;
	}
	
	
	private static String convertTableStatus(int status) {
		String ret = "";
		if (status == REST_MODE ) {
			ret = MessagesCommandLine.getString("form.admintree.status.ng");
		} else if (status == NOREST_MODE){
			ret = MessagesCommandLine.getString("form.admintree.status.ok");
		} else {
			// FIXME:
			ret = "UNKNOWN VALUE";
		}
		return ret;
	}

}
