/**
    File        : Account.cpp
    Description : Encapsulates a single account
    Author      : Raymond Marsh
    Created     : 08/01/2009
    Copyright 2009, 2010 Raymond Marsh

    This program is distributed under the terms of the GNU General Public License.
    To view a copy of the license see the file 'copying.txt'

    This file is part of the Ace Personal Finance Assistant Suite.

    Ace Personal Finance Assistant is free software: you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free Software Foundation, either
    version 3 of the License, or (at your option) any later version.

    Ace Personal Finance Assistant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
    without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along with Ace Personal Finance Assistant.
    If not, see <http://www.gnu.org/licenses/>.
**/

#include "Account.h"

bool SortTransactionsByDate(const STransaction& A, const STransaction& B)
{
    bool result = false;
    struct SDate { long month; long day; long year; } a, b;

    wxStringTokenizer ST(A.Date, _("/"), wxTOKEN_RET_EMPTY);
    (ST.GetNextToken() ).ToLong(&a.month);
    (ST.GetNextToken() ).ToLong(&a.day);
    (ST.GetNextToken() ).ToLong(&a.year);
    ST.SetString(B.Date, _("/"), wxTOKEN_RET_EMPTY);
    (ST.GetNextToken() ).ToLong(&b.month);
    (ST.GetNextToken() ).ToLong(&b.day);
    (ST.GetNextToken() ).ToLong(&b.year);

    if (a.year == b.year)
    {
        if (a.month == b.month)
            result = a.day < b.day;
        else
            result = a.month < b.month;
    }
    else
        result = a.year < b.year;
    return result;
}


///** class CAccount ********************************************************///


CAccount::CAccount(wxWindow* pParent, CAccountManagerBase* pAM, IPluginHost* pHost, SAccountInfo& AccountInfo)
: wxSplitterWindow( (wxWindow*)pParent, idAccountWindow),
  m_pParent(pParent), m_pAM(pAM), m_pHost(pHost), m_AccountInfo(AccountInfo),
  m_LastTransactionNumber(0), m_pTransactionRenderer(NULL), m_LastCheckNum(0),
  m_CurrentTransaction(m_TransactionList.begin() ), m_IsBalancing(false), m_IsSearching(false),
  m_pControlPanel(NULL), m_pBalancePanel(NULL), m_pSearchPanel(NULL), m_PageNum(-1)
{
    // assign a page number
    m_PageNum = m_pAM->GetNewPageNum();

    // get the transaction viewer from the list of loaded plugins
    VerifyTransactionRenderer();

    // set the filename and load transactions
#ifndef _DEBUG
    m_Filename = wxStandardPaths::Get().GetUserConfigDir();
    m_Filename += wxString::Format(_("\\AcePFA\\%s.adf"), AccountInfo.AccountName);
#else
    m_Filename = wxString::Format(_("Data\\%s.adf"), AccountInfo.AccountName);
#endif

    // *** *** set up the top panel *** ***
    m_pTopPanel = new wxSplitterWindow(this, idTopPanel);

    // *** set up the control panel ***
    m_pControlPanel = new wxPanel(m_pTopPanel, idControlPanel);

    // date box
    wxStaticBoxSizer *pDateSizer = new wxStaticBoxSizer(wxVERTICAL,
                                                        m_pControlPanel,
                                                        _("Today Is") );
    wxDateTime TimeNow = wxDateTime::Now();
    wxString TimeStr = TimeNow.Format(_("%A\n%B\n%d, %Y"),
                                      wxDateTime::Local).c_str();
    pDateSizer->Add(new wxStaticText(m_pControlPanel, wxID_ANY, TimeStr),
                    1,
                    wxALIGN_CENTER | wxALL | wxEXPAND,
                    10);

    // transactions box
    wxStaticBoxSizer *pTransactionSizer = new wxStaticBoxSizer(wxVERTICAL,
                                                               m_pControlPanel,
                                                               _("Transactions") );
    wxButton* pNewTransactionBtn = new wxButton(m_pControlPanel, idNewTransactionBtn, _("New Transaction") );
    wxButton* pSearchTransactionBtn = new wxButton(m_pControlPanel, idSearchTransactionBtn, _("Search Transactions") );
    wxButton* pPrintTransactionBtn = new wxButton(m_pControlPanel, idPrintTransactionBtn, _("Print Transactions") );
    pTransactionSizer->Add(pNewTransactionBtn, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pTransactionSizer->Add(pSearchTransactionBtn, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pTransactionSizer->Add(pPrintTransactionBtn, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pTransactionSizer->AddStretchSpacer(2);

    // account box
    wxStaticBoxSizer *pAccountSizer = new wxStaticBoxSizer(wxVERTICAL,
                                                           m_pControlPanel,
                                                           _("Accounts") );
    wxButton* pBalanceAccountBtn = new wxButton(m_pControlPanel, idBalanceAccountBtn, _("Balance Account") );
    wxButton* pAccountDetailsBtn = new wxButton(m_pControlPanel, idAccountDetailsBtn, _("Account Details") );
    pAccountSizer->Add(pBalanceAccountBtn, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pAccountSizer->Add(pAccountDetailsBtn, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pAccountSizer->AddStretchSpacer(3);

    // account balance box
    wxStaticBoxSizer *pBalanceSizer = new wxStaticBoxSizer(wxVERTICAL,
                                                           m_pControlPanel,
                                                           _("Balance") );
    pBalanceSizer->Add(m_pBalanceCtrl = new wxTextCtrl(m_pControlPanel,
                                    idBalanceCtrl,
                                    _(""),
                                    wxDefaultPosition,
                                    wxDefaultSize,
                                    wxTE_READONLY | wxTE_RIGHT),
                       1, wxALIGN_CENTER | wxALL, 4);
    pBalanceSizer->AddStretchSpacer(4);

    // info box
    wxStaticBoxSizer *pInfoSizer = new wxStaticBoxSizer(wxVERTICAL,
                                                        m_pControlPanel,
                                                        _("") );
    wxStaticText* pText1 = new wxStaticText(m_pControlPanel, wxID_ANY, _("Left click a transaction to select it") );
    wxStaticText* pText2 = new wxStaticText(m_pControlPanel, wxID_ANY, _("Double click a transaction to view it") );
    wxStaticText* pText3 = new wxStaticText(m_pControlPanel, wxID_ANY, _("Right click a transaction for more options") );
    m_pHideReconciledCtrl = new wxCheckBox(m_pControlPanel, idHideReconciledCtrl, _("Hide Reconciled Transactions") );
    m_pHideReconciledCtrl->SetValue(ace::g_pOptions->GLOBAL.HideReconciledTransactionsOnStart);
    pInfoSizer->Add(pText1, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pInfoSizer->Add(pText2, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pInfoSizer->Add(pText3, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);
    pInfoSizer->AddStretchSpacer(1);
    pInfoSizer->Add(m_pHideReconciledCtrl, 1, wxALIGN_CENTER | wxALL | wxEXPAND, 4);

    // set up the sizer
    wxBoxSizer *pHSizer = new wxBoxSizer(wxHORIZONTAL);
    pHSizer->Add(pDateSizer, 9, wxALIGN_CENTER | wxALL | wxEXPAND, 10);
    pHSizer->Add(pTransactionSizer, 12, wxALIGN_CENTER | wxALL | wxEXPAND, 10);
    pHSizer->Add(pAccountSizer, 12, wxALIGN_CENTER | wxALL | wxEXPAND, 10);
    pHSizer->Add(pBalanceSizer, 12, wxALIGN_CENTER | wxALL | wxEXPAND, 10);
    pHSizer->Add(pInfoSizer, 27, wxALIGN_CENTER | wxALL | wxEXPAND, 10);
    m_pControlPanel->SetSizer(pHSizer);

    // *** *** set up the bottom panel *** ***
    // we need to set up a panel to place the splitter window on so that the
    // bottom window will not take up the entire bottom part of the screen
    // we want the bottom part of the screen to be set in some
    wxPanel* pPanel = new wxPanel(this, wxID_ANY);

    m_pBottomPanel = new wxSplitterWindow(pPanel, idBottomPanel);

    wxBoxSizer* pSizer = new wxBoxSizer(wxVERTICAL);
    pSizer->Add(m_pBottomPanel, 1, wxALL | wxEXPAND, 20);
    pPanel->SetSizer(pSizer);

    // *** set up the header window ***
    m_pHeader = new CHeader(m_pBottomPanel, idHeader);

    // *** set up the transaction list window ***
    m_pTransactionList = new CTransactionList(m_pBottomPanel, idTransactionList);

    // *** *** split the main window *** ***
    this->SplitHorizontally(m_pTopPanel, pPanel, 200);

    // *** initialize the top panel and split the bottom panel ***
    m_pTopPanel->Initialize(m_pControlPanel);
    m_pBottomPanel->SplitHorizontally(m_pHeader, m_pTransactionList, 30);

    // attach handlers
    // event handlers
    Connect(idNewTransactionBtn, wxEVT_COMMAND_BUTTON_CLICKED,
            wxCommandEventHandler(CAccount::OnNewTransaction) );
    Connect(idSearchTransactionBtn, wxEVT_COMMAND_BUTTON_CLICKED,
            wxCommandEventHandler(CAccount::OnSearchTransactions) );
    Connect(idPrintTransactionBtn, wxEVT_COMMAND_BUTTON_CLICKED,
            wxCommandEventHandler(CAccount::OnPrintTransactions) );
    Connect(idHideReconciledCtrl, wxEVT_COMMAND_CHECKBOX_CLICKED,
            wxCommandEventHandler(CAccount::OnToggleHideReconciled) );
    Connect(idBalanceAccountBtn, wxEVT_COMMAND_BUTTON_CLICKED,
            wxCommandEventHandler(CAccount::OnBalanceAccount) );
    Connect(idAccountDetailsBtn, wxEVT_COMMAND_BUTTON_CLICKED,
            wxCommandEventHandler(CAccount::OnDisplayAccountDetails) );
}

CAccount::~CAccount()
{
}

void CAccount::VerifyTransactionRenderer()
{
    wxCHECK_RET(!m_AccountInfo.AccountType.IsEmpty(), _("Transaction renderer name is empty") );
    m_pHost->GetViewer(m_AccountInfo.AccountType, &m_pTransactionRenderer);
    wxCHECK_RET(m_pTransactionRenderer != NULL, _("No transaction renderer availiable for this account type!") );
}

bool CAccount::LoadTransactionsFromDisk()
{
    CInFile iFile(m_Filename);
    if (iFile.IsOpen() )
    {
        STransaction Transaction;
        while (iFile.ReadObject(Transaction) )
        {
            if (Transaction.TransactionNumber > m_LastTransactionNumber)
                m_LastTransactionNumber = Transaction.TransactionNumber;
            if (Transaction.TransferNumber > m_pAM->GetLastTransferNumber() )
                m_pAM->SetLastTransferNumber(Transaction.TransferNumber);
            if (Transaction.CheckNumber > m_LastCheckNum)
                m_LastCheckNum = Transaction.CheckNumber;

            m_TransactionList.push_back(Transaction);
            ace::g_pData->AddNewPayee(Transaction.Payee);
        }
        UpdateBalances(true);
    }

    return true;
}


bool CAccount::SaveTransactionsToDisk()
{
    // do not save anything if there are no transactions
    if (m_TransactionList.empty() )
        return true;

    COutFile oFile(m_Filename);
    if (!oFile.IsOpen() )
    {
        aceLOGERROR("Cannot open output file", false);
        return false;
    }

    std::vector<STransaction>::iterator iter;
    for (iter = m_TransactionList.begin(); iter != m_TransactionList.end(); ++iter)
    {
        oFile.WriteObject(*iter);
    }
    return true;
}

bool CAccount::SaveTransaction(STransaction Transaction, const bool Transfering, const bool UpdateDisplay)
{
    // is it an account transfer?
    if (Transaction.Type == TT_TRANSFER && Transfering)
    {
        Transaction.TransferNumber = m_pAM->GetNextTransferNumber();
        m_pAM->TransferFunds(Transaction);
    }

    // is it a check?
    if (Transaction.Type == TT_CHECK)
    {
        m_LastCheckNum = Transaction.CheckNumber;
    }

    // get a new transaction number
    Transaction.TransactionNumber = GetNextTransactionNumber();

    // record the transaction
    m_TransactionList.push_back(Transaction);
    sort(m_TransactionList.begin(), m_TransactionList.end(), SortTransactionsByDate);
    UpdateBalances(UpdateDisplay);
    if (UpdateDisplay)
    {
        m_pTransactionList->SetScrollPosition();
        m_pTransactionList->Refresh();
    }
    wxString Label;
    GetAccountLabel(Label);
    m_pAM->SetPageLabel(m_PageNum, Label);
    return true;
}

STransaction* CAccount::GetTransaction(const ULONG TransactionNumber)
{
    if (m_TransactionList.empty() )
    {
        return NULL;
    }

    std::vector<STransaction>::iterator iter;
    for (iter = m_TransactionList.begin(); iter != m_TransactionList.end(); ++iter)
    {
        if (TransactionNumber == iter->TransactionNumber)
        {
            return &(*iter);
        }
    }
    return NULL;
}

bool CAccount::HasMoreTransactions()
{
    if (m_CurrentTransaction != m_TransactionList.end() && !m_TransactionList.empty() )
        return true;
    return false;
}

const STransaction& CAccount::GetNextTransaction()
{
    if (m_CurrentTransaction != m_TransactionList.end() )
        m_CurrentTransaction++;
    return *(m_CurrentTransaction - 1);
}

void CAccount::UpdateBalanceDisplay(const double Balance)
{
    m_pBalanceCtrl->ChangeValue(wxString::Format(_("%1.2f"), Balance) );
}

void CAccount::UpdateBalances(const bool UpdateDisplay)
{
    double total = 0.00f;
    std::vector<STransaction>::iterator iter1, iter2;
    iter1 = m_TransactionList.begin();
    total = iter1->Amount;
    iter1->Balance = total;
    ++iter1;
    for (iter2 = iter1; iter2 != m_TransactionList.end(); ++iter2)
    {
        (iter2->IsDeposit) ? total += iter2->Amount : total -= iter2->Amount;
        iter2->Balance = total;
    }
    m_AccountInfo.Balance = total;
    if (UpdateDisplay)
        UpdateBalanceDisplay(total);
}

void CAccount::OnNewTransaction(wxCommandEvent &event)
{
    // set up the transaction
    STransaction Transaction =
    {
        0,
        0,
        m_AccountInfo.AccountName,
        _(""),
        NULL,
        _(""),
        TT_NONE,
        0,
        _(""),
        0.00f,
        _(""),
        _(""),
        _(""),
        TS_OPEN,
        false,
        0.00f
    };

    // get the transaction
    CNewTransactionDlg* pNewTransactionDlg = new CNewTransactionDlg(this, Transaction, true, true);
    const ULONG Ret = pNewTransactionDlg->ShowModal();
    if (Ret != wxID_OK)
    {
        return;
    }

    // save the transaction
    SaveTransaction(Transaction);
}

void CAccount::OnToggleHideReconciled(wxCommandEvent &event)
{
    m_pTransactionList->ToggleHideReconciled();
    m_pTransactionList->Refresh();
}

void CAccount::OnBalanceAccount(wxCommandEvent &event)
{
    if (m_pBalancePanel)
    {
        m_pBalancePanel->Destroy();
        m_pBalancePanel = NULL;
    }
    m_IsBalancing = true;
    wxASSERT(m_pBalancePanel == NULL);

    // disable the balance and search buttons and disable the hide reconciled button and hide them
    DisableButtons();
    if (!m_pTransactionList->IsReconciledHidden() )
    {
        m_pHideReconciledCtrl->SetValue(true);
        m_pTransactionList->ToggleHideReconciled();
    }
    m_pHideReconciledCtrl->Disable();

    SBalanceInfo BalanceInfo;
    CBalanceDlg BalanceDlg(this, BalanceInfo);
    if (BalanceDlg.ShowModal() != wxOK)
    {
        m_IsBalancing = false;
        EnableButtons();
        m_pHideReconciledCtrl->Enable();
        return;
    }
    m_pBalancePanel = new CBalancePanel(m_pTopPanel, this, BalanceInfo);
    m_pTopPanel->SplitVertically(m_pControlPanel, m_pBalancePanel, -400);
}

void CAccount::OnSearchTransactions(wxCommandEvent &event)
{
    if (m_pSearchPanel)
    {
        m_pSearchPanel->Destroy();
        m_pSearchPanel = NULL;
    }
    m_IsSearching = true;
    wxASSERT(m_pSearchPanel == NULL);

    // disable the balance and search buttons
    DisableButtons();


    m_pSearchPanel = new wxPanel(m_pTopPanel, wxID_ANY);
    m_pTopPanel->SplitVertically(m_pControlPanel, m_pSearchPanel, -400);
// ADD (USER#1#): Implement Search Transactions
}

void CAccount::OnPrintTransactions(wxCommandEvent &event)
{
// ADD (USER#1#): Implement Print Transactions

}

void CAccount::OnDisplayAccountDetails(wxCommandEvent &event)
{
    wxString str;

    str = wxString::Format (_("Account Name      : %s\n"), m_AccountInfo.AccountName);
    str += wxString::Format(_("Account Type      : %s\n"), m_AccountInfo.AccountType);
    str += wxString::Format(_("Account Number    : %s\n"), m_AccountInfo.AccountNumber);
    str += wxString::Format(_("Bank Name         : %s\n"), m_AccountInfo.BankName);
    str += wxString::Format(_("Bank Phone Number : %s\n"), m_AccountInfo.BankPhoneNumber);
    str += wxString::Format(_("Date Opened       : %s\n"), m_AccountInfo.DateOpened);
    str += wxString::Format(_("Last Balanced On  : %s\n"), m_AccountInfo.LastStatementDate);

    aceMSGBOX(this, ace::MB_INFO, str, NULL);
}

void CAccount::DeleteTransaction(const ULONG TransactionNumber, const bool UseTransferNumber)
{
    if (UseTransferNumber)
    {
        std::vector<STransaction>::iterator iter;
        for (iter = m_TransactionList.begin(); iter != m_TransactionList.end(); ++iter)
        {
            if (iter->TransferNumber == TransactionNumber)
            {
                m_TransactionList.erase(iter);
                UpdateBalances();
                return;
            }
        }
    }
    else
    {
        std::vector<STransaction>::iterator iter;
        for (iter = m_TransactionList.begin(); iter != m_TransactionList.end(); ++iter)
        {
            if (iter->TransactionNumber == TransactionNumber)
            {
                // if the transaction is a transfer, delete both transactions
                if (iter->TransferNumber != 0)
                {
                    CAccountBase* pAccount = m_pAM->GetAccountByName(iter->TransferAccount);
                    pAccount->DeleteTransaction(iter->TransferNumber, true);
                }
                m_TransactionList.erase(iter);
                UpdateBalances();
                return;
            }
        }
    }
}

void CAccount::UpdateBalancingStatus(const double Amount, const bool IsDeposit, const bool IsCleared, const bool IsEdit)
{
    m_pBalancePanel->UpdateBalancingStatus(Amount, IsDeposit, IsCleared, IsEdit);
}

void CAccount::StopBalancing()
{
    m_pTopPanel->Unsplit();
    m_IsBalancing = false;
    EnableButtons();
    m_pHideReconciledCtrl->Enable();
}

void CAccount::BalancingAccountFinished(wxString DateBalanced)
{
    m_AccountInfo.LastStatementBalance = m_AccountInfo.Balance;
    m_AccountInfo.LastStatementDate = DateBalanced;
    std::vector<STransaction>::iterator iter;
    for (iter = m_TransactionList.begin(); iter != m_TransactionList.end(); ++iter)
    {
        if (iter->Status == TS_CLEARED)
        {
            iter->Status = TS_RECONCILED;
        }
    }
    m_pAM->SaveAccountsToDisk();
    SaveTransactionsToDisk();
    m_pTopPanel->Unsplit();
    m_IsBalancing = false;
    EnableButtons();
    m_pHideReconciledCtrl->Enable();
}

void CAccount::EnableButtons()
{
    (FindWindowById(idBalanceAccountBtn, this) )->Enable();
    (FindWindowById(idSearchTransactionBtn, this) )->Enable();
}

void CAccount::DisableButtons()
{
    (FindWindowById(idBalanceAccountBtn, this) )->Disable();
    (FindWindowById(idSearchTransactionBtn, this) )->Disable();
}

bool CAccount::GetAccountLabel(wxString& Label)
{
    Label = m_AccountInfo.AccountName;
    Label += _(" (...");
    Label += m_AccountInfo.AccountNumber.Right(4);
    Label += _(") ");
    Label += wxString::Format(_("%1.2f"),m_AccountInfo.Balance);
    return true;
}




///** class : CHeader *******************************************************///



CHeader::CHeader(wxWindow* pParent, ULONG ID)
: wxPanel(pParent, ID), m_pParent(pParent), m_pAccount(NULL)
{
    wxWindow* win = GetGrandParent();
    m_pAccount = (CAccount*)win->GetParent();
    SetOwnBackgroundColour(wxColor(240, 240, 240) );

    Connect(wxEVT_PAINT, wxPaintEventHandler(CHeader::OnPaint), NULL, this);
}

CHeader::~CHeader()
{
}

void CHeader::OnPaint(wxPaintEvent& paint_event)
{
    wxPaintDC DC(this);

    const int offset = 0;

    // draw the column lables
    std::vector<std::pair<wxString, int> > Header;
    std::vector<std::pair<wxString, int> >::iterator iter;
    m_pAccount->GetTransactionRenderer()->GetHeaderToDisplay(Header);
    for (iter = Header.begin(); iter != Header.end(); ++iter)
    {
        DC.DrawText( (*iter).first, ( (*iter).second + offset), 10);
    }
}


///** class : CTransactionList **********************************************///


CTransactionList::CTransactionList(wxWindow* pParent, ULONG ID)
: wxScrolledWindow(pParent, ID, wxDefaultPosition, wxDefaultSize), m_pParent(pParent),
  m_yCount(0), m_pCurrentTransaction(NULL), m_pAccount(NULL)
{
    m_HideReconciled = ace::g_pOptions->GLOBAL.HideReconciledTransactionsOnStart;

    wxWindow* win = GetGrandParent();
    m_pAccount = (CAccount*)win->GetParent();
    SetOwnBackgroundColour(wxColour(255, 255, 255) );

    // set line sizes
    wxClientDC DC(this);

    wxCoord w, h, d, e;
    DC.GetTextExtent(_("Yy"), &w, &h, &d, &e);
    m_LineHeight = (int)(h + d + e + 6);

    wxSize ClientSize = GetClientSize();
    m_LinesPerScreen = ClientSize.GetHeight() / m_LineHeight;

    // set the scroll bar position
    SetScrollPosition();

    // attach handlers
    // event handlers
    Connect(wxEVT_CHAR,
            wxKeyEventHandler(CTransactionList::OnProcessKey) );
    Connect(wxEVT_LEFT_DCLICK,
            wxMouseEventHandler(CTransactionList::OnTransactionLeftDoubleClick) );
    Connect(wxEVT_LEFT_UP,
            wxMouseEventHandler(CTransactionList::OnTransactionLeftClick) );
    Connect(wxEVT_RIGHT_UP,
            wxMouseEventHandler(CTransactionList::OnTransactionRightClick) );
    Connect(wxEVT_MOUSEWHEEL,
            wxMouseEventHandler(CTransactionList::OnMouseWheelMovement) );
    // popup command menu from grid
    Connect(idViewImages, wxEVT_COMMAND_MENU_SELECTED,
            wxCommandEventHandler(CTransactionList::OnViewImages) );
    Connect(idEditTransaction, wxEVT_COMMAND_MENU_SELECTED,
            wxCommandEventHandler(CTransactionList::OnEditTransaction) );
    Connect(idDeleteTransaction, wxEVT_COMMAND_MENU_SELECTED,
            wxCommandEventHandler(CTransactionList::OnDeleteTransaction) );
    Connect(idChangeTransactionStatus, wxEVT_COMMAND_MENU_SELECTED,
            wxCommandEventHandler(CTransactionList::OnChangeTransactionStatus) );
}

CTransactionList::~CTransactionList()
{
}

void CTransactionList::ToggleHideReconciled()
{
    m_HideReconciled = !m_HideReconciled;

    wxClientDC DC(this);
    OnDraw(DC);
    const int NumLines = GetNumLinesToDisplay();
    SetScrollbars(0, m_LineHeight, 0, NumLines, 0, NumLines - m_LinesPerScreen);
}

void CTransactionList::SetScrollPosition(int StartPosition)
{
    wxClientDC DC(this);
    wxASSERT(DC.IsOk() );
    OnDraw(DC);
    const int NumLines = GetNumLinesToDisplay();
    if (StartPosition == 0)
        StartPosition = NumLines - m_LinesPerScreen;
    SetScrollbars(0, m_LineHeight, 0, NumLines, 0, StartPosition);
}

int CTransactionList::GetNumLinesToDisplay()
{
    int NumYUnits = 0;
    if (m_HideReconciled)
    {
        m_pAccount->ResetTransactionCounter();
        while (m_pAccount->HasMoreTransactions() )
        {
            STransaction Transaction = m_pAccount->GetNextTransaction();
            if (Transaction.Status != TS_RECONCILED)
            {
                NumYUnits++;
            }
        }
        NumYUnits++;
    }
    else
    {
        NumYUnits = m_pAccount->GetNumTransactions() + 1;
    }
    return NumYUnits;
}

void CTransactionList::OnDraw(wxDC &DC)
{
    wxCoord xCoord = 0, yCoord = 0;
    int yPos = 0;
    int LineYPos = 0;
    m_yCount = 0;
    wxString str;
    m_LineMap.clear();
    wxColor ForegroundColor(0, 0, 0);
    double TotalAmount = 0.00f;

    // get starting balance
    if (m_HideReconciled)
    {
        TotalAmount = m_pAccount->GetLastStatementBalance();
    }

    DC.SetTextForeground(wxColor(0, 0, 0) );
    DC.SetFont(wxFont(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL) );

    DC.GetSize(&xCoord, &yCoord);

    m_pAccount->ResetTransactionCounter();
    while (m_pAccount->HasMoreTransactions() )
    {
        const STransaction Transaction = m_pAccount->GetNextTransaction();
        if ( (!m_HideReconciled) || (m_HideReconciled && Transaction.Status != TS_RECONCILED) )
        {
            // draw each line of text
            yPos = (m_yCount * m_LineHeight);
            if (m_pCurrentTransaction)
            {
                if (m_pCurrentTransaction->TransactionNumber == Transaction.TransactionNumber)
                {
                    ForegroundColor = wxColor(0, 0, 200);
                }
                else
                {
                    ForegroundColor = wxColor(0, 0, 0);
                }
            }
            else
            {
                ForegroundColor = wxColor(0, 0, 0);
            }
            DC.SetTextForeground(ForegroundColor);

            wxArrayString ArrItems;
            std::vector<int> Pos;
            int n = 0;
            const int NumItems = m_pAccount->GetTransactionRenderer()->GetItemsToDisplay(Transaction, ArrItems, Pos);
            std::vector<int>::iterator iter;
            for (iter = Pos.begin(); iter != Pos.end(); ++iter)
            {
                if ( (n + 1) != NumItems)
                {
                    DC.DrawText(ArrItems[n], *iter, yPos);
                }
                else
                {
                    double Val = 0.00f;
                    ArrItems[n].ToDouble(&Val);
                    if (Val < 0.00f)
                    {
                        DC.SetTextForeground(wxColor(250, 0, 0) );
                        DC.DrawText(ArrItems[n], *iter, yPos);
                        DC.SetTextForeground(ForegroundColor);
                    }
                    else
                    {
                        DC.DrawText(ArrItems[n], *iter, yPos);
                    }
                }
                n++;
            }

            // update the running total for the balance display
            TotalAmount = Transaction.Balance;

            // draw a seperating line
            LineYPos = (yPos + m_LineHeight) - 3;
            DC.SetPen(wxPen(wxColor(200, 200, 200) ) );
            DC.DrawLine(0, LineYPos, xCoord, LineYPos);

            m_yCount++;

            // associate the line number with the transaction number
            m_LineMap.insert(std::make_pair(m_yCount, Transaction.TransactionNumber) );
        }
    }
    // update the balance display
    m_pAccount->UpdateBalanceDisplay(TotalAmount);
}

bool CTransactionList::HitTest(wxMouseEvent& event, ULONG& RowNumber, STransaction** ppTransaction)
{
    RowNumber = 0;
    *ppTransaction = NULL;
    int xStartPos = 0, yStartPos = 0;
    GetViewStart(&xStartPos, &yStartPos);

    const int MaxPosition = m_yCount * m_LineHeight;

    wxPoint PointClickedOn = event.GetPosition();
    if (PointClickedOn.y > MaxPosition)
    {
        return false;
    }
    RowNumber = ( (PointClickedOn.y / m_LineHeight)  + yStartPos) + 1;
    const std::map<ULONG, ULONG>::iterator iter = m_LineMap.find(RowNumber);
    if (iter == m_LineMap.end() )
    {
        wxFAIL_MSG(_("Line Not Found!") );
        return false;
    }

    *ppTransaction = m_pAccount->GetTransaction(iter->second);
    if (!(*ppTransaction) )
    {
        wxFAIL_MSG(_("Transaction Not Found!") );
        return false;
    }
    return true;
}

void CTransactionList::OnTransactionLeftDoubleClick(wxMouseEvent& event)
{
    ULONG RowNumber = 0;
    STransaction* pTransaction;
    if (!HitTest(event, RowNumber, &pTransaction) )
    {
        return;
    }
    m_pCurrentTransaction = pTransaction;
    wxString str = _("View Transaction");
    m_pAccount->GetTransactionRenderer()->GetTransactionViewer(str, *m_pCurrentTransaction, false, false);
}

void CTransactionList::OnTransactionLeftClick(wxMouseEvent& event)
{
    ULONG RowNumber = 0;
    STransaction* pTransaction = NULL;
    if (!HitTest(event, RowNumber, &pTransaction) )
    {
        return;
    }
    if (pTransaction == NULL || pTransaction == m_pCurrentTransaction)
    {
        m_pCurrentTransaction = NULL;
    }
    else
    {
        m_pCurrentTransaction = pTransaction;
    }
    Refresh();
}

void CTransactionList::OnTransactionRightClick(wxMouseEvent& event)
{
    ULONG RowNumber = 0;
    STransaction* pTransaction;
    if (!HitTest(event, RowNumber, &pTransaction) )
    {
        return;
    }
    m_pCurrentTransaction = pTransaction;
    Refresh();

    // create the command popup window
    wxMenu *pCommandMenu = new wxMenu(wxString::Format(_("Transaction : %s"), m_pCurrentTransaction->Payee) );
    pCommandMenu->Append(idChangeTransactionStatus, _("Clear / Unclear <ctrl-c>") );
    pCommandMenu->Append(idEditTransaction, _("Edit Transaction Details <ctrl-e>") );
    pCommandMenu->Append(idViewImages, _("Edit Images <ctrl-i>") );
    pCommandMenu->Append(idDeleteTransaction, _("Delete This Transaction <delete>") );

    PopupMenu(pCommandMenu);
}

void CTransactionList::OnMouseWheelMovement(wxMouseEvent &event)
{
    (event.GetWheelRotation() < 0) ? LineDown() : LineUp();
}

void CTransactionList::OnViewImages(wxCommandEvent& event)
{
// ADD (USER#1#): Add image handler
    aceMSGBOX(this, ace::MB_INFO, _("Sorry, this feature has not\nbeen implemented yet"), NULL);
}

void CTransactionList::OnEditTransaction(wxCommandEvent& event)
{
    const double OldAmount = m_pCurrentTransaction->Amount;
    wxString msg;
    if (m_pCurrentTransaction->Status == TS_RECONCILED)
    {
        aceMSGBOX(this, ace::MB_INFO, _("You cannot edit a transaction\nthat has already been Reconciled!"), NULL);
        return;
    }

    wxString str = _("Edit Transaction");
    m_pAccount->GetTransactionRenderer()->GetTransactionViewer(str, *m_pCurrentTransaction, false, true);
    m_pAccount->UpdateBalances();
    const double NewAmount = OldAmount - m_pCurrentTransaction->Amount;
    if (m_pAccount->IsBalancing() && NewAmount != 0.00f)
    {
        if (m_pCurrentTransaction->Status == TS_CLEARED)
        {
            m_pAccount->UpdateBalancingStatus(NewAmount, (!m_pCurrentTransaction->IsDeposit), true, true);
        }
    }
    Refresh();
}

void CTransactionList::OnDeleteTransaction(wxCommandEvent& event)
{
    wxString msg;
    if (m_pCurrentTransaction->Status != TS_OPEN)
    {
        aceMSGBOX(this, ace::MB_INFO, _("You cannot delete a transaction\nthat has already been cleared!"), NULL);
        return;
    }
    msg = _("You are about to delete a transaction.\nAre you sure you want to do this?");
    if (aceMSGBOX(this, ace::MB_QUESTION, msg, NULL) == wxID_YES)
    {
        m_pAccount->DeleteTransaction(m_pCurrentTransaction->TransactionNumber);
    }
    m_pCurrentTransaction = NULL;
    Refresh();
}

void CTransactionList::OnChangeTransactionStatus(wxCommandEvent& event)
{
// TODO (USER#1#): if transaction is a transfer then 'clear/unclear' transaction on "to account" as well
    switch (m_pCurrentTransaction->Status)
    {
        case TS_OPEN:
        m_pCurrentTransaction->Status = TS_CLEARED;
        if (m_pAccount->IsBalancing() )
        {
            m_pAccount->UpdateBalancingStatus(m_pCurrentTransaction->Amount, m_pCurrentTransaction->IsDeposit, true);
        }
        Refresh();
        break;

        case TS_CLEARED:
        m_pCurrentTransaction->Status = TS_OPEN;
        if (m_pAccount->IsBalancing() )
        {
            m_pAccount->UpdateBalancingStatus(m_pCurrentTransaction->Amount, m_pCurrentTransaction->IsDeposit, false);
        }
        Refresh();
        break;

        case TS_RECONCILED:
        aceMSGBOX(this, ace::MB_INFO, _("Transaction has already been reconciled!"), NULL);
        break;
    }
}

void CTransactionList::OnProcessKey(wxKeyEvent& event)
{
    if (!m_pCurrentTransaction)
    {
        return;
    }

    const int KeyCode = event.GetKeyCode();
    wxCommandEvent Event;
    std::map<ULONG, ULONG>::iterator iter;
    switch(KeyCode)
    {
    case 3:
        // ctrl-c
        OnChangeTransactionStatus(Event);
        break;

    case 5:
        // ctrl-e
        OnEditTransaction(Event);
        break;

    case 9:
        // ctrl-i
        OnViewImages(Event);
        break;

    case WXK_UP:
        // up arrow
        for (iter = m_LineMap.begin(); iter != m_LineMap.end(); ++iter)
        {
            if (iter->second == m_pCurrentTransaction->TransactionNumber)
            {
                if (iter != m_LineMap.begin() )
                {
                    iter--;
                    m_pCurrentTransaction = m_pAccount->GetTransaction(iter->second);
                    break;
                }
            }
        }
        if (iter->first < (ULONG)(GetScrollPos(wxVERTICAL) * m_LineHeight) )
            LineUp();
        break;

    case WXK_DOWN:
        // down arrow
        for (iter = m_LineMap.begin(); iter != m_LineMap.end(); ++iter)
        {
            if (iter->second == m_pCurrentTransaction->TransactionNumber)
            {
                iter++;
                if (iter != m_LineMap.end() )
                    m_pCurrentTransaction = m_pAccount->GetTransaction(iter->second);
                break;
            }
        }
        if (iter->first > (ULONG)(GetScrollPos(wxVERTICAL) * m_LineHeight) )
            LineDown();
        break;

    case WXK_DELETE:
        // delete key
        OnDeleteTransaction(Event);
        break;

    case WXK_PAGEUP:
        // page up key
        {
            PageUp();
            break;
        }

    case WXK_PAGEDOWN:
        // page down key
        {
            PageDown();
            break;
        }
    }
    Refresh();
}


///** class CNewTransactionDlg **********************************************///


CNewTransactionDlg::CNewTransactionDlg(CAccount* pParent, STransaction& Transaction, const bool IsNew, const bool CanEdit)
: wxDialog(pParent, wxID_ANY, _(""), wxDefaultPosition, wxSize(800, 300), wxCAPTION),
  m_pAccount(pParent), m_pTransaction(&Transaction), m_IsNew(IsNew), m_CanEdit(CanEdit)
{
    // set the dialog title
    (m_IsNew) ? this->SetTitle(_("New Transaction") ) : this->SetTitle(_("Transaction") );

    // get transaction types and categories
    ace::g_pData->GetAllCategories(m_CategoryChoices);
    ace::g_pData->GetAllTransactionTypes(m_TransactionTypeChoices, false);
    wxArrayString Choices = ace::g_pData->GetPayeeList();

    // set starting transaction date, check number and amount
    wxDateTime StartDate;
    if (m_IsNew)
    {
        m_Amount = _("");
        m_CheckNumber = _("");
        StartDate = wxDateTime::Today();
        m_pTransaction->Type = TT_DEBIT;
    }
    else
    {
        m_Amount = wxString::Format(_("%#1.2f"), m_pTransaction->Amount);
        m_CheckNumber = wxString::Format(_("%u"), m_pTransaction->CheckNumber);
        if (StartDate.ParseDate(m_pTransaction->Date) == NULL)
        {
            wxFAIL_MSG(_("wxDateTime::ParseDate() returned NULL") );
        }
    }

    // set up the left panel
    wxPanel* pLeftPanel = new wxPanel(this, wxID_ANY);

    m_pRadioBox = new wxRadioBox(pLeftPanel,
                                 idRadioBoxCtrl,
                                 _("Transaction Type"),
                                 wxDefaultPosition,
                                 wxDefaultSize,
                                 m_TransactionTypeChoices,
                                 0,
                                 wxRA_SPECIFY_ROWS);

    // set up the right panel
    wxPanel* pRightPanel = new wxPanel(this, wxID_ANY);
    wxGridSizer* pGSizer = new wxGridSizer(4, 10, 3);

    // date *** Manually transfer this data ***
    pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, _("Date") ),
                 1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pDateCtrl = new wxDatePickerCtrl(pRightPanel,
                                                        idDateCtrl,
                                                        StartDate,
                                                        wxDefaultPosition,
                                                        wxDefaultSize,
                                                        wxDP_DROPDOWN,
                                                        wxDefaultValidator),
                     3, wxALIGN_LEFT | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, m_pTransaction->Date) );
    }

    // category
    pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, _("Category") ),
                 1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pCategoryCtrl = new wxComboBox(pRightPanel,
                                                      idCategoryCtrl,
                                                      m_pTransaction->Category,
                                                      wxDefaultPosition,
                                                      wxDefaultSize,
                                                      m_CategoryChoices,
                                                      0,
                                                      wxGenericValidator(&(m_pTransaction->Category) ) ),
                     3, wxEXPAND | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, m_pTransaction->Category) );
    }

    // check number *** Manually transfer this data ***
    pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, _("Check #") ),
                 1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pCheckNumCtrl = new wxTextCtrl(pRightPanel,
                                                      idCheckNumCtrl,
                                                      m_CheckNumber,
                                                      wxDefaultPosition,
                                                      wxDefaultSize,
                                                      0,
                                                      wxTextValidator(wxFILTER_NUMERIC,
                                                                      &m_CheckNumber) ),
                     3, wxALIGN_LEFT | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY,
                                      wxString::Format(_("%u"), m_pTransaction->CheckNumber) ) );
    }

    // sub category
    pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, _("Sub Category") ),
                 1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pSubCategoryCtrl = new wxComboBox(pRightPanel,
                                                         idSubCategoryCtrl,
                                                         m_pTransaction->SubCategory,
                                                         wxDefaultPosition,
                                                         wxDefaultSize,
                                                         m_SubCategoryChoices,
                                                         0,
                                                         wxGenericValidator(&(m_pTransaction->SubCategory) ) ),
                     3, wxEXPAND | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, m_pTransaction->SubCategory) );
    }

    // payee
    pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, _("Payee") ),
                 1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pPayeeCtrl = new CAutoCompleteComboBox(pRightPanel,
                                                   idPayeeCtrl,
                                                   m_pTransaction->Payee,
                                                   wxDefaultPosition,
                                                   wxDefaultSize,
                                                   Choices,
                                                   /*ace::g_pData->GetPayeeList(),*/
                                                   wxCB_DROPDOWN,
                                                   wxTextValidator(wxFILTER_ASCII,
                                                                   &m_pTransaction->Payee) ),
                     3, wxEXPAND | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, m_pTransaction->Payee) );
    }

    // comments
    pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, _("Comments") ),
                 1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pCommentCtrl = new wxTextCtrl(pRightPanel,
                                                     idCommentCtrl,
                                                     m_pTransaction->Comment,
                                                     wxDefaultPosition,
                                                     wxDefaultSize,
                                                     0,
                                                     wxTextValidator(wxFILTER_ASCII,
                                                                     &(m_pTransaction->Comment) ) ),
                     3, wxEXPAND | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, m_pTransaction->Comment) );
    }

    // amount *** Manually transfer this data ***
    pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, _("Amount") ),
                 1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pAmountCtrl = new wxTextCtrl(pRightPanel,
                                                    idAmountCtrl,
                                                    m_Amount,
                                                    wxDefaultPosition,
                                                    wxDefaultSize,
                                                    0,
                                                    wxTextValidator(wxFILTER_NUMERIC,
                                                                    &m_Amount) ),
                     3, wxALIGN_LEFT | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY,
                                      wxString::Format(_("$%#1.2f"), m_pTransaction->Amount) ) );
    }

    pGSizer->AddStretchSpacer(1);
    pGSizer->AddStretchSpacer(3);

    // transfer to / from account
    wxString s;
    (m_pTransaction->IsDeposit) ? s = _("Transfer From") : s = _("Transfer To");
    pGSizer->Add(m_pTransferAccountText = new wxStaticText(pRightPanel, wxID_ANY, s),
                                                           1, wxALIGN_RIGHT | wxLEFT | wxRIGHT, 10);
    if (m_CanEdit)
    {
        pGSizer->Add(m_pTransferAccountCtrl = new wxComboBox(pRightPanel,
                                                             idTransferAccountCtrl,
                                                             m_pTransaction->TransferAccount,
                                                             wxDefaultPosition,
                                                             wxDefaultSize,
                                                             m_pAccount->GetAllAccountNames(),
                                                             0,
                                                             wxTextValidator(wxFILTER_ASCII,
                                                                             &m_pTransaction->TransferAccount) ),
                     3, wxALIGN_LEFT | wxLEFT | wxRIGHT, 10);
    }
    else
    {
        pGSizer->Add(new wxStaticText(pRightPanel, wxID_ANY, m_pTransaction->TransferAccount) );
    }

    pRightPanel->SetSizer(pGSizer);

    // set up the buttons
    wxStdDialogButtonSizer* pBtnSizer = new wxStdDialogButtonSizer();
    if (m_CanEdit)
    {
        pBtnSizer->AddButton(new wxButton(this, wxID_OK, _("Save") ) );
    }
    pBtnSizer->AddButton(new wxButton(this, wxID_CANCEL, _("Cancel") ) );
    pBtnSizer->Realize();

    // put it all together
    wxBoxSizer* pHSizer = new wxBoxSizer(wxHORIZONTAL);
    pHSizer->Add(pLeftPanel, 0, wxALL, 10);
    pHSizer->Add(pRightPanel, 0, wxALL, 10);

    wxBoxSizer* pMSizer = new wxBoxSizer(wxVERTICAL);
    pMSizer->Add(pHSizer, 3, wxEXPAND, 5);
    pMSizer->Add(new wxStaticLine(this), 0, wxALIGN_CENTER | wxALL | wxEXPAND, 15);
    pMSizer->Add(pBtnSizer, 1);
    this->SetSizer(pMSizer);

    // set the starting transaction type
    if (m_IsNew)
    {
        m_LastRadioBoxSelection = _("Debit");
        m_pRadioBox->SetStringSelection(m_LastRadioBoxSelection);
    }
    else
    {
        if (m_pTransaction->Type == TT_START)
            m_pTransaction->Type = TT_DEPOSIT;
        m_pRadioBox->SetStringSelection(ace::g_pData->GetTransactionTypeString(m_pTransaction->Type) );
    }
    ShowControls(m_pTransaction->Type);
    if (!m_CanEdit) m_pRadioBox->Enable(false);

    // attach handlers
    Connect(wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED,
            wxCommandEventHandler(CNewTransactionDlg::OnSave) );
    Connect(idCategoryCtrl, wxEVT_COMMAND_COMBOBOX_SELECTED,
            wxCommandEventHandler(CNewTransactionDlg::OnCategorySelection) );
    Connect(idRadioBoxCtrl, wxEVT_COMMAND_RADIOBOX_SELECTED ,
            wxCommandEventHandler(CNewTransactionDlg::OnRadioButtonSelect) );
}

CNewTransactionDlg::~CNewTransactionDlg()
{

}

void CNewTransactionDlg::ShowControls(const ULONG type)
{
    m_pTransaction->Type = type;
    switch (type)
    {
        case TT_START:
        m_pCheckNumCtrl->ChangeValue(_("") );
        m_pCheckNumCtrl->Show(false);
        m_pPayeeCtrl->ChangeValue(_("Starting Balance") );
        m_pPayeeCtrl->Show(false);
        m_pCategoryCtrl->SetValue(_("") );
        m_pCategoryCtrl->Show(true);
        m_pSubCategoryCtrl->SetValue(_("") );
        m_pSubCategoryCtrl->Show(true);
        m_pTransferAccountText->Show(false);
        m_pTransferAccountCtrl->Show(false);
        break;

        case TT_ATM:
        m_pCheckNumCtrl->ChangeValue(_("") );
        m_pCheckNumCtrl->Show(false);
        m_pPayeeCtrl->ChangeValue(_("ATM Withdrawal") );
        m_pPayeeCtrl->Show(false);
        m_pCategoryCtrl->SetValue(_("ATM Withdrawal") );
        m_pCategoryCtrl->Show(true);
        m_pSubCategoryCtrl->SetValue(_("") );
        m_pSubCategoryCtrl->Show(false);
        m_pTransferAccountCtrl->SetValue(_("") );
        m_pTransferAccountText->Show(false);
        m_pTransferAccountCtrl->Show(false);
        break;

        case TT_DEPOSIT:
        m_pCheckNumCtrl->ChangeValue(_("") );
        m_pCheckNumCtrl->Show(false);
        m_pPayeeCtrl->ChangeValue(_("") );
        m_pPayeeCtrl->Show(true);
        m_pCategoryCtrl->SetValue(_("") );
        m_pCategoryCtrl->Show(true);
        m_pSubCategoryCtrl->SetValue(_("") );
        m_pSubCategoryCtrl->Show(true);
        m_pTransferAccountCtrl->SetValue(_("") );
        m_pTransferAccountText->Show(false);
        m_pTransferAccountCtrl->Show(false);
        break;

        case TT_CHECK:
        m_pCheckNumCtrl->ChangeValue(wxString::Format(_("%u"), m_pAccount->GetLastCheckNum() + 1) );
        m_pCheckNumCtrl->Show(true);
        m_pPayeeCtrl->ChangeValue(_("") );
        m_pPayeeCtrl->Show(true);
        m_pCategoryCtrl->SetValue(_("") );
        m_pCategoryCtrl->Show(true);
        m_pSubCategoryCtrl->SetValue(_("") );
        m_pSubCategoryCtrl->Show(true);
        m_pTransferAccountCtrl->SetValue(_("") );
        m_pTransferAccountText->Show(false);
        m_pTransferAccountCtrl->Show(false);
        break;

        case TT_DEBIT:
        m_pCheckNumCtrl->ChangeValue(_("") );
        m_pCheckNumCtrl->Show(false);
        m_pPayeeCtrl->ChangeValue(m_pTransaction->Payee);
        m_pPayeeCtrl->Show(true);
        m_pCategoryCtrl->SetValue(_("") );
        m_pCategoryCtrl->Show(true);
        m_pSubCategoryCtrl->SetValue(_("") );
        m_pSubCategoryCtrl->Show(true);
        m_pTransferAccountCtrl->SetValue(_("") );
        m_pTransferAccountText->Show(false);
        m_pTransferAccountCtrl->Show(false);
        break;

        case TT_TRANSFER:
        m_pCheckNumCtrl->Show(false);
        m_pPayeeCtrl->Show(false);
        m_pCategoryCtrl->SetValue(_("Account Transfer") );
        m_pCategoryCtrl->Show(true);
        m_pSubCategoryCtrl->Show(false);
        m_pTransferAccountText->Show(true);
        m_pTransferAccountCtrl->Show(true);
        break;
    }
}

bool CNewTransactionDlg::TransferDataFromWindow()
{
    wxString Type = m_pRadioBox->GetStringSelection();

    // account name
    m_pTransaction->AccountName = m_pAccount->GetAccountName();

    // transfer account
    if (Type == _("Transfer") )
    {
        m_pTransaction->TransferAccount = m_pTransferAccountCtrl->GetValue();
        if ( (m_pTransaction->TransferAccount).IsEmpty() )
        {
            aceMSGBOX(this, ace::MB_INFO, _("You must select an account name."), NULL);
            return false;
        }
        else
        {
            if (m_pTransaction->TransferAccount == m_pTransaction->AccountName)
            {
                aceMSGBOX(this, ace::MB_INFO, _("You cannot transfer funds to and from the same account!"), NULL);
                return false;
            }
        }
    }
    else
    {
        m_pTransaction->TransferAccount = _(" ");
    }

    // date
    wxDateTime date = m_pDateCtrl->GetValue();
    if (!date.IsValid() )
    {
        aceMSGBOX(this, ace::MB_INFO, _("Invalid date"), NULL);
        return false;
    }
    m_pTransaction->Date = date.Format(_("%m/%d/%Y") );

    // transaction type
    m_pTransaction->Type = ace::g_pData->GetTransactionTypeValue(Type);

    // check number
    if (Type == _("Check") )
    {
        wxString s = m_pCheckNumCtrl->GetValue();
        s.ToULong(&m_pTransaction->CheckNumber);
        if (m_pTransaction->CheckNumber < 1 || s.IsEmpty() )
        {
            aceMSGBOX(this, ace::MB_INFO, _("Invalid check number."), NULL);
            return false;
        }
    }
    else
    {
        m_pTransaction->CheckNumber = 0;
    }

    // payee
    if (Type == _("Transfer") )
    {
        m_pTransaction->Payee = _("Transfer to ") + m_pTransaction->TransferAccount;
    }
    else
    {
        m_pTransaction->Payee = m_pPayeeCtrl->GetValue();
        if ( (m_pTransaction->Payee).IsEmpty() )
        {
            aceMSGBOX(this, ace::MB_INFO, _("You must enter a Payee name."), NULL);
            return false;
        }
    }

    // amount
    m_Amount = m_pAmountCtrl->GetValue();
    if (!ace::g_pData->VerifyCorrectAmountFormat(m_Amount) )
    {
        aceMSGBOX(this, ace::MB_INFO, _("Invalid dollar amount"), NULL);
        return false;
    }
    m_Amount.ToDouble(&m_pTransaction->Amount);

    // category
    m_pTransaction->Category = m_pCategoryCtrl->GetValue();
    if (m_pTransaction->Category.IsEmpty() && ace::g_pOptions->WARNINGS.IsCategory)
    {
        wxString msg = _("You did not enter a category for this transaction.\n");
        msg += _("Categories help to track expenses and are needed to run reports.\n");
        msg += _("Do you wish to enter a category?\n\n");
        msg += _("Click 'OK' to return and enter a category\n");
        msg += _("or click 'CANCEL' to continue.");
    }

    // sub category
    m_pTransaction->SubCategory = m_pSubCategoryCtrl->GetValue();

    // comments
    m_pTransaction->Comment = m_pCommentCtrl->GetValue();

    // status
    m_pTransaction->Status = TS_OPEN;

    // is deposit
    if (m_pTransaction->Type == TT_TRANSFER)
    {
        m_pTransaction->IsDeposit = false;
    }
    else
    {
        m_pTransaction->IsDeposit = ace::g_pData->IsDeposit(Type);
    }

// TODO (USER#1#): check if there is a cat or subcat entered, if not ask if you want to since this will help track expenses.
// TODO (USER#1#): put check mark not to show this warning again.
// TODO (USER#1#): add option to show this check mark and menu option to show disabled warnings

    return true;
}

void CNewTransactionDlg::OnSave(wxCommandEvent &event)
{
    wxCHECK_RET(Validate(), _("Could not validate data!") );
    if (TransferDataFromWindow() )
    {
        EndModal(wxID_OK);
    }
}

void CNewTransactionDlg::OnCategorySelection(wxCommandEvent &event)
{
    wxArrayString SubCategoryChoices;
    if (m_pSubCategoryCtrl->GetCount() != 0)
        m_pSubCategoryCtrl->Clear();
    wxString Category = m_pCategoryCtrl->GetValue();
    ace::g_pData->GetAllSubCategories(Category, SubCategoryChoices);
    m_pSubCategoryCtrl->Append(SubCategoryChoices);
}

void CNewTransactionDlg::OnRadioButtonSelect(wxCommandEvent &event)
{
    // cannot change type of an existing transaction!
    if (!m_IsNew)
    {
        aceMSGBOX(this, ace::MB_INFO, _("You can not change the type of an existing transaction!\nIf you want to change the type, you must first delete this one."), NULL);
        m_pRadioBox->SetStringSelection(m_LastRadioBoxSelection);
        return;
    }

    wxString str = m_pRadioBox->GetStringSelection();

    // is transfer selected and more than one account present
    if ( (str == _("Transfer") ) && (m_pAccount->GetNumberOfAccounts() < 2) )
    {
        aceMSGBOX(this, ace::MB_INFO, _("You must have more than one account\nset up to transfer funds!"), NULL);
        m_pRadioBox->SetStringSelection(m_LastRadioBoxSelection);
        return;
    }

    m_LastRadioBoxSelection = str;
    ShowControls(ace::g_pData->GetTransactionTypeValue(str) );
}
