/*
 * TableSorter2.java
 *
 * This class adds some basic sorting by clicking on the table headers.
 * This is a much simplified version of the Sun TableSorter
 * it does _not_ do any mapping. It basically handles drawing the
 * header icons and passing the information requests to the real 
 * table models.
 *
 * Jim Frankenberger
 * USDA-ARS, West Lafayette IN
 * jrf@purdue.edu
 *
 * Created on August 27, 2004, 2:28 PM
 */
package ex1;

import java.awt.*;
import java.awt.event.*;
import java.io.Serializable;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.*;

/**
 *
 * When the tableHeader property is set, either by using the setTableHeader() method or the two
 * argument constructor, the table header may be used as a complete UI for TableSorter. The default
 * renderer of the tableHeader is decorated with a renderer that indicates the sorting status of
 * each column. In addition, a mouse listener is installed with the following behavior:
 * <ul>
 * <li>
 * Mouse-click: Clears the sorting status of all other columns and advances the sorting status of
 * that column through three values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to NOT_SORTED
 * again).
 * <li>
 * SHIFT-mouse-click: Clears the sorting status of all other columns and cycles the sorting status
 * of the column through the same three values, in the opposite order: {NOT_SORTED, DESCENDING,
 * ASCENDING}.
 * <li>
 * CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that the changes to the column
 * do not cancel the statuses of columns that are already sorting - giving a way to initiate a
 * compound sort.
 * </ul>
 * <p/>
 *
 */
public class TableSorter2 extends AbstractTableModel {

	private static final long serialVersionUID = 1L;

	/**
	 *
	 */
	protected TableModel tableModel;

	/**
	 *
	 */
	protected AbstractTableModel auxModel;

	/**
	 *
	 */
	protected AbstractTableModel mainModel;

	/**
	 *
	 */
	public static final int DESCENDING = -1;

	/**
	 *
	 */
	public static final int NOT_SORTED = 0;

	/**
	 *
	 */
	public static final int ASCENDING = 1;

	private static Directive EMPTY_DIRECTIVE = new Directive(-123, NOT_SORTED);

	private JTableHeader tableHeader;
	private MouseListener mouseListener;
	private TableModelListener tableModelListener;
	private final List<Directive> sortingColumns = new ArrayList<Directive>();

	/**
	 *
	 */
	public TableSorter2() {
		this.mouseListener = new MouseHandler();
		this.tableModelListener = new TableModelHandler();
		auxModel = mainModel = null;
	}

	/**
	 *
	 * @param tableModel
	 */
	public TableSorter2(TableModel tableModel) {
		this();
		setTableModel(tableModel);
		auxModel = mainModel = null;
	}

	/**
	 *
	 * @param tableModel
	 * @param tableHeader
	 */
	public TableSorter2(TableModel tableModel, JTableHeader tableHeader) {
		this();
		setTableHeader(tableHeader);
		setTableModel(tableModel);
		auxModel = mainModel = null;
	}

	/**
	 *
	 * @param t
	 */
	public void setAuxModel(AbstractTableModel t) {
		auxModel = t;
	}

	/**
	 *
	 * @param t
	 */
	public void setMainModel(AbstractTableModel t) {
		mainModel = t;
	}

	/**
	 *
	 * @return
	 */
	public TableModel getTableModel() {
		return tableModel;
	}

	/**
	 *
	 * @param tableModel
	 */
	public void setTableModel(TableModel tableModel) {
		if (this.tableModel != null) {
			this.tableModel.removeTableModelListener(tableModelListener);
		}

		this.tableModel = tableModel;
		if (this.tableModel != null) {
			this.tableModel.addTableModelListener(tableModelListener);
		}

		fireTableStructureChanged();
	}

	/**
	 *
	 * @return
	 */
	public JTableHeader getTableHeader() {
		return tableHeader;
	}

	/**
	 *
	 * @param tableHeader
	 */
	public void setTableHeader(JTableHeader tableHeader) {
		if (this.tableHeader != null) {
			this.tableHeader.removeMouseListener(mouseListener);
			TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
			if (defaultRenderer instanceof SortableHeaderRenderer) {
				this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
			}
		}
		this.tableHeader = tableHeader;
		if (this.tableHeader != null) {
			this.tableHeader.addMouseListener(mouseListener);
			this.tableHeader.setDefaultRenderer(
					new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
		}
	}

	/**
	 *
	 * @return
	 */
	public boolean isSorting() {
		return sortingColumns.size() != 0;
	}

	private Directive getDirective(int column) {
        for (Directive directive : sortingColumns) {
            if (directive.column == column) {
                return directive;
            }
        }
		return EMPTY_DIRECTIVE;
	}

	/**
	 *
	 * @param column
	 * @return
	 */
	public int getSortingStatus(int column) {
		return getDirective(column).direction;
	}

	private void sortingStatusChanged() {
		fireTableDataChanged();

		if (mainModel != null) {
			mainModel.fireTableDataChanged();
		}

		if (auxModel != null) {
			auxModel.fireTableDataChanged();
		}

		if (tableHeader != null) {
			tableHeader.repaint();
		}
	}

	/**
	 *
	 * @param column
	 * @param status
	 */
	public void setSortingStatus(int column, int status, boolean frozen) {
		Directive directive = getDirective(column);
		if (directive != EMPTY_DIRECTIVE) {
			sortingColumns.remove(directive);
		}
		if (status != NOT_SORTED) {
			sortingColumns.add(new Directive(column, status));
		}
		if (tableModel instanceof CropTableModel) {
			((CropTableModel) tableModel).sort(column, status);
		} else if (tableModel instanceof OprnTableModel) {
			((OprnTableModel) tableModel).sort(column, status);
		} else if(tableModel instanceof OpDetailTableModel){
                        if(frozen) ((OpDetailTableModel) tableModel).sort(column, status);
                        else ((OpDetailTableModel) tableModel).sort(column, status);
                }

		sortingStatusChanged();
	}
        
        TableSorter2.MouseHandler extractSorter()
        {
            return (TableSorter2.MouseHandler) mouseListener;
        }

	/**
	 *
	 * @param column
	 * @param size
	 * @return
	 */
	protected Icon getHeaderRendererIcon(int column, int size) {
		Directive directive = getDirective(column);
		if (directive == EMPTY_DIRECTIVE) {
			return null;
		}
		return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
	}

	private void cancelSorting() {
		sortingColumns.clear();
		sortingStatusChanged();
	}

	/**
	 *
	 * @param viewIndex
	 * @return
	 */
	public int modelIndex(int viewIndex) {
		return viewIndex;
	}

    // TableModel interface methods 
	/**
	 *
	 * @return
	 */
	@Override
	public int getRowCount() {
		return (tableModel == null) ? 0 : tableModel.getRowCount();
	}

	/**
	 *
	 * @return
	 */
	@Override
	public int getColumnCount() {
		return (tableModel == null) ? 0 : tableModel.getColumnCount();
	}

	/**
	 *
	 * @param column
	 * @return
	 */
	@Override
	public String getColumnName(int column) {
		return tableModel.getColumnName(column);
	}

	/**
	 *
	 * @param column
	 * @return
	 */
	@Override
	public Class<?> getColumnClass(int column) {
		return tableModel.getColumnClass(column);
	}

	/**
	 *
	 * @param row
	 * @param column
	 * @return
	 */
	@Override
	public boolean isCellEditable(int row, int column) {
		return tableModel.isCellEditable(modelIndex(row), column);
	}

	/**
	 *
	 * @param row
	 * @param column
	 * @return
	 */
	@Override
	public Object getValueAt(int row, int column) {
		return tableModel.getValueAt(modelIndex(row), column);
	}

	/**
	 *
	 * @param aValue
	 * @param row
	 * @param column
	 */
	@Override
	public void setValueAt(Object aValue, int row, int column) {
		tableModel.setValueAt(aValue, modelIndex(row), column);
	}

	private class TableModelHandler implements TableModelListener, Serializable {

		private static final long serialVersionUID = 1L;

		@Override
		public void tableChanged(TableModelEvent e) {
			// If we're not sorting by anything, just pass the event along.             
			if (!isSorting()) {
				fireTableChanged(e);
				return;
			}

            // If the table structure has changed, cancel the sorting; the             
			// sorting columns may have been either moved or deleted from             
			// the model. 
			if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
				cancelSorting();
				fireTableChanged(e);
				return;
			}

            // We can map a cell event through to the view without widening             
			// when the following conditions apply: 
			// 
			// a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and, 
			// b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
			// c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and, 
			// d) a reverse lookup will not trigger a sort (modelToView != null)
			//
			// Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
			// 
			// The last check, for (modelToView != null) is to see if modelToView 
			// is already allocated. If we don't do this check; sorting can become 
			// a performance bottleneck for applications where cells  
			// change rapidly in different parts of the table. If cells 
			// change alternately in the sorting column and then outside of             
			// it this class can end up re-sorting on alternate cell updates - 
			// which can be a performance problem for large tables. The last 
			// clause avoids this problem. 
			int column = e.getColumn();
			if (e.getFirstRow() == e.getLastRow()
					&& column != TableModelEvent.ALL_COLUMNS
					&& getSortingStatus(column) == NOT_SORTED) {

				int viewIndex = e.getFirstRow();
				fireTableChanged(new TableModelEvent(TableSorter2.this,
						viewIndex, viewIndex,
						column, e.getType()));
				return;
			}

			// Something has happened to the data that may have invalidated the row order. 
			fireTableDataChanged();
			return;
		}
	}

	class MouseHandler extends MouseAdapter implements Serializable {

		private static final long serialVersionUID = 1L;

		@Override
		public void mouseClicked(MouseEvent e) {
			JTableHeader h = (JTableHeader) e.getSource();
			TableColumnModel columnModel = h.getColumnModel();
			int viewColumn = columnModel.getColumnIndexAtX(e.getX());
			int column = columnModel.getColumn(viewColumn).getModelIndex();
                        /*We need to check if it is the unfrozen portion of detail, 
                                so we can sort the first column.*/
                        boolean nonfroze = h.getTable().getModel() instanceof TableSorter2 ?
                                ((TableSorter2)h.getTable().getModel()).getTableModel() instanceof OpDetailTableModel
                                : false;
			if (column > 0 || nonfroze) {
                                boolean frozen = h.getTable() instanceof OpDetailTableModel.FrozenTable;
                                if(frozen) column -= 3;//So we can pass to the frozen table of the opdetailtablemodel
				int status = getSortingStatus(column);
				if (!e.isControlDown()) {
					cancelSorting();
				}
                // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or 
				// {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed. 
				status = status + (e.isShiftDown() ? -1 : 1);
				status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
				setSortingStatus(column, status, frozen);
			}
		}
	}

	private static class Arrow implements Icon {

		private final boolean descending;
		private final int size;
		private final int priority;

		public Arrow(boolean descending, int size, int priority) {
			this.descending = descending;
			this.size = size;
			this.priority = priority;
		}

		@Override
		public void paintIcon(Component c, Graphics g, int x, int y) {
			Color color = c == null ? Color.GRAY : c.getBackground();
            // In a compound sort, make each succesive triangle 20% 
			// smaller than the previous one. 
			int dx = (int) (size / 2.0 * Math.pow(0.8, priority));
			int dy = descending ? dx : -dx;
			// Align icon (roughly) with font baseline. 
			y = y + 5 * size / 6 + (descending ? -dy : 0);
			int shift = descending ? 1 : -1;
			g.translate(x, y);

			// Right diagonal. 
			g.setColor(color.darker());
			g.drawLine(dx / 2, dy, 0, 0);
			g.drawLine(dx / 2, dy + shift, 0, shift);

			// Left diagonal. 
			g.setColor(color.brighter());
			g.drawLine(dx / 2, dy, dx, 0);
			g.drawLine(dx / 2, dy + shift, dx, shift);

			// Horizontal line. 
			if (descending) {
				g.setColor(color.darker().darker());
			} else {
				g.setColor(color.brighter().brighter());
			}
			g.drawLine(dx, 0, 0, 0);

			g.setColor(color);
			g.translate(-x, -y);
		}

		@Override
		public int getIconWidth() {
			return size;
		}

		@Override
		public int getIconHeight() {
			return size;
		}
	}

	private class SortableHeaderRenderer implements TableCellRenderer {

		private TableCellRenderer tableCellRenderer;

		public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
			this.tableCellRenderer = tableCellRenderer;
		}

		@Override
		public Component getTableCellRendererComponent(JTable table,
				Object value,
				boolean isSelected,
				boolean hasFocus,
				int row,
				int column) {
                    	Component c = tableCellRenderer.getTableCellRendererComponent(table,
					value, isSelected, hasFocus, row, column);
			if (c instanceof JLabel) {
				JLabel l = (JLabel) c;
				l.setHorizontalTextPosition(JLabel.LEFT);
				int modelColumn = table.convertColumnIndexToModel(column);
				l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
				l.setPreferredSize(new Dimension(0, 30));
                                if(column == 0)
                                {
                                    boolean nonfroze = table.getModel() instanceof TableSorter2 ?
                                        ((TableSorter2) table.getModel()).getTableModel() instanceof OpDetailTableModel
                                        : false;
                                    if(nonfroze)
                                    {
                                        OpDetailTableModel op = (OpDetailTableModel) ((TableSorter2) table.getModel()).getTableModel();
                                        for(int index = -3; index <0; index ++)
                                        {
                                            TableColumn froz = op.getFrozenTable().getColumn(index + 3);
                                            Icon ic;
                                            Directive directive = getDirective(index);
                                            if (directive == EMPTY_DIRECTIVE) ic = null;
                                            else ic = new Arrow(directive.direction == DESCENDING, l.getFont().getSize(), sortingColumns.indexOf(directive));
                                            JLabel fullHead = new JLabel(op.getFrozenTable().getColumnName(index + 3), ic, JLabel.CENTER);
                                            Border header = UIManager.getBorder("TableHeader.cellBorder");
                                            fullHead.setBorder(header);
                                            froz.setHeaderValue(fullHead);
                                        }
                                    }
                                }
			}
			return c;
		}
	}

	private static class Directive {

		private int column;
		private int direction;

		public Directive(int column, int direction) {
			this.column = column;
			this.direction = direction;
		}
                
                @Override
                public boolean equals(Object other)
                {
                    if(other instanceof Directive)
                    {
                        Directive oth = (Directive)other;
                        if(this.column != oth.column) return false;
                        if(this.direction != oth.direction) return false;
                        return true;
                    }
                    else return false;
                }
                
                @Override
                public int hashCode()
                {
                    return column * 4 + direction;
                }
	}
}
