/**
    File        : Data.cpp
    Description : Global data object
    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 "Data.h"

namespace ace
{
CData* g_pData;

bool CData::Initialize()
/*
DESCRIPTION: initializes global data sources
RETURNS    : true on success, false on failure
PARAMETERS : none
*/
{
    // only initialize once
    if (m_IsInitialized)
    {
        wxFAIL_MSG(_("Global data object already initialized!") );
        return false;
    }

    m_IsInitialized = true;

    // set categories filename
    wxString DefaultCategoryFilename;
#ifndef _DEBUG
    m_CategoryFilename       = wxStandardPaths::Get().GetUserConfigDir();
    m_CategoryFilename      += _("\\AcePFA\\categories.adf");
    DefaultCategoryFilename  = wxStandardPaths::Get().GetDataDir();
    DefaultCategoryFilename += _("\\Data\\default categories.adf");
#else
    m_CategoryFilename      = _("Data\\categories.adf");
    DefaultCategoryFilename = _("Data\\default categories.adf");
#endif

    // set up transaction type keys and values
    m_TransactionTypes[_("Start") ]    = TT_START;
    m_TransactionTypes[_("Deposit") ]  = TT_DEPOSIT;
    m_TransactionTypes[_("Transfer") ] = TT_TRANSFER;
    m_TransactionTypes[_("ATM") ]      = TT_ATM;
    m_TransactionTypes[_("Debit") ]    = TT_DEBIT;
    m_TransactionTypes[_("Check") ]    = TT_CHECK;

    // set up transaction status keys and values
    m_TransactionStatuses[_("Open") ]       = TS_OPEN;
    m_TransactionStatuses[_("Cleared") ]    = TS_CLEARED;
    m_TransactionStatuses[_("Reconciled") ] = TS_RECONCILED;

    // set up frequency type keys and values
    m_FrequencyTypes[_("Once") ]         = BO_ONCE;
    m_FrequencyTypes[_("Every Day") ]    = BO_DAILY;
    m_FrequencyTypes[_("1x / Week") ]    = BO_WEEKLY;
    m_FrequencyTypes[_("1x / 2 Weeks") ] = BO_BIWEEKLY;
    m_FrequencyTypes[_("2x / Month") ]   = BO_TWICEMONTHLY;
    m_FrequencyTypes[_("1x / Month") ]   = BO_MONTHLY;
    m_FrequencyTypes[_("1x / Quarter") ] = BO_QUARTERLY;
    m_FrequencyTypes[_("2x / Year") ]    = BO_TWICEANNUALLY;
    m_FrequencyTypes[_("1x / Year") ]    = BO_ANNUALLY;

    // set up reoccuring type keys and values
    m_ReoccuringTypes[_("Debit") ]    = RT_DEBIT;
    m_ReoccuringTypes[_("Deposit") ]  = RT_DEPOSIT;
    m_ReoccuringTypes[_("Transfer") ] = RT_TRANSFER;

    // load categories and sub categories from disk
    if (ace::g_pOptions->GLOBAL.FirstRun)
    {
        LoadCategories(DefaultCategoryFilename);
        m_IsNewCategoryAdded = true;
    }
    else
    {
        LoadCategories(m_CategoryFilename);
        m_IsNewCategoryAdded = false;
    }

    return true;
}

bool CData::LoadCategories(wxString& Filename)
{
    CInFile iFile(Filename);
    if (iFile.IsOpen() )
    {
        wxString str;
        while (iFile.ReadString(str) )
        {
            wxStringTokenizer ST(str, _(":"), wxTOKEN_RET_EMPTY);
            wxString key = ST.GetNextToken();
            wxArrayString value;
            while (ST.HasMoreTokens() )
                value.Add(ST.GetNextToken() );

            m_Categories.insert(std::make_pair(key, value) );
        }
    }
    return true;
}

bool CData::SaveCategories()
/*
DESCRIPTION: save all categories and sub categories
RETURNS    : true on success, false on failure
PARAMETERS : none
*/
{
    // do not do anything if there are no new categories
    if (!m_IsNewCategoryAdded)
        return false;

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

    wxString str;
    std::map<wxString, wxArrayString>::iterator i;
    for (i = m_Categories.begin(); i != m_Categories.end(); ++i)
    {
        str = i->first;
        str.Append(_(":") );
        for (size_t x = 0; x < i->second.GetCount(); x++)
        {
            str.Append(i->second[x] );
            str.Append(_(":") );
        }
        oFile.WriteString(str);
        str.Clear();
    }
    m_IsNewCategoryAdded = false;
    return true;
}

bool CData::GetAllAccountTypes(wxArrayString &AccountTypeChoices)
/*
DESCRIPTION: gets all account types
RETURNS    : true on success, false on failure
PARAMETERS : [in][out] AccountTypeChoices - array of account types
*/
{
    if (m_AccountTypes.empty() )
    {
        return false;
    }
    if (AccountTypeChoices.GetCount() > 0)
    {
        AccountTypeChoices.Clear();
    }
    std::map<wxString, ULONG>::iterator i;
    for (i = m_AccountTypes.begin(); i != m_AccountTypes.end(); ++i)
    {
        AccountTypeChoices.Add(i->first);
    }
    return true;
}

const wxString CData::GetAccountTypeString(const ULONG type)
/*
DESCRIPTION: get the string value of an account type code
RETURNS    : the string value
PARAMETERS : [in] the account type numeric value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_AccountTypes.begin(); i != m_AccountTypes.end(); ++i)
    {
        if (i->second == type)
            return i->first;
    }
    return _("");
}

const ULONG CData::GetAccountTypeValue(const wxString &str)
/*
DESCRIPTION: get the numeric value of an account type code
RETURNS    : the numeric value
PARAMETERS : [in] the account type string value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_AccountTypes.begin(); i != m_AccountTypes.end(); ++i)
    {
        if (i->first == str)
            return i->second;
    }
    return 0;
}

void CData::AddNewAccountType(wxString AccountTypeStr)
{
    m_AccountTypes[AccountTypeStr] = GetNewAccountTypeValue();
}

const ULONG CData::GetNewAccountTypeValue()
{
    return m_LastAccountTypeValue++;
}

bool CData::GetAllTransactionTypes(wxArrayString &TransactionTypes, const bool IncludeStart)
/*
DESCRIPTION: gets all transaction types
RETURNS    : true on success, false on failure
PARAMETERS : [in][out] TransactionTypes - array of transaction types
             [in] IncludeStart - bool include the first type in the array
*/
{
    if (m_TransactionTypes.empty() )
    {
        return false;
    }
    if (TransactionTypes.GetCount() > 0)
    {
        TransactionTypes.Clear();
    }
    std::map<wxString, ULONG>::iterator i;
    for (i = m_TransactionTypes.begin(); i != m_TransactionTypes.end(); ++i)
    {
        if ( (IncludeStart && i->second == TT_START) || (i->second != TT_START) )
            TransactionTypes.Add(i->first);
    }
    return true;
}

const wxString CData::GetTransactionTypeString(const ULONG type)
/*
DESCRIPTION: get the string value of a transaction type
RETURNS    : the string value
PARAMETERS : [in] the transaction type numeric value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_TransactionTypes.begin(); i != m_TransactionTypes.end(); ++i)
    {
        if (i->second == type)
            return i->first;
    }
    return _("");
}

const ULONG CData::GetTransactionTypeValue(const wxString &str)
/*
DESCRIPTION: get the numeric value of a transaction type
RETURNS    : the numeric value
PARAMETERS : [in] the transaction type string value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_TransactionTypes.begin(); i != m_TransactionTypes.end(); ++i)
    {
        if (i->first == str)
            return i->second;
    }
    return 0;
}

bool CData::GetAllTransactionStatuses(wxArrayString& TransactionStatuses)
/*
DESCRIPTION: gets all transaction types
RETURNS    : true on success, false on failure
PARAMETERS : [in][out] TransactionTypes - array of transaction types
             [in] IncludeStart - bool include the first type in the array
*/
{
    if (m_TransactionStatuses.empty() )
    {
        return false;
    }
    if (TransactionStatuses.GetCount() > 0)
    {
        TransactionStatuses.Clear();
    }
    std::map<wxString, ULONG>::iterator i;
    for (i = m_TransactionStatuses.begin(); i != m_TransactionStatuses.end(); ++i)
    {
        TransactionStatuses.Add(i->first);
    }
    return true;
}

const wxString CData::GetTransactionStatusString(const ULONG type)
/*
DESCRIPTION: get the string value of a transaction status code
RETURNS    : the string value
PARAMETERS : [in] the transaction status numeric value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_TransactionStatuses.begin(); i != m_TransactionStatuses.end(); ++i)
    {
        if (i->second == type)
            return i->first;
    }
    return _("");
}

const ULONG CData::GetTransactionStatusValue(const wxString &str)
/*
DESCRIPTION: get the numeric value of a transaction status code
RETURNS    : the numeric value
PARAMETERS : [in] the transaction status string value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_TransactionStatuses.begin(); i != m_TransactionStatuses.end(); ++i)
    {
        if (i->first == str)
            return i->second;
    }
    return 0;
}

bool CData::GetAllReoccuringTypes(wxArrayString& ReoccuringTypes)
{
    if (m_ReoccuringTypes.empty() )
    {
        return false;
    }
    if (ReoccuringTypes.GetCount() > 0)
    {
        ReoccuringTypes.Clear();
    }
    std::map<wxString, ULONG>::iterator i;
    for (i = m_ReoccuringTypes.begin(); i != m_ReoccuringTypes.end(); ++i)
    {
        ReoccuringTypes.Add(i->first);
    }
    return true;
}

const wxString CData::GetReoccuringTypeString(const ULONG type)
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_ReoccuringTypes.begin(); i != m_ReoccuringTypes.end(); ++i)
    {
        if (i->second == type)
            return i->first;
    }
    return _("");
}

const ULONG CData::GetReoccuringTypeValue(const wxString& str)
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_ReoccuringTypes.begin(); i != m_ReoccuringTypes.end(); ++i)
    {
        if (i->first == str)
            return i->second;
    }
    return 0;
}

bool CData::GetAllFrequencyTypes(wxArrayString &OccuranceTypes, bool IncludeFirst)
/*
DESCRIPTION: gets all bill occurance string types
RETURNS    : true on success, false on failure
PARAMETERS : [in][out] OccuranceTypes - array holding the occurance type strings
             [in] IncludeFirst - bool include the first string type
*/
{
    if (m_FrequencyTypes.empty() )
    {
        return false;
    }
    if (OccuranceTypes.GetCount() > 0)
    {
        OccuranceTypes.Clear();
    }
    std::map<wxString, ULONG>::iterator i;
    for (i = m_FrequencyTypes.begin(); i != m_FrequencyTypes.end(); ++i)
    {
        if (IncludeFirst && i == m_FrequencyTypes.begin() )
        {
            OccuranceTypes.Add(i->first);
        }
        else
        {
            OccuranceTypes.Add(i->first);
        }
    }
    return true;
}

const wxString CData::GetFrequencyTypeString(const ULONG type)
/*
DESCRIPTION: get the string value of a bill occurance type code
RETURNS    : the string value
PARAMETERS : [in] the bill occurance type numeric value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_FrequencyTypes.begin(); i != m_FrequencyTypes.end(); ++i)
    {
        if (i->second == type)
            return i->first;
    }
    return _("");
}

const ULONG CData::GetFrequencyTypeValue(const wxString &str)
/*
DESCRIPTION: get the numeric value of a bill occurance type code
RETURNS    : the numeric value
PARAMETERS : [in] the bill occurance type string value
*/
{
    std::map<wxString, ULONG>::iterator i;
    for (i = m_FrequencyTypes.begin(); i != m_FrequencyTypes.end(); ++i)
    {
        if (i->first == str)
            return i->second;
    }
    return 0;
}

bool CData::GetAllCategories(wxArrayString &CategoryChoices)
/*
DESCRIPTION: get all categorys
RETURNS    : true on success, false on failure
PARAMETERS : [in][out] CategoryChoices - array holding categorys
*/
{
    if (m_Categories.empty() ) return false;
    std::map<wxString, wxArrayString>::iterator i;
    for (i = m_Categories.begin(); i != m_Categories.end(); ++i)
    {
        CategoryChoices.Add(i->first);
    }
    return true;
}

bool CData::GetAllSubCategories(const wxString &Category, wxArrayString &SubCategoryChoices)
/*
DESCRIPTION: get all sub categorys for one category
RETURNS    : true on success, false on failure
PARAMETERS : [in] Category - the category to retrieve sub categories for
             [in][out] SubCategoryChoices - array holding sub categorys
*/
{
    if (m_Categories.empty() ) return false;
    std::map<wxString, wxArrayString>::iterator i;
    for (i = m_Categories.begin(); i != m_Categories.end(); ++i)
    {
        if (i->first == Category)
            SubCategoryChoices = i->second;
    }
    if (SubCategoryChoices.IsEmpty() )
        return false;
    return true;
}

bool CData::AddNewCategory(const wxString &NewCategory)
/*
DESCRIPTION: add a new category to the list
RETURNS    : true on success, false on failure
PARAMETERS : [in] NewCategory - the category to add
*/
{
    if (!m_IsNewCategoryAdded)
        m_IsNewCategoryAdded = true;

    wxArrayString array;
    m_Categories.insert(std::make_pair(NewCategory, array) );
    return true;
}

bool CData::AddNewSubCategory(const wxString &Category, const wxString &NewSubCategory)
/*
DESCRIPTION: add a new sub category to the list
RETURNS    : true on success, false on failure
PARAMETERS : [in] Category to add to
             [in] NewSubCategory - the sub category to add
*/
{
    if (!m_IsNewCategoryAdded)
        m_IsNewCategoryAdded = true;

    std::map<wxString, wxArrayString>::iterator i = m_Categories.find(Category);
    if (i != m_Categories.end() )
    {
        i->second.Add(NewSubCategory);
        i->second.Sort();
        return true;
    }
    else
    {
        wxFAIL_MSG(wxString::Format(_("No match for Category: %s"), Category) );
        return false;
    }
}

bool CData::VerifyCorrectAmountFormat(wxString &Amount)
/*
DESCRIPTION: verify that a dollar amount is in correct format
RETURNS    : true if it is, false if not
PARAMETERS : [in][out] Amount - string value of the dollar amount
*/
{
    // make sure dollar amount is an accepted value (x.xx)
    wxString strAmount = Amount;
    wxString strCents;
    int index = -1;
    int strLength = -1;
    bool isDecimal = false;
    if (!strAmount.IsEmpty() )
    {
        // make sure there is only one decimal ('.')
        strLength = strAmount.Len();
        for (int i = 0; i < strLength; i++)
        {
            if (strAmount[i] == _('.') )
            {
                if (!isDecimal)
                {
                    isDecimal = true;
                }
                 else
                {
                    aceMSGBOX(NULL, ace::MB_INFO, _("Too many decimal points in dollar amount!"), NULL);
                    return false;
                }
           }
        }
        index = strAmount.Find(_('.') );
        if (index == wxNOT_FOUND)
        {
            index = strAmount.Len();
            strCents = _(".00");
        }
        else
        {
            strCents = strAmount.Mid(index);
            switch (strCents.Len() )
            {
                case 1:
                strCents.Append(_("00") );
                break;

                case 2:
                strCents.Append(_("0") );
                break;

                case 3:
                break;

                default:
                strCents.Truncate(3);
                return false;
            }
        }
        strAmount.Truncate(index);
        strAmount.Append(strCents);
    }
    strAmount.Trim(false);
    Amount = strAmount;
    return true;
}

bool CData::IsDeposit(const TRANSACTIONTYPE type)
{
    switch (type)
    {
        case TT_START:
        case TT_DEPOSIT:
        return true;

        default:
        return false;
    }
}

bool CData::IsDeposit(const wxString str)
{
    const TRANSACTIONTYPE type = (TRANSACTIONTYPE)GetTransactionTypeValue(str);
    return IsDeposit(type);
}

const wxSortedArrayString& CData::GetPayeeList() const
{
    return m_PayeeList;
}

bool CData::GetPayeeList(wxArrayString& Choices)
{
    if (m_PayeeList.IsEmpty() )
        return false;
    Choices = m_PayeeList;
    return true;
}

void CData::AddNewPayee(const wxString& Payee, bool sort)
{
    if (!Payee.IsEmpty() )
        if (m_PayeeList.Index(Payee) == wxNOT_FOUND)
            m_PayeeList.Add(Payee);
}


}; // end namespace ace
