/*
 * OpDetailTableModel.java
 *
 * This table model handles the operation tables for the indivdual processes. Each of these appears
 * as a seperate tab. The main view is handled by the OprnTableModel. Each process has its own 
 * instance of this table model. Each process view will have a different number of columns depending
 * on how many parameters the process contains.
 *
 * Jim Frankenberger
 * USDA-ARS, West Lafayette IN
 * jrf@purdue.edu
 *
 * Created on August 17, 2004, 2:15 PM
 */
package ex1;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serializable;
import javax.swing.table.AbstractTableModel;
import javax.swing.JTable;

import java.util.*;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

/**
 * This table model handles the operation tables for the indivdual processes. Each of these appears
 * as a seperate tab. The main view is handled by the OprnTableModel. Each process has its own
 * instance of this table model. Each process view will have a different number of columns depending
 * on how many parameters the process contains.
 *
 * @author jrf
 */
public class OpDetailTableModel extends AbstractTableModel {

    private static final long serialVersionUID = 1L;

    private final int modelTab;
    private final OprnTableModel opModel;
    private boolean altUnits;
    private final DefnFileParser parms;
    private final FrozenTable frTa;

    /**
     * the first row to have the status represented in ts
     */
    private CellCoordinates allowSave = new CellCoordinates();
    
    /**
     * unique string to use as a key in the OprnTableModel for unseen values being
     * out of limits.
     */
    String name = "";

    /**
     * Creates a new instance of OpDetailTableModel
     *
     * @param tab
     * @param p
     * @param ops
     */
    public OpDetailTableModel(int tab, OprnTableModel ops, DefnFileParser p, FixedTableModel model) {
        modelTab = tab;     // This is the index into the actionNames array 
        opModel = ops;
        altUnits = false;
        parms = p;
        frTa = new FrozenTable(model);
    }

    /**
     * Returns the number of columns for this particular group
     *
     * @return the number of columns/parameters in this process 
     */
    @Override
    public int getColumnCount() {
        int cols = parms.getOpParmCount(modelTab); // for number and name columns
        return cols;
    }
    
    public FrozenTable getFrozenTable()
    {
        if(frTa != null) return frTa;
        else return new FrozenTable(new FixedTableModel());
    }

    /**
     * Class of the column, will be either JComboBox or String
     *
     * @param c
     * @return class for column
     */
    @Override
    public Class<?> getColumnClass(int c) {
        Object p = getValueAt(0, c);
        if (p != null) {
            return p.getClass();
        } else {
            return "???".getClass();
        }
    }

    /**
     * Check if this cell can be modified, for files that don't have a particular action false
     * (readonly) will be returned.
     *
     * @param row row to check
     * @param col column to check
     * @return true if cell can be changed, false otherwise
     */
    @Override
    public boolean isCellEditable(int row, int col) {
//        if (col <= 2) {
//            return false;
//        } // column number and name are always readonly
//        else {
            if (opModel.getEdit()) {
                int lrow = opModel.getLogicalRow(modelTab, row);
                if (opModel.isReadOnly(lrow)) {
                    return false;
                } else {
                    String pstr = (String) getValueAt(row, 3);
                    return !pstr.equals("--");
                }
            } else {
                return false;
            }   // not even in edit mode
//        }
    }

    /**
     * Return the number of rows this view has. This may be different between the table tabs if a
     * file has a process that appears more than once.
     *
     * @return the number of rows this process needs in the table
     */
    @Override
    public int getRowCount() {
        return opModel.getRowCount(modelTab);
    }

    /**
     * Set whether alternate units are used which cause conversions.
     *
     * @param val True if alternate (English) units.
     */
    public void setAltUnits(boolean val) {
        altUnits = val;
    }
    
    /**
     * Sets the unique key
    */
    public void setName(String input)
    {
        name = input;
    }
    
    /**
     * get the unique key
     */
    public String getName()
    {
        return name;
    }

    /**
     * Get the type of data in this column
     *
     * @param col column number to get
     * @return type of data
     */
    public ParamDef.ColumnType getColumnType(int col) {
//        if (col <= 2) {
//            return ParamDef.ColumnType.FLOAT;
//        }
//
//        col -= 3;
        return parms.getOpParmType(modelTab, col);
    }

    /**
     * Get number of choices a dropdown list would have for a column
     *
     * @param col column number to get
     * @return number of choices
     */
    public int getChoiceCount(int col) {
//        if (col <= 2) {
//            return 0;
//        }
//        col -= 3;
        return parms.getOpChoiceCount(modelTab, col);
    }

    /**
     * Get a specific choice string from a column.
     *
     * @param col column number to get
     * @param ind index in choice array to return.
     * @return choice string at the specific index.
     */
    public String getChoice(int col, int ind) {
//        if (col <= 2) {
//            return "???";
//        }
//
//        col -= 3;
        return parms.getOpChoice(modelTab, col, ind);
    }

    /**
     * Get the prompt(header) for a particular detail column.
     *
     * @param col the column number to get
     * @return the header for the column
     */
    @Override
    public String getColumnName(int col) {
//        if (col == 0) {
//            return "Num";
//        }
//        if (col == 1) {
//            return "Name";
//        }
//        if (col == 2) {
//            return "Subdirectory";
//        }
//        col -= 3;
        String cname = parms.getOpParmPrompt(modelTab, col, altUnits);

        return cname;
    }

    /**
     * Get the value at a specific row and column.
     *
     * @param row the row to get
     * @param col the column to get
     * @return the string of the value
     */
    @Override
    public Object getValueAt(int row, int col) {
//        if (col <= 2) // row number
//        {
//            return opModel.getValueAt(modelTab, row, col);
//        } else {
            String pstr = (String) opModel.getValueAt(modelTab, row, col + 3);

            return pstr;
//        }
    }

    /**
     * Stores a value into the table
     *
     * @param o Object to store
     * @param row row where object came from
     * @param col column where object came from
     */
    @Override
    public void setValueAt(Object o, int row, int col) {

//        if (col <= 2) {
//            return;
//        }

        String pstr = (String) o;
        if (pstr != null) {
            opModel.setValueAt(o, modelTab, row, col + 3);
        }
    }

    /**
     * This sorts the rows based on the contents of a specific column.
     *
     * @param col column to sort
     * @param status ascending, descending or nothing
     */
    public void sort(int col, int status) {
        if(col < 0) frTa.sort(col + 3, status);
        else
        {
            HashSet<Integer> logMap = new HashSet<Integer>();

            TreeMap<String, ArrayList<Integer>> tree = new TreeMap<>(new ParamCompare<String>());
            for (int i = 0; i < opModel.getRowCount(modelTab); i++) {
                String str = (String) getValueAt(i, col);
                str = str.toUpperCase();
                int logrow = opModel.getLogicalRow(modelTab, i);
                Integer li = logrow;
                if (logMap.contains(li) == false) {
                    logMap.add(li);
                    ArrayList<Integer> v = tree.get(str);
                    if (v != null) {
                        v.add(li);
                    } else {
                        ArrayList<Integer> al = new ArrayList<Integer>();
                        al.add(li);
                        tree.put(str, al);
                    }
                }
            }
            ArrayList<WepsDBFile> xmlFiles2 = new ArrayList<WepsDBFile>();
            Collection<ArrayList<Integer>> c = tree.values();

            if (status == TableSorter2.ASCENDING) {
                // get elements out in order
                Iterator<ArrayList<Integer>> it = c.iterator();
                while (it.hasNext()) {
                    ArrayList<Integer> o = /*(ArrayList<Integer>)*/ it.next();
                    for (Integer o1 : o) {
                        Integer li = o1;
                        int inx = li;
                        xmlFiles2.add(opModel.getXmlFile(inx));
                    }
                }
                opModel.setXmlFiles(xmlFiles2);
            } else if (status == TableSorter2.DESCENDING) { //FIXME: this code does not seem to allow checking to be implemented -- may be incorrect code
                ArrayList<ArrayList<Integer>> temp = new ArrayList<ArrayList<Integer>>();
                for (ArrayList<Integer> o : c) {
                    temp.add(o);
                }
                Collections.reverse(temp);
                for (ArrayList<Integer> o : temp) {
                    for (Integer li : o) {
                        int inx = li;
                        xmlFiles2.add(opModel.getXmlFile(inx));
                    }
                }
                opModel.setXmlFiles(xmlFiles2);
            } else {

            }
            opModel.computeRowMaps();
            frTa.repaint();
        }
    }

    /**
     *
     * @return
     */
    public int getModelTab() {
        return modelTab;

    }
    
    /**
     * will only increase the severity of the table status, never decrease
     * @param newStatus 
     */
    public void setTableStatus(InputLimits.TableStatus newStatus, int row, int column) {
        InputLimits.TableStatus old = allowSave.getTotalStatus();
        allowSave.put(row, column, newStatus);
        opModel.setTableStatus(newStatus, row, column);
        InputLimits.TableStatus after = allowSave.getTotalStatus();
        if(old != after) archive();
    }
    
    public boolean getColor()
    {
        InputLimits.TableStatus save = allowSave.getTotalStatus();
        //We need to store the current tablestatus by name.
        archive();
        //We return true if there is a NOSAVE, false if there is not.
        if(save == InputLimits.TableStatus.NOSAVE) return true;
        else return false;
    }
    
    /**
     * This method will archive this detail table for tabs.
     */
    private void archive()
    {
        opModel.archive(name, allowSave.getTotalStatus());
    }
    
    /**
     * This method will only be called when initializing the table, and will set
     * the table status without giving any coordinates.
     */
    public void setOverrideStatus(InputLimits.TableStatus input)
    {
        allowSave = new CellCoordinates(input);
    }
    
    private class CellCoordinates
    {
        //Stores the number of values outside of the hard and soft limits
        private int numRed;
        private int numYellow;
        
        private InputLimits.TableStatus override;

        //Stores the coordinates of each value outside of the limits.
        private Hashtable<IndividualCoordinates, InputLimits.TableStatus> outVals;

        public CellCoordinates()
        {
            numRed = 0;
            numYellow = 0;
            outVals = new Hashtable<IndividualCoordinates, InputLimits.TableStatus>();
        }
        
        public CellCoordinates(InputLimits.TableStatus input)
        {
            this();
            override = input;
        }

        public void put(int row, int column, InputLimits.TableStatus input)
        {
            if(input == null) return;
            IndividualCoordinates current = new IndividualCoordinates(row, column);
            InputLimits.TableStatus old = outVals.remove(current);
            if(input != InputLimits.TableStatus.OKAY) outVals.put(current, input);
            switch(input)
            {
                case NOSAVE:
                    numRed ++;
                    break;
                case WARNSAVE:
                    numYellow ++;
                    break;
                default:
            }
            if(old == null) return;
            switch(old)
            {
                case NOSAVE:
                    numRed --;
                    break;
                case WARNSAVE:
                    numYellow --;
                    break;
                default:
            }
            override = null;
        }

        public InputLimits.TableStatus getTotalStatus()
        {
            if(override != null) return override;
            if(numRed > 0)
            {
                return InputLimits.TableStatus.NOSAVE;
            }
            if(numYellow > 0)
            {
                return InputLimits.TableStatus.WARNSAVE;
            }
            return InputLimits.TableStatus.OKAY;
        }

        private class IndividualCoordinates
        {
            //The {row, column} of the data 
            public int[] coordinates;

            public IndividualCoordinates(int row, int column)
            {
                coordinates = new int[]{row, column};
            }

            @Override
            public boolean equals(Object other)
            {
                if(other instanceof IndividualCoordinates)
                {
                    IndividualCoordinates input = (IndividualCoordinates) other;
                    if(this.coordinates[0] == input.coordinates[0])
                    {
                        if(this.coordinates[1] == input.coordinates[1])
                        {
                            return true;
                        }
                    }
                }
                return false;
            }
            
            @Override
            public int hashCode()
            {
                return 37 * coordinates[0] + 29 * coordinates[1];
            }
        }
    }
    
    class FrozenTable extends JTable
    {
        private static final long serialVersionUID = 1L;
        
        private final String[] column = {"Num", "Name", "Subdirectory"};
        private final ArrayList<Integer> changedRows = new ArrayList<Integer>();
        private final ArrayList<Integer> mismatchedRows = new ArrayList<Integer>();
        private final ArrayList<Integer> outOfOrderRows = new ArrayList<Integer>();
        
//        private final TableSorter2.MouseHandler mouseListener;
        
        public FrozenTable(FixedTableModel model)
        {
            super(model);
            TableCellRenderer renderer = new JComponentTableCellRenderer();
            for(int index = 0; index < this.getColumnModel().getColumnCount(); index ++)
            {
                getColumnModel().getColumn(index).setHeaderRenderer(renderer);
            }
        }
        
        public void addChangedRow(int row)
        {
            if(changedRows.size() != 0) { if(!changedRows.contains(row)) changedRows.add(row); }
            else changedRows.add(row);
        }
        
        public void removeChangedRow(int row)
        {
            if(changedRows.size() != 0) changedRows.remove((Integer) row);
        }
        
        public void addMismatchedRow(int row)
        {
            if(mismatchedRows.size() != 0) {if(!mismatchedRows.contains(row)) mismatchedRows.add(row);}
            else mismatchedRows.add(row);
            
        }
        
        public void addOutOfOrderRow(int row)
        {
            if(outOfOrderRows.size() != 0) { if(!outOfOrderRows.contains(row)) outOfOrderRows.add(row); }
            else outOfOrderRows.add(row);
        }
        
        public void removeOutOfOrderRow(int row)
        {
            if(outOfOrderRows.size() != 0) outOfOrderRows.remove((Integer) row);
        }
        
        public void removeMismatchedRow(int row)
        {
            if(mismatchedRows.size() != 0) mismatchedRows.remove((Integer) row);
        }
        
        @Override
        public int getColumnCount()
        {
            return 3;
        }
        
        @Override
        public TableColumn getColumn(Object identifier)
        {
            if(identifier instanceof Integer) 
            {
                return columnModel.getColumn((Integer) identifier);
            }
            else return super.getColumn(identifier);
        }
        
        @Override
        public String getColumnName(int column)
        {
            return this.column[column];
        }
        
        @Override
        public int getRowCount()
        {
            return  opModel.getRowCount(modelTab);
        }
        
        @Override
        public Object getValueAt(int row, int column)
        {
            return opModel.getValueAt(modelTab, row, column);
        }
        
        @Override
        public Dimension getPreferredSize()
        {
            Dimension dim = super.getPreferredSize();
            dim.width = 820;
            return dim;
        }
        
        @Override
        public Component prepareRenderer(TableCellRenderer renderer, int row, int column)
        {
            Component comp = super.prepareRenderer(renderer, row, column);
            int xml = opModel.getLogicalRow(modelTab, row);
            
            if(column == 1)
            {
                
                if(mismatchedRows.contains(xml))
                {
                    comp.setBackground(Color.pink);
                }
                else if(changedRows.contains(xml))
                {
                    comp.setBackground(Color.green);
                }
                else if(outOfOrderRows.contains(xml))
                {
                    comp.setBackground(Color.red);
                }
                else 
                {
                    comp.setBackground(Color.white);
                }
            }
            else
            {
                comp.setBackground(Color.white);
            }
            return comp;
        }
        
        public void setSorter(TableSorter2.MouseHandler moHa)
        {
            this.tableHeader.addMouseListener(moHa);
        }
        
        public void sort(int col, int status)
        {
            HashSet<Integer> logMap = new HashSet<Integer>();

            TreeMap<String, ArrayList<Integer>> tree = new TreeMap<>(new ParamCompare<String>());
            for (int i = 0; i < opModel.getRowCount(modelTab); i++) {
                String str = (String) getValueAt(i, col);
                str = str.toUpperCase();
                int logrow = opModel.getLogicalRow(modelTab, i);
                Integer li = logrow;
                if (logMap.contains(li) == false) {
                    logMap.add(li);
                    ArrayList<Integer> v = tree.get(str);
                    if (v != null) {
                        v.add(li);
                    } else {
                        ArrayList<Integer> al = new ArrayList<Integer>();
                        al.add(li);
                        tree.put(str, al);
                    }
                }
            }
            ArrayList<WepsDBFile> xmlFiles2 = new ArrayList<WepsDBFile>();
            Collection<ArrayList<Integer>> c = tree.values();

            if (status == TableSorter2.ASCENDING) {
                // get elements out in order
                Iterator<ArrayList<Integer>> it = c.iterator();
                while (it.hasNext()) {
                    ArrayList<Integer> o = /*(ArrayList<Integer>)*/ it.next();
                    for (Integer o1 : o) {
                        Integer li = o1;
                        int inx = li;
                        xmlFiles2.add(opModel.getXmlFile(inx));
                    }
                }
                opModel.setXmlFiles(xmlFiles2);
            } else if (status == TableSorter2.DESCENDING) { //FIXME: this code does not seem to allow checking to be implemented -- may be incorrect code
                ArrayList<ArrayList<Integer>> temp = new ArrayList<ArrayList<Integer>>();
                for (ArrayList<Integer> o : c) {
                    temp.add(o);
                }
                Collections.reverse(temp);
                for (ArrayList<Integer> o : temp) {
                    for (Integer li : o) {
                        int inx = li;
                        xmlFiles2.add(opModel.getXmlFile(inx));
                    }
                }
                opModel.setXmlFiles(xmlFiles2);
            } else {

            }
            
            opModel.computeRowMaps();
        }
        
        class JComponentTableCellRenderer implements TableCellRenderer {
            public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) 
            {
              return (JComponent) value;
            }
          }
    }
    
}
