/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : InputInterfaceTable.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */
package ciss.phase_viewer.inputinterface.inputinterfacetable;

import java.util.Stack;
import java.util.Vector;

import org.apache.log4j.Logger;

import ciss.phase_viewer.inputinterface.InputInterfaceEntry;

public class InputInterfaceTable extends InputInterfaceEntry {
    private Logger logger = Logger.getLogger(InputInterfaceTable.class
            .getName());

    private String[] tableIdentifiers;

    private String[] columnIdentifiers;
    private Vector tableData = new Vector();

    private int numRows = 0;
    private int numColumns = 0;

    private Vector listeners = new Vector();

    private InputInterfaceTableColumns[] columns;

    public static int REPLACE = 0;
    public static int REMOVE = 1;

    private int lastChangeScheme = REPLACE;

    private int[] lastChangeRow = { -1 };

    private Stack undoStack = new Stack();

    public InputInterfaceTable() {
    }

    public InputInterfaceTable(String name, String[] tableIdentifiers,
            Vector tableData) {
        super(name);
        this.tableIdentifiers = tableIdentifiers;
        this.tableData = tableData;
    }

    /**
     * InputInterfaceTableのインスタンスを生成する. このオブジェクトは決められたカラムの性質を持っているが,
     * このオブジェクトの作成元となる入力ファイルやテーブルのテーブルカラムがその全てを網羅しているとは限らない. そこで,
     * 引数として作成元のカラム名の配列の他, このオブジェクトが元々持っているカラムの性質をカプセル化した
     * InputInterfaceTableColumnsオブジェクトの配列も取っている. この, 「元々持っているカラムの性質」は
     * 設定ファイルから読み込まれる.
     * 
     * @param name
     *            テーブル名
     * @param tableIdentifiers
     *            作成元のカラム名配列
     * @param columns
     *            元々持っているカラムの性質の配列
     * @param tableData
     *            テーブルのデータそのもの. String配列のVectorで, Vectorの各要素がデータの各行に対応する.
     */
    public InputInterfaceTable(String name, String[] tableIdentifiers,
            InputInterfaceTableColumns[] columns, Vector tableData) {
        super(name);
        this.tableIdentifiers = tableIdentifiers;
        this.columns = columns;
        this.tableData = tableData;

        this.createColumnIdentifiers();
        this.fitTableDatatoSpec(tableIdentifiers);
    }

    /**
     * 最後に施した編集が行の差し替えなのか削除なのかを返す.
     * 
     * @return 最後に施した変更が削除ならREMOVE, 差し替えならREPLACE
     */
    public int lastChangedScheme() {
        return lastChangeScheme;
    }

    /**
     * 最後に編集された行のインデックスを返す. 複数の変更を施す場合も有り得るので 配列を返す.
     * 
     * @return 最後に編集された行のインデックス配列.
     */
    public int[] lastChangedRow() {
        return lastChangeRow;
    }

    /**
     * カラム名の中から, 指定の識別子のインデックスを探す. 見つけられない場合, またはカラム名が定義されていない場合 -1を返す.
     * 
     * @param identName
     *            見つけたい識別氏名
     * @return indexNameに対応するインデックス
     */
    public int getIndexFor(String identName) {
        String[] idents = getColumnIdentifiers();
        if (idents == null)
            return -1;
        for (int i = 0; i < idents.length; i++) {
            if (idents[i].equalsIgnoreCase(identName))
                return i;
        }
        return -1;
    }

    /**
     * このクラスによって表現されるテーブル型データが「本来持っている」カラム情報をカプセル化した,
     * InputInterfaceTableColumnsオブジェクトの配列をセットする.
     * 
     * @param columns
     *            カラム情報配列.
     */
    public void setInputInterfaceTableColumns(
            InputInterfaceTableColumns[] columns) {
        this.columns = columns;
        this.createColumnIdentifiers();
        this.fitTableDatatoSpec(tableIdentifiers);
    }

    /**
     * このオブジェクトに関連付けられたInputInterfaceTableColumnsオブジェクトを返す.
     * 
     * @return InputInterfaceTableColumns オブジェクト.
     */
    public InputInterfaceTableColumns[] getInputInterfaceTableColumns() {
        return this.columns;
    }

    /**
     * tableDataをまずInputSpecificationに合わせて拡大? する (途中でカラムの増減を管理するのは面倒).
     * Specificationにはあるけど渡された引数の識別子にはないデータには空の値を入れておく.
     * 
     * @param idents
     *            入力ファイルやテーブルなどから渡される, 不充分である可能性のある識別子配列.
     */
    private void fitTableDatatoSpec(String[] idents) {
        if (columnIdentifiers == null || idents == null) {
            return;
        }

        int[] map = getIndexArrays(idents);
        int row = tableData.size();

        Vector tmpVec = new Vector();
        for (int j = 0; j < columnIdentifiers.length; j++) {
            logger.debug(" " + columnIdentifiers[j]);
        }

        for (int i = 0; i < row; i++) {
            String[] tmpString = (String[]) tableData.elementAt(i);
            String[] fullString = new String[columnIdentifiers.length];
            for (int j = 0; j < columnIdentifiers.length; j++) {
                if (map[j] == -1) {
                    fullString[j] = "";
                } else {
                    fullString[j] = tmpString[map[j]];
                }
                logger.debug(" " + fullString[j] + " ");
            }
            tmpVec.addElement(fullString);
        }

        tableData = tmpVec;
    }

    /**
     * このテーブルが「本来持っている」カラム名の配列を作成する. この配列と整合するようにテーブルデータの更新などは行われる.
     */
    private void createColumnIdentifiers() {
        if (columns == null) {
            return;
        }

        Vector tmpVec = new Vector();
        for (int i = 0; i < columns.length; i++) {
            if (!columns[i].ignoreOnSave()) {
                tmpVec.addElement(columns[i].getInputSpecifications());
            }
        }

        columnIdentifiers = new String[tmpVec.size()];
        for (int i = 0; i < tmpVec.size(); i++) {
            columnIdentifiers[i] = (String) tmpVec.elementAt(i);
        }
    }

    /**
     * Specificationによって決まっている識別子配列を返す.
     * 
     * @return 識別子の配列(プログラム中変化することはない; ただしセットされていない場合, 'tableidentifier'を返す).
     */
    public String[] getColumnIdentifiers() {
        if (columnIdentifiers == null)
            return tableIdentifiers;
        return this.columnIdentifiers;
    }

    /**
     * columnIdentifiersと引数の配列のマッピングをする配列を決めるメソッド. コード例を下記. int map [] =
     * getIndexArrays(idents); for ( int i=0 ; i<subData.size() ; i++ ) { String
     * [] subdat = (String []) subData.elementAt(i); String [] fulldat = new
     * String[columnIdentifiers.length]; for ( int j=0 ;
     * j<columnIdentifiers.length ; j++ ) { if ( map[j] == -1 ) { fulldat[j] =
     * ""; } else { fulldat[j] = subdat[map[j]]; } }
     * tableData.addElement(fulldat); }
     * 
     * @param ident
     *            この配列の内容と整合するようにマッピング配列が作成される.
     * @return マッピング用配列. 対応する要素がない場合-1.
     */
    private int[] getIndexArrays(String[] ident) {
        int[] map = new int[columnIdentifiers.length];
        for (int i = 0; i < columnIdentifiers.length; i++) {
            map[i] = -1;
            for (int j = 0; j < ident.length; j++) {
                if (columnIdentifiers[i].equalsIgnoreCase(ident[j])) {
                    map[i] = j;
                    break;
                }
            }
        }
        return map;
    }

    public int getEntryType() {
        return super.TABLE;
    }

    public void setTableIdentifiers(String[] tableIdentifiers) {
        if (tableIdentifiers == null)
            return;
        this.tableIdentifiers = tableIdentifiers;
        setNumColumns(tableIdentifiers.length);
    }

    /**
     * とある行からとある行までのデータを削除するメソッド.
     * 
     * @param indexfrom
     *            この行以降のデータが削除される.
     * @param indexto
     *            この行までのデータが削除される.
     */
    public void removeTableDataRow(int indexfrom, int indexto) {
        if (indexto < indexfrom) {
            return;
        }
        lastChangeRow = new int[indexto - indexfrom];
        for (int i = indexfrom; i < indexto + 1; i++) {
            try {
                tableData.remove(i);
                tableData.trimToSize();
                lastChangeRow[i] = i;
                lastChangeScheme = REMOVE;
            } catch (ArrayIndexOutOfBoundsException oe) {
            }
        }
    }

    /**
     * 一括削除
     */
    public void removeAll() {
        tableData = new Vector();
    }

    /**
     * とある行のデータを差し替える(その行のインデックスがこのオブジェクトが 保持するデータよりも大きい場合は新たに付け加える). 識別子配列と, この
     * オブジェクトが持つカラム情報とが整合するようにデータを差し替える (付け足す).
     * 
     * @param idents
     *            呼び出し元の識別子配列.
     * @param rowData
     *            1行データ
     * @param rowIndex
     *            差し替えたい行のインデックス.
     */
    public void replaceTableDataRow(String[] idents, String[] rowData,
            int rowIndex) {
        if (columnIdentifiers == null || idents == null || rowData == null) {
            return;
        }

        int[] map = getIndexArrays(idents);
        String[] fulldat = new String[columnIdentifiers.length];
        String[] orig = null;
        if (rowIndex < tableData.size()) {
            orig = (String[]) tableData.elementAt(rowIndex);
        }
        for (int j = 0; j < columnIdentifiers.length; j++) {
            if (map[j] == -1) {
                if (orig == null) {
                    fulldat[j] = "";
                } else {
                    fulldat[j] = orig[j];
                }
            } else {
                fulldat[j] = rowData[map[j]];
            }
        }
        replaceTableDataRow(fulldat, rowIndex);
    }

    /**
     * とある行のデータを差し替える(その行のインデックスがこのオブジェクトが 保持するデータよりも大きい場合は新たに付け加える).
     * 識別子のチェックなどは 行わないことに注意.
     * 
     * @param rowData
     *            1行データ.
     * @param 差し替えたい行のインデックス
     *            .
     */
    public void replaceTableDataRow(String[] rowData, int rowIndex) {
        logger.debug("at replaceTableDataRow");
        logger.debug("size of tableData: " + tableData.size());
        if (rowIndex + 1 > tableData.size()) {
            tableData.add(rowData);
        } else {
            tableData.set(rowIndex, rowData);
        }
        lastChangeRow = new int[1];
        lastChangeRow[0] = rowIndex;
        lastChangeScheme = REPLACE;
        logger.debug("last changed row is: " + tableData.size());
    }

    /**
     * とある行のデータを挿入する(その行のインデックスがこのオブジェクトが 保持するデータよりも大きい場合は新たに付け加える). 識別子配列と, この
     * オブジェクトが持つカラム情報とが整合するようにデータを挿入する. (付け足す).
     * 
     * @param idents
     *            呼び出し元の識別子配列.
     * @param rowData
     *            1行データ
     * @param rowIndex
     *            差し替えたい行のインデックス.
     */
    public void insertTableDataRow(String[] idents, String[] rowData,
            int rowIndex) {
        if (columnIdentifiers == null || idents == null || rowData == null) {
            return;
        }
        int[] map = getIndexArrays(idents);
        String[] fulldat = new String[columnIdentifiers.length];
        String[] orig = null;
        if (rowIndex < tableData.size()) {
            orig = (String[]) tableData.elementAt(rowIndex);
        }
        for (int j = 0; j < columnIdentifiers.length; j++) {
            if (map[j] == -1) {
                if (orig == null) {
                    fulldat[j] = "";
                } else {
                    fulldat[j] = orig[j];
                }
            } else {
                fulldat[j] = rowData[map[j]];
            }
        }
        insertTableDataRow(fulldat, rowIndex);
    }

    /**
     * とある行にデータを挿入する(その行のインデックスがこのオブジェクトが 保持するデータよりも大きい場合は新たに付け加える). 識別子のチェックなどは
     * 行わないことに注意.
     * 
     * @param rowData
     *            1行データ.
     * @param 差し替えたい行のインデックス
     *            .
     */
    public void insertTableDataRow(String[] rowData, int rowIndex) {
        if (rowIndex + 1 > tableData.size()) {
            tableData.add(rowData);
        } else {
            tableData.add(rowIndex, rowData);
        }
    }

    public String[] getTableIdentifiers() {
        return this.tableIdentifiers;
    }

    public Vector getTableData() {
        return this.tableData;
    }

    private void setNumRows(int numRows) {
        this.numRows = numRows;
    }

    private void setNumColumns(int numColumns) {
        this.numColumns = numColumns;
    }

    public int getNumRows() {
        return this.numRows;
    }

    public int getNumColumns() {
        return this.numColumns;
    }

    /**
     * 「とあるカラムに有効なデータが一つでも含まれていれば真」という配列を返す.
     * 
     * @return 有効な行は真.
     */
    public boolean[] getValidColumns() {
        String[] testidents = columnIdentifiers;
        if (columnIdentifiers == null || columnIdentifiers.length == 0) {
            testidents = tableIdentifiers;
        }

        boolean[] test = new boolean[testidents.length];
        for (int i = 0; i < testidents.length; i++) {
            test[i] = false;
        }

        for (int i = 0; i < tableData.size(); i++) {
            String[] foo = (String[]) tableData.elementAt(i);
            if (foo == null) {
                continue;
            }
            for (int j = 0; j < foo.length; j++) {
                if (foo[j] != null && foo[j].trim().length() != 0
                        && j <= test.length - 1) {
                    test[j] = true;
                }
            }
        }
        return test;
    }

    /**
     * データを初期化する.
     */
    public void initializeData() {
        tableData = new Vector();
    }

    /**
     * for the time being, always returns false.
     */
    public boolean equals(InputInterfaceEntry entry) {
        return false;
    }

    private int chrow = -1;
    private int changeState = REPLACE;

    /**
     * 「これから変化する行」の状態を指定.
     * 
     * @param ind
     *            変化する行のインデックス.
     * @param changeState
     *            どのような変化を起こすか. REPLACEかREMOVE
     */
    public void setWillChangeRow(int ind, int changeState) {
        this.chrow = ind;
        this.changeState = changeState;
    }

    private void pushDummyRow(int chrow) {
        String[] dummy = new String[columnIdentifiers.length];
        for (int i = 0; i < dummy.length; i++) {
            dummy[i] = "";
        }
        RowState rowState = new RowState(dummy, columnIdentifiers, chrow);
        undoStack.push(rowState);
        replaceTableDataRow(columnIdentifiers, dummy, chrow);
    }

    public void saveState() {
        if (chrow < 0) {
            return;
        }

        logger.debug("row no. " + chrow);

        if (chrow >= tableData.size()) {
            pushDummyRow(chrow);
            return;
        }

        String[] rowdata = (String[]) tableData.elementAt(chrow);
        if (rowdata == null) {
            pushDummyRow(chrow);
            return;
        }

        String[] browdata = new String[rowdata.length];
        for (int j = 0; j < rowdata.length; j++) {
            logger.debug("saving state...: " + rowdata[j]);
            if (rowdata[j] != null) {
                browdata[j] = new String(rowdata[j]);
            }
        }

        RowState rowState = new RowState(browdata, columnIdentifiers, chrow,
                changeState);
        undoStack.push(rowState);
    }

    /**
     * 「行」の状態を保持しておくためのクラス. とある行は, 行のデータ, 識別子, インデックスによって状態を指定することが 可能. なはず.
     */
    class RowState {
        private String[] row;
        private String[] idents;
        private int index;
        private int state = REPLACE;

        RowState(String[] row, String[] idents, int index) {
            this.row = row;
            this.idents = idents;
            this.index = index;
        }

        RowState(String[] row, String[] idents, int index, int state) {
            this.row = row;
            this.idents = idents;
            this.index = index;
            this.state = state;
        }

        int getIndex() {
            return this.index;
        }

        String[] getRowData() {
            return row;
        }

        String[] getIdentifer() {
            return this.idents;
        }

        int getState() {
            return state;
        }

    }

    /**
     * undo操作を列ごとに行うのか, とある行について一気に全列行うのかを返す.
     * 
     * @return trueの時列ごとに行う. デフォルトはtrue.
     */
    public boolean undoEachColumns() {
        if (columns == null || columns[0] == null
                || columns[0].getTableSpec() == null) {
            return true;
        }

        return columns[0].getTableSpec().undoEachColumns();
    }

    /**
     * undo操作を行う. undo操作は, 行をごっそり行うか列ごとに行うのかの二通りがある.
     */
    public void undo() {
        if (undoStack.empty()) {
            logger.info("stack empty.");
            return;
        }

        logger.debug("at InputInterfaceTable.undo()");
        RowState state = (RowState) undoStack.pop();
        int ind = state.getIndex();
        String[] row = state.getRowData();
        String[] iden = state.getIdentifer();

        for (int i = 0; i < row.length; i++) {
            logger.debug("at undo: " + row[i]);
        }
        if (ind >= tableData.size()) {
            ind = tableData.size() - 1;
        }
        // if ( state.getState() == REMOVE ) {
        // ind = tableData.size();
        // }

        redoStack.push(state);

        if (state.getState() == REMOVE) {
            insertTableDataRow(iden, row, ind);
        } else {
            replaceTableDataRow(iden, row, ind);
        }

        if (!undoStack.empty() && columns != null && !this.undoEachColumns()) {
            RowState st = (RowState) undoStack.peek();
            if (st.getIndex() == ind) {
                undo();
            }
        }

    }

    public void redo() {
        if (redoStack.empty()) {
            logger.info("stack empty.");
            return;
        }

        logger.debug("at InputInterfaceTable.redo()");
        RowState state = (RowState) redoStack.pop();
        int ind = state.getIndex();
        String[] row = state.getRowData();
        String[] iden = state.getIdentifer();
        for (int i = 0; i < row.length; i++) {
            logger.debug("at redo: " + row[i]);
        }
        if (ind >= tableData.size()) {
            ind = tableData.size() - 1;
        }

        undoStack.push(state);
        replaceTableDataRow(iden, row, ind);

        if (!redoStack.empty() && !columns[0].getTableSpec().undoEachColumns()) {
            RowState st = (RowState) redoStack.peek();
            if (st.getIndex() == ind) {
                redo();
            }
        }

    }

    /**
     * 書き出しの際, とある行を書き出すか否かを判定する.
     * 
     * @return とある行に有効なデータが一つもない場合falseを返す
     */
    public static boolean isValidRow(String[] foo) {
        if (foo == null) {
            return false;
        }
        for (int i = 0; i < foo.length; i++) {
            if (foo[i] != null && foo[i].trim().length() != 0
                    && !foo[i].trim().equals("*")) {
                return true;
            }
        }
        return false;
    }

}
