/*
 * Copyright (C) 2005 NTT DATA Corporation
 * 
 */
package org.postgresforest.tool.action;

import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import org.postgresforest.tool.bean.ColumnInfo;
import org.postgresforest.tool.bean.DbLinkInfo;
import org.postgresforest.tool.bean.GSCInfo;
import org.postgresforest.tool.bean.LoginInfo;
import org.postgresforest.tool.bean.ServerInfo;
import org.postgresforest.tool.bean.ServerPriorityInfo;
import org.postgresforest.tool.bean.TableInfo;
import org.postgresforest.tool.db.DbBase;
import org.postgresforest.tool.db.GSCDataCheck;
import org.postgresforest.tool.db.UserData;
import org.postgresforest.tool.db.UserDataCheck;
import org.postgresforest.tool.util.MessagesCommandLine;

/**
 * グローバルシステムカタログ整合性チェックアクション
 * 
 * <pre>
 * 
 *  
 *   グローバルシステムカタログの整合性チェックを行う。
 *  　・PrimaryとBackupのグローバルシステムカタログ用データベースの存在をチェックします。
 *  　・PrimaryとBackupのグローバルシステムカタログ用データベースの内容が同じかチェックします。
 *  　・グローバルシステムカタログのforest_serverテーブルとforest_servdbテーブルのレコードを検索し、ユーザデータ用データベースの存在をチェックします。
 *  　・グローバルシステムカタログのforest_tablepartテーブルとforest_tablepartdtlテーブルのレコードを検索し、ユーザデータ用データベースにパーティションテーブルまたは多重化テーブルが存在するかチェックします。
 *   
 *   
 *  
 * </pre>
 */
public class CheckAction extends ActionBase {

    private UserDataCheck m_userData;
    private ArrayList m_serverList;

    /**
     * @param gsc
     */
    public CheckAction() {
        super(null);
    }

    /**
     * グローバルシステムカタログ整合性チェックメイン処理
     * 
     * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping,
     *      org.apache.struts.action.ActionForm,
     *      javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    public boolean execute(LoginInfo login)  {

        boolean ret = false;

        System.out.println(MessagesCommandLine.getString("message.check.header")); //$NON-NLS-1$
        System.out.println(MessagesCommandLine.getString("message.check.start")); //$NON-NLS-1$

        while(true){

            System.out.println(MessagesCommandLine.getString("message.check.db.gsc")); //$NON-NLS-1$

            //--------------------------------------------------------------------------------
	        //グローバルシステムカタログ用データアクセス初期化
	        //--------------------------------------------------------------------------------
	        if (!initGsc(login)) {
		        break;
	        }
	        //--------------------------------------------------------------------------------
	        //グローバルシステムカタログ用データベースの存在をチェックします。
	        //--------------------------------------------------------------------------------
	        if (!chkGscDb()) {
		        break;
	        }
	
	        //--------------------------------------------------------------------------------
	        //グローバルシステムカタログ用データベースの内容が同じかチェックします。
	        //--------------------------------------------------------------------------------
	        if (!chkGscTables()) {
		        break;
	        }
	

	        
            System.out.println();
            System.out.println();
            System.out.println(MessagesCommandLine.getString("message.check.db.user")); //$NON-NLS-1$
            //--------------------------------------------------------------------------------
	        //ユーザデータアクセス初期化
	        //--------------------------------------------------------------------------------
	        if (!initUserData()) {
		        break;
	        }

	        //--------------------------------------------------------------------------------
	        //グローバルシステムカタログのforest_serverテーブルとforest_servdbテーブルのレコードを検索し、ユーザデータ用データベースの存在をチェックします。
	        //--------------------------------------------------------------------------------
	        if (!chkUserData()) {
		        break;
	        }
	
	        ret = true;
	        break;
        }
        
        if (!ret) {
            System.out.println(MessagesCommandLine.getString("message.check.error")); //$NON-NLS-1$
        }

        System.out.println(MessagesCommandLine.getString("message.check.end")); //$NON-NLS-1$

        return ret;
    }

    /**
     * @return
     * @throws Exception
     * @since
     */
    protected boolean chkUserData() {


        try {
            if (!connectSelect(m_gsc)) {
                return false;
            }

            if (!connectTemlate(m_userData)) {
                return false;
            }

            ArrayList dbMapList = m_userData.getDbMapList();
            HashMap serverDbMap = new HashMap();
            for (int i = 0; i < m_serverList.size(); i++) {
                ServerInfo serverInfo = (ServerInfo) m_serverList.get(i);
                Integer serverId = new Integer(serverInfo.getId());
                serverDbMap.put(serverId, dbMapList.get(i));

            }

            m_userData.close();
            m_userData.clearServers();

            ArrayList dbLinkList = m_gsc.getDbLinkList(m_serverList);

            for (Iterator iter = dbLinkList.iterator(); iter.hasNext();) {
                DbLinkInfo dbLinkInfo = (DbLinkInfo) iter.next();

                System.out.println("  " + MessagesCommandLine.getString("message.check.dbname", dbLinkInfo.getName() ) ); //$NON-NLS-1$ //$NON-NLS-2$


                //UserDBの存在チェック
                if(!chkUserDataDb(serverDbMap, dbLinkInfo)){
                    return false;
                }
                

                //--------------------------------------------------------------------------------
                //グローバルシステムカタログのforest_tablepartテーブルとforest_tablepartdtlテーブルのレコードを検索し、ユーザデータ用データベースにパーティションテーブルまたは多重化テーブルが存在するかチェックします。
                //--------------------------------------------------------------------------------
                String userDb = dbLinkInfo.getName();
                ArrayList tableList = m_gsc.getTableList(userDb);

                if (!connect(m_userData, true)) {
                    return false;
                }

                for (Iterator itr = tableList.iterator(); itr.hasNext();) {

                    TableInfo tableInfo = (TableInfo) itr.next();

                    String tableName = tableInfo.getName();

                    if (tableInfo.isPartition()) {

                        //--------------------------------------------------------------------------------
                        //パーティション化テーブル
                        //--------------------------------------------------------------------------------
                        chkPartitionTable(userDb, tableInfo);

                    } else {

                        //--------------------------------------------------------------------------------
                        //多重化テーブル
                        //--------------------------------------------------------------------------------
                        chkMultiTable(userDb, tableName);

                    }

                }
                //DBが複数の時に、前のDBの情報を参照しているので、クリア
                m_userData.close();
                m_userData.clearServers();
                System.out.println();

            }

        } catch (SQLException e) {
            return false;
        } finally {
            m_userData.close();
            m_gsc.close();

        }


        return true;
    }

    /**
     * @param serverDbMap
     * @param dbLinkInfo
     * @param userDb
     * @return
     * @since 
     */
    private boolean chkUserDataDb(HashMap serverDbMap, DbLinkInfo dbLinkInfo ) {

        String userDb = dbLinkInfo.getName();

        System.out.print("    " + MessagesCommandLine.getString("message.check.db")); //$NON-NLS-1$ //$NON-NLS-2$

        m_userData.setDbName(userDb);

        //サーバIDでインスタンス情報取得
        ArrayList dbServerList = dbLinkInfo.getServer();
        for (Iterator iterator = dbServerList.iterator(); iterator
                .hasNext();) {
            ServerInfo serverInfo = (ServerInfo) iterator.next();
            Integer keyID = new Integer(serverInfo.getId());
            HashMap dbNameMap = (HashMap) serverDbMap.get(keyID);

            String host = serverInfo.getHost();
            String port = serverInfo.getPort();

            if (!dbNameMap.containsKey(userDb)) {
                //DBが存在しません
                String url = UserData.makeUrl(host, port, ""); //$NON-NLS-1$
                Object[] arg = { url, userDb };
                System.out.println("    " + MessagesCommandLine.getString( //$NON-NLS-1$
                        "errors.user.noexist", arg)); //$NON-NLS-1$
                return false;
            }

            //ユーザDBクラスに追加
            m_userData.addUrl(host, port);

        }

        System.out.println(MessagesCommandLine.getString("message.check.ok")); //$NON-NLS-1$
        
        return true;
    }

    /**
     * @return
     * @throws Exception
     * @since
     */
    protected boolean initUserData() {
    
        m_userData = new UserDataCheck(m_gsc.getUserPassword());
    
        try {
            if (!connectSelect(m_gsc)) {
                return false;
            }
    
            m_serverList = m_gsc.getServerList();

            //サーバIDでインスタンス情報取得
            for (Iterator iterator = m_serverList.iterator(); iterator.hasNext();) {
                ServerInfo serverInfo = (ServerInfo) iterator.next();
                String host = serverInfo.getHost();
                String port = serverInfo.getPort();
    
                //ユーザDBクラスに追加
                m_userData.addUrl(host, port);
            }
    
        } catch (SQLException e) {
            return false;
        } finally {
            m_gsc.close();
        }
    
        return true;
    }

    /**
     * 
     * @param userDb
     * @param tableInfo
     * @throws SQLException
     * @since
     */
    protected void chkPartitionTable(String userDb, TableInfo tableInfo)
            throws SQLException  {

        boolean chkFlg = true;

        DecimalFormat dcFmt = new DecimalFormat("00"); //$NON-NLS-1$
        String tableName = tableInfo.getName();

        System.out.println("    " + MessagesCommandLine.getString("message.check.table.partition", tableName)  ); //$NON-NLS-1$ //$NON-NLS-2$

        
        //サーバープライオリティ情報Map（Key：URL）
        HashMap svrPrioInfoMap = new HashMap();

        ArrayList priorityList = m_gsc.getPriorityList(userDb, tableName, m_gsc
                .getServerList(userDb));
        for (Iterator iter = priorityList.iterator(); iter.hasNext();) {
            ServerPriorityInfo serverPriorityInfo = (ServerPriorityInfo) iter
                    .next();
            String url = DbBase.makeUrl(serverPriorityInfo.getHost(),
                    serverPriorityInfo.getPort(), userDb);
            svrPrioInfoMap.put(url, serverPriorityInfo);
        }

        //パーティションカラム情報
        ArrayList partitionColumnList = m_gsc.getPartitionColumn(userDb,
                tableName);


        System.out.print("      " + MessagesCommandLine.getString("message.check.view")); //$NON-NLS-1$ //$NON-NLS-2$
        
        //--------------
        //VIEWのチェック
        //--------------
        ArrayList urlList = m_userData
                .checkTable(tableName, UserData.TYPE_VIEW);
        for (Iterator iterator1 = urlList.iterator(); iterator1.hasNext();) {

            String url = (String) iterator1.next();

            ServerPriorityInfo serverPriorityInfo = (ServerPriorityInfo) svrPrioInfoMap
                    .get(url);
            if (serverPriorityInfo.getPartition().size() > 0) {
                if(chkFlg){
                    System.out.println(MessagesCommandLine.getString("message.check.ng")); //$NON-NLS-1$
                    chkFlg= false;
                }
                //VIEWが存在しません
                System.out.println("        " + MessagesCommandLine.getString( //$NON-NLS-1$
                        "errors.user.view.noexist", new Object[] { url, //$NON-NLS-1$
                                tableName }));
            }

        }
        if(chkFlg){
            System.out.println(MessagesCommandLine.getString("message.check.ok")); //$NON-NLS-1$
        }


        
        
        
        //--------------
        //パーティション項目チェック
        //--------------

        System.out.print("      " + MessagesCommandLine.getString("message.check.partition")); //$NON-NLS-1$ //$NON-NLS-2$
        chkFlg= true;

        //Viewが存在するはずのURLのMAPを作成し、チェック関数を呼ぶ
        HashMap viewUrlMap = new HashMap();
        for (Iterator iter = priorityList.iterator(); iter.hasNext();) {
            ServerPriorityInfo serverPriorityInfo = (ServerPriorityInfo) iter
                    .next();
            if (serverPriorityInfo.getPartition().size() > 0) {
                String url = DbBase.makeUrl(serverPriorityInfo.getHost(),
                        serverPriorityInfo.getPort(), userDb);
                viewUrlMap.put(url, url);
            }
        }
        chkPartitionColumn(tableName, partitionColumnList, viewUrlMap);

        //データ数チェック(パーティション２の場合、VIEWがないあるいはレコード数が違うため実施しない)
        if (tableInfo.getPartType() != TableInfo.TYPE_PARTITION_2) {
            //データ数チェック
            if (!m_userData.checkDataCount(tableName)) {
                if(chkFlg){
                    System.out.println(MessagesCommandLine.getString("message.check.ng")); //$NON-NLS-1$
                    chkFlg= false;
                }
                System.out.println("        " + MessagesCommandLine.getString( //$NON-NLS-1$
                        "errors.user.data.difference", new Object[] { userDb, //$NON-NLS-1$
                                tableName }));
            }
        }

        if(chkFlg){
            System.out.println(MessagesCommandLine.getString("message.check.ok")); //$NON-NLS-1$
        }
        

        
        
        
        
        
        //--------------
        //実テーブルのチェック
        //--------------
        System.out.print("      " + MessagesCommandLine.getString("message.check.table.dhysical")); //$NON-NLS-2$
        chkFlg= true;

        int partCount = tableInfo.getPartCount();
        for (int i = 0; i < partCount; i++) {
            String physicalTable = tableName + "_" + dcFmt.format(i); //$NON-NLS-1$
            urlList = m_userData.checkTable(physicalTable, UserData.TYPE_TABLE);
            for (Iterator iterator3 = urlList.iterator(); iterator3.hasNext();) {
                String url = (String) iterator3.next();

                ServerPriorityInfo serverPriorityInfo = (ServerPriorityInfo) svrPrioInfoMap
                        .get(url);
                if (serverPriorityInfo.getPartition(i) != null) {
                    if(chkFlg){
                        System.out.println(MessagesCommandLine.getString("message.check.ng")); //$NON-NLS-1$
                        chkFlg= false;
                    }
                    System.out.println("        " + MessagesCommandLine.getString( //$NON-NLS-1$
                            "errors.user.table.noexist", new Object[] { url, //$NON-NLS-1$
                                    physicalTable }));
                }

            }

            //パーティション項目チェック
            HashMap partUrlMap = new HashMap();
            for (Iterator iter = priorityList.iterator(); iter.hasNext();) {
                ServerPriorityInfo serverPriorityInfo = (ServerPriorityInfo) iter
                        .next();
                if (serverPriorityInfo.getPartition(i) != null) {
                    String url = DbBase.makeUrl(serverPriorityInfo.getHost(),
                            serverPriorityInfo.getPort(), userDb);
                    partUrlMap.put(url, url);
                }
            }

            chkPartitionColumn(physicalTable, partitionColumnList, partUrlMap);

            //データ数チェック
            if (!m_userData.checkDataCount(physicalTable, partUrlMap)) {
                if(chkFlg){
                    System.out.println(MessagesCommandLine.getString("message.check.ng")); //$NON-NLS-1$
                    chkFlg= false;
                }
                System.out.println("        " + MessagesCommandLine.getString( //$NON-NLS-1$
                        "errors.user.data.difference", new Object[] { userDb, //$NON-NLS-1$
                                physicalTable }));
            }

        }
        
        if(chkFlg){
            System.out.println(MessagesCommandLine.getString("message.check.ok")); //$NON-NLS-1$
        }
        
        
    }

    /**
     * @param login
     *            入力情報
     * @return
     * @since
     */
    protected boolean initGsc(LoginInfo login) {

        //GSCクラス生成
        m_gsc = new GSCDataCheck(login.getUser(), login.getPassword());

        //接続情報設定
        String hostName = login.getHostname();
        String portNo = login.getPortno();
        String dbName = login.getDbname();
        if (dbName != null && dbName.compareTo("") != 0) { //$NON-NLS-1$
            m_gsc.addUrl(hostName, portNo, dbName);
        }

        //GSC設定チェック
        if (m_gsc.getURL().length == 0) {
            System.out.println(MessagesCommandLine.getString("errors.gsc.input")); //$NON-NLS-1$
            return false;
        }

        return true;
    }

    /**
     * @return
     * @throws SQLException
     * @since
     */
    protected boolean chkGscDb()  {

        System.out.print("  " + MessagesCommandLine.getString("message.check.gsc.exist")); //$NON-NLS-1$ //$NON-NLS-2$
        try {
            if (!connect(m_gsc)) {
                return false;
            }

            //GSC情報を更新
            if (!m_gsc.updateGscServer()) {
                return false;
            }

            ArrayList gscList = m_gsc.getGscList();

            m_gsc.close();

            if (!connectTemlate(m_gsc)) {
                return false;
            }

            ArrayList dbMapList = m_gsc.getDbMapList();

            for (int i = 0; i < gscList.size(); i++) {
                GSCInfo gscinfo = (GSCInfo) gscList.get(i);
                String dbName = gscinfo.getDb();
                HashMap dbNameMap = (HashMap) dbMapList.get(i);
                if (!dbNameMap.containsKey(dbName)) {
                    //DBが存在しません
                    System.out.println(MessagesCommandLine.getString("message.check.ng")); //$NON-NLS-1$
                    String gscName = "//" + gscinfo.getHost() + ":" //$NON-NLS-1$ //$NON-NLS-2$
                            + gscinfo.getPort() + "/"; //$NON-NLS-1$
                    Object[] arg = { gscName, dbName };
                    System.out.println("    " + MessagesCommandLine.getString("errors.gsc.noexist", //$NON-NLS-1$ //$NON-NLS-2$
                            arg));
                    return false;
                }

            }
        } catch (SQLException e) {
            return false;

        } finally {
            m_gsc.close();
        }

        System.out.println(MessagesCommandLine.getString("message.check.ok")); //$NON-NLS-1$
        return true;
    }

    /**
     * 
     * @since
     */
    protected boolean chkGscTables() {
        //--------------------------------------------------------------------------------
        //グローバルシステムカタログ用データベースの内容が同じかチェックします。
        //--------------------------------------------------------------------------------
        boolean tableChk = true;
        
        System.out.print("  " + MessagesCommandLine.getString("message.check.gsc.data")); //$NON-NLS-1$ //$NON-NLS-2$

        
        
        GSCDataCheck gscChk = (GSCDataCheck) m_gsc;

        try {
            if (!connect(gscChk)) {
                return false;
            }

            String[] tables = gscChk.getTableNames();
            for (int i = 0; i < tables.length; i++) {

                if (!gscChk.checkTableData(tables[i])) {
                    if(tableChk){
                        tableChk = false;
                        System.out.println(MessagesCommandLine.getString("message.check.ng"));
                	}
                    System.out.println("    " + MessagesCommandLine.getString("errors.gsc.difference", //$NON-NLS-1$ //$NON-NLS-2$
                            tables[i]));
                }
                
            }

        } finally {
            gscChk.close();
        }

        if(tableChk)
            System.out.println(MessagesCommandLine.getString("message.check.ok")); //$NON-NLS-1$
            
        return true;
    }

    /**
     * 多重化テーブルチェック
     * 
     * @param userDb
     * @param tableName
     * @since
     */
    protected void chkMultiTable(String userDb, String tableName) {

        boolean chkFlg = true;

        System.out.print("    " + MessagesCommandLine.getString("message.check.table.muliti",  tableName)); //$NON-NLS-1$ //$NON-NLS-2$


        //テーブルの存在チェック
        ArrayList urlList = m_userData.checkTable(tableName,
                UserData.TYPE_TABLE);

        //VIEWの場合はconListに渡すcheckTable()の引数を変更
        if (urlList.size() > 0) {
            urlList = m_userData.checkTable(tableName, UserData.TYPE_VIEW);
        }


        for (Iterator iterator3 = urlList.iterator(); iterator3.hasNext();) {
            if(chkFlg){
                System.out.println(MessagesCommandLine.getString("message.check.ng")); //$NON-NLS-1$
                chkFlg= false;
            }
            //テーブルが存在しません
            System.out.println("      " + MessagesCommandLine.getString("errors.user.table.noexist", //$NON-NLS-1$ //$NON-NLS-2$
                    new Object[] { iterator3.next(), tableName }));

        }

        
        //データ数チェック
        if (!m_userData.checkDataCount(tableName)) {
            if(chkFlg){
                System.out.println(MessagesCommandLine.getString("message.check.ng")); //$NON-NLS-1$
                chkFlg= false;
            }
            System.out.println("      " + MessagesCommandLine.getString( //$NON-NLS-1$
                    "errors.user.data.difference", new Object[] { userDb, //$NON-NLS-1$
                            tableName }));
        }

        if(chkFlg)
            System.out.println(MessagesCommandLine.getString("message.check.ok")); //$NON-NLS-1$
        
    }

    /**
     * 
     * @param tableName
     * @param partitionColumnList
     * @throws SQLException
     * @since
     */
    protected void chkPartitionColumn(String tableName,
            ArrayList partitionColumnList) throws SQLException {

        //--------------------------------------------------------------------------------
        //グローバルシステムカタログのforest_partatrテーブルのレコードを検索し、ユーザデータ用データベースのパーティションテーブルにカラムが存在するかチェックします。
        //--------------------------------------------------------------------------------

        for (Iterator itr = partitionColumnList.iterator(); itr.hasNext();) {

            ColumnInfo columnInfo = (ColumnInfo) itr.next();

            ArrayList urlList = m_userData.checkColumn(tableName, columnInfo);

            for (Iterator itr2 = urlList.iterator(); itr2.hasNext();) {
                //パーティション属性がない
                System.out.println("      " + MessagesCommandLine.getString("errors.user.partition", //$NON-NLS-1$ //$NON-NLS-2$
                        new Object[] { itr2.next(), tableName,
                                columnInfo.getName() }));

            }

        }
    }

    /**
     * 
     * 
     * @param tableName
     * @param partitionColumnList
     * @param svrPrioInfoMap
     * @throws SQLException
     * @since
     */
    protected void chkPartitionColumn(String tableName,
            ArrayList partitionColumnList, HashMap urlMap) throws SQLException {

        for (Iterator itr = partitionColumnList.iterator(); itr.hasNext();) {

            ColumnInfo columnInfo = (ColumnInfo) itr.next();

            ArrayList urlList = m_userData.checkColumn(tableName, columnInfo);

            for (Iterator itr2 = urlList.iterator(); itr2.hasNext();) {

                String url = (String) urlMap.get(itr2.next());
                if (url != null) {
                    //パーティション属性がない
                    System.out.println("        " + MessagesCommandLine.getString( //$NON-NLS-1$
                            "errors.user.partition", new Object[] { url, //$NON-NLS-1$
                                    tableName, columnInfo.getName() }));
                }
            }

        }

    }

}