/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : TableModelForTabularData.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.primitiveguis.tableforinputinterface;

import java.text.DecimalFormat;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Vector;

import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;

import org.apache.log4j.Logger;

import ciss.phase_viewer.common.Utils;
import ciss.phase_viewer.inputinterface.InputInterface;
import ciss.phase_viewer.inputinterface.InputInterfaceEntry;
import ciss.phase_viewer.inputinterface.InputInterfaceEntryChangeEvent;
import ciss.phase_viewer.inputinterface.inputinterfacetable.InputInterfaceTable;
import ciss.phase_viewer.inputinterface.inputinterfacetable.InputInterfaceTableColumns;

public class TableModelForTabularData extends TableModelForInputInterface {
    private static Logger logger = Logger
            .getLogger(TableModelForTabularData.class.getName());

    private Vector tableData = new Vector();

    private String[] idents;

    public TableModelForTabularData(String[] inputSpecifications,
            InputInterface input, int[] mapSpecCol, EventListener el) {
        super(inputSpecifications, input, mapSpecCol, el);
        super.init();
    }

    public TableModelForTabularData(String[] inputSpecifications,
            InputInterface input, int[] mapSpecCol, EventListener el,
            int columnIndex) {
        super(inputSpecifications, input, mapSpecCol, el, columnIndex);
        super.init();
    }

    /**
     * フォーマットすべきデータはフォーマットするようにsetValueAtメソッドをオーバーライド.
     * 「フォーマットすべきかいなか」は設定ファイルに記述する.
     * 
     * @param data
     *            セットしたいデータ.
     * @param row
     *            セットする行.
     * @param col
     *            セットする列.
     */
    public void setValueAt(Object data, int row, int col) {
        // System.out.println(" data data, row, col: "+data+" "+row+" "+col);
        if (row >= getRowCount()) {
            logger.error("tried to set " + data + " at " + row + ", " + col);
        }
        if (col >= mapSpecCol.length) {
            super.setValueAt(data, row, col);
            return;
        }
        if (!columns[mapSpecCol[col]].format()) {
            super.setValueAt(data, row, col);
        } else {
            DecimalFormat formatter = columns[mapSpecCol[col]].getFormatter();
            Object formatedData;
            if (data instanceof Double) {
                formatedData = (Object) formatter.format((Double) data);
            } else if (data instanceof String) {
                try {
                    Double d = Double.valueOf((String) data);
                    formatedData = (Object) formatter.format(d);
                } catch (NumberFormatException ne) {
                    formatedData = data;
                }
            } else {
                formatedData = data;
            }
            super.setValueAt(formatedData, row, col);
        }
    }

    protected void createTableColumns() {
        if (columns == null) {
            return;
        }

        for (int i = 0; i < mapSpecCol.length; i++) {
            addColumn(columns[mapSpecCol[i]].getName());
        }

        int nrw = manager.getTableSpec(inputSpecifications[0]).getNumRows();
        if (nrw > 0) {
            setRowCount(nrw);
        }
    }

    protected void extractInputFile() {
        InputInterfaceTable iitable = input
                .getInputInterfaceTable(inputSpecifications[0]);
        idents = iitable.getColumnIdentifiers();
        tableData = iitable.getTableData();
        setInputToModel();
    }

    protected void setInputToModel() {
        input.setUpdateUndoStack(false);
        updateInputInterface = false;

        int icol = getColumnCount();
        int irow = getRowCount();

        logger.debug("at setInputToModel(): dump tableData, whose size is "
                + tableData.size());
        logger.debug("at setInputToModel(): icol, irow: " + icol + " " + irow);
        for (int i = 0; i < tableData.size(); i++) {
            String[] tmp = (String[]) tableData.elementAt(i);
            for (int j = 0; j < tmp.length; j++) {
                logger.debug("tableData at " + i + " " + j + " : " + tmp[j]);
            }
        }

        String[] columnNames = new String[icol];
        Vector map = new Vector();
        int firstLetterCapsCol = -1;

        if (idents != null && tableData != null) {
            for (int i = 0; i < icol; i++) {
                columnNames[i] = getColumnName(i);
                if (columns[mapSpecCol[i]].isFirstLetterCaps()) {
                    firstLetterCapsCol = i;
                }
            }

            for (int i = 0; i < icol; i++) {
                for (int j = 0; j < idents.length; j++) {
                    if (columnNames[i].equalsIgnoreCase(idents[j])) {
                        Integer[] it = { new Integer(i), new Integer(j) };
                        map.addElement(it);
                    }
                }
            }

            Vector itable = new Vector();
            Vector iinput = new Vector();
            for (int i = 0; i < map.size(); i++) {
                Integer[] it = (Integer[]) map.get(i);
                itable.addElement(it[0]);
                iinput.addElement(it[1]);
            }

            int dataSize = tableData.size();
            Vector dummy = new Vector();
            if (dataSize > irow) {
                for (int i = irow; i < dataSize; i++) {
                    for (int j = 0; j < icol; j++) {
                        dummy.addElement("");
                    }
                    addRow(dummy);
                    dummy = new Vector();
                }
            }

            for (int i = 0; i < tableData.size(); i++) {
                String[] rowData = (String[]) tableData.get(i);
                for (int j = 0; j < map.size(); j++) {
                    int it = ((Integer) itable.get(j)).intValue();
                    int ii = ((Integer) iinput.get(j)).intValue();
                    String dat = rowData[ii];
                    if (it == firstLetterCapsCol) {
                        dat = Utils.firstLetterCaps(dat);
                    }
                    logger.debug("setting value : " + dat + " at row: " + i
                            + " and column: " + it);
                    setValueAt(dat, i, it);
                }
            }
        } else {
            logger.debug("ident or tableData was null.");
        }

        updateInputInterface = true;
        input.setUpdateUndoStack(true);
    }

    /**
     * inputInterface初期化の際に呼ばれる.
     */
    public void inputInterfaceInitializing() {
        logger.debug("intializing TableModel: " + inputSpecifications[0]);
        TableModelListener[] listeners = this.getTableModelListeners();
        for (int i = 0; i < listeners.length; i++) {
            this.removeTableModelListener(listeners[i]);
        }

        int row = getRowCount();
        int col = getColumnCount();
        int nullCount = 0;

        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                Object obj = getValueAt(i, j);
                if (obj != null && obj instanceof String) {
                    if (((String) obj).trim().length() == 0) {
                        nullCount++;
                    }
                }
                setValueAt("", i, j);
            }
            if (nullCount == col) {
                break;
            }
            nullCount = 0;
        }

        for (int i = 0; i < listeners.length; i++) {
            this.addTableModelListener(listeners[i]);
        }
    }

    /**
     * inputinterfaceに変更が施される度に呼ばれるメソッド.
     */
    public void inputInterfaceEntryChanged(InputInterfaceEntryChangeEvent e) {
        logger.debug("entry changed: " + e.getTag());

        if (e.getSource() == this) {
            return;
        }

        if (e == null || e.getTag() == null) {
            logger.debug("something is wrong... InputInterfaceEntryChangeEvent not properly initialized.");
            return;
        }

        if (!e.getTag().trim().equalsIgnoreCase(inputSpecifications[0])) {
            return;
        }

        InputInterfaceEntry entry = e.getEntry();
        if (entry.getEntryType() != InputInterfaceEntry.TABLE) {
            return;
        }

        logger.debug("e.getSource(): " + e.getSource());
        logger.debug("this: " + this);

        // TODO: カラムの増減も気にする必要がありそう.
        InputInterfaceTable iitable = input
                .getInputInterfaceTable(inputSpecifications[0]);
        if (iitable.getColumnIdentifiers() != null) {
            idents = iitable.getColumnIdentifiers();
        }

        tableData = iitable.getTableData();
        logger.debug("dump tableData, whose size is " + tableData.size());
        for (int i = 0; i < tableData.size(); i++) {
            String[] tmp = (String[]) tableData.elementAt(i);
            for (int j = 0; j < tmp.length; j++) {
                logger.debug("tableData at " + i + " " + j + " : " + tmp[j]);
            }
        }

        TableModelListener[] listeners = this.getTableModelListeners();
        for (int i = 0; i < listeners.length; i++) {
            this.removeTableModelListener(listeners[i]);
        }

        int rowCount = getEffectiveRowCount();
        int colCount = getColumnCount();
        if (rowCount > tableData.size()) {
            for (int i = rowCount - 1; i >= tableData.size(); i--) {
                for (int j = 0; j < colCount; j++) {
                    setValueAt("", i, j);
                }
            }
        }
        // Utils.clearTableModel(this);
        this.setInputToModel();

        restoreListeners(listeners);

        if (getParentTable() != null) {
            getParentTable().repaint();
            getParentTable().revalidate();
        }
    }

    private int getEffectiveRowCount() {
        int count = 0;
        for (int i = 0; i < getRowCount(); i++) {
            int nullcount = 0;
            for (int j = 0; j < getColumnCount(); j++) {
                Object obj = getValueAt(i, j);
                if (obj == null || obj.toString().trim().length() == 0) {
                    nullcount++;
                }
            }
            if (nullcount == getColumnCount())
                return count;
            count++;
        }
        return count;
    }

    /**
     * テーブルモデルリスナーが呼ぶメソッド. InputInterfaceオブジェクトなどを更新する. TODO:
     * inputinterface更新の一元化. あるいは, inputinterface自身へ委譲.
     */
    public void execute(EventObject e) {
        // temporarily remove the table model listeners during update.
        TableModelListener[] listeners = this.getTableModelListeners();

        for (int i = 0; i < listeners.length; i++) {
            this.removeTableModelListener(listeners[i]);
        }

        // obtaining indices associated with the change of this TableModel.
        TableModelEvent tmde = (TableModelEvent) e;
        int first = 0;
        int last = 0;
        if (tmde != null) {
            first = tmde.getFirstRow();
            last = tmde.getLastRow();
            logger.debug("first row that changed: " + tmde.getFirstRow());
            logger.debug("last row that changed: " + tmde.getLastRow());
            logger.debug("row count: " + getRowCount());
            logger.debug("type: " + tmde.getType());
        } else {
            restoreListeners(listeners);
            return;
        }

        if (last >= getRowCount() || first < 0) {
            restoreListeners(listeners);
            return;
        }

        if (first < 0 || last < 0 || last >= getRowCount()
                || first >= getRowCount()) {
            restoreListeners(listeners);
            return;
        }

        for (int i = 0; i < mapSpecCol.length; i++) {
            if (columns[mapSpecCol[i]].getDefaultValue().trim().equals("INCRE")
                    && columns[mapSpecCol[i]].ignoreOnSave()) {
                setValueAt(new Integer(last + 1), last, i);
            }
        }

        // don't update InputInterface object if the call is from InputInterface
        // itself!
        if (!updateInputInterface) {
            restoreListeners(listeners);
            return;
        }

        InputInterfaceTable iitable = input
                .getInputInterfaceTable(inputSpecifications[0]);

        Vector data = new Vector();
        logger.debug("updating table data ...");
        int nullCount = 0;

        String[] iden = new String[mapSpecCol.length];
        String[] row = new String[mapSpecCol.length];

        // creating row data and identifiers associated with the change of this
        // TableModel.
        int rowC = getRowCount();
        for (int i = first; i < last + 1; i++) {
            for (int j = 0; j < mapSpecCol.length; j++) {
                InputInterfaceTableColumns col = columns[mapSpecCol[j]];
                if (col.ignoreOnSave()) {
                    nullCount++;
                    continue;
                }

                iitable.setWillChangeRow(i, InputInterfaceTable.REPLACE);
                iitable.saveState(); // for undo/redo operations...
                String nam = columns[mapSpecCol[j]].getInputSpecifications();
                if (i >= rowC) {
                    iitable.removeTableDataRow(i, last);
                    restoreListeners(listeners);
                    return;
                }
                Object obj = getValueAt(i, j);
                iden[j] = nam;

                String string = "";
                if (obj instanceof String) {
                    string = (String) obj;
                } else if (obj instanceof Boolean) {
                    boolean bool = ((Boolean) obj).booleanValue();
                    string = bool ? "1" : "0";
                }

                if (string == null || string.trim().length() == 0) {
                    row[j] = "";
                    nullCount++;
                } else {
                    row[j] = string;
                }
            }

            for (int j = 0; j < mapSpecCol.length; j++) {
                logger.debug("updating inputinterface object...");
                logger.debug("ident: " + iden[j]);
                logger.debug("value: " + row[j]);
            }

            // update the InputInterfaceTable object, stored within
            // InputInterface object.
            iitable.replaceTableDataRow(iden, row, i);
        }
        // replace the edited InputInterfaceTable object.
        input.replaceEntry(iitable, this, callListener);
        logger.debug("size of data: " + data.size());

        restoreListeners(listeners);
    }

    public void clearInputInterfaceTableData() {
        InputInterfaceTable iitable = input
                .getInputInterfaceTable(inputSpecifications[0]);
        iitable.initializeData();
    }

    public String toString() {
        String ret = "cols: ";
        for (int i = 0; i < columns.length; i++) {
            ret += " " + columns[i].getName();
        }
        return ret;
    }

    public void removeRow(int row) {
        logger.debug("removing row.");

        InputInterfaceTable iitable = input
                .getInputInterfaceTable(inputSpecifications[0]);
        iitable.setWillChangeRow(row, InputInterfaceTable.REMOVE);
        iitable.saveState();
        iitable.removeTableDataRow(row, row);
        input.replaceEntry(iitable, this, callListener);

        logger.debug("removing row from table.");
        updateInputInterface = false;
        super.removeRow(row);
        updateInputInterface = true;
        logger.debug("finished removing row");
    }

}
