/*
 * Ledger.java
 *
 * Status: Functional
 */

package finance;

import java.util.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultMutableTreeNode;

/**
 * A ledger is a collection of accounts.  The General Ledger contains all
 * accounts; subsidiary ledgers may contain only a select subset of all
 * accounts.  Support for subsidiary ledgers has not been implemented in this
 * version.
 */
public final class Ledger
 {
  /**
   * Node class for displaying account types.
   */
  public static class AccountTypeNode extends DefaultMutableTreeNode
   {
    static TreeMap<String, AccountTypeNode> map = new TreeMap<String, AccountTypeNode>();
    AccountType data = null;

    private AccountTypeNode(AccountType a)
     {
      data = a;
      map.put(data.getName(), this);
     }

    public AccountType getData()
     { return data; }

    public boolean isLeaf()
     { return false; }

    public String toString()
     {
      return data.getName();
     }
   }


  /**
   * Node class for displaying accounts.
   */
  public static class AccountNode extends DefaultMutableTreeNode implements TableModelListener
   {
    static TreeMap<Integer, AccountNode> map = new TreeMap<Integer, AccountNode>();
    private Ledger ledger = null;
    private Account data = null;

    private AccountNode(Ledger l, Account a)
     {
      ledger = l;
      data = a;
      map.put(data.getID(), this);
      data.getDataModel().addTableModelListener(this);
     }

    public Account getData()
     { return data; }

    public boolean isLeaf()
     { return true; }

    public String toString()
     {
      String balance = String.format("%.2f", data.getBalance().getValue());
      if(data.getBalance().getType() != data.getType().getNormalBalance())
        balance = "(" + balance + ")";
      if("".equals(data.getNumber()))
        return String.format("%s  [Balance: %s]", data.getName(), balance);
      else
        return String.format("%s  [%s]  Balance: %s", data.getNumber(), data.getName(), balance);
     }

    public void	tableChanged(TableModelEvent e)
     { ledger.dataModel.nodeChanged(this); }
   }


  DefaultTreeModel dataModel = new DefaultTreeModel(new DefaultMutableTreeNode(), false);

  private LinkedList<Account> accounts = new LinkedList<Account>();
  private FinancialDatabase DB = null;


  Ledger(FinancialDatabase db)
   { DB = db; }


  public List<Account> getAccounts()
   { return Collections.unmodifiableList(accounts); }

  //
  // Begin Account Type
  //

  void addAccountType(AccountType a)
   {
    DefaultMutableTreeNode root = (DefaultMutableTreeNode)(dataModel.getRoot());
    dataModel.insertNodeInto(new AccountTypeNode(a), root, dataModel.getChildCount(root));
   }


  void removeAccountType(AccountType a)
   {
    AccountTypeNode node = AccountTypeNode.map.get(a.getName());
    dataModel.removeNodeFromParent(node);
    AccountTypeNode.map.remove(a.getName());
   }

  //
  // End Account Type
  //
  // Begin Account
  //

  void addOrUpdate(Account account)
   {
    if(account == null)
      return;
    removeAccount(account.getID());
    addAccount(account);
   }


  void addAccount(Account account)
   {
    accounts.add(account);
    AccountTypeNode parent = AccountTypeNode.map.get(account.getType().getName());
    AccountNode node = new AccountNode(this, account);
    int index = insertIndex(parent, node);
    dataModel.insertNodeInto(node, parent, index);
   }


  private int insertIndex(TreeNode parent, AccountNode child)
   {
    int ret = 0;
    for(int i=0; i<parent.getChildCount(); i++)
     {
      if(parent.getChildAt(i) instanceof AccountNode)
       {
        AccountNode node = (AccountNode)(parent.getChildAt(i));
        if(node.data.getName().compareTo(child.data.getName()) < 0)
          ret++;
        else
          break;
       }
     }
    return ret;
   }


  /**
   * Removes the specified {@code Account} from this {@code Ledger}.
   *
   * @param account the account to remove.
   */
  void removeAccount(Account account)
   {
    accounts.remove(account);
    AccountNode node = AccountNode.map.get(account.getID());
    dataModel.removeNodeFromParent(node);
    AccountNode.map.remove(account.getID());
   }


  /**
   * Removes the specified {@link Account Account} from this {@code Ledger}.
   *
   * @param id the ID of the account to remove.
   */
  void removeAccount(int id)
   {
    for(Account a : accounts)
      if(a.getID() == id)
       {
        removeAccount(a);
        break;
       }
   }

  //
  // End Account
  //

  public TreeModel getDataModel()
   { return dataModel; }
 }