/*
 * WepsTable.java
 *
 * Created on June 8, 2006, 9:00 AM
 */

package usda.weru.util.table;

import com.klg.jclass.cell.editors.JCStringCellEditor;
import com.klg.jclass.cell.renderers.JCImageCellRenderer;
import com.klg.jclass.cell.renderers.JCWordWrapCellRenderer;
import com.klg.jclass.table.CellRangeValue;
import com.klg.jclass.table.CellStyleModel;
import com.klg.jclass.table.JCCellBorder;
import com.klg.jclass.table.JCCellPosition;
import com.klg.jclass.table.JCCellRange;
import com.klg.jclass.table.JCCellStyle;
import com.klg.jclass.table.JCPrintListener;
import com.klg.jclass.table.JCTableEnum;
import com.klg.jclass.table.SpanHandler;
import com.klg.jclass.table.TableChangeHandler;
import com.klg.jclass.table.beans.JCLiveCellStyle;
import com.klg.jclass.table.beans.LiveTable;
import com.klg.jclass.table.data.JCVectorDataSource;
import com.klg.jclass.util.JCListenerList;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import de.schlichtherle.truezip.file.TFile;
import java.awt.Color;
import java.util.ConcurrentModificationException;
import java.util.Enumeration;
import org.jdom2.Element;
import org.openide.util.Exceptions;
import usda.weru.weps.reports.DetailReport;

/**
 * Custom implementation of the JCTable for the WEPS program.
 * A WepsTable object represents a JCTable which can be configured using an XML file.
 * @author Joseph Levin
 */
public class WepsTable extends LiveTable implements XmlObject, ParseListener, KeyListener {
    private static final long serialVersionUID = 1L;
    
    private WepsTableMeta c_meta;
    private TFile c_xmlFile;
    private String c_unitsSystem = usda.weru.util.Util.SIUnits;
    
	/**
	 *
	 */
	protected JCListenerList c_parseListeners = null;    
    
    private CellStyleModel quick_plot_style;
    
    
    /** Creates a new instance of WepsTable.  Uses default values.*/    
    public WepsTable() {    
        super(true);
        init();
        applyWepsDefaults();
    }
    
    /**
     * Creates a new instance of WepsTable and loads settings from an xml file.
     * @param file A File object referencing an XML file containing the specification for the table to be created.
     */
    
    public WepsTable(TFile file) {    
        super(true);
        init();
        applyWepsDefaults();
        fromFile(file);
    }
    
    /** Creates a new instance of WepsTable and loads settings from a jdom xml node.
     *  @param node An org.jdom2.Element representing the root of an XML structure containing the specification for the table to be created.
     */
    
    public WepsTable(Element node) {   
        super(true);
        init();
        applyWepsDefaults();
        fromXml(node);
        
    }
    // specify the default cell renderers for Image and String cell objects
    private void init(){
        
        //Key LIstener
        addKeyListener(this);
        
        //Default renderers
        setCellRenderer(Image.class, JCImageCellRenderer.class);
        setCellRenderer(String.class, JCWordWrapCellRenderer.class);
        
        //Default editors
        setCellEditor(String.class, JCStringCellEditor.class);        
    }
    
    //Defaults settings that differ from the default JCTable
    private void applyWepsDefaults(){
        setAllowCellResize(JCTableEnum.RESIZE_COLUMN);        
        setSelectionPolicy(JCTableEnum.SELECT_RANGE);
        setSelectIncludeLabels(false);
        setDoubleBuffered(true);
        setPopupMenuEnabled(false);
        setHorizSBPosition(JCTableEnum.POSITION_AT_SIDE);
        setVertSBPosition(JCTableEnum.POSITION_AT_SIDE);        
        setPixelHeight(JCTableEnum.ALLCELLS, JCTableEnum.VARIABLE);        
        
        
        
    }
    
    /** Loads settings from a jdom xml node into the table.
     *  @param node An org.jdom2.Element representing the root of an XML structure containing the specification for the table.
     */
    @Override
    public void fromXml(Element node){
        getMeta().fromXml(node);
        getMeta().applyToTable(this);
        repaint();
    }
    
    /** Loads settings from an XML file into the table.
     *  @param file A File object representing an XML file containing the specification for the table.
     */
    public void fromFile(TFile file){
        c_xmlFile = file;
        Element node = Helper.getRootNode(file);
        
        //TODO: Notify of fail.
        if (node == null) return;
        fromXml(node);
    }
    
    /** Returns the XML file that specifies the configuration of this table.
     *  @return A File object referencing the XML file used to create this table.
     */
    public TFile getXmlFile(){
        return c_xmlFile;
    }
    
    /**
     * Set the system of units to be used by the table.
     * @param system A String containing the name of the unit system to be used. ex "SI", "US"
     */
    public void setUnitsSystem(String system){
        if (system == null) return;
        if (system.equals(c_unitsSystem) == false){
            c_unitsSystem = system;
            //Fire a table reset.
            repaint();
        }
    }

    /**
     * 
     * @param y
     * @return
     */
    @Override
    public int YtoRow(int y) {
        return super.YtoRow(y);
    }

    /**
     * 
     * @param x
     * @return
     */
    @Override
    public  int XtoColumn(int x) {
        return super.XtoColumn(x);
    }

    /**
     * 
     * @param x
     * @param y
     * @return
     */
    @Override
    public JCCellPosition XYToCell(int x, int y) {
        return super.XYToCell(x, y);
    }

	/**
	 *
	 * @param quickplot
	 */
	public void setQuickPlot(boolean quickplot){
        getDataView().setQuickPlot(quickplot);
        reapplyMeta();
    }

	/**
	 *
	 * @return
	 */
	public boolean isQuickPlot(){
        return getDataView().isQuickPlot();
    }
    
        
    
    /**
     * Returns the current system of units being used by the table.
     * @return The system of units being used by the table.
     */
    public String getUnitsSystem(){
        if (c_unitsSystem == null || c_unitsSystem.length() == 0){
            return WepsTableEnum.NO_UNITS_SYSTEM;
        } else{
            return c_unitsSystem;
        }
    }
    
    //TODO: Verify that when these change the interface updates, may need to fire an event somewhere
    /**
     * Defines the default CellStyleModel to be used for cells in this table.
     * @param style The default CellStyleModel to be used on this table.
     */
    public void setDefaultCellStyle(CellStyleModel style) {
        default_cell_style = style;
    }
    
    /**
     * Defines the default CellStyleModel to be used for labels in this table
     * @param style The default CellStyleModel to be used for labels.
     */
    public void setDefaultLabelStyle(CellStyleModel style) {
        default_label_style = style;
    }
    
    //Overide the default data view
    @Override
    protected void createDefaultDataView(){
        WepsTableDataView dataView = new WepsTableDataView();
        dataView.setTable(this);
        setDataView(dataView);
    }
    
    /**
     * Get the WepsTableDataView which defines this table.
     * @return The WepsTableDataView object used by this table
     */
    @Override
    public WepsTableDataView getDataView() {
        return (WepsTableDataView)super.getDataView();
    }
    
    @Override
    protected void createDefaultSpanHandler(){
        try {
            SpanHandler spanHandler = new WepsSpanHandler();        //Allows labels and cells to mix.
            spanHandler.setTable(this);
            setSpanHandler(spanHandler);
        } catch(Exception e) {
            //System.out.println("SpanHandler initialization error: " + e.getMessage());
        }
    }
    
    /**
     * Gets the WepsTableMeta object used by this table.
     * @return The WepsTableMeta object used by this table.
     */
    public WepsTableMeta getMeta(){
        if (c_meta == null) {
            c_meta = new WepsTableMeta();
            setMeta(c_meta);
        }
        return c_meta;
    }
    
    /**
     * Sets the WepsTableMeta to be used by this table.
     * @param meta The WepsTableMeta to be used by this table.
     */
    public void setMeta(WepsTableMeta meta){
        if (c_meta == meta) return;
        
        if(c_meta != null){
        }
        
        c_meta = meta;
        c_meta.applyToTable(this);
               
    }
    
    /**
     * Reapply the WepsTableMeta associated with this table.
     */
    public void reapplyMeta(){
        getMeta().applyToTable(this);
    }
//
//    @Override
//    public boolean isEditable(int row, int column) {
//        if(getDataView().isQuickPlotRow(row) && column >= getFrozenColumns()){
//            return true;
//        }
//        else{
//            return false;
//        }
//    }
//    
    
    
    
    private synchronized CellStyleModel getQuickPlotCellStyle(){
        if(quick_plot_style == null){
            quick_plot_style = new JCCellStyle(getDefaultLabelStyle());            
            
            quick_plot_style.setHorizontalAlignment(JCTableEnum.CENTER);
            quick_plot_style.setBackground(new Color(197,231,254));      
            
            quick_plot_style.setCellRenderer(new QuickPlotRenderer());
            quick_plot_style.setTraversable(false);            
            quick_plot_style.setEditable(false);            
            
//            quick_plot_style.setTraversable(true);            
//            quick_plot_style.setEditable(true);            
//            JCCheckBoxCellEditor editor = new JCCheckBoxCellEditor();
//            editor.setHorizontalAlignment(SwingConstants.CENTER);
//            quick_plot_style.setCellEditor(editor);
        }
        return quick_plot_style;
    }
    /**
     * Get the CellStyleModel used in cell (rowIndex, columnIndex).
     * @param rowIndex The row containing the cell to be queried.
     * @param columnIndex The column containing the cell to be queried.
     * @return The CellStyleModel used in the queried cell.
     */
    @Override
    public CellStyleModel getCellStyle(int rowIndex, int columnIndex) {
        //Is this a label?
        if (columnIndex == JCTableEnum.ALLCELLS){
            if (rowIndex == JCTableEnum.LABEL){
                return getDefaultLabelStyle();
            } else{
                return getDefaultCellStyle();
            }
        } else if(columnIndex == JCTableEnum.LABEL){
            CellStyle style =  getMeta().getDefaultLabelStyle();
            if (style.hasConditionalStyles()){
                for (CellStyle conditionalStyle : style.getConditionalStyles()){
                    String expression = conditionalStyle.getExpression();
                    boolean result = getMeta().evaluate(expression, this, rowIndex, columnIndex);
                    if (result){
                        return conditionalStyle;
                    }
                }
            }
            return style;
        }
        if(getDataView().isQuickPlotRow(rowIndex)){
            if(columnIndex >= getFrozenColumns()){
                return getQuickPlotCellStyle();
            }
            else{
                return getDefaultLabelStyle();
            }
        }
        else if (getDataView().isLabelRow(rowIndex)){
            try{
                rowIndex = rowIndex + 1;   //-1 --> 0
                Label label = getMeta().getLabelMatrix()[rowIndex][columnIndex];
                CellStyleModel styleModel = label.getCellStyle();
                if (styleModel != null){
                    return styleModel;
                } else return getDefaultLabelStyle();
            } catch(NullPointerException npe){
                return getDefaultLabelStyle();
            }
        } else{
            try{
                Column column = getMeta().getColumn(columnIndex);
                if (column == null) return super.getCellStyle(rowIndex, columnIndex);
                CellStyle style = column.getCellStyle();
                if (style != null){
                    ///*
                    if (style.hasConditionalStyles()){
                        for (CellStyle conditionalStyle : style.getConditionalStyles()){
                            String expression = conditionalStyle.getExpression();
                            boolean result = getMeta().evaluate(expression, this, rowIndex, columnIndex);
                            if (result){
                                return conditionalStyle;
                            }
                        }
                    }
                    //*/
                    return style;
                } else {
                    CellStyleModel temp = super.getCellStyle(rowIndex, columnIndex);
                    if (temp != null) {return temp;}
                    else{return getDefaultCellStyle();}
                }
            } catch(NullPointerException npe){
                return getDefaultCellStyle();
            }
        }
    }
    
    
    /**
     * Specify the number of rows that will be used for column headers.
     * @param rows The number of rows to be set static for column headers.
     */
    @Override
    public void setFrozenRows(int rows) {
        int rowsInLabel = getMeta().getLabelRowCount();
        super.setFrozenRows(rowsInLabel - 1 + rows + (getDataView().isQuickPlot() ? 1 : 0));
    }
    
    /**
     * Not implemented.
     * @param event The ParseEvent to be handled.
     */
    @Override
    public void parse(ParseEvent event){}
    
    
    /**
     * Creates a default data source and assigns it to this table.
     * @param editable Artifact of super class.  Is not used in this implementation.
     */
    @Override
    protected void makeDataSource(boolean editable){
        JCVectorDataSource dataSource = new JCVectorDataSource();
        
        dataSource.setNumRows(0);
        dataSource.setNumColumns(0);
        setDataSource(dataSource);
        setNumEditorRows(0);
        setNumEditorColumns(0);
    }
    
    /**
     * Applies a supplied FilterSet to the table, showing or hiding columns and rows as defined by the filter.
     * @param filterSet Set of filters to be applied to the data displayed in the table.
     */
    public void applyFilterSet(final FilterSet filterSet){
        try {
            clearSelectedCells();
            setRepaintEnabled(false);
            getDataView().clearRowFilters();
            getDataView().clearColumnFilters();
            if (filterSet.columnFilters() != null){
                getDataView().addColumnFilter(filterSet.columnFilters());
            }
            if (filterSet.rowFilters() != null){
                getDataView().addRowFilter(filterSet.rowFilters());
            }
            //quick plot columns            
            for(Column column : getMeta().getColumns()){                
                column.setQuickPlotL(filterSet.quickPlotColumnsL.contains(column.getId()));  
                column.setQuickPlotR(filterSet.quickPlotColumnsR.contains(column.getId())); 
            }
            
            
            
            setRepaintEnabled(true);
        } catch (ConcurrentModificationException ex) { 
            System.out.println("shouldn't get here...WepsTable.applyFilterSet()");
            //attempt to apply the filterset again.
            if(!tmp) {
                tmp = true;
                applyFilterSet(filterSet);
            } else {
                System.out.println("repeated failure in WepsTable.applyFilterSet()");
            }
        }
        
        
    }
    
    private boolean tmp = false;
    
    /**
     * Adds the specified ParseListener to the current list of listeners for this table.
     * @param listener The listener to be added to the list.
     */
    public synchronized void addParseListener(ParseListener listener) {
        c_parseListeners = JCListenerList.add(c_parseListeners, listener);
    }
    
    /**
     * Removes the specified ParseListener from the current list of listeners for this table.
     * @param listener The listener to be removed from the list.
     */
    public synchronized void removeParseListener(ParseListener listener) {
        c_parseListeners = JCListenerList.remove(c_parseListeners, listener);
    }
    
    /**
     * Pass the specified ParseEvent to all registered listeners.
     * @param event The ParseEvent to be handled.
     */
    public void fireParseEvent(ParseEvent event){
        @SuppressWarnings({"unchecked", "static-access"})
        Enumeration <ParseListener> listeners = (Enumeration <ParseListener>) JCListenerList.elements(c_parseListeners);
        while(listeners.hasMoreElements() && event.getParsed() == null){
            listeners.nextElement().parse(event);
        }
    }
    
    /**
     * Check to see if the cell at (row,column) is selected.
     * @param row The row containing the cell in question.
     * @param column The ccolumn containing the cell in question.
     * @return <B>true</B> if the cell is selected, <B>false</B> if it is not.
     */
    @Override
    public boolean isSelected(int row, int column){
        if (selectionHandler == null) {
            return(false);
        }
        if ((isLabel(row, column) || getDataView().isLabelRow(row))&& !select_include_labels) {
            return(false);
        }
        return(selectionHandler.isSelected(row, column));
    }
    
    /**
     * Set the cursor used on the table to the specified Cursor.  Does not accept Cursor.TEXT_CURSOR or Cursor.CROSSHAIR_CURSOR, replacing them with Cursor.DEFAULT_CURSOR.
     * @param cursor One of the cursors defined by the Cursor class.
     */
    @Override
    public void setCursor(int cursor) {
        if (cursor == Cursor.TEXT_CURSOR || cursor == Cursor.CROSSHAIR_CURSOR){
            cursor = Cursor.DEFAULT_CURSOR;
        }
        super.setCursor(cursor);
    }
    
    /**
     * Checks for the Ctrl-A and Ctrl-C key events.  If Ctrl-A is pressed the entire table is selected.  If Ctrl-C is pressed, the selected cells are copied to the clipboard.
     * @param ke The KeyEvent specifying the keys pressed.
     */
    @Override
    public void keyPressed(KeyEvent ke){
        int code = ke.getKeyCode();
        int mod = ke.getModifiersEx();
        //System.out.println(ke);
        if(code == 67 && Helper.bitFlag(mod, KeyEvent.CTRL_DOWN_MASK)){
            performCopyAction();
        } else if(code == 65 && Helper.bitFlag(mod, KeyEvent.CTRL_DOWN_MASK)){
            selectAll();
        }
    }
    /**
     * Not implemented.
     * @param ke The KeyEvent specifying the keys released.
     */
    @Override
    public void keyReleased(KeyEvent ke){}
    /**
     * Not implementd.
     * @param ke The KeyEvent specifying the keys typed.
     */
    @Override
    public void keyTyped(KeyEvent ke){}
    
    /**
     * Copy the current selection to the system Clipboard.
     */
    public void performCopyAction(){
        // Custom copy requires a local clipboard. This will not be
        // supported in the 4.0 release.
        JCCellRange cr;
        WepsTable table = this;
        if (table.getSelectedCells() != null && table.getSelectedCells().size() > 0){
            JCCellRange temp_cr = (JCCellRange)(table.getSelectedCells().iterator().next());
            // Flip range to top-left, bottom-right order if selection was made
            // out of order
            cr = new JCCellRange(Math.min(temp_cr.start_row, temp_cr.end_row),
                Math.min(temp_cr.start_column, temp_cr.end_column),
                Math.max(temp_cr.start_row, temp_cr.end_row),
                Math.max(temp_cr.start_column,
                temp_cr.end_column));
        } else {
            cr = new JCCellRange(table.current_row, table.current_column, table.current_row, table.current_column);
        }
        ClipboardString selection = new ClipboardString(table, cr);
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
    }

	/**
	 *
	 * @param listener
	 */
	public void printPreview(JCPrintListener listener){
        printPreview(listener, 0);
    }

	/**
	 *
	 * @param listener
	 * @param page
	 */
	public void printPreview(JCPrintListener listener, final int page){
        final WepsPrintTable table = new WepsPrintTable(this, .75);
        if (listener != null) table.addPrintListener(listener);
        PageFormat format = new PageFormat();
        format.setOrientation(PageFormat.LANDSCAPE);
        Paper paper = new Paper();
        paper.setSize(612, 792);    //8.5 x 11
        paper.setImageableArea(36,36,540,720);
        format.setPaper(paper);
        table.setPageFormat(format);
        table.addCellDisplayListener(getDataView());
        
        Thread task = new Thread("WepsTable Print Preview"){
            @Override
            public void run(){
                WepsPrintPreview pf = new WepsPrintPreview("Print Preview", table);
                pf.showPage(page);
            }
        };
        task.start();
    }   
        
        @SuppressWarnings("unchecked")
        public void hookBorderPainters(DetailReport source)
    {
        int defaultBorderType = 8;
        int defaultLabelType = 4;
        try
        {
            defaultBorderType = ((JCCellBorder) default_cell_style.getCellBorder()).getBorderType();
        }
        catch(ClassCastException cce){}
        try
        {
            defaultLabelType = ((JCCellBorder) default_label_style.getCellBorder()).getBorderType();
        }
        catch(ClassCastException cce){}
        for(Object val : super.cellStyles.getSeriesValues())
        {
            JCLiveCellStyle temp = (JCLiveCellStyle) ((CellRangeValue) val).value;
            int type = temp.getCellBorderType();
            temp.setCellBorder(source.instanceofBoundaryBorder(type));
        }
        default_cell_style.setCellBorder(source.instanceofBoundaryBorder(defaultBorderType));
        default_label_style.setCellBorder(source.instanceofBoundaryBorder(defaultLabelType));
        this.getDataView().getRenderersTable().put(String.class, source.instanceofBoundaryFinder());
//        default_label_style.setCellRenderer();
    }

    public void repaintNow()
    {
        Thread repainter = new Thread()
        {
            @Override
            public void run()
            {
                setRepaintRequired(true);
                requestChange(TableChangeHandler.TABLE_REPAINT);
                setVisible(false);
                repaint();
                try
                {
                    sleep(50);
                }
                catch(InterruptedException ex){}
                setVisible(true);
                try
                {
                    sleep(50);
                }
                catch(InterruptedException ex){}
                repaint();
            }
        };
        repainter.start();
    }
}
