/****************************************************************************
 
  Copyright (c) 1998-1999 Microsoft Corporation
                                                              
  Module Name:  card.cpp
                                                              
     Abstract:  Calling Card Object implementation
                                                              
       Author:  noela - 09/11/98
              
        Notes:

        
  Rev History:

****************************************************************************/

#include <windows.h>
#include <windowsx.h>

#if WINNT
#else
#include <help.h>
#endif

#include <tchar.h>
#include <prsht.h>
#include <stdlib.h>
#include "tapi.h"
#include "tspi.h"
#include "utils.h"
#include "cplResource.h"
#include "client.h"
#include "clntprivate.h"
#include "card.h"
#include "location.h"
#include <shlwapi.h>
#include <shlwapip.h>
#include "rules.h"

#include "tregupr2.h"

#define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0]))

#ifdef DBG
#define assert(condition)   if(condition);else  \
        { DebugAssertFailure (__FILE__, __LINE__, #condition); }

static void DebugAssertFailure (LPCSTR file, DWORD line, LPCSTR condition);

#else

#define assert(condition)

#endif


#define CLIENT_FREE(x)  \
    if (x) { ClientFree(x); (x)=NULL;} else;

#define TRACE_DWERROR() LOG((TL_ERROR, "Error %x at line %d in file %hs", dwError, __LINE__, __FILE__))
#define TRACE_HRESULT() LOG((TL_ERROR, "Error %x at line %d in file %hs", Result, __LINE__, __FILE__))

#define EXIT_IF_DWERROR()     \
    if(dwError !=ERROR_SUCCESS)  \
    {                               \
        TRACE_DWERROR();            \
        goto forced_exit;           \
    }   

#define EXIT_IF_FAILED()     \
    if(FAILED(Result))        \
    {                               \
        TRACE_HRESULT();            \
        goto forced_exit;           \
    }   


#define MAX_NUMBER_LEN      15



const TCHAR gszCardsPath[]        = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Telephony\\Cards");
const TCHAR gszTelephonyPath[]    = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Telephony");

const TCHAR gszCardW[]              = TEXT("Card");
const TCHAR gszCardNameW[]          = TEXT("Name");
const TCHAR gszLocalRuleW[]         = TEXT("LocalRule");
const TCHAR gszLDRuleW[]            = TEXT("LDRule");
const TCHAR gszInternationalRuleW[] = TEXT("InternationalRule");
const TCHAR gszLocalAccessNumberW[] = TEXT("LocalAccessNumber");
const TCHAR gszLDAccessNumberW[]    = TEXT("LDAccessNumber");
const TCHAR gszInternationalAccessNumberW[] = TEXT("InternationalAccessNumber");
const TCHAR gszAccountNumberW[]     = TEXT("AccountNumber");
const TCHAR gszPinW[]               = TEXT("Pin");

const TCHAR gszFlags[]              = TEXT("Flags");
const TCHAR gszCard[]               = TEXT("Card");
const TCHAR gszCards[]              = TEXT("Cards");
const TCHAR gszNextID[]             = TEXT("NextID");
const TCHAR gszCardListVersion[]    = TEXT("CardListVersion");

const TCHAR gszResourceLibrary[]    = TEXT("TAPIUI.DLL");

const TCHAR gszSystemSetupPath[]    = TEXT("System\\Setup");
const TCHAR gszSystemSetupInProgress[] = TEXT("SystemSetupInProgress"); 

static BOOL ValidValue(PWSTR);

/****************************************************************************

    Class : CCallingCard        
   Method : Constructor

****************************************************************************/
CCallingCard::CCallingCard()
{
    m_dwCardID = 0;
    m_pszCardName = NULL;;
    
    m_pszPIN = NULL;
    m_pszEncryptedPIN = NULL;
    m_pszAccountNumber = NULL;

    m_pszLocalAccessNumber = NULL;
    m_pszLongDistanceAccessNumber = NULL;
    m_pszInternationalAccessNumber = NULL;

    m_pszCardPath = NULL;
    m_hCard = NULL;

    m_bDirty = FALSE;
    m_bDeleted = FALSE;

    m_dwFlags = 0; // by default

    m_dwCryptInitResult = TapiCryptInitialize();
}



/****************************************************************************

    Class : CCallingCard         
   Method : Destructor

****************************************************************************/
CCallingCard::~CCallingCard()
{
    CleanUp();

    TapiCryptUninitialize();
}


/****************************************************************************

    Class : CCallingCard         
   Method : CleanUp

            Clean up memory allocations

****************************************************************************/


void  CCallingCard::CleanUp(void)
{
    CLIENT_FREE(m_pszCardName);
    CLIENT_FREE(m_pszPIN);
    CLIENT_FREE(m_pszEncryptedPIN);
    CLIENT_FREE(m_pszAccountNumber);
    CLIENT_FREE(m_pszLocalAccessNumber);
    CLIENT_FREE(m_pszLongDistanceAccessNumber);
    CLIENT_FREE(m_pszInternationalAccessNumber);

    CLIENT_FREE(m_Rules.m_pszInternationalRule);
    CLIENT_FREE(m_Rules.m_pszLongDistanceRule);
    CLIENT_FREE(m_Rules.m_pszLocalRule);

    CLIENT_FREE(m_pszCardPath);
    CloseCardKey();
}


/****************************************************************************

    Class : CCallingCard         
   Method : Initialize - external parameters

            Only for new calling cards (not present in registry)

****************************************************************************/
HRESULT CCallingCard::Initialize
                  (
                   DWORD dwCardID,
                   PWSTR pszCardName,
                   DWORD dwFlags,
                   PWSTR pszPIN,
                   PWSTR pszAccountNumber,
                   PWSTR pszInternationalRule,
                   PWSTR pszLongDistanceRule,
                   PWSTR pszLocalRule,
                   PWSTR pszInternationalAccessNumber,
                   PWSTR pszLongDistanceAccessNumber,
                   PWSTR pszLocalAccessNumber
                  )
{
    HRESULT     Result = S_OK;
    
#define CALLING_CARD_INIT(param)                                                            \
    m_##param = ClientAllocString(param);                                                   \
    if(m_##param == NULL)                                                                   \
    {                                                                                       \
        LOG((TL_ERROR, "Initialize create m_%s failed", _T(#param)));    \
        CleanUp();                                                                          \
        return E_OUTOFMEMORY;                                                               \
    }

    CALLING_CARD_INIT(pszCardName)
    CALLING_CARD_INIT(pszPIN)
    CALLING_CARD_INIT(pszAccountNumber)
    CALLING_CARD_INIT(pszInternationalAccessNumber)
    CALLING_CARD_INIT(pszLongDistanceAccessNumber)
    CALLING_CARD_INIT(pszLocalAccessNumber)

#undef CALLING_CARD_INIT

    //////////////////////////////////////////////////
    // copy the set of three Rules
    //
    Result = m_Rules.Initialize(    pszInternationalRule,
                                    pszLongDistanceRule,
                                    pszLocalRule );
    if(FAILED(Result))
    {
        LOG((TL_ERROR, "Initialize create m_Rules failed" ));
        CleanUp();
        return Result;
    }

    m_dwCardID = dwCardID;
    m_dwFlags = dwFlags;

    m_bDirty = TRUE;


    return Result;

}
        

/****************************************************************************

    Class : CCallingCard         
   Method : Initialize - from registry


****************************************************************************/
HRESULT CCallingCard::Initialize
                    (
                     DWORD dwCardID
                    )
{

    DWORD       dwError;
    DWORD       dwType;
    DWORD       dwLength;

    m_dwCardID = dwCardID;

    // open the registry key
    dwError = OpenCardKey(FALSE);

    if(dwError != ERROR_SUCCESS)
    {
        // If the key does not exist, it will be created at the first save
        if(dwError==ERROR_FILE_NOT_FOUND)
        {
            m_bDirty = TRUE;
        }
        return(HRESULT_FROM_WIN32(dwError));
    }

#define READ_CARD_VAL(Member, Name)      \
    dwError = ReadOneStringValue(&##Member, Name);  \
    if(dwError != ERROR_SUCCESS)                    \
    {                                               \
        TRACE_DWERROR();                            \
        CleanUp();                                  \
        return HRESULT_FROM_WIN32(dwError);         \
    }
    
    READ_CARD_VAL(m_pszCardName,                    gszCardNameW);
    READ_CARD_VAL(m_pszEncryptedPIN,                gszPinW);
//    READ_CARD_VAL(m_pszPIN,                gszPinW);
    READ_CARD_VAL(m_pszAccountNumber,               gszAccountNumberW);
    READ_CARD_VAL(m_pszLocalAccessNumber,           gszLocalAccessNumberW);
    READ_CARD_VAL(m_pszLongDistanceAccessNumber,    gszLDAccessNumberW);
    READ_CARD_VAL(m_pszInternationalAccessNumber,   gszInternationalAccessNumberW);
    READ_CARD_VAL(m_Rules.m_pszLocalRule,           gszLocalRuleW);
    READ_CARD_VAL(m_Rules.m_pszLongDistanceRule,    gszLDRuleW);
    READ_CARD_VAL(m_Rules.m_pszInternationalRule,   gszInternationalRuleW);

#undef READ_CARD_VAL

    // Decrypt the PIN number 
    dwError = DecryptPIN();
    if(dwError != ERROR_SUCCESS)
    {
        TRACE_DWERROR();
        CleanUp();
        return HRESULT_FACILITY(dwError)==0 ? HRESULT_FROM_WIN32(dwError) : dwError;
    }

    dwLength = sizeof(m_dwFlags);
    dwError = RegQueryValueEx ( m_hCard,
                                gszFlags,
                                NULL,
                                &dwType,
                                (PBYTE)&m_dwFlags,
                                &dwLength
                                );
    
    if(dwError != ERROR_SUCCESS)
    {
        TRACE_DWERROR();
        CleanUp();
        return HRESULT_FROM_WIN32(dwError);
    }

    CloseCardKey();

    return S_OK;

}

/****************************************************************************

    Class : CCallingCard         
   Method : SaveToRegistry
        

****************************************************************************/

HRESULT     CCallingCard::SaveToRegistry(void)
{
    DWORD   dwError;

    if(!m_bDirty)
        // nothing to save
        return S_OK;
    //open/create the registry key
    dwError = OpenCardKey(TRUE);
    if(dwError != ERROR_SUCCESS)
        return HRESULT_FROM_WIN32(dwError);

    assert(m_hCard);
    assert(m_pszCardPath);

    if(m_bDeleted)
    {
        CloseCardKey();

        dwError = RegDeleteKey (HKEY_CURRENT_USER,
                                m_pszCardPath);

        if(dwError != ERROR_SUCCESS)
            return HRESULT_FROM_WIN32(dwError);

        m_bDirty = FALSE;

        return S_OK;
    }
    // Encrypt the PIN number 
    dwError = EncryptPIN();
    if(dwError != ERROR_SUCCESS)
    {
        TRACE_DWERROR();
        return HRESULT_FACILITY(dwError)==0 ? HRESULT_FROM_WIN32(dwError) : dwError;
    }

    // Save !
    
#define CARD_SAVE_STRING(Member, Name)          \
    dwError = TAPIRegSetValueExW(   m_hCard,    \
                                    Name,       \
                                    0,          \
                                    REG_SZ,     \
                                    (PBYTE) Member, \
                                    (wcslen(Member)+1)*sizeof(WCHAR)  \
                                ) ;             \
    if(dwError != ERROR_SUCCESS)                \
    {                                           \
        TRACE_DWERROR();                        \
        CloseCardKey();                         \
        return HRESULT_FROM_WIN32(dwError);     \
    }

    CARD_SAVE_STRING(m_pszCardName,                    gszCardNameW);
    CARD_SAVE_STRING(m_pszEncryptedPIN,                gszPinW);
    CARD_SAVE_STRING(m_pszAccountNumber,               gszAccountNumberW);
    CARD_SAVE_STRING(m_pszLocalAccessNumber,           gszLocalAccessNumberW);
    CARD_SAVE_STRING(m_pszLongDistanceAccessNumber,    gszLDAccessNumberW);
    CARD_SAVE_STRING(m_pszInternationalAccessNumber,   gszInternationalAccessNumberW);
    CARD_SAVE_STRING(m_Rules.m_pszLocalRule,           gszLocalRuleW);
    CARD_SAVE_STRING(m_Rules.m_pszLongDistanceRule,    gszLDRuleW);
    CARD_SAVE_STRING(m_Rules.m_pszInternationalRule,   gszInternationalRuleW);
        
#undef CARD_SAVE_STRING

    dwError = RegSetValueEx (   m_hCard,
                                gszFlags,
                                0,
                                REG_DWORD,
                                (PBYTE)&m_dwFlags,
                                sizeof(m_dwFlags)
                           );
    if(dwError != ERROR_SUCCESS)                
    {                                           
        TRACE_DWERROR();
        CloseCardKey();                         
        return HRESULT_FROM_WIN32(dwError);     
    }

    m_bDirty = FALSE;

    CloseCardKey();

    return S_OK;
}


/****************************************************************************

    Class : CCallingCard         
   Method : MarkDeleted

            Mark the card as deleted

****************************************************************************/

HRESULT CCallingCard::MarkDeleted(void)
{
    m_bDirty = TRUE;
    
    if (m_dwFlags & CARD_BUILTIN)
    {
        // a builtin card is only hidden, not deleted
        m_dwFlags |= CARD_HIDE;
        return S_OK;
    }
    
    m_bDeleted = TRUE;

    return S_OK;
}


/****************************************************************************

    Class : CCallingCard         
   Method : Validate

            Returns 0 if the card contains complete and valid rules, or
            a series of flags if the card is missing any required data.

****************************************************************************/

DWORD CCallingCard::Validate(void)
{
    DWORD dwResult = 0;

    // Does the card have a name?
    if (!*m_pszCardName)
    {
        dwResult |= CCVF_NOCARDNAME;
    }

    // Does the card have any rules?
    if (!*(m_Rules.m_pszInternationalRule) &&
        !*(m_Rules.m_pszLocalRule) &&
        !*(m_Rules.m_pszLongDistanceRule) )
    {
        dwResult |= CCVF_NOCARDRULES;
    }
    else
    {
        DWORD dwFieldsToCheck = 0;;

        struct
        {
            PWSTR   pwszRule;
            DWORD   dwAccesFlag;
        }
        aData[] =
        {
            {m_Rules.m_pszInternationalRule, CCVF_NOINTERNATIONALACCESSNUMBER},
            {m_Rules.m_pszLocalRule,         CCVF_NOLOCALACCESSNUMBER},
            {m_Rules.m_pszLongDistanceRule,  CCVF_NOLONGDISTANCEACCESSNUMBER},
        };

        // Foreach rule, is the required rule data available?  We need to check
        // for use of the PIN number, CardNumber, and all three access numbers.
        // If any of these fields is used, then we need to verify that data has
        // been entered for those fields.  The data is only required if it is
        // actually used in a rule.
        for (int i=0; i<ARRAYSIZE(aData); i++)
        {
            if (StrChrW(aData[i].pwszRule, L'K'))
            {
                dwFieldsToCheck |= CCVF_NOCARDNUMBER;
            }
            if (StrChrW(aData[i].pwszRule, L'H'))
            {
                dwFieldsToCheck |= CCVF_NOPINNUMBER;
            }
            if (StrChrW(aData[i].pwszRule, L'J'))
            {
                dwFieldsToCheck |= aData[i].dwAccesFlag;
            }
        }
        if (dwFieldsToCheck & CCVF_NOCARDNUMBER)
        {
            if (!ValidValue(m_pszAccountNumber))
            {
                dwResult |= CCVF_NOCARDNUMBER;
            }
        }
        if (dwFieldsToCheck & CCVF_NOPINNUMBER)
        {
            if (!ValidValue(m_pszPIN))
            {
                dwResult |= CCVF_NOPINNUMBER;
            }
        }
        if (dwFieldsToCheck & CCVF_NOINTERNATIONALACCESSNUMBER)
        {
            if (!ValidValue(m_pszInternationalAccessNumber))
            {
                dwResult |= CCVF_NOINTERNATIONALACCESSNUMBER;
            }
        }
        if (dwFieldsToCheck & CCVF_NOLOCALACCESSNUMBER)
        {
            if (!ValidValue(m_pszLocalAccessNumber))
            {
                dwResult |= CCVF_NOLOCALACCESSNUMBER;
            }
        }
        if (dwFieldsToCheck & CCVF_NOLONGDISTANCEACCESSNUMBER)
        {
            if (!ValidValue(m_pszLongDistanceAccessNumber))
            {
                dwResult |= CCVF_NOLONGDISTANCEACCESSNUMBER;
            }
        }
    }

    return dwResult;
}

/****************************************************************************

    Class : CCallingCard         
   Method : Set##Member 

****************************************************************************/

#define     SET_METHOD(Member)                  \
HRESULT CCallingCard::Set##Member(PWSTR Value)  \
{                                               \
    PWSTR   pszTemp = NULL;                     \
                                                \
    if(Value == NULL)                           \
        return E_INVALIDARG;                    \
                                                \
    pszTemp = ClientAllocString(Value);         \
    if(pszTemp==NULL)                           \
    {                                           \
        return E_OUTOFMEMORY;                   \
    }                                           \
                                                \
    CLIENT_FREE(m_psz##Member);                 \
    m_psz##Member = pszTemp;                    \
                                                \
    m_bDirty = TRUE;                            \
                                                \
    return S_OK;                                \
}

SET_METHOD(CardName);
SET_METHOD(PIN);
SET_METHOD(AccountNumber);
SET_METHOD(InternationalAccessNumber);
SET_METHOD(LongDistanceAccessNumber);
SET_METHOD(LocalAccessNumber);

#define m_pszInternationalRule  m_Rules.m_pszInternationalRule
#define m_pszLongDistanceRule   m_Rules.m_pszLongDistanceRule
#define m_pszLocalRule          m_Rules.m_pszLocalRule

SET_METHOD(InternationalRule);
SET_METHOD(LongDistanceRule);
SET_METHOD(LocalRule);

#undef m_pszInternationalRule
#undef m_pszLongDistanceRule
#undef m_pszLocalRule

#undef SET_METHOD

                    




/****************************************************************************

    Class : CCallingCard         
   Method : OpenCardKey

            Opens the registry key for the card

****************************************************************************/

DWORD CCallingCard::OpenCardKey(BOOL bWrite)
{
    DWORD   dwDisp;
    DWORD   dwError;
     
    if(m_pszCardPath==NULL)
    {
        DWORD   dwLength;
        
        // Absolute path: Software.....Cards\CardX
        dwLength = ARRAYSIZE(gszCardsPath)+ARRAYSIZE(gszCard)+MAX_NUMBER_LEN;
 
        m_pszCardPath = (PTSTR)ClientAlloc(dwLength*sizeof(TCHAR));
        if (m_pszCardPath==NULL)
            return ERROR_OUTOFMEMORY;

        wsprintf(m_pszCardPath, TEXT("%s\\%s%d"), gszCardsPath, gszCard, m_dwCardID);
    }
    
    if(bWrite)
    {
        // Creates the key if it does not exist
        dwError = RegCreateKeyEx ( HKEY_CURRENT_USER,
                                   m_pszCardPath,
                                   0,
                                   NULL,
                                   REG_OPTION_NON_VOLATILE,
                                   KEY_QUERY_VALUE | KEY_SET_VALUE,
                                   NULL,
                                   &m_hCard,
                                   &dwDisp
                                  );
    }
    else
    {
        dwError = RegOpenKeyEx( HKEY_CURRENT_USER,
                                m_pszCardPath,
                                0,
                                KEY_QUERY_VALUE,
                                &m_hCard
                                );
    }
    return dwError;

}

/****************************************************************************

    Class : CCallingCard         
   Method : CloseCardKey

            Opens the registry key for the card

****************************************************************************/

DWORD CCallingCard::CloseCardKey(void)
{
    if(m_hCard)
    {
        RegCloseKey(m_hCard);
        m_hCard = NULL;
    }
    return ERROR_SUCCESS;
}

/****************************************************************************

    Class : CCallingCard         
   Method : ReadOneStringValue

            Read and allocates a string value

****************************************************************************/

DWORD CCallingCard::ReadOneStringValue(PWSTR *pMember, const TCHAR *pszName)
{
    DWORD   dwError;
    DWORD   dwLength;
    DWORD   dwType;
    PTSTR   pszBuffer;
    
    assert(m_hCard);
    assert(pMember);
    assert(pszName);

    // find the length needed
    dwError = RegQueryValueEx ( m_hCard,
                                pszName,
                                NULL,
                                &dwType,
                                NULL,
                                &dwLength
                                );
    if (dwError != ERROR_SUCCESS)
        return dwError;
    if (dwType != REG_SZ)
        return ERROR_INVALID_DATA;

    pszBuffer = (PTSTR)ClientAlloc(dwLength);
    if (pszBuffer == NULL)
        return ERROR_OUTOFMEMORY;

    dwError = RegQueryValueEx ( m_hCard,
                                pszName,
                                NULL,
                                &dwType,
                                (PBYTE)(pszBuffer),
                                &dwLength
                                );
    if(dwError != ERROR_SUCCESS)
    {
        ClientFree(pszBuffer);
        return dwError;
    }

    // convert the required bytes for the TCHAR string into the number of characters in the string.
    dwLength = dwLength / sizeof(TCHAR);
    *pMember = (PWSTR)ClientAlloc( dwLength * sizeof(WCHAR) );
    if ( NULL == *pMember )
    {
        ClientFree(pszBuffer);
        return ERROR_OUTOFMEMORY;
    }
    SHTCharToUnicode(pszBuffer, *pMember, dwLength);

    ClientFree(pszBuffer);
    return ERROR_SUCCESS;
}

/****************************************************************************

    Class : CCallingCard         
   Method : EncryptPIN

            Encrypts the PIN

****************************************************************************/


DWORD CCallingCard::EncryptPIN(void)
{
    DWORD   dwError;
    DWORD   dwLength;
    PWSTR   pszTemp = NULL;

    // free any existing encrypted string
    CLIENT_FREE(m_pszEncryptedPIN);

    dwError = TapiEncrypt(m_pszPIN, m_dwCardID, NULL, &dwLength);
    if(dwError != ERROR_SUCCESS)
    {
        LOG((TL_ERROR, "EncryptPIN: TapiEncrypt (1) failed"));
        return dwError;
    }

    pszTemp = (PWSTR)ClientAlloc(dwLength*sizeof(WCHAR));
    if(pszTemp==NULL)
    {
        return ERROR_OUTOFMEMORY;
    }

    dwError = TapiEncrypt(m_pszPIN, m_dwCardID, pszTemp, &dwLength);
    if(dwError != ERROR_SUCCESS)
    {
        LOG((TL_ERROR, "EncryptPIN: TapiEncrypt (2) failed"));
        ClientFree(pszTemp);
        return dwError;
    }
    
    m_pszEncryptedPIN = pszTemp;   

    return dwError;

}

/****************************************************************************

    Class : CCallingCard         
   Method : DecryptPIN

            Decrypts the PIN

****************************************************************************/

DWORD CCallingCard::DecryptPIN(void)
{
    DWORD   dwError;
    DWORD   dwLength;
    PWSTR   pszTemp = NULL;

    // free any existing string
    CLIENT_FREE(m_pszPIN);
 
    dwError = TapiDecrypt(m_pszEncryptedPIN, m_dwCardID, NULL, &dwLength);
    if(dwError != ERROR_SUCCESS)
    {
        LOG((TL_ERROR, "DecryptPIN: TapiDecrypt (1) failed"));
        return dwError;
    }

    pszTemp = (PWSTR)ClientAlloc(dwLength*sizeof(WCHAR));
    if(pszTemp==NULL)
    {
        return ERROR_OUTOFMEMORY;
    }

    dwError = TapiDecrypt(m_pszEncryptedPIN, m_dwCardID, pszTemp, &dwLength);
    if(dwError != ERROR_SUCCESS)
    {
        LOG((TL_ERROR, "DecryptPIN: TapiDecrypt (2) failed"));
        // return a NULL PIN
        *pszTemp = L'\0';
        dwError = ERROR_SUCCESS;
    }
    
    m_pszPIN = pszTemp;   

    return dwError;

}



/****************************************************************************

    Class : CCallingCards        
   Method : Constructor

****************************************************************************/

CCallingCards::CCallingCards()
{
    m_dwNumEntries = 0;
    m_dwNextID = 0;

    m_hCards = NULL;

    m_hEnumNode = m_CallingCardList.head();
    
}


/****************************************************************************

    Class : CCallingCards        
   Method : Destructor

****************************************************************************/

CCallingCards::~CCallingCards()
{
    CCallingCardNode *node;

    node = m_CallingCardList.head(); 

    while( !node->beyond_tail() )
    {
        delete node->value();
        node = node->next();
    }
    m_CallingCardList.flush();

    
    node = m_DeletedCallingCardList.head(); 

    while( !node->beyond_tail() )
    {
        delete node->value();
        node = node->next();
    }
    m_DeletedCallingCardList.flush();

    if(m_hCards)
        RegCloseKey(m_hCards);
}

/****************************************************************************

    Class : CCallingCards        
   Method : Initialize

            Checks the presence of the Cards key. If not present, it creates 
        and populates it from string resources.
            Verify the format of the registry config. If it is the old
        one, it converts it to the new one.
            Reads the cards from registry

****************************************************************************/

HRESULT CCallingCards::Initialize(void)
{
    DWORD       dwError;
    DWORD       dwDisp;
    BOOL        bNewCards = FALSE;
    DWORD       dwIndex;
    DWORD       dwLength;
    DWORD       dwType;
    CCallingCard    *pCard = NULL;
    HRESULT     Result = S_OK;
    BOOL        bNeedToConvert = FALSE;
    BOOL        bNeedToGenerateDefault = FALSE;

    // Test the presence of the Cards key
    dwError = RegOpenKeyEx( HKEY_CURRENT_USER,
                            gszCardsPath,
                            0,
                            KEY_READ,
                            &m_hCards
                            );
    if(dwError==ERROR_SUCCESS)
    {
        // Read the NextID value
        dwLength = sizeof(m_dwNextID);
        dwError = RegQueryValueEx ( m_hCards,
                                    gszNextID,
                                    NULL,
                                    &dwType,
                                    (PBYTE)&m_dwNextID,
                                    &dwLength
                                  );
        if(dwError==ERROR_SUCCESS)
        {
            if(dwType == REG_DWORD)
            {
                // Test the registry format and upgrade if necessary
                if(IsCardListInOldFormat(m_hCards))
                {   
                    bNeedToConvert = TRUE;
                }
            }
            else
            {
                dwError = ERROR_INVALID_DATA;
            }
        }
    }

    if(dwError != ERROR_SUCCESS)
        bNeedToGenerateDefault = TRUE;

    if(bNeedToGenerateDefault || bNeedToConvert)
    {
        // If the Cards key is missing or it has bad info, so we create a fresh, default set of cards.
        // If the cards key is in the old format, it have to be converted.
        // There's an excepted case, though: during the system setup no cards should be created or converted.
        // So detect setup case first:
        // 
        //

        HKEY    hSetupKey;
        DWORD   dwSetupValue;

        // close the cards key handle 
        if(m_hCards)
        {
            RegCloseKey(m_hCards);
            m_hCards = NULL;
        }
        
        dwError = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
                                gszSystemSetupPath,
                                0,
                                KEY_QUERY_VALUE,
                                &hSetupKey
                                );
        if(dwError == ERROR_SUCCESS)
        {
            dwLength = sizeof(dwSetupValue);
            dwSetupValue = 0;

            dwError = RegQueryValueEx(  hSetupKey,
                                        gszSystemSetupInProgress,
                                        NULL,
                                        &dwType,
                                        (PBYTE)&dwSetupValue,
                                        &dwLength
                                        );
            RegCloseKey(hSetupKey);
                                        
            if(dwError == ERROR_SUCCESS && dwType == REG_DWORD)
            {
                if(dwSetupValue == 1)
                {
                    // Setup time !
                    dwError = ERROR_SUCCESS;
                    goto forced_exit;
                }
            }
        }

        if(dwError != ERROR_SUCCESS)
        {
            // Hmm, cannot open the key or read the value...
            TRACE_DWERROR();
        }

        if(bNeedToConvert)
        {
            dwError = ConvertCallingCards(HKEY_CURRENT_USER);
            if(dwError!=ERROR_SUCCESS)
            {
                // fallback to default cards
                bNeedToGenerateDefault = TRUE;
            }
        }
                

        if(bNeedToGenerateDefault)   
        {
            dwError = CreateFreshCards();
            EXIT_IF_DWERROR();
        }

        // Reopen Cards key
        dwError = RegOpenKeyEx( HKEY_CURRENT_USER,
                                gszCardsPath,
                                0,
                                KEY_READ,
                                &m_hCards
                              );
        EXIT_IF_DWERROR();
  
        // ReRead the NextID value
        dwLength = sizeof(m_dwNextID);
        dwError = RegQueryValueEx ( m_hCards,
                                    gszNextID,
                                    NULL,
                                    &dwType,
                                    (PBYTE)&m_dwNextID,
                                    &dwLength
                                  );
        EXIT_IF_DWERROR();
       
    }

    // Read all entries
    dwIndex = 0;
    while(TRUE) 
    {
        TCHAR   szKeyName[ARRAYSIZE(gszCard)+MAX_NUMBER_LEN];
        DWORD   dwKeyLength;
        DWORD   dwCardID;

        // Enumerate next entry
        dwKeyLength = sizeof(szKeyName)/sizeof(TCHAR);
        dwError = RegEnumKeyEx (m_hCards,
                                dwIndex,
                                szKeyName,
                                &dwKeyLength,
                                NULL,
                                NULL,
                                NULL,
                                NULL
                                );
                        
        if(dwError == ERROR_NO_MORE_ITEMS)
        {
            m_dwNumEntries = dwIndex;
            dwError = ERROR_SUCCESS;
            break;
        }
        
        EXIT_IF_DWERROR();

        // Create a CCallingCard object
        dwCardID = StrToInt(szKeyName+ARRAYSIZE(gszCard)-1);
        pCard = new CCallingCard;
        if(pCard != NULL)
        {
        
            // Read the card information from registry
            Result = pCard->Initialize(dwCardID);
            if(SUCCEEDED(Result))
            {
                // Add it to the list
                m_CallingCardList.tail()->insert_after(pCard);
            }
            else
            {
                LOG((TL_ERROR, "Error %x reading card %d", Result, dwCardID));
                delete pCard;
                pCard=NULL;
            }
        }

        dwIndex++;
    }
  
forced_exit:

    if(m_hCards)
    {
        RegCloseKey(m_hCards);
        m_hCards = NULL;
    }

    if(dwError != ERROR_SUCCESS)
        Result = HRESULT_FROM_WIN32(dwError);

    return Result;
}

/****************************************************************************

    Class : CCallingCards         
   Method : RemoveCard

****************************************************************************/
void CCallingCards::RemoveCard(CCallingCard *pCard)
{
    CCallingCardNode *node = m_CallingCardList.head(); 

    while( !node->beyond_tail() )
    {
        if ( pCard == node->value() ) 
        {
            InternalDeleteCard(node);
            return;
        }
        node = node->next();
    }
    assert(FALSE);
}


/****************************************************************************

    Class : CCallingCards         
   Method : RemoveCard

****************************************************************************/
void CCallingCards::RemoveCard(DWORD dwID)
{
    CCallingCardNode *node = m_CallingCardList.head(); 

    while( !node->beyond_tail() )
    {
        if ( dwID == (node->value())->GetCardID()  ) 
        {
            InternalDeleteCard(node);
            return;
        }
        node = node->next();
    }
    assert(FALSE);
}

/****************************************************************************

    Class : CCallingCards         
   Method : GetCallingCard

****************************************************************************/
CCallingCard * CCallingCards::GetCallingCard(DWORD  dwID)
{
    CCallingCardNode *node = m_CallingCardList.head(); 

    while( !node->beyond_tail() )
    {
        if ( dwID == (node->value())->GetCardID() ) 
        {
            return node->value();
        }
        node = node->next();

    }

    return NULL;


}

/****************************************************************************

    Class : CCallingCards        
   Method : SaveToRegistry


****************************************************************************/
HRESULT     CCallingCards::SaveToRegistry(void)
{
    DWORD       dwError = ERROR_SUCCESS;
    HRESULT     Result = S_OK;
    CCallingCardNode    *node;

    // Open the Cards key
    dwError = RegOpenKeyEx (HKEY_CURRENT_USER,
                            gszCardsPath,
                            0,
                            KEY_SET_VALUE,
                            &m_hCards);
    EXIT_IF_DWERROR();
 
    //first - save the next ID
    dwError = RegSetValueEx (   m_hCards,
                                gszNextID,
                                0,
                                REG_DWORD,
                                (PBYTE)&m_dwNextID,
                                sizeof(m_dwNextID)
                                );
    EXIT_IF_DWERROR();

    // save all cards (from both lists)
    node = m_CallingCardList.head();
    while( !node->beyond_tail() )
    {
        Result = node->value()->SaveToRegistry();
        EXIT_IF_FAILED();

        node = node->next();
    }

    node = m_DeletedCallingCardList.head();
    while( !node->beyond_tail() )
    {
        Result = node->value()->SaveToRegistry();
        EXIT_IF_FAILED();

        node = node->next();
    }


forced_exit:

    if(m_hCards)
    {
        RegCloseKey(m_hCards);
        m_hCards = NULL;
    }

    if(dwError != ERROR_SUCCESS)
        Result = HRESULT_FROM_WIN32(dwError);

    return Result;


}

/****************************************************************************

    Class : CCallingCards        
   Method : Reset


****************************************************************************/
HRESULT     CCallingCards::Reset(BOOL bInclHidden)
{
    m_hEnumNode = m_CallingCardList.head();
    m_bEnumInclHidden = bInclHidden;
    return S_OK;
}

/****************************************************************************

    Class : CCallingCards        
   Method : Next


****************************************************************************/
HRESULT     CCallingCards::Next(DWORD  NrElem, CCallingCard **ppCard, DWORD *pNrElemFetched)
{
    
    DWORD   dwIndex = 0;
    
    if(pNrElemFetched == NULL && NrElem != 1)
        return E_INVALIDARG;
    if(ppCard==NULL)
        return E_INVALIDARG;

    while( !m_hEnumNode->beyond_tail() && dwIndex<NrElem )
    {
        CCallingCard    *pCard;
        
        pCard = m_hEnumNode->value();
        
        if(m_bEnumInclHidden || !pCard->IsMarkedHidden())
        {
            *ppCard++ = pCard;
            dwIndex++;
        }
        
        m_hEnumNode = m_hEnumNode->next();
    }
    
    if(pNrElemFetched!=NULL)
        *pNrElemFetched = dwIndex;

    return dwIndex<NrElem ? S_FALSE : S_OK;
}

/****************************************************************************

    Class : CCallingCards        
   Method : Skip


****************************************************************************/
HRESULT     CCallingCards::Skip(DWORD  NrElem)
{
    
    DWORD   dwIndex = 0;
    
    while( !m_hEnumNode->beyond_tail() && dwIndex<NrElem )
    {
       
        if(m_bEnumInclHidden || !m_hEnumNode->value()->IsMarkedHidden())
        {
            dwIndex++;
        }
        
        m_hEnumNode = m_hEnumNode->next();
    }

    return dwIndex<NrElem ? S_FALSE : S_OK;
}


 
/****************************************************************************

    Class : CCallingCards        
   Method : CreateFreshCards


****************************************************************************/

DWORD CCallingCards::CreateFreshCards(void)
{
    
    DWORD       dwError = ERROR_SUCCESS;
    HRESULT     Result = S_OK;
    HKEY        hTelephony = NULL;
    HINSTANCE   hTapiui = NULL;
    int         iNrChars;
    DWORD       dwNumCards;
    DWORD       dwDisp;
    DWORD       dwIndex;
    DWORD       dwValue;

    WCHAR       wBuffer[512];
    PWSTR       pTemp;

    CCallingCard    *pCard = NULL;

    // Open/Create the Telephony key
    dwError = RegCreateKeyEx(   HKEY_CURRENT_USER,
                                gszTelephonyPath,
                                0,
                                NULL,
                                REG_OPTION_NON_VOLATILE,
                                KEY_WRITE,
                                NULL,
                                &hTelephony,
                                &dwDisp
                                );
    EXIT_IF_DWERROR();

    // Delete any existing tree
    RegDeleteKeyRecursive( hTelephony, gszCards);

    // Open/Create the Cards key
    dwError = RegCreateKeyEx(   hTelephony,
                                gszCards,
                                0,
                                NULL,
                                REG_OPTION_NON_VOLATILE,
                                KEY_SET_VALUE | KEY_READ,
                                NULL,
                                &m_hCards,
                                &dwDisp
                                );
    EXIT_IF_DWERROR();

    // Write the version
    dwValue = TAPI_CARD_LIST_VERSION;
    dwError = RegSetValueEx (   m_hCards,
                                gszCardListVersion,
                                0,
                                REG_DWORD,
                                (PBYTE)&dwValue,
                                sizeof(dwValue)
                                );
    EXIT_IF_DWERROR();

    // Load the TAPIUI.DLL resource library
    hTapiui = LoadLibrary(gszResourceLibrary);
    if(hTapiui==NULL)
        dwError = GetLastError();
    EXIT_IF_DWERROR();

    // Load the number of cards. Use wBuffer as a temporary space
    assert( MAX_NUMBER_LEN <= ARRAYSIZE(wBuffer) );
    iNrChars = LoadString( hTapiui, RC_CARD_ID_BASE, (PTSTR)wBuffer, MAX_NUMBER_LEN);
    if(iNrChars==0)
        dwError = GetLastError();
    EXIT_IF_DWERROR();

    dwNumCards = StrToInt( (PTSTR)wBuffer );

    //Read cards from string resources and write them to registry
    for(dwIndex = 0; dwIndex<dwNumCards; dwIndex++)
    {
        PWSTR pszName;
        PWSTR pszPin;
        PWSTR pszLocalRule;
        PWSTR pszLDRule;
        PWSTR pszInternationalRule;
        PWSTR pszAccountNumber;
        PWSTR pszLocalAccessNumber;
        PWSTR pszLDAccessNumber;
        PWSTR pszInternationalAccessNumber;
        DWORD dwFlags;

        // read the card description
        iNrChars = TAPILoadStringW( hTapiui,
                                    RC_CARD_ID_BASE + dwIndex + 1,
                                    wBuffer,
                                    ARRAYSIZE(wBuffer)
                                    );
        if(iNrChars==0)
            dwError = GetLastError();
        EXIT_IF_DWERROR();

        // tokenize it
#define TOKENIZE(Pointer)                       \
        Pointer = (wcschr(pTemp+1, L'"'))+1;    \
        pTemp = wcschr(Pointer, L'"');          \
        *pTemp = L'\0';

        pTemp = wBuffer-1;

        TOKENIZE(pszName);
        TOKENIZE(pszPin);
        TOKENIZE(pszLocalRule);
        TOKENIZE(pszLDRule);
        TOKENIZE(pszInternationalRule);
        TOKENIZE(pszAccountNumber);
        TOKENIZE(pszLocalAccessNumber);
        TOKENIZE(pszLDAccessNumber);
        TOKENIZE(pszInternationalAccessNumber);

#undef TOKENIZE
        // don't forget the flags
        dwFlags = _wtoi(pTemp+2);
        
        // create the card object
        pCard = new CCallingCard();
        if(pCard==NULL)
            dwError = ERROR_OUTOFMEMORY;
        EXIT_IF_DWERROR();

        Result = pCard->Initialize( dwIndex,
                                    pszName,
                                    dwFlags,
                                    pszPin,
                                    pszAccountNumber,
                                    pszInternationalRule,
                                    pszLDRule,
                                    pszLocalRule,
                                    pszInternationalAccessNumber,
                                    pszLDAccessNumber,
                                    pszLocalAccessNumber
                                    );
        EXIT_IF_FAILED();

        // save it
        Result = pCard->SaveToRegistry();

        EXIT_IF_FAILED();

        delete pCard;
        pCard = NULL;
    }

    // Write NextID value
    dwError = RegSetValueEx (   m_hCards,
                                gszNextID,
                                0,
                                REG_DWORD,
                                (PBYTE)&dwNumCards,
                                sizeof(dwNumCards)
                                );
    EXIT_IF_DWERROR();


forced_exit:

    if(hTelephony)
        RegCloseKey(hTelephony);
    if(m_hCards)
    {
        RegCloseKey(m_hCards);
        m_hCards = NULL;
    }
    if(pCard)
        delete pCard;
    if(hTapiui)
        FreeLibrary(hTapiui);

    if(FAILED(Result))
        dwError = HRESULT_CODE(Result);

    return dwError;
}

/****************************************************************************

    Class : CCallingCards        
   Method : InternalDeleteCard


****************************************************************************/

void CCallingCards::InternalDeleteCard(CCallingCardNode *pNode)
{

    CCallingCard *pCard = pNode->value();
    
    pCard->MarkDeleted();

    if(!pCard->IsMarkedHidden())
    {
        pNode->remove();
        m_dwNumEntries--;
        m_DeletedCallingCardList.tail()->insert_after(pCard);
    }
}


/****************************************************************************

    Helpers

****************************************************************************/

BOOL ValidValue(PWSTR pwszString)
{
    // An empty string or with spaces only is not valid
    WCHAR const * pwcCrt = pwszString;
    while(*pwcCrt)
        if(*pwcCrt++ != L' ')
            return TRUE;

    return FALSE;        
}






#ifdef DBG

static void DebugAssertFailure (LPCSTR file, DWORD line, LPCSTR condition)
{
    DbgPrt (0, TEXT("%hs(%d) : Assertion failed, condition: %hs\n"), file, line, condition);

    DebugBreak();
}


#endif