/*
 * JournalComponent.java
 *
 * Status: Functional
 *
 * Todo list:
 */

package gui;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;

import java.util.*;

import finance.*;
import gui.dialog.JournalEntryDialog;

import static finance.Journal.DataModel.DATE_COL;
import static finance.Journal.DataModel.DESC_COL;
import static finance.Journal.DataModel.DEBIT_COL;
import static finance.Journal.DataModel.CREDIT_COL;
import static finance.Journal.DataModel.COLUMN_COUNT;

/**
 * The user interface component for {@link Journal Journal}s.
 */
public class JournalComponent extends JPanel
 {
  private static class CellRenderer extends JPanel implements TableCellRenderer
   {
    private static Border innerBorder = BorderFactory.createMatteBorder(0, 0, 1, 0, (Color)UIManager.getDefaults().get("Table.gridColor"));
    private static Border selectedBorder = BorderFactory.createMatteBorder(2, 0, 1, 0, SystemColor.textHighlight);
    private static Border panelBorder = BorderFactory.createMatteBorder(2, 0, 1, 0, SystemColor.controlHighlight);

    private GridBagConstraints constraints = new GridBagConstraints();
    private JPanel panel = new JPanel();


    public CellRenderer()
     {
      super();
      setOpaque(true);
      setLayout(new GridBagLayout());
      setBorder(panelBorder);

      panel.setOpaque(false);
      panel.setLayout(new GridBagLayout());
      //panel.setBorder(BorderFactory.createEmptyBorder());

      constraints.gridwidth = GridBagConstraints.REMAINDER;
      constraints.anchor = GridBagConstraints.NORTHWEST;
      constraints.fill = GridBagConstraints.BOTH;
      constraints.weightx = 1;

      add(panel, constraints);
     }


    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
     {
      int height = 4;

      super.setBackground(row % 2 == 0 ? Constants.bgRowEven : Constants.bgRowOdd);
      if(isSelected)
        setBorder(selectedBorder);
      else
        setBorder(panelBorder);

      setFont(table.getFont());

      panel.removeAll();

      if(value != null && value instanceof JournalEntry)
       {
        JournalEntry entry = (JournalEntry)value;
        boolean dateAdded = false;
        JLabel label = null;

        for(AccountEntry ae : entry.getAmounts())
         {
          label = getLabel(column, entry, ae);
          if(column == DATE_COL && dateAdded == false)
           {
            label.setText(Constants.dateShort.format(entry.getDate()));
            dateAdded = true;
           }

          panel.add(label, constraints);
          height += label.getPreferredSize().height;
         }
        label = getLabel(column, entry, null);
        panel.add(label, constraints);
        height += label.getPreferredSize().height;

        if(table.getRowHeight(row) != height)
          table.setRowHeight(row, height);
       }

      return this;
     }


    protected JLabel getLabel(int column, JournalEntry je, AccountEntry ae)
     {
      JLabel label = null;
      String s = "";

      switch(column)
       {
        case DATE_COL:
          label = new JLabel(" ");
          break;
        case DESC_COL:
          if(ae != null)
           {
            if(!ae.getDebit().equals(""))
              label = new JLabel(" " + ae.getAccountName());
            else if(!ae.getCredit().equals(""))
              label = new JLabel("      " + ae.getAccountName());
           }
          else
            label = new JLabel("   " + je.getDescription());
          break;
        case DEBIT_COL:
          if(ae != null && !(s = ae.getDebit()).equals(""))
           {
            label = new JLabel(s);
            label.setBackground(ae.getBalanceType() == BalanceType.DEBIT ? Constants.bgBalanceNormal : Constants.bgBalanceAbnormal);
            label.setOpaque(true);
           }
          else
            label = new JLabel(" ");
          break;
        case CREDIT_COL:
          if(ae != null && !(s = ae.getCredit()).equals(""))
           {
            label = new JLabel(s);
            label.setBackground(ae.getBalanceType() == BalanceType.CREDIT ? Constants.bgBalanceNormal : Constants.bgBalanceAbnormal);
            label.setOpaque(true);
           }
          else
            label = new JLabel(" ");
          break;
        default:
          label = new JLabel(" ");
          break;
       }

      if(column == DESC_COL && ae == null)
        label.setFont(getFont().deriveFont(Font.ITALIC));
      else
        label.setFont(getFont());

      if(column == DATE_COL || column == DESC_COL)
        label.setHorizontalAlignment(SwingConstants.LEFT);
      else
        label.setHorizontalAlignment(SwingConstants.RIGHT);
      label.setBorder(innerBorder);

      return label;
     }
   }


  /**
   * This class is used to monitor the table for double-click events, and invoke
   * ButtonHandler.button_EditEntry_Handler when one is detected.
   */
  private static class DblClickHandler extends MouseAdapter
   {
    JournalComponent container;

    DblClickHandler(JournalComponent container)
     { this.container = container; }

    public void mousePressed(MouseEvent e)
     {
      if(e.getClickCount() < 2)
        return;

      //TODO: Construct a proper ActionEvent to pass.
      //ActionEvent(e.getSource(),, "edit")
      container.buttonHandler.button_EditEntry_Handler(null);
     }
   }


  private class SelectionHandler implements ListSelectionListener
   {
    public void valueChanged(ListSelectionEvent e)
     {
      if(e.getValueIsAdjusting())
        return;
      if(e.getSource() instanceof ListSelectionModel)
       {
        ListSelectionModel list = (ListSelectionModel)e.getSource();
        if(list.isSelectionEmpty())
         {
          buttonEdit.setEnabled(false);
          buttonDelete.setEnabled(false);
         }
        else
         {
          buttonEdit.setEnabled(true);
          buttonDelete.setEnabled(true);
         }
       }
     }
   }


  private class ButtonHandler
   {
    /**
     * Adds a new journal entry.  This is the {@link ActionListener
     * ActionListener} method for the "Add Entry" button.
     */
    public void button_AddEntry_Handler(ActionEvent event)
     { JournalEntryDialog.showAdd(journal); }


    /**
     * Edits the selected journal entry.  This is the {@link ActionListener
     * ActionListener} method for the "Edit Entry" button.
     */
    public void button_EditEntry_Handler(ActionEvent event)
     {
      int row = table.getSelectedRow();

      if(row == -1)
        return;

      Object entry = model.getValueAt(row, 0);
      if(entry instanceof JournalEntry)
        JournalEntryDialog.showEdit(journal, (JournalEntry)entry);
     }


    /**
     * Deletes the selected journal entry, after prompting the user.  This is
     * the {@link ActionListener ActionListener} method for the "Delete Entry"
     * button.
     */
    public void button_DeleteEntry_Handler(ActionEvent event)
     {
      int row = table.getSelectedRow();
      String msg = "Are you sure you want to delete the selected entry?";

      if(row == -1)
        return;

      if(JOptionPane.showConfirmDialog(Main.instance, msg, "Confirm Delete", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
       {
        Object entry = model.getValueAt(row, 0);
        if(entry instanceof JournalEntry)
          journal.remove((JournalEntry)entry);
       }
     }
   }


  /** The table used to display entries from the {@code Journal}. */
  JTable table = null;
  TableModel model = null;
  /** The {@code Journal} interfaced to by this {@code JournalComponent}. */
  Journal journal = null;

  private JButton buttonAdd = new JButton("Add Entry");
  private JButton buttonEdit = new JButton("Edit Entry");
  private JButton buttonDelete = new JButton("Delete Entry");

  private ButtonHandler buttonHandler = new ButtonHandler();


  /**
   * Constructs a new {@code JournalComponent}.
   *
   * @param journal the {@code Journal} interfaced to by this {@code
   * JournalComponent}.
   */
  public JournalComponent(Journal journal)
   {
    this.journal = journal;

    setLayout(new GridBagLayout());

    GridBagConstraints c = new GridBagConstraints();

    c.gridwidth = GridBagConstraints.REMAINDER;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;

    model = journal.getDataModel();
    table = new JTable(model);
    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    table.getSelectionModel().addListSelectionListener(new SelectionHandler());
    table.getTableHeader().setReorderingAllowed(false);
    table.addMouseListener(new DblClickHandler(this));
    for(int i=0; i<table.getColumnCount(); i++)
     {
      TableColumn column = table.getColumnModel().getColumn(i);
      column.setCellRenderer(new CellRenderer());
      column.setResizable(false);
     }
    setColumnWidths();
    setRowHeights();
    add(new JScrollPane(table), c);
    table.scrollRectToVisible(table.getCellRect(table.getRowCount()-1, 0, true));


    c.gridwidth = 1;
    c.weighty = 0;

    buttonAdd.addActionListener(new ActionHandler(buttonHandler, "button_AddEntry_Handler"));
    buttonAdd.setMnemonic(KeyEvent.VK_A);
    add(buttonAdd, c);

    buttonEdit.addActionListener(new ActionHandler(buttonHandler, "button_EditEntry_Handler"));
    buttonEdit.setMnemonic(KeyEvent.VK_E);
    buttonEdit.setEnabled(false);
    add(buttonEdit, c);

    c.gridwidth = GridBagConstraints.REMAINDER;

    buttonDelete.addActionListener(new ActionHandler(buttonHandler, "button_DeleteEntry_Handler"));
    buttonDelete.setMnemonic(KeyEvent.VK_D);
    buttonDelete.setEnabled(false);
    add(buttonDelete, c);
   }


  /**
   * Called by the constructor to set the column widths for the table.
   */
  private void setColumnWidths()
   {
    Date date = null;
    String dateString = "26 Nov 2005";
    try
     {
      date = Constants.dateShort.parse("26 Nov 2005");
      dateString = Constants.dateShort.format(date);
     }
    catch(Exception ignored)
     { }
    String numberString = "$90,000.00";
    int dateWidth = 0;
    int numberWidth = 0;
    TableColumn column = null;

    try
     {
      Graphics2D g = Main.instance.getGraphicsConfiguration().createCompatibleImage(1, 1).createGraphics();
      dateWidth = (int)table.getFont().getStringBounds(dateString, g.getFontRenderContext()).getWidth();
      numberWidth = (int)table.getFont().getStringBounds(numberString, g.getFontRenderContext()).getWidth();
     }
    catch(Exception e)
     { return; }

    dateWidth *= 1.3;
    numberWidth *= 1.3;

    column = table.getColumnModel().getColumn(DATE_COL);
    column.setMinWidth(dateWidth);
    column.setMaxWidth(dateWidth);

    column = table.getColumnModel().getColumn(DEBIT_COL);
    column.setMinWidth(numberWidth);
    column.setMaxWidth(numberWidth);
    column = table.getColumnModel().getColumn(CREDIT_COL);
    column.setMinWidth(numberWidth);
    column.setMaxWidth(numberWidth);
   }


  /**
   * Called by the constructor to set the heights of the rows in the table.
   */
  private void setRowHeights()
   {
    //Create a renderer and get the component for each row.  The component sets
    //the height of the table row to the value needed to display the component.
    CellRenderer renderer = new CellRenderer();
    for(int i=0; i<model.getRowCount(); i++)
      renderer.getTableCellRendererComponent(table, model.getValueAt(i, 0), false, false, i, 0);
   }
 }
