package DataEditor;

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.JTableHeader;
import java.util.Vector;
import java.util.Comparator;
import java.util.Arrays;
import java.io.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.InputEvent;

/*******************************************************************************
 * Represents a single document in the program.
 *******************************************************************************
 * The outer class offers functions for saving the data to a file and
 * for displaying the JTable.
 * The inner class is used as a TableModel for the JTable.
 *******************************************************************************
 * @author Markus Wilthaner
 * @version v0.1
 *******************************************************************************
 */

public class TableDocument extends JScrollPane {
  protected boolean saved;
  protected String filename;
  protected JTable table;
  protected ReaderTableModel rtm;
  protected TableColumnModel colModel;

  /*********************************************************************************
   * Creates a new, empty document.
   *********************************************************************************/
  public TableDocument() {
    rtm = new ReaderTableModel();

    Vector rowA;
    rtm.columns.add("Number");
    rtm.columns.add("String");
    rtm.columns.add("Boolean");

    rtm.data.add(rowA = new Vector());
    rowA.add(new Integer(0));
    rowA.add("");
    rowA.add(new Boolean(false));

    rtm.data.add(rowA = new Vector());
    rowA.add(new Integer(0));
    rowA.add("");
    rowA.add(new Boolean(false));

    rtm.data.add(rowA = new Vector());
    rowA.add(new Integer(0));
    rowA.add("");
    rowA.add(new Boolean(false));

    createTable();
    this.setSize(new Dimension(700, 450));

    this.saved = true;
  }

  /*********************************************************************************
   * Creates a new document and opens the specified file.
   * @throws java.io.FileNotFoundException    If the specified file does not exist
   *         IOException              If a general read error occurs
   *         ClassNotFoundException   If the data in the file is not compatible
   *********************************************************************************/
  public TableDocument(String newFile) throws FileNotFoundException, IOException, ClassNotFoundException {
    rtm = new ReaderTableModel();

    FileInputStream fis = new FileInputStream(newFile);
    ObjectInputStream ois = new ObjectInputStream(fis);

    Object nColumns = ois.readObject();
    rtm.columns = (Vector) nColumns;

    Object nData = ois.readObject();
    rtm.data = (Vector) nData;

    ois.close();
    fis.close();

    createTable();

    this.setSize(new Dimension(700, 450));

    this.filename = newFile;
    this.saved = true;
  }

  /*********************************************************************************
   * Creates a JTable and adds it to the viewport.
   * Also contains the MouseListener that initiates the sorting of the table.
   *********************************************************************************/
  private void createTable() {
    table = new JTable(rtm);
    colModel = table.getColumnModel();
    table.setPreferredScrollableViewportSize(new Dimension(700, 400));

    table.setColumnSelectionAllowed(false);

    JTableHeader jtHeader = table.getTableHeader();
    jtHeader.addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent evt) {
        int viewColumn = colModel.getColumnIndexAtX(evt.getX());
        int col = table.convertColumnIndexToModel(viewColumn);
        if (evt.getClickCount() == 1 && col != -1) {
          boolean asc = (evt.getModifiers() & InputEvent.SHIFT_MASK) == 0;

          RowSorter rSorter = new RowSorter(col, asc);
          Object[] sortData = rtm.data.toArray();
          Arrays.sort(sortData, rSorter);

          for (int i = 0; i < sortData.length; i++) {
            rtm.data.set(i, sortData[i]);
          }

          saved = false;
          rtm.fireTableDataChanged();

        }
      }
    });
    this.setViewportView(table);
  }


  /*********************************************************************************
   * Tries to close this document.
   * @return true     If everything went alright.
   *         false    If the user canceled the operation.
   *********************************************************************************/
  public boolean close() {
    if (this.saved == false) {
      int n = JOptionPane.showOptionDialog(this, "Document not saved. Save now?", "Document not saved",
          JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null);
      switch (n) {

        case JOptionPane.YES_OPTION:
          if (filename != null) {
            return this.save();
          }
          return this.saveAsDialog();

        case JOptionPane.NO_OPTION:
          return true;
      }
      return false;
    }
    return true;
  }

  /*********************************************************************************
   * Displays a "Save As..." Dialog and calls the save function.
   * @return  true    If the file was saved.
   *          false   If the user cancelled the save or the program couldn't write
   *                  the file.
   *********************************************************************************/
  public boolean saveAsDialog() {
    JFileChooser chooser = new JFileChooser();
    int ret = chooser.showSaveDialog(this);
    if (ret == JFileChooser.APPROVE_OPTION) {
      if (saveAs(chooser.getSelectedFile()) == false) {
        JOptionPane.showMessageDialog(this, "Error writing file", "Save error", JOptionPane.ERROR_MESSAGE);
        return false;
      }
      return true;
    }
    return false;
  }

  /*********************************************************************************
   * Saves the data of this table to the given file.
   * @return    true    If everything went alright.
   *            false   If an error occured while opening/writing the file.
   *********************************************************************************/
  protected boolean saveAs(File file) {
    if (saved == false || filename != file.getName()) {
      FileOutputStream fos = null;
      ObjectOutputStream oos = null;
      try {
        fos = new FileOutputStream(file);
        oos = new ObjectOutputStream(fos);

        oos.writeObject(rtm.columns);
        oos.writeObject(rtm.data);

        oos.close();
        fos.close();
        this.filename = file.getName();
        saved = true;
      } catch (FileNotFoundException e) {
        System.out.println("Error: File not found");
        return false;
      } catch (IOException e) {
        System.out.println("Error: Writing to file failed");
        return false;
      }
    }
    return true;
  }

  /*********************************************************************************
   * Saves the data of this table to the current file.
   * @return    true    If everything went alright.
   *            false   If an error occured while opening/writing the file.
   *********************************************************************************/
  public boolean save() {
    File myFile = new File(filename);
    return saveAs(myFile);
  }

  public String getFilename() {
    return this.filename;
  }

  public boolean getSaved() {
    return this.saved;
  }

  /*********************************************************************************
   * Adds a single row to the table.
   * @return    true    If everything went alright.
   *            false   If an error occured while opening/writing the file.
   *********************************************************************************/
  protected boolean addRows(int n) {
    Vector row;
    String strClass;

    for (int i = 0; i < n; i++) {
      row = new Vector();
      for (int j = 0; j < rtm.getColumnCount(); j++) {
        strClass = rtm.getColumnClass(j).getName();
        if (strClass.compareTo("java.lang.String") == 0) {
          row.add(new String(""));
        } else {
          if (strClass.compareTo("java.lang.Integer") == 0) {
            row.add(new Integer(0));
          } else {
            if (strClass.compareTo("java.lang.Boolean") == 0) {
              row.add(new Boolean(false));
            } else {
              System.out.println("Error: Type mismatch");
              return false;
            }
          }
        }
      }
      rtm.data.add(row);
    }
    rtm.fireTableDataChanged();
    return true;
  }

  /*********************************************************************************
   * Inner class for the TableModel.
   *********************************************************************************/
  protected class ReaderTableModel extends AbstractTableModel {
    protected Vector data;
    protected Vector columns;

    public ReaderTableModel() {
      data = new Vector();
      columns = new Vector();
    }

    public int getColumnCount() {
      return columns.size();
    }

    public int getRowCount() {
      return data.size();
    }

    public String getColumnName(int index) {
      return (String) columns.get(index);
    }

    public Object getValueAt(int row, int col) {
      return ((Vector) data.get(row)).get(col);
    }

    public Class getColumnClass(int c) {
      return getValueAt(0, c).getClass();
    }

    public boolean isCellEditable(int row, int col) {
      return true;
    }

    public void setValueAt(Object value, int row, int col) {
      ((Vector) data.get(row)).set(col, value);
      saved = false;
      fireTableCellUpdated(row, col);
    }
  }

  /*********************************************************************************
   * Used to compare two rows of the table.
   ********************************************************************************/
  protected class RowSorter implements Comparator {
    protected int column;
    protected boolean ascending;

    /*********************************************************************************
     * Constructor.
     * @param  sColumn     The column which should be used to compare the rows
     *         sAscending  If the rows should be sorted ascending or descending
     ********************************************************************************/
    public RowSorter(int sColumn, boolean sAscending) {
      this.column = sColumn;
      this.ascending = sAscending;
    }

    /*********************************************************************************
     * Compares the two rows.
     * @return  < 0    First object is "less"
     *          0      Objects are equal
     *          > 0    First object is "more"
     ********************************************************************************/
    public int compare(Object a, Object b) {
      Vector vA = (Vector) a;
      Vector vB = (Vector) b;
      int ret = 0;

      if (vA.get(this.column) instanceof Comparable) {
        ret = (((Comparable) vA.get(this.column)).compareTo(vB.get(this.column)));
      } else {
        if (vA.get(this.column) instanceof Boolean) {
          if (((Boolean) vA.get(this.column)).booleanValue()) {
            ret = -1;
          } else {
            ret = 1;
          }
        } else {
          System.out.println("Object not comparable");
          ret = 0;
        }
      }

      return ascending ? ret : (ret * -1);
    }
  }

}

