/*
 * AccountDialog.java
 *
 * Status: Tentatively complete
 */

package gui.dialog;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.util.Date;

import finance.Account;
import finance.AccountType;
import finance.Amount;
import finance.BalanceType;
import gui.ActionHandler;
import gui.Constants;

/**
 * The dialog for adding and editing accounts.  This is a Singleton class
 * interfaced with through the static methods {@link #showAdd showAdd} and
 * {@link #showEdit showEdit}.
 */
public final class AccountDialog extends JDialog
 {
  /** The only instance of this class. */
  private static final AccountDialog instance = new AccountDialog();


  /**
   * Shows an {@code AccountDialog} in "Add Account" mode.
   */
  public static void showAdd()
   { instance.mShowAdd(gui.Main.getDB().getAccountType("Asset")); }


  /**
   * Shows an {@code AccountDialog} in "Add Account" mode, with the specified
   * {@code AccountType} initially selected.
   *
   * @param type the {@code AccountType} to select initially.
   */
  public static void showAdd(AccountType type)
   { instance.mShowAdd(type); }


  /**
   * Shows an {@code AccountDialog} in "Edit Account" mode.
   *
   * @param account the {@code Account} to edit.
   */
  public static void showEdit(Account account)
   { instance.mShowEdit(account); }


  /** The text field used to edit the name. */
  private JTextField textName = new JTextField();
  /** The text field used to edit the number. */
  private JTextField textNumber = new JTextField();
  /** The text field used to edit the reference date. */
  private JTextField textDate = new JTextField();
  /** The text field used to edit the reference balance. */
  private JTextField textBalance = new JTextField();
  /** The text field used to edit the comments. */
  private JTextArea textComments = new JTextArea();
  /** The combo box used to edit the account type. */
  private JComboBox comboType = new JComboBox();


  /** Validated value for account name. */
  private String validName = null;
  /** Validated value for account number. */
  private String validNumber = null;
  /** Validated value for account type. */
  private AccountType validType = null;
  /** Validated value for reference date. */
  private Date validDate = null;
  /** Validated value for reference balance. */
  private Amount validBalance = null;
  /** Validated value for comments. */
  private String validComments = null;


  /** The {@code Account} being edited. */
  private Account editAccount = null;
  /** Identifies if the dialog was cancelled or not. */
  private boolean cancelled = true;


  /**
   * Constructs the {@code AccountDialog}.
   */
  private AccountDialog()
   {
    super(gui.Main.instance, true);
    setSize(600, 300);
    setLayout(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();

    //Add the number label and field
    c.ipadx = 1;
    c.gridwidth = 1;
    c.weightx = 0;
    c.fill = GridBagConstraints.BOTH;
    c.anchor = GridBagConstraints.NORTHWEST;
    add(new JLabel("Number:"), c);
    c.weightx = 2;
    //c.gridwidth = GridBagConstraints.REMAINDER;
    add(textNumber, c);

    //Add the account type label and field
    //c.gridwidth = 1;
    c.weightx = 0;
    c.fill = GridBagConstraints.BOTH;
    c.anchor = GridBagConstraints.NORTHWEST;
    add(new JLabel("Type:"), c);
    //c.weightx = 1;
    c.gridwidth = GridBagConstraints.REMAINDER;
    add(comboType, c);

    //Add the name label and field
    c.gridwidth = 1;
    c.weightx = 0;
    c.fill = GridBagConstraints.BOTH;
    c.anchor = GridBagConstraints.NORTHWEST;
    add(new JLabel("Name:"), c);
    c.weightx = 1;
    c.gridwidth = GridBagConstraints.REMAINDER;
    add(textName, c);

    //Add the date label and field
    c.weightx = 0;
    c.gridwidth = 1;
    add(new JLabel("Reference Date:"), c);
    c.weightx = 1;
    add(textDate, c);

    //Add the balance label and field
    c.weightx = 0;
    add(new JLabel("Reference Balance:"), c);
    c.weightx = 1;
    c.gridwidth = GridBagConstraints.REMAINDER;
    textBalance.setHorizontalAlignment(SwingConstants.RIGHT);
    add(textBalance, c);

    //Add the comments label and field
    add(new JLabel("Comments:"), c);
    c.weightx = 1;
    c.weighty = 1;
    add(new JScrollPane(textComments), c);

    //Add the OK and Cancel buttons
    c.weighty = 0;
    c.gridwidth = 1;
    JPanel panel = new JPanel();
    panel.setLayout(new GridBagLayout());
    JButton button = new JButton("OK");
    button.addActionListener(new ActionHandler(this, "button_OK_Handler"));
    panel.add(button, c);
    button = new JButton("Cancel");
    button.addActionListener(new ActionHandler(this, "button_Cancel_Handler"));
    panel.add(button, c);
    c.gridwidth = GridBagConstraints.REMAINDER;
    add(panel, c);
   }


  /**
   * Resets the dialog's data, preparing it to add or edit an account.
   */
  private void reset()
   {
    textName.setText("");
    textNumber.setText("");
    textDate.setText("");
    textBalance.setText("");
    textComments.setText("");

    comboType.removeAllItems();
    for(AccountType e : gui.Main.getDB().getAccountTypes())
      comboType.addItem(e.getName());

    validName = null;
    validNumber = null;
    validType = null;
    validDate = null;
    validBalance = null;
    validComments = null;

    editAccount = null;

    cancelled = true;
   }


  /**
   * Shows an {@code AccountDialog} in "Add Account" mode. This is an internal
   * method used by the static {@link #showAdd showAdd} method.
   *
   * @param type the {@code AccountType} to have initially selected for this
   * {@code Account}.
   */
  private void mShowAdd(AccountType type)
   {
    reset();
    setTitle("Add Account");

    textDate.setText(Constants.dateLong.format(new Date()));
    textBalance.setText("0.00");
    for(int i=0; i<comboType.getItemCount(); i++)
      if(comboType.getItemAt(i).equals(type.getName()))
       {
        comboType.setSelectedIndex(i);
        break;
       }

    setVisible(true);
    if(cancelled)
      return;

    Account account = new Account(validName, validNumber, validType, validDate, validBalance, validComments);
    gui.Main.getDB().addAccount(account);
   }


  /**
   * Shows an {@code AccountDialog} in "Edit Account" mode. This is an internal
   * method used by the static {@link #showEdit showEdit} method.
   *
   * @param account the {@code Account} to edit.
   */
  private void mShowEdit(Account account)
   {
    reset();
    setTitle("Edit Account");

    editAccount = account;

    textNumber.setText(account.getNumber());
    for(int i=0; i<comboType.getItemCount(); i++)
      if(comboType.getItemAt(i).equals(account.getType().getName()))
       {
        comboType.setSelectedIndex(i);
        break;
       }

    textName.setText(account.getName());
    textDate.setText(Constants.dateLong.format(account.getReferenceDate()));

    String balance = String.format("%.2f", account.getReferenceBalance().getValue());
    if(account.getBalance().getType() != account.getType().getNormalBalance())
      balance = "-" + balance;
    textBalance.setText(balance);

    textComments.setText(account.getComments());

    setVisible(true);
    if(cancelled)
      return;

    account.update(validName, validNumber, validType, validDate, validBalance, validComments);
    gui.Main.getDB().addAccount(account);
   }


  /**
   * Checks the dialog's data for validity.
   *
   * @return {@code true} if the data is valid, {@code false} if it is invalid.
   */
  private boolean validateData()
   {
    Account account = null;
    String error = null;

    //Validate name
    validName = textName.getText().trim();
    account = gui.Main.getDB().getAccountByName(validName);
    if(account != null && account != editAccount)
      error = appendLine(error, "Account name already in use.");
    else if("".equals(validName))
      error = appendLine(error, "Account name must be specified.");

    //Validate number
    validNumber = textNumber.getText().trim();
    account = gui.Main.getDB().getAccountByNumber(validNumber);
    if(account != null && account != editAccount)
      error = appendLine(error, "Account number already in use.");

    //Validate account type
    validType = gui.Main.getDB().getAccountType((String)(comboType.getSelectedItem()));
    if(validType == null)
      error = appendLine(error, "Invalid account type.");

    //Validate reference date
    try
     {
      validDate = Constants.dateShort.parse(textDate.getText().trim());
      validDate = Constants.dateLong.parse(textDate.getText().trim());
     }
    catch(java.text.ParseException e)
     {
      if(validDate == null)
        error = appendLine(error, "Invalid date/time format.");
     }

    //Validate reference balance
    try
     {
      java.math.BigDecimal amount = new java.math.BigDecimal(textBalance.getText());
      amount = amount.setScale(2);
      BalanceType type = validType.getNormalBalance();
      if(amount.signum() == -1)
       {
        amount = amount.negate();
        type = type == BalanceType.DEBIT ? BalanceType.CREDIT : BalanceType.DEBIT;
       }
      validBalance = new Amount(amount, type);
     }
    catch(Exception e)
     {
      error = appendLine(error, "Invalid amount format.");
     }

    //Validate comments
    validComments = textComments.getText();
    if(validComments == null)
      validComments = "";

    if(error == null)
      return true;

    validName = null;
    validNumber = null;
    validType = null;
    validDate = null;
    validBalance = null;
    validComments = null;

    errorMessage(error);

    return false;
   }


  /**
   * Appends {@code end} to {@code start}, with a newline between them.  Either
   * or both parameters may be {@code null}.  If one parameter is {@code null},
   * the non-{@code null} parameter is returned unchanged.  If both parameters
   * are {@code null}, {@code null} is returned.
   *
   * @param start the initial line(s) of the string.
   * @param end the string to be appended.
   *
   * @return {@code end} appended to {@code start}, with a newline between them
   * unless one is {@code null}, or {@code null} if {@code start} and {@code
   * end} are both {@code null}.
   */
  private String appendLine(String start, String end)
   {
    if(end == null)
      return start;
    else if(start == null)
      return end;
    else
      return start + "\n" + end;
   }


  /**
   * Displays an error message to the user.
   *
   * @param message the error message to display.
   */
  private void errorMessage(String message)
   { JOptionPane.showMessageDialog(this, message, "Validation Error", JOptionPane.ERROR_MESSAGE); }


  /**
   * Attempts to validate the input, and closes the dialog if successful.  This
   * is the {@link ActionListener ActionListener} method for the "OK" button.
   */
  public void button_OK_Handler(ActionEvent event)
   {
    if(!validateData())
      return;

    cancelled = false;
    setVisible(false);
   }


  /**
   * Closes the dialog and cancels the current add or edit.  This is the {@link
   * ActionListener ActionListener} method for the "Cancel" button.
   */
  public void button_Cancel_Handler(ActionEvent event)
   { setVisible(false); }
 }