// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 1995  Microsoft Corporation.  All Rights Reserved.
//
//  MODULE: TapiCode.c
//
//  PURPOSE: Handles all the TAPI routines for TapiComm.
//
//
//  EXPORTED FUNCTIONS:  These functions are for use by other modules.
//
//    InitializeTAPI    - Initialize this app with TAPI.
//    ShutdownTAPI      - Shutdown this app from TAPI.
//    DialCall          - Dial a Call.
//    HangupCall        - Hangup an existing Call.
//    PostHangupCall    - Posts a HangupCall message to the main window.
//
//  INTERNAL FUNCTIONS:  These functions are for this module only.
//
//    DialCallInParts           - Actually Dial the call.
//
//    lineCallbackFunc          - TAPI callback for async messages.
//
//    CheckAndReAllocBuffer     - Helper function for I_ wrappers functions.
//
//    I_lineNegotiateAPIVersion - Wrapper for lineNegotiateAPIVersion.
//    I_lineGetDevCaps          - Wrapper for lineGetDevCaps.
//    I_lineGetAddressStatus    - Wrapper for lineGetAddressStatus.
//    I_lineTranslateAddress    - Wrapper for lineTranslateAddress.
//    I_lineGetCallStatus       - Wrapper for lineGetCallStatus.
//    I_lineGetAddressCaps      - Wrapper for lineGetAddressCaps.
//
//    WaitForCallState          - Resynchronize by Waiting for a CallState.
//    WaitForReply              - Resynchronize by Waiting for a LINE_REPLY.
//
//    DoLineReply               - Handle asynchronous LINE_REPLY.
//    DoLineClose               - Handle asynchronous LINE_CLOSE.
//    DoLineDevState            - Handle asynchronous LINE_LINEDEVSTATE.
//    DoLineCallState           - Handle asynchronous LINE_CALLSTATE.
//    DoLineCreate              - Handle asynchronous LINE_CREATE.
//
//    HandleLineErr             - Handler for most LINEERR errors.
//
//    HandleIniFileCorrupt      - LINEERR handler for INIFILECORRUPT.
//    HandleNoDriver            - LINEERR handler for NODRIVER.
//    HandleNoDevicesInstalled  - LINEERR handler for NODEVICE.
//    HandleReInit              - LINEERR handler for REINIT.
//    HandleNoMultipleInstance  - LINEERR handler for NOMULTIPLEINSTANCE.
//    HandleNoMem               - LINEERR handler for NOMEM.
//    HandleOperationFailed     - LINEERR handler for OPERATIONFAILED.
//    HandleResourceUnavail     - LINEERR handler for RESOURCEUNAVAIL.
//
//    LaunchModemControlPanelAdd - Launches the Modem Control Panel.
//
//    GetAddressToDial          - Launches a GetAddressToDial dialog.
//    DialDialogProc            - Dialog Proc for the GetAddressToDial API.
//
//    I_lineNegotiateLegacyAPIVersion - Wrapper to negoitiate with legacy TSPs
//    VerifyUsableLine          - Verify that a line device is usable
//    FillTAPILine              - Fill a combobox with TAPI Device names
//    VerifyAndWarnUsableLine   - Verify and warn if a line device is usable
//    FillCountryCodeList       - Fill a combobox with country codes
//    FillLocationInfo          - Fill a combobox with current TAPI locations
//    UseDialingRules           - Enable/Disable dialing rules controls
//    DisplayPhoneNumber        - Create and display a valid phone number
//    PreConfigureDevice        - Preconfigure a device line


#include <tapi.h>
#include <windows.h>
#include <string.h>
#include "globals.h"
#include "TapiInfo.h"
#include "TapiCode.h"
#include "CommCode.h"
#include "resource.h"
// #include "statbar.h"
// #include "toolbar.h"
#include <logit.h>

HANDLE g_hConnectionEvent = NULL;

extern "C" HINSTANCE hInst;

// All TAPI line functions return 0 for SUCCESS, so define it.
#define SUCCESS 0

// Possible return error for resynchronization functions.
#define WAITERR_WAITABORTED  1
#define WAITERR_WAITTIMEDOUT 2

// Reasons why a line device might not be usable by TapiComm.
#define LINENOTUSEABLE_ERROR            1
#define LINENOTUSEABLE_NOVOICE          2
#define LINENOTUSEABLE_NODATAMODEM      3
#define LINENOTUSEABLE_NOMAKECALL       4
#define LINENOTUSEABLE_ALLOCATED        5
#define LINENOTUSEABLE_INUSE            6
#define LINENOTUSEABLE_NOCOMMDATAMODEM  7

// Constant used in WaitForCallState when any new
// callstate message is acceptable.
#define I_LINECALLSTATE_ANY 0

 // Wait up to 30 seconds for an async completion.
#define WAITTIMEOUT 30000

// TAPI version that this sample is designed to use.
#define SAMPLE_TAPI_VERSION 0x00010004


// Global TAPI variables.
HWND     g_hWndMainWindow = NULL;   // Apps main window.
HWND     g_hDlgParentWindow = NULL; // This will be the parent of all dialogs.
HLINEAPP g_hLineApp = NULL;
DWORD    g_dwNumDevs = 0;

// Global variable that holds the handle to a TAPI dialog
// that needs to be dismissed if line conditions change.
HWND g_hDialog = NULL;

// Global flags to prevent re-entrancy problems.
BOOL g_bShuttingDown = FALSE;
BOOL g_bStoppingCall = FALSE;
BOOL g_bInitializing = FALSE;


// This sample only supports one call in progress at a time.
BOOL g_bTapiInUse = FALSE;


// Data needed per call.  This sample only supports one call.
HCALL g_hCall = NULL;
HLINE g_hLine = NULL;
DWORD g_dwDeviceID = 0;
DWORD g_dwAPIVersion = 0;
DWORD g_dwCallState = 0;
char  g_szTranslatedNumber[128] = "";
char  g_szDisplayableAddress[128] = "";
char  g_szDialableAddress[128] = "";
BOOL  g_bConnected = FALSE;
LPVOID g_lpDeviceConfig = NULL;
DWORD g_dwSizeDeviceConfig;

// Global variables to allow us to do various waits.
BOOL  g_bReplyRecieved;
DWORD g_dwRequestedID;
long  g_lAsyncReply;
BOOL  g_bCallStateReceived;

// Structures needed to handle special non-dialable characters.
#define g_sizeofNonDialable (sizeof(g_sNonDialable)/sizeof(g_sNonDialable[0]))

typedef struct {
    LONG lError;
    DWORD dwDevCapFlag;
    LPSTR szToken;
    LPSTR szMsg;
} NONDIALTOKENS;

NONDIALTOKENS g_sNonDialable[] = {
    {LINEERR_DIALBILLING,  LINEDEVCAPFLAGS_DIALBILLING,  "$",
            "Wait for the credit card bong tone" },
    {LINEERR_DIALDIALTONE, LINEDEVCAPFLAGS_DIALDIALTONE, "W",
            "Wait for the second dial tone" },
    {LINEERR_DIALDIALTONE, LINEDEVCAPFLAGS_DIALDIALTONE, "w",
            "Wait for the second dial tone" },
    {LINEERR_DIALQUIET,    LINEDEVCAPFLAGS_DIALQUIET,    "@",
            "Wait for the remote end to answer" },
    {LINEERR_DIALPROMPT,   0,                            "?",
            "Press OK when you are ready to continue dialing"},
};

// "Dial" dialog controls and their associated help page IDs
DWORD g_adwSampleMenuHelpIDs[] =
{
    IDC_COUNTRYCODE          , IDC_COUNTRYCODE,
    IDC_STATICCOUNTRYCODE    , IDC_COUNTRYCODE,
    IDC_AREACODE             , IDC_AREACODE,
    IDC_STATICAREACODE       , IDC_AREACODE,
    IDC_PHONENUMBER          , IDC_PHONENUMBER,
    IDC_STATICPHONENUMBER    , IDC_PHONENUMBER,
    IDC_USEDIALINGRULES      , IDC_USEDIALINGRULES,
    IDC_LOCATION             , IDC_LOCATION,
    IDC_STATICLOCATION       , IDC_LOCATION,
    IDC_CALLINGCARD          , IDC_CALLINGCARD,
    IDC_STATICCALLINGCARD    , IDC_CALLINGCARD,
    IDC_DIALINGPROPERTIES    , IDC_DIALINGPROPERTIES,
    IDC_TAPILINE             , IDC_TAPILINE,
    IDC_STATICTAPILINE       , IDC_TAPILINE,
    IDC_CONFIGURELINE        , IDC_CONFIGURELINE,
    IDC_DIAL                 , IDC_DIAL,
    IDC_LINEICON             , IDC_LINEICON,
    //IDC_STATICWHERETODIAL    , IDC_STATICWHERETODIAL,
    //IDC_STATICHOWTODIAL      , IDC_STATICHOWTODIAL,
    //IDC_STATICCONNECTUSING   , IDC_STATICCONNECTUSING,
    //IDC_STATICPHONENUMBER    , IDC_PHONENUMBER,
    0,0
};

//**************************************************
// Prototypes for functions used only in this module.
//**************************************************

BOOL DialCallInParts (
    LPLINEDEVCAPS lpLineDevCaps,
    LPCSTR lpszAddress,
    LPCSTR lpszDisplayableAddress);

LPLINECALLPARAMS CreateCallParams (
    LPLINECALLPARAMS lpCallParams,
    LPCSTR lpszDisplayableAddress);

DWORD I_lineNegotiateAPIVersion (
    DWORD dwDeviceID);

LPLINECALLINFO I_lineGetCallInfo(LPLINECALLINFO lpLineCallInfo);

volatile DWORD g_dwRate = 0;
BOOL g_bCallCancel = FALSE;

LPVOID CheckAndReAllocBuffer(
    LPVOID lpBuffer, size_t sizeBufferMinimum,
    LPTCH szApiPhrase);

LPLINEDEVCAPS I_lineGetDevCaps (
    LPLINEDEVCAPS lpLineDevCaps,
    DWORD dwDeviceID,
    DWORD dwAPIVersion);

LPLINEADDRESSSTATUS I_lineGetAddressStatus (
    LPLINEADDRESSSTATUS lpLineAddressStatus,
    HLINE hLine,
    DWORD dwAddressID);

LPLINETRANSLATEOUTPUT I_lineTranslateAddress (
    LPLINETRANSLATEOUTPUT lpLineTranslateOutput,
    DWORD dwDeviceID,
    DWORD dwAPIVersion,
    LPCSTR lpszDialAddress);

LPLINECALLSTATUS I_lineGetCallStatus (
    LPLINECALLSTATUS lpLineCallStatus,
    HCALL hCall);

LPLINEADDRESSCAPS I_lineGetAddressCaps (
    LPLINEADDRESSCAPS lpLineAddressCaps,
    DWORD dwDeviceID, DWORD dwAddressID,
    DWORD dwAPIVersion, DWORD dwExtVersion);

long WaitForCallState (DWORD dwNewCallState);

long WaitForReply (long lRequestID);

void CALLBACK lineCallbackFunc(
    DWORD hDevice, DWORD dwMsg, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3);

void DoLineReply(
    DWORD dwDevice, DWORD dwMsg, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3);
void DoLineClose(
    DWORD dwDevice, DWORD dwMsg, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3);
void DoLineDevState(
    DWORD dwDevice, DWORD dwsg, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3);
void DoLineCallState(
    DWORD dwDevice, DWORD dwMsg, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3);
void DoLineCreate(
    DWORD dwDevice, DWORD dwMessage, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3);

BOOL HandleLineErr(long lLineErr);

BOOL HandleIniFileCorrupt();
BOOL HandleNoDriver();
BOOL HandleNoDevicesInstalled();
BOOL HandleReInit();
BOOL HandleNoMultipleInstance();
BOOL HandleNoMem();
BOOL HandleOperationFailed();
BOOL HandleResourceUnavail();

BOOL LaunchModemControlPanelAdd();

INT_PTR CALLBACK DialDialogProc(
    HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

BOOL GetAddressToDial();

DWORD I_lineNegotiateLegacyAPIVersion(DWORD dwDeviceID);
long VerifyUsableLine(DWORD dwDeviceID);
void FillTAPILine(HWND hwndDlg);
BOOL VerifyAndWarnUsableLine(HWND hwndDlg);
void FillCountryCodeList(HWND hwndDlg, DWORD dwDefaultCountryID);
void FillLocationInfo(HWND hwndDlg, LPSTR lpszCurrentLocation,
    LPDWORD lpdwCountryID, LPSTR lpszAreaCode);
void UseDialingRules(HWND hwndDlg);
void DisplayPhoneNumber(HWND hwndDlg);
void PreConfigureDevice(HWND hwndDlg, DWORD dwDeviceID);


//**************************************************
// Entry points from the UI
//**************************************************


//
//  FUNCTION: BOOL InitializeTAPI(HWND)
//
//  PURPOSE: Initializes TAPI
//
//  PARAMETERS:
//    hWndParent - Window to use as parent of any dialogs.
//
//  RETURN VALUE:
//    Always returns 0 - command handled.
//
//  COMMENTS:
//
//    This is the API that initializes the app with TAPI.
//    If NULL is passed for the hWndParent, then its assumed
//    that re-initialization has occurred and the previous hWnd
//    is used.
//
//

BOOL InitializeTAPI(HWND hWndParent)
{
    long lReturn;
    BOOL bTryReInit = TRUE;

    // If we're already initialized, then initialization succeeds.
    if (g_hLineApp)
        return TRUE;

    // If we're in the middle of initializing, then fail, we're not done.
    if (g_bInitializing)
        return FALSE;

    g_bInitializing = TRUE;

    // Initialize TAPI
    do
    {
        lReturn = lineInitialize(&g_hLineApp, hInst,
            lineCallbackFunc, "DPlayComm", &g_dwNumDevs);

        // If we get this error, its because some other app has yet
        // to respond to the REINIT message.  Wait 5 seconds and try
        // again.  If it still doesn't respond, tell the user.
        if (lReturn == LINEERR_REINIT)
        {
            if (bTryReInit)
            {
                MSG msg;
                DWORD dwTimeStarted;

                dwTimeStarted = GetTickCount();

                while(GetTickCount() - dwTimeStarted < 5000)
                {
                    if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
                    {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                    }
                }

                bTryReInit = FALSE;
                continue;
            }
            else
            {
                g_bInitializing = FALSE;
                return FALSE;
            }
        }

        if (lReturn == LINEERR_NODEVICE)
        {
            if (HandleNoDevicesInstalled())
                continue;
            else
            {

                TSHELL_INFO(TEXT("No devices installed."));

                g_bInitializing = FALSE;
                return FALSE;
            }
        }

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineInitialize unhandled error: %x"), lReturn));
            g_bInitializing = FALSE;
            return FALSE;
        }
    }
    while(lReturn != SUCCESS);

    g_hDlgParentWindow = g_hWndMainWindow = NULL;

    g_hCall = NULL;
    g_hLine = NULL;


    TSHELL_INFO(TEXT("Tapi initialized."));

    g_bInitializing = FALSE;
    return TRUE;
}


//
//  FUNCTION: BOOL ShutdownTAPI()
//
//  PURPOSE: Shuts down all use of TAPI
//
//  PARAMETERS:
//    None
//
//  RETURN VALUE:
//    True if TAPI successfully shut down.
//
//  COMMENTS:
//
//    If ShutdownTAPI fails, then its likely either a problem
//    with the service provider (and might require a system
//    reboot to correct) or the application ran out of memory.
//
//

BOOL ShutdownTAPI()
{
    long lReturn;

    // If we aren't initialized, then Shutdown is unnecessary.
    if (g_hLineApp == NULL)
        return TRUE;

    // Prevent ShutdownTAPI re-entrancy problems.
    if (g_bShuttingDown)
        return TRUE;

    g_bShuttingDown = TRUE;

    HangupCall(__LINE__);

    do
    {
        lReturn = lineShutdown(g_hLineApp);
        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineShutdown unhandled error: %x"), lReturn));
            break;
        }
    }
    while(lReturn != SUCCESS);

    g_bTapiInUse = FALSE;
    g_bConnected = FALSE;
    g_hLineApp = NULL;
    g_hCall = NULL;
    g_hLine = NULL;
    g_bShuttingDown = FALSE;

    TSHELL_INFO(TEXT("TAPI uninitialized."));

    return TRUE;
}



//
//  FUNCTION: BOOL HangupCall()
//
//  PURPOSE: Hangup the call in progress if it exists.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE if call hung up successfully.
//
//  COMMENTS:
//
//    If HangupCall fails, then its likely either a problem
//    with the service provider (and might require a system
//    reboot to correct) or the application ran out of memory.
//
//

extern BOOL g_bPostHangup;
BOOL HangupCall(DWORD dwCallLine)
{

    DBG_INFO((DBGARG, TEXT("HangupCall was called from %d"), dwCallLine));

    if (g_hConnectionEvent)
        SetEvent(g_hConnectionEvent);

    if (g_bPostHangup)
        return(TRUE);
    else
        return(HangupCallI());
}
BOOL HangupCallI()
{
    LPLINECALLSTATUS pLineCallStatus = NULL;
    long lReturn;


    // Prevent HangupCall re-entrancy problems.
    if (g_bStoppingCall)
        return TRUE;

    // if the 'Call' dialog is up, dismiss it.
    if (g_hDialog)
        PostMessage(g_hDialog, WM_COMMAND, IDCANCEL, 0);

    // If Tapi is not being used right now, then the call is hung up.
    if (!g_bTapiInUse)
        return TRUE;

    g_bStoppingCall = TRUE;

    TSHELL_INFO(TEXT("Stopping Call in progress"));

    // Stop any data communications on the comm port.
    StopComm(g_hConnectionEvent);

    // If there is a call in progress, drop and deallocate it.
    if (g_hCall)
    {
        TSHELL_INFO(TEXT("Calling lineGetCallStatus"));

        // I_lineGetCallStatus returns a LocalAlloc()d buffer
        pLineCallStatus = I_lineGetCallStatus(pLineCallStatus, g_hCall);
        if (pLineCallStatus == NULL)
        {
            ShutdownTAPI();
            g_bStoppingCall = FALSE;
            return FALSE;
        }

        // Only drop the call when the line is not IDLE.
        if (!((pLineCallStatus -> dwCallState) & LINECALLSTATE_IDLE))
        {

            TSHELL_INFO(TEXT("Line isn't idle, call lineDrop"));

            do
            {
                lReturn = WaitForReply(lineDrop(g_hCall, NULL, 0));

                if (lReturn == WAITERR_WAITTIMEDOUT)
                {

                    TSHELL_INFO(TEXT("Call timed out in WaitForReply."));

                    break;
                }

                if (lReturn == WAITERR_WAITABORTED)
                {

                    TSHELL_INFO(TEXT("lineDrop: WAITERR_WAITABORTED."));

                    break;
                }

                // Was the call already in IDLE?
                if (lReturn == LINEERR_INVALCALLSTATE)
                    break;

                if (HandleLineErr(lReturn))
                    continue;
                else
                {
                    DBG_INFO((DBGARG, TEXT("lineDrop unhandled error: %x"), lReturn));
                    break;
                }
            }
            while(lReturn != SUCCESS);

            // Wait for the dropped call to go IDLE before continuing.
            lReturn = WaitForCallState(LINECALLSTATE_IDLE);

#ifdef DEBUG
            if (lReturn == WAITERR_WAITTIMEDOUT)
                TSHELL_INFO(TEXT("Call timed out waiting for IDLE state."));

            if (lReturn == WAITERR_WAITABORTED)
                TSHELL_INFO(TEXT("WAITERR_WAITABORTED while waiting for IDLE state."));
#endif

            TSHELL_INFO(TEXT("Call Dropped."));
        }

        // The call is now idle.  Deallocate it!
        do
        {
            lReturn = lineDeallocateCall(g_hCall);
            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineDeallocateCall unhandled error: %x"), lReturn));
                break;
            }
        }
        while(lReturn != SUCCESS);


        TSHELL_INFO(TEXT("Call Deallocated."));

    }
    else
    {
        TSHELL_INFO(TEXT("g_hCall is NULL."));
    }




    // if we have a line open, close it.
    if (g_hLine)
    {
        do
        {
            lReturn = lineClose(g_hLine);
            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineClose unhandled error: %x"), lReturn));
                break;
            }
        }
        while(lReturn != SUCCESS);


        TSHELL_INFO(TEXT("Line Closed."));

    }

    else
    {
        TSHELL_INFO(TEXT("g_hLine is NULL."));
    }


    // Call and Line are taken care of.  Finish cleaning up.

    // If there is device configuration information, free the memory.
    if (g_lpDeviceConfig)
        LocalFree(g_lpDeviceConfig);
    g_lpDeviceConfig = NULL;

    g_hCall = NULL;
    g_hLine = NULL;
    g_bConnected = FALSE;

    g_bTapiInUse = FALSE;

    g_bStoppingCall = FALSE; // allow HangupCall to be called again.


    TSHELL_INFO(TEXT("Call stopped"));


    // Need to free LocalAlloc()d buffer returned from I_lineGetCallStatus
    if (pLineCallStatus)
        LocalFree(pLineCallStatus);

    return TRUE;
}




//
//  FUNCTION: LONG GetDefaultLine()
//
//  PURPOSE: Get Default line device.
//
LONG GetDefaultLine()
{
    DWORD dwDeviceID;
    DWORD dwAPIVersion;
    LPLINEDEVCAPS lpLineDevCaps = NULL;
    DWORD dwDefaultDevice = MAXDWORD;


    TSHELL_INFO(TEXT("GetDefaultLine"));


    for (dwDeviceID = 0;    dwDeviceID < g_dwNumDevs
                         && dwDefaultDevice == MAXDWORD; dwDeviceID ++)
    {
        dwAPIVersion = I_lineNegotiateLegacyAPIVersion(dwDeviceID);

        if (dwAPIVersion)
        {
            lpLineDevCaps = I_lineGetDevCaps(lpLineDevCaps,
                dwDeviceID, dwAPIVersion);
            if (lpLineDevCaps)
            {
                if (   (lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM)
                    && VerifyUsableLine(dwDeviceID) == SUCCESS)
                {
                    dwDefaultDevice = dwDeviceID;
                }
                else;  // Line isn't valid, unuseable.
            }
            else;  // Couldn't GetDevCaps.  Line is unavail.
        }
        else;  // Couldn't NegotiateAPIVersion.  Line is unavail.
    }

    if (lpLineDevCaps)
        LocalFree(lpLineDevCaps);

    if (dwDefaultDevice == MAXDWORD)
        return(-1);
    else
        return((LONG) dwDefaultDevice);
}

//
//  FUNCTION: ReceiveCall()
//
//  PURPOSE: Wait for someone to call us.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE if able to find a line.
//
//  COMMENTS:
//
//    This function makes several assumptions:
//

BOOL ReceiveCall()
{
    long lReturn;
    LPLINEADDRESSSTATUS lpLineAddressStatus = NULL;
    LPLINEDEVCAPS lpLineDevCaps = NULL;



    TSHELL_INFO(TEXT("Receive Call"));



    if (g_bTapiInUse)
    {

        TSHELL_INFO(TEXT("A call is already being handled"));

        return FALSE;
    }

    // If TAPI isn't initialized, its either because we couldn't initialize
    // at startup (and this might have been corrected by now), or because
    // a REINIT event was received.  In either case, try to init now.

    if (!g_hLineApp)
    {
        if (!InitializeTAPI(NULL))
            return FALSE;
    }

    // If there are no line devices installed on the machine, lets give
    // the user the opportunity to install one.
    if (g_dwNumDevs < 1)
    {
        if (!HandleNoDevicesInstalled())
            return FALSE;
    }

    // We now have a call active.  Prevent future calls.
    g_bTapiInUse = TRUE;

    if ((lReturn = GetDefaultLine()) < 0)
        return(FALSE);

    g_dwDeviceID = (DWORD) lReturn;

    // Negotiate the API version to use for this device.
    g_dwAPIVersion = I_lineNegotiateAPIVersion(g_dwDeviceID);
    if (g_dwAPIVersion == 0)
    {
        TSHELL_INFO(TEXT("Line Version problem."));
        HangupCall(__LINE__);
        goto DeleteBuffers;
    }

    // Open the Line for an incomming DATAMODEM call.
    do
    {
        lReturn = lineOpen(g_hLineApp, g_dwDeviceID, &g_hLine,
            g_dwAPIVersion, 0, 0,
            LINECALLPRIVILEGE_OWNER, LINEMEDIAMODE_DATAMODEM,
            0);

        if(lReturn == LINEERR_ALLOCATED)
        {
            TSHELL_INFO(TEXT("Fatal Error"));
            HangupCall(__LINE__);
            goto DeleteBuffers;
        }

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineOpen unhandled error: %x"), lReturn));

            HangupCall(__LINE__);
            goto DeleteBuffers;
        }
    }
    while(lReturn != SUCCESS);

    // Tell the service provider that we want all notifications that
    // have anything to do with this line.
    do
    {
        // Set the messages we are interested in.

        // Note that while most applications aren't really interested
        // in dealing with all of the possible messages, its interesting
        // to see which come through the callback for testing purposes.

        lReturn = lineSetStatusMessages(g_hLine,
            LINEDEVSTATE_OTHER          |
            LINEDEVSTATE_RINGING        |  // Important state!
            LINEDEVSTATE_CONNECTED      |  // Important state!
            LINEDEVSTATE_DISCONNECTED   |  // Important state!
            LINEDEVSTATE_MSGWAITON      |
            LINEDEVSTATE_MSGWAITOFF     |
            LINEDEVSTATE_INSERVICE      |
            LINEDEVSTATE_OUTOFSERVICE   |  // Important state!
            LINEDEVSTATE_MAINTENANCE    |  // Important state!
            LINEDEVSTATE_OPEN           |
            LINEDEVSTATE_CLOSE          |
            LINEDEVSTATE_NUMCALLS       |
            LINEDEVSTATE_NUMCOMPLETIONS |
            LINEDEVSTATE_TERMINALS      |
            LINEDEVSTATE_ROAMMODE       |
            LINEDEVSTATE_BATTERY        |
            LINEDEVSTATE_SIGNAL         |
            LINEDEVSTATE_DEVSPECIFIC    |
            LINEDEVSTATE_REINIT         |  // Not allowed to disable this.
            LINEDEVSTATE_LOCK           |
            LINEDEVSTATE_CAPSCHANGE     |
            LINEDEVSTATE_CONFIGCHANGE   |
            LINEDEVSTATE_COMPLCANCEL    ,

            LINEADDRESSSTATE_OTHER      |
            LINEADDRESSSTATE_DEVSPECIFIC|
            LINEADDRESSSTATE_INUSEZERO  |
            LINEADDRESSSTATE_INUSEONE   |
            LINEADDRESSSTATE_INUSEMANY  |
            LINEADDRESSSTATE_NUMCALLS   |
            LINEADDRESSSTATE_FORWARD    |
            LINEADDRESSSTATE_TERMINALS  |
            LINEADDRESSSTATE_CAPSCHANGE);


        if (HandleLineErr(lReturn))
            continue;
        else
        {
            // If we do get an unhandled problem, we don't care.
            // We just won't get notifications.
            DBG_INFO((DBGARG, TEXT("lineSetStatusMessages unhandled error: %x"), lReturn));
            break;
        }
    }
    while(lReturn != SUCCESS);


    return(TRUE);


DeleteBuffers:

    if (lpLineAddressStatus)
        LocalFree(lpLineAddressStatus);
    if (lpLineDevCaps)
        LocalFree(lpLineDevCaps);

    return g_bTapiInUse;
}






//
//  FUNCTION: DialCall(LPSTR lpDisplay, LPSTR lpDialable, DWORD dwDeviceID, HANDLE hEvent)
//
//  PURPOSE: Get a number from the user and dial it.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE if able to get a number, find a line, and dial successfully.
//
//  COMMENTS:
//
//    This function makes several assumptions:
//    - The number dialed will always explicitly come from the user.
//    - There will only be one outgoing address per line.
//

BOOL DialCall(LPSTR lpDisplay, LPSTR lpDialable, LPDWORD pdwDeviceID, HANDLE hEvent)
{
    long lReturn;
    LPLINEADDRESSSTATUS lpLineAddressStatus = NULL;
    LPLINEDEVCAPS lpLineDevCaps = NULL;

    g_bCallCancel = FALSE;

    if (g_bTapiInUse)
    {

        TSHELL_INFO(TEXT("A call is already being handled"));

        return FALSE;
    }


    g_hConnectionEvent = hEvent;
    // If TAPI isn't initialized, its either because we couldn't initialize
    // at startup (and this might have been corrected by now), or because
    // a REINIT event was received.  In either case, try to init now.

    if (!g_hLineApp)
    {
        if (!InitializeTAPI(NULL))
            return FALSE;
    }

    // If there are no line devices installed on the machine, lets give
    // the user the opportunity to install one.
    if (g_dwNumDevs < 1)
    {
        if (!HandleNoDevicesInstalled())
            return FALSE;
    }

    // We now have a call active.  Prevent future calls.
    g_bTapiInUse = TRUE;

    //
    // See if we can find the users Window.
    //
    {
        HWND hWndTop;
        DWORD   dwWindowProcId;
        DWORD   dwMyProcId;



        hWndTop = GetActiveWindow();
        dwMyProcId = GetCurrentProcessId();
        GetWindowThreadProcessId( hWndTop, &dwWindowProcId);

        DBG_INFO((DBGARG, TEXT("My process is %8x and the active window proc is %8x"),
            dwMyProcId, dwWindowProcId));

        if (dwMyProcId == dwWindowProcId)
            g_hDlgParentWindow = hWndTop;
        else
        {
            hWndTop = GetTopWindow(NULL);

            GetWindowThreadProcessId( hWndTop, &dwWindowProcId);

            if (dwMyProcId == dwWindowProcId)
                g_hDlgParentWindow = hWndTop;

            DBG_INFO((DBGARG,  TEXT("My process is %8x and the top window proc is %8x"),
                dwMyProcId, dwWindowProcId));
        }


    }


    if (lpDialable[0])
    {
        DBG_INFO((DBGARG, TEXT("Dialing with old data (%s)\r\n"), lpDialable));

        //
        // We were supplied with remembered data.  Use that.
        //
        lstrcpy( g_szDialableAddress, lpDialable);
        if ((lReturn = GetDefaultLine()) < 0)
            return(FALSE);

        g_dwDeviceID = (DWORD) lReturn;

    }
    else
    {

        TSHELL_INFO( TEXT("Get number from user"));

        // Get a phone number from the user.
        // Phone number will be placed in global variables if successful
        if (!GetAddressToDial())
        {
            g_bCallCancel = TRUE;
            HangupCall(__LINE__);
            TSHELL_INFO(TEXT("User didn't cooperate, bailing out."));
            goto DeleteBuffers;
        }
        lstrcpy( lpDisplay , g_szDisplayableAddress);
        lstrcpy( lpDialable, g_szDialableAddress   );
        *pdwDeviceID = g_dwDeviceID;

    }

    // Negotiate the API version to use for this device.
    g_dwAPIVersion = I_lineNegotiateAPIVersion(g_dwDeviceID);

    if (g_dwAPIVersion == 0)
    {
        HangupCall(__LINE__);
        TSHELL_INFO(TEXT("Line Version problem."));
        goto DeleteBuffers;
    }

    // Need to check the DevCaps to make sure this line is usable.
    // The 'Dial' dialog checks also, but better safe than sorry.
    lpLineDevCaps = I_lineGetDevCaps(lpLineDevCaps,
        g_dwDeviceID, g_dwAPIVersion);
    if (lpLineDevCaps == NULL)
    {
        HangupCall(__LINE__);
        TSHELL_INFO(TEXT("No useable line."));
        goto DeleteBuffers;
    }

    if (!(lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE ))
    {
        HangupCall(__LINE__);
        goto DeleteBuffers;
    }

    if (!(lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM))
    {
        HangupCall(__LINE__);
        TSHELL_INFO(TEXT("No Datamodem capacity."));
        goto DeleteBuffers;
    }

    // Does this line have the capability to make calls?
    // It is possible that some lines can't make outbound calls.
    if (!(lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL))
    {
        HangupCall(__LINE__);
        TSHELL_INFO(TEXT("Can't make calls on the device."));
        goto DeleteBuffers;
    }

    // Open the Line for an outgoing DATAMODEM call.
    do
    {
        TSHELL_INFO(TEXT("Opening line for Datamodem service."));

        lReturn = lineOpen(g_hLineApp, g_dwDeviceID, &g_hLine,
            g_dwAPIVersion, 0, 0,
            LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM,
            0);

        if(lReturn == LINEERR_ALLOCATED)
        {
            HangupCall(__LINE__);
            TSHELL_INFO(TEXT("Fatal Error"));
            goto DeleteBuffers;
        }

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineOpen unhandled error: %x"), lReturn));

            HangupCall(__LINE__);
            goto DeleteBuffers;
        }
    }
    while(lReturn != SUCCESS);

    TSHELL_INFO(TEXT("Line is OPEN."));


    // Tell the service provider that we want all notifications that
    // have anything to do with this line.
    do
    {
        // Set the messages we are interested in.

        // Note that while most applications aren't really interested
        // in dealing with all of the possible messages, its interesting
        // to see which come through the callback for testing purposes.

        lReturn = lineSetStatusMessages(g_hLine,
            LINEDEVSTATE_OTHER          |
            LINEDEVSTATE_RINGING        |
            LINEDEVSTATE_CONNECTED      |  // Important state!
            LINEDEVSTATE_DISCONNECTED   |  // Important state!
            LINEDEVSTATE_MSGWAITON      |
            LINEDEVSTATE_MSGWAITOFF     |
            LINEDEVSTATE_INSERVICE      |
            LINEDEVSTATE_OUTOFSERVICE   |  // Important state!
            LINEDEVSTATE_MAINTENANCE    |  // Important state!
            LINEDEVSTATE_OPEN           |
            LINEDEVSTATE_CLOSE          |
            LINEDEVSTATE_NUMCALLS       |
            LINEDEVSTATE_NUMCOMPLETIONS |
            LINEDEVSTATE_TERMINALS      |
            LINEDEVSTATE_ROAMMODE       |
            LINEDEVSTATE_BATTERY        |
            LINEDEVSTATE_SIGNAL         |
            LINEDEVSTATE_DEVSPECIFIC    |
            LINEDEVSTATE_REINIT         |  // Not allowed to disable this.
            LINEDEVSTATE_LOCK           |
            LINEDEVSTATE_CAPSCHANGE     |
            LINEDEVSTATE_CONFIGCHANGE   |
            LINEDEVSTATE_COMPLCANCEL    ,

            LINEADDRESSSTATE_OTHER      |
            LINEADDRESSSTATE_DEVSPECIFIC|
            LINEADDRESSSTATE_INUSEZERO  |
            LINEADDRESSSTATE_INUSEONE   |
            LINEADDRESSSTATE_INUSEMANY  |
            LINEADDRESSSTATE_NUMCALLS   |
            LINEADDRESSSTATE_FORWARD    |
            LINEADDRESSSTATE_TERMINALS  |
            LINEADDRESSSTATE_CAPSCHANGE);


        if (HandleLineErr(lReturn))
            continue;
        else
        {
            // If we do get an unhandled problem, we don't care.
            // We just won't get notifications.

            DBG_INFO((DBGARG, TEXT("lineSetStatusMessages unhandled error: %x"), lReturn));
            break;
        }
    }
    while(lReturn != SUCCESS);

    // Get LineAddressStatus so we can make sure the line
    // isn't already in use by a TAPI application.
    lpLineAddressStatus =
        I_lineGetAddressStatus(lpLineAddressStatus, g_hLine, 0);

    if (lpLineAddressStatus == NULL)
    {
        TSHELL_INFO(TEXT("Fatal Error"));
        HangupCall(__LINE__);
        goto DeleteBuffers;
    }

    // MAKECALL will be set if there are any available call appearances
    if ( ! ((lpLineAddressStatus -> dwAddressFeatures) &
            LINEADDRFEATURE_MAKECALL) )
    {

        TSHELL_INFO(TEXT("This line is not available to place a call."));

        HangupCall(__LINE__);
        goto DeleteBuffers;
    }

    // If the line was configured in the 'Dial' dialog, then
    // we need to actually complete the configuration.
    if (g_lpDeviceConfig)
        lineSetDevConfig(g_dwDeviceID, g_lpDeviceConfig,
            g_dwSizeDeviceConfig, "comm/datamodem");

    // Start dialing the number
    if (DialCallInParts(lpLineDevCaps, g_szDialableAddress,
            g_szDisplayableAddress))
    {
        TSHELL_INFO(TEXT("DialCallInParts succeeded."));
    }
    else
    {

        TSHELL_INFO(TEXT("DialCallInParts failed."));

        HangupCall(__LINE__);
        goto DeleteBuffers;
    }


DeleteBuffers:

    if (lpLineAddressStatus)
        LocalFree(lpLineAddressStatus);
    if (lpLineDevCaps)
        LocalFree(lpLineDevCaps);

    return g_bTapiInUse;
}


//**************************************************
// These APIs are specific to this module
//**************************************************



//
//  FUNCTION: DialCallInParts(LPLINEDEVCAPS, LPCSTR, LPCSTR)
//
//  PURPOSE: Dials the call, handling special characters.
//
//  PARAMETERS:
//    lpLineDevCaps - LINEDEVCAPS for the line to be used.
//    lpszAddress   - Address to Dial.
//    lpszDisplayableAddress - Displayable Address.
//
//  RETURN VALUE:
//    Returns TRUE if we successfully Dial.
//
//  COMMENTS:
//
//    This function dials the Address and handles any
//    special characters in the address that the service provider
//    can't handle.  It requires input from the user to handle
//    these characters; this can cause problems for fully automated
//    dialing.
//
//    Note that we can return TRUE, even if we don't reach a
//    CONNECTED state.  DIalCallInParts returns as soon as the
//    Address is fully dialed or when an error occurs.
//
//

#ifdef WINNT
#define Xstrcspn strcspn
#else
//
// Source for strcspn here because it isn't in C10 std lib.
//
static size_t __cdecl Xstrcspn (
        const char * string,
        const char * control
        )
{
        const unsigned char *str = (const unsigned char *) string;
        const unsigned char *ctrl = (const unsigned char *) control;

        unsigned char map[32];
        int count;

        /* Clear out bit map */
        for (count=0; count<32; count++)
                map[count] = 0;

        /* Set bits in control map */
        while (*ctrl)
        {
                map[*ctrl >> 3] |= (1 << (*ctrl & 7));
                ctrl++;
        }


        /* 1st char in control map stops search */
        count=0;
        map[0] |= 1;    /* null chars not considered */
        while (!(map[*str >> 3] & (1 << (*str & 7))))
        {
                count++;
                str++;
        }
        return(count);
}
#endif


BOOL DialCallInParts(LPLINEDEVCAPS lpLineDevCaps,
    LPCSTR lpszAddress, LPCSTR lpszDisplayableAddress)
{
    LPLINECALLPARAMS  lpCallParams = NULL;
    LPLINEADDRESSCAPS lpAddressCaps = NULL;
    LPLINECALLSTATUS  lpLineCallStatus = NULL;

    long lReturn;
    int i;
    DWORD dwDevCapFlags;
    char szFilter[1+sizeof(g_sNonDialable)] = "";
    BOOL bFirstDial = TRUE;

    // Variables to handle Dialable Substring dialing.
    LPSTR lpDS; // This is just so we can free lpszDialableSubstring later.
    LPSTR lpszDialableSubstring;
    int nAddressLength = 0;
    int nCurrentAddress = 0;
    char chUnhandledCharacter;

    // Get the capabilities for the line device we're going to use.
    lpAddressCaps = I_lineGetAddressCaps(lpAddressCaps,
        g_dwDeviceID, 0, g_dwAPIVersion, 0);

    if (lpAddressCaps == NULL)
        return FALSE;

    // Setup our CallParams for DATAMODEM settings.
    lpCallParams = CreateCallParams (lpCallParams, lpszDisplayableAddress);
    if (lpCallParams == NULL)
        return FALSE;

    // Determine which special characters the service provider
    // does *not* handle so we can handle them manually.
    // Keep list of unhandled characters in szFilter.

    dwDevCapFlags = lpLineDevCaps -> dwDevCapFlags;  // SP handled characters.
    for (i = 0; i < g_sizeofNonDialable ; i++)
    {
        if ((dwDevCapFlags & g_sNonDialable[i].dwDevCapFlag) == 0)
        {
            strcat(szFilter, g_sNonDialable[i].szToken);
        }
    }

    // szFilter now contains the set of tokens which delimit dialable substrings

    // Setup the strings for substring dialing.

    nAddressLength = strlen(lpszAddress);
    lpDS = lpszDialableSubstring = (LPSTR) LocalAlloc(LPTR, nAddressLength + 1);
    if (lpszDialableSubstring == NULL)
    {
        DBG_INFO((DBGARG, TEXT("LocalAlloc failed: %x"), GetLastError()));

        HandleNoMem();
        goto errExit;
    }

    // Lets start dialing substrings!
    while (nCurrentAddress < nAddressLength)
    {
  retryAfterError:

        // Find the next undialable character
        i = Xstrcspn(&lpszAddress[nCurrentAddress], szFilter);

        // Was there one before the end of the Address string?
        if (i + nCurrentAddress < nAddressLength)
        {
            // Make sure this device can handle partial dial.
            if (! (lpAddressCaps -> dwAddrCapFlags &
                   LINEADDRCAPFLAGS_PARTIALDIAL))
            {
                goto errExit;
            }
            // Remember what the unhandled character is so we can handle it.
            chUnhandledCharacter = lpszAddress[nCurrentAddress+i];

            // Copy the dialable string to the Substring.
            memcpy(lpszDialableSubstring, &lpszAddress[nCurrentAddress], i);

            // Terminate the substring with a ';' to signify the partial dial.
            lpszDialableSubstring[i] = ';';
            lpszDialableSubstring[i+1] = '\0';

            // Increment the address for next iteration.
            nCurrentAddress += i + 1;
        }
        else // No more partial dials.  Dial the rest of the Address.
        {
            lpszDialableSubstring = (LPSTR) &lpszAddress[nCurrentAddress];
            chUnhandledCharacter = 0;
            nCurrentAddress = nAddressLength;
        }

        do
        {
            if (bFirstDial)
            {
                DBG_INFO((DBGARG, TEXT("lineMakeCall %8s %8x"), lpszDialableSubstring, lpCallParams));

                lReturn = WaitForReply(
                    lineMakeCall(g_hLine, &g_hCall, lpszDialableSubstring,
                        0, lpCallParams) );

            }
            else
            {
                DBG_INFO((DBGARG, TEXT("lineDial %8x %8s"), g_hCall, lpszDialableSubstring));

                lReturn = WaitForReply(
                    lineDial(g_hCall, lpszDialableSubstring, 0) );
            }

            DBG_INFO((DBGARG, TEXT("LineDial return %8x"), lReturn));

            switch(lReturn)
            {
            // We should not have received these errors because of the
            // prefiltering strategy, but there may be some ill-behaved
            // service providers which do not correctly set their
            // devcapflags.  Add the character corresponding to the error
            // to the filter set and retry dialing.
            //
            case LINEERR_DIALBILLING:
            case LINEERR_DIALDIALTONE:
            case LINEERR_DIALQUIET:
            case LINEERR_DIALPROMPT:
                {

                    TSHELL_INFO(TEXT("Service Provider incorrectly sets dwDevCapFlags"));


                    for (i = 0; i < g_sizeofNonDialable; i++)
                        if (lReturn == g_sNonDialable[i].lError)
                        {
                            strcat(szFilter, g_sNonDialable[i].szToken);
                        }

                    goto retryAfterError;
                }

            case WAITERR_WAITABORTED:

                    TSHELL_INFO(TEXT("While Dialing, WaitForReply aborted."));

                    goto errExit;

            }

            if (HandleLineErr(lReturn))
                continue;
            else
            {
#ifdef DEBUG
                if (bFirstDial)
                    DBG_INFO((DBGARG, TEXT("lineMakeCall unhandled error: %x"), lReturn));
                else
                    DBG_INFO((DBGARG, TEXT("lineDial unhandled error: %x"), lReturn));
#endif
                TSHELL_INFO(TEXT("Error Exit!"));

                goto errExit;
            }

        }
        while (lReturn != SUCCESS);

        bFirstDial = FALSE;

        // The dial was successful; now handle characters the service
        // provider didn't (if any).
        if (chUnhandledCharacter)
        {
            LPSTR lpMsg = "";

            // First, wait until we know we can continue dialing.  While the
            // last string is still pending to be dialed, we can't dial another.

            while(TRUE)
            {

                lpLineCallStatus = I_lineGetCallStatus(lpLineCallStatus, g_hCall);
                if (lpLineCallStatus == NULL)
                    goto errExit;

                // Does CallStatus say we can dial now?
                if ((lpLineCallStatus->dwCallFeatures) & LINECALLFEATURE_DIAL)
                {

                    TSHELL_INFO(TEXT("Ok to continue dialing."));

                    break;
                }

                // We can't dial yet, so wait for a CALLSTATE message

                TSHELL_INFO(TEXT("Waiting for dialing to be enabled."));


                if (WaitForCallState(I_LINECALLSTATE_ANY) != SUCCESS)
                    goto errExit;
            }

            for (i = 0; i < g_sizeofNonDialable; i++)
                if (chUnhandledCharacter == g_sNonDialable[i].szToken[0])
                    lpMsg = g_sNonDialable[i].szMsg;

            TCHAR   achTitle[MAX_PATH];

            LoadString(hInst, IDS_DIALDIALOG, achTitle, MAX_PATH);

            MessageBox(g_hDlgParentWindow, lpMsg, achTitle, MB_OK);
        }

    } // continue dialing until we dial all Dialable Substrings.

    LocalFree(lpCallParams);
    LocalFree(lpDS);
    LocalFree(lpAddressCaps);
    if (lpLineCallStatus)
        LocalFree(lpLineCallStatus);

    return TRUE;

  errExit:
        // if lineMakeCall has already been successfully called, there's a call in progress.
        // let the invoking routine shut down the call.
        // if the invoker did not clean up the call, it should be done here.

    if (lpLineCallStatus)
        LocalFree(lpLineCallStatus);
    if (lpDS)
        LocalFree(lpDS);
    if (lpCallParams)
        LocalFree(lpCallParams);
    if (lpAddressCaps)
        LocalFree(lpAddressCaps);

    return FALSE;
}


//
//  FUNCTION: CreateCallParams(LPLINECALLPARAMS, LPCSTR)
//
//  PURPOSE: Allocates and fills a LINECALLPARAMS structure
//
//  PARAMETERS:
//    lpCallParams -
//    lpszDisplayableAddress -
//
//  RETURN VALUE:
//    Returns a LPLINECALLPARAMS ready to use for dialing DATAMODEM calls.
//    Returns NULL if unable to allocate the structure.
//
//  COMMENTS:
//
//    If a non-NULL lpCallParams is passed in, it must have been allocated
//    with LocalAlloc, and can potentially be freed and reallocated.  It must
//    also have the dwTotalSize field correctly set.
//
//

LPLINECALLPARAMS CreateCallParams (
    LPLINECALLPARAMS lpCallParams, LPCSTR lpszDisplayableAddress)
{
    size_t sizeDisplayableAddress;

    if (lpszDisplayableAddress == NULL)
        lpszDisplayableAddress = "";

    sizeDisplayableAddress = strlen(lpszDisplayableAddress) + 1;

    lpCallParams = (LPLINECALLPARAMS) CheckAndReAllocBuffer(
        (LPVOID) lpCallParams,
        sizeof(LINECALLPARAMS) + sizeDisplayableAddress,
        TEXT("CreateCallParams: "));

    if (lpCallParams == NULL)
        return NULL;

    // This is where we configure the line for DATAMODEM usage.
    lpCallParams -> dwBearerMode = LINEBEARERMODE_VOICE;
    lpCallParams -> dwMediaMode  = LINEMEDIAMODE_DATAMODEM;

    // This specifies that we want to use only IDLE calls and
    // don't want to cut into a call that might not be IDLE (ie, in use).
    lpCallParams -> dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;

    // if there are multiple addresses on line, use first anyway.
    // It will take a more complex application than a simple tty app
    // to use multiple addresses on a line anyway.
    lpCallParams -> dwAddressMode = LINEADDRESSMODE_ADDRESSID;
    lpCallParams -> dwAddressID = 0;

    // Since we don't know where we originated, leave these blank.
    lpCallParams -> dwOrigAddressSize = 0;
    lpCallParams -> dwOrigAddressOffset = 0;

    // Unimodem ignores these values.
    (lpCallParams -> DialParams) . dwDialSpeed = 0;
    (lpCallParams -> DialParams) . dwDigitDuration = 0;
    (lpCallParams -> DialParams) . dwDialPause = 0;
    (lpCallParams -> DialParams) . dwWaitForDialtone = 0;

    // Address we are dialing.
    lpCallParams -> dwDisplayableAddressOffset = sizeof(LINECALLPARAMS);
    lpCallParams -> dwDisplayableAddressSize = sizeDisplayableAddress;
    strcpy((LPSTR)lpCallParams + sizeof(LINECALLPARAMS),
           lpszDisplayableAddress);

    return lpCallParams;
}


//
//  FUNCTION: long WaitForReply(long)
//
//  PURPOSE: Resynchronize by waiting for a LINE_REPLY
//
//  PARAMETERS:
//    lRequestID - The asynchronous request ID that we're
//                 on a LINE_REPLY for.
//
//  RETURN VALUE:
//    - 0 if LINE_REPLY responded with a success.
//    - LINEERR constant if LINE_REPLY responded with a LINEERR
//    - 1 if the line was shut down before LINE_REPLY is received.
//
//  COMMENTS:
//
//    This function allows us to resynchronize an asynchronous
//    TAPI line call by waiting for the LINE_REPLY message.  It
//    waits until a LINE_REPLY is received or the line is shut down.
//
//    Note that this could cause re-entrancy problems as
//    well as mess with any message preprocessing that might
//    occur on this thread (such as TranslateAccelerator).
//
//    This function should to be called from the thread that did
//    lineInitialize, or the PeekMessage is on the wrong thread
//    and the synchronization is not guaranteed to work.  Also note
//    that if another PeekMessage loop is entered while waiting,
//    this could also cause synchronization problems.
//
//    One more note.  This function can potentially be re-entered
//    if the call is dropped for any reason while waiting.  If this
//    happens, just drop out and assume the wait has been canceled.
//    This is signaled by setting bReentered to FALSE when the function
//    is entered and TRUE when it is left.  If bReentered is ever TRUE
//    during the function, then the function was re-entered.
//
//    This function times out and returns WAITERR_WAITTIMEDOUT
//    after WAITTIMEOUT milliseconds have elapsed.
//
//


long WaitForReply (long lRequestID)
{
    static BOOL bReentered;
    bReentered = FALSE;

    if (lRequestID > SUCCESS)
    {
        MSG msg;
        DWORD dwTimeStarted;

        g_bReplyRecieved = FALSE;
        g_dwRequestedID = (DWORD) lRequestID;

        // Initializing this just in case there is a bug
        // that sets g_bReplyRecieved without setting the reply value.
        g_lAsyncReply = LINEERR_OPERATIONFAILED;

        dwTimeStarted = GetTickCount();

        while(!g_bReplyRecieved)
        {
            if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }

            // This should only occur if the line is shut down while waiting.
            if (!g_bTapiInUse || bReentered)
            {
                bReentered = TRUE;
                return WAITERR_WAITABORTED;
            }

            // Its a really bad idea to timeout a wait for a LINE_REPLY.
            // If we are execting a LINE_REPLY, we should wait till we get
            // it; it might take a long time to dial (for example).

            // If 5 seconds go by without a reply, it might be a good idea
            // to display a dialog box to tell the user that a
            // wait is in progress and to give the user the capability to
            // abort the wait.
        }

        bReentered = TRUE;
        return g_lAsyncReply;
    }

    bReentered = TRUE;
    return lRequestID;
}


//
//  FUNCTION: long WaitForCallState(DWORD)
//
//  PURPOSE: Wait for the line to reach a specific CallState.
//
//  PARAMETERS:
//    dwDesiredCallState - specific CallState to wait for.
//
//  RETURN VALUE:
//    Returns 0 (SUCCESS) when we reach the Desired CallState.
//    Returns WAITERR_WAITTIMEDOUT if timed out.
//    Returns WAITERR_WAITABORTED if call was closed while waiting.
//
//  COMMENTS:
//
//    This function allows us to synchronously wait for a line
//    to reach a specific LINESTATE or until the line is shut down.
//
//    Note that this could cause re-entrancy problems as
//    well as mess with any message preprocessing that might
//    occur on this thread (such as TranslateAccelerator).
//
//    One more note.  This function can potentially be re-entered
//    if the call is dropped for any reason while waiting.  If this
//    happens, just drop out and assume the wait has been canceled.
//    This is signaled by setting bReentered to FALSE when the function
//    is entered and TRUE when it is left.  If bReentered is ever TRUE
//    during the function, then the function was re-entered.
//
//    This function should to be called from the thread that did
//    lineInitialize, or the PeekMessage is on the wrong thread
//    and the synchronization is not guaranteed to work.  Also note
//    that if another PeekMessage loop is entered while waiting,
//    this could also cause synchronization problems.
//
//    If the constant value I_LINECALLSTATE_ANY is used for the
//    dwDesiredCallState, then WaitForCallState will return SUCCESS
//    upon receiving any CALLSTATE messages.
//
//
//

long WaitForCallState(DWORD dwDesiredCallState)
{
    MSG msg;
    DWORD dwTimeStarted;
    static BOOL bReentered;

    bReentered = FALSE;

    dwTimeStarted = GetTickCount();

    g_bCallStateReceived = FALSE;

    while ((dwDesiredCallState == I_LINECALLSTATE_ANY) ||
           (g_dwCallState != dwDesiredCallState))
    {
        if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // If we are waiting for any call state and get one, succeed.
        if ((dwDesiredCallState == I_LINECALLSTATE_ANY) &&
            g_bCallStateReceived)
        {
            break;
        }

        // This should only occur if the line is shut down while waiting.
        if (!g_bTapiInUse || bReentered)
        {
            bReentered = TRUE;

            TSHELL_INFO(TEXT("WAITABORTED"));

            return WAITERR_WAITABORTED;
        }

        // If we don't get the reply in a reasonable time, we time out.
        if (GetTickCount() - dwTimeStarted > WAITTIMEOUT)
        {
            bReentered = TRUE;

            TSHELL_INFO(TEXT("WAITTIMEDOUT"));

            return WAITERR_WAITTIMEDOUT;
        }

    }

    bReentered = TRUE;
    return SUCCESS;
}

//**************************************************
// lineCallback Function and Handlers.
//**************************************************


//
//  FUNCTION: lineCallbackFunc(..)
//
//  PURPOSE: Receive asynchronous TAPI events
//
//  PARAMETERS:
//    dwDevice  - Device associated with the event, if any
//    dwMsg     - TAPI event that occurred.
//    dwCallbackInstance - User defined data supplied when opening the line.
//    dwParam1  - dwMsg specific information
//    dwParam2  - dwMsg specific information
//    dwParam3  - dwMsg specific information
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    This is the function where all asynchronous events will come.
//    Almost all events will be specific to an open line, but a few
//    will be general TAPI events (such as LINE_REINIT).
//
//    Its important to note that this callback will *ALWAYS* be
//    called in the context of the thread that does the lineInitialize.
//    Even if another thread (such as the COMM threads) calls the API
//    that would result in the callback being called, it will be called
//    in the context of the main thread (since in this sample, the main
//    thread does the lineInitialize).
//
//


void CALLBACK lineCallbackFunc(
    DWORD dwDevice, DWORD dwMsg, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3)
{

#ifdef DEBUG
    OutputDebugLineCallback(
        dwDevice, dwMsg, dwCallbackInstance,
        dwParam1, dwParam2, dwParam3);
#endif

    // All we do is dispatch the dwMsg to the correct handler.
    switch(dwMsg)
    {
        case LINE_CALLSTATE:
            DoLineCallState(dwDevice, dwMsg, dwCallbackInstance,
                dwParam1, dwParam2, dwParam3);
            break;

        case LINE_CLOSE:
            DoLineClose(dwDevice, dwMsg, dwCallbackInstance,
                dwParam1, dwParam2, dwParam3);
            break;

        case LINE_LINEDEVSTATE:
            DoLineDevState(dwDevice, dwMsg, dwCallbackInstance,
                dwParam1, dwParam2, dwParam3);
            break;

        case LINE_REPLY:
            DoLineReply(dwDevice, dwMsg, dwCallbackInstance,
                dwParam1, dwParam2, dwParam3);
            break;

        case LINE_CREATE:
            DoLineCreate(dwDevice, dwMsg, dwCallbackInstance,
                dwParam1, dwParam2, dwParam3);
            break;

    default:

            TSHELL_INFO(TEXT("lineCallbackFunc message ignored"));

            break;

    }

    return;

}


//
//  FUNCTION: DoLineReply(..)
//
//  PURPOSE: Handle LINE_REPLY asynchronous messages.
//
//  PARAMETERS:
//    dwDevice  - Line Handle associated with this LINE_REPLY.
//    dwMsg     - Should always be LINE_REPLY.
//    dwCallbackInstance - Unused by this sample.
//    dwParam1  - Asynchronous request ID.
//    dwParam2  - success or LINEERR error value.
//    dwParam3  - Unused.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    All line API calls that return an asynchronous request ID
//    will eventually cause a LINE_REPLY message.  Handle it.
//
//    This sample assumes only one call at time, and that we wait
//    for a LINE_REPLY before making any other line API calls.
//
//    The only exception to the above is that we might shut down
//    the line before receiving a LINE_REPLY.
//
//

void DoLineReply(
    DWORD dwDevice, DWORD dwMessage, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3)
{

#ifdef DEBUG
    if ((long) dwParam2 != SUCCESS)
        DBG_INFO((DBGARG, TEXT("LINE_REPLY error: %x"), (long) dwParam2));
    else
        TSHELL_INFO(TEXT("LINE_REPLY: successfully replied."));
#endif

    // If we are currently waiting for this async Request ID
    // then set the global variables to acknowledge it.
    if (g_dwRequestedID == dwParam1)
    {
        g_bReplyRecieved = TRUE;
        g_lAsyncReply = (long) dwParam2;
    }
}


//
//  FUNCTION: DoLineClose(..)
//
//  PURPOSE: Handle LINE_CLOSE asynchronous messages.
//
//  PARAMETERS:
//    dwDevice  - Line Handle that was closed.
//    dwMsg     - Should always be LINE_CLOSE.
//    dwCallbackInstance - Unused by this sample.
//    dwParam1  - Unused.
//    dwParam2  - Unused.
//    dwParam3  - Unused.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    This message is sent when something outside our app shuts
//    down a line in use.
//
//    The hLine (and any hCall on this line) are no longer valid.
//
//

void DoLineClose(
    DWORD dwDevice, DWORD dwMessage, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3)
{
    // Line has been shut down.  Clean up our internal variables.
    g_hLine = NULL;
    g_hCall = NULL;
    HangupCall(__LINE__);
}


//
//  FUNCTION: DoLineDevState(..)
//
//  PURPOSE: Handle LINE_LINEDEVSTATE asynchronous messages.
//
//  PARAMETERS:
//    dwDevice  - Line Handle that was closed.
//    dwMsg     - Should always be LINE_LINEDEVSTATE.
//    dwCallbackInstance - Unused by this sample.
//    dwParam1  - LINEDEVSTATE constant.
//    dwParam2  - Depends on dwParam1.
//    dwParam3  - Depends on dwParam1.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    The LINE_LINEDEVSTATE message is received if the state of the line
//    changes.  Examples are RINGING, MAINTENANCE, MSGWAITON.  Very few of
//    these are relevant to this sample.
//
//    Assuming that any LINEDEVSTATE that removes the line from use by TAPI
//    will also send a LINE_CLOSE message.
//
//

void DoLineDevState(
    DWORD dwDevice, DWORD dwMessage, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3)
{
    switch(dwParam1)
    {
    case LINEDEVSTATE_RINGING:

            TSHELL_INFO(TEXT("Line Ringing."));

            break;

        case LINEDEVSTATE_REINIT:
        // This is an important case!  Usually means that a service provider
        // has changed in such a way that requires TAPI to REINIT.
        // Note that there are both 'soft' REINITs and 'hard' REINITs.
        // Soft REINITs don't actually require a full shutdown but is instead
        // just an informational change that historically required a REINIT
        // to force the application to deal with.  TAPI API Version 1.3 apps
        // will still need to do a full REINIT for both hard and soft REINITs.

            switch(dwParam2)
            {
                // This is the hard REINIT.  No reason given, just REINIT.
                // TAPI is waiting for everyone to shutdown.
                // Our response is to immediately shutdown any calls,
                // shutdown our use of TAPI and notify the user.
                case 0:
                    ShutdownTAPI();
                    TSHELL_INFO(TEXT("Tapi line configuration has changed."));
                    break;

            case LINE_CREATE:

                    TSHELL_INFO(TEXT("Soft REINIT: LINE_CREATE."));

                    DoLineCreate(dwDevice, (DWORD)dwParam2, dwCallbackInstance,
                        dwParam3, 0, 0);
                    break;

            case LINE_LINEDEVSTATE:

                    TSHELL_INFO(TEXT("Soft REINIT: LINE_LINEDEVSTATE."));

                    DoLineDevState(dwDevice, (DWORD)dwParam2, dwCallbackInstance,
                        dwParam3, 0, 0);
                    break;

                // There might be other reasons to send a soft reinit.
                // No need to to shutdown for these.
            default:

                    TSHELL_INFO(TEXT("Ignoring soft REINIT"));

                    break;
            }
            break;

        case LINEDEVSTATE_OUTOFSERVICE:
            TSHELL_INFO(TEXT("Line selected is now Out of Service."));
            HangupCall(__LINE__);
            break;

        case LINEDEVSTATE_DISCONNECTED:
            TSHELL_INFO(TEXT("Line selected is now disconnected."));
            HangupCall(__LINE__);
            break;

        case LINEDEVSTATE_MAINTENANCE:
            TSHELL_INFO(TEXT("Line selected is now out for maintenance."));
            HangupCall(__LINE__);
            break;

        case LINEDEVSTATE_TRANSLATECHANGE:
            if (g_hDialog)
                PostMessage(g_hDialog, WM_COMMAND, IDC_CONFIGURATIONCHANGED, 0);
            break;

        case LINEDEVSTATE_REMOVED:

            TSHELL_INFO(TEXT("A Line device has been removed;")
                " no action taken.");

            break;

        default:
            TSHELL_INFO(TEXT("Unhandled LINEDEVSTATE message"));
    }
}


//
//  FUNCTION: DoLineCreate(..)
//
//  PURPOSE: Handle LINE_LINECREATE asynchronous messages.
//
//  PARAMETERS:
//    dwDevice  - Unused.
//    dwMsg     - Should always be LINE_CREATE.
//    dwCallbackInstance - Unused.
//    dwParam1  - dwDeviceID of new Line created.
//    dwParam2  - Unused.
//    dwParam3  - Unused.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    This message is new for Windows 95.  It is sent when a new line is
//    added to the system.  This allows us to handle new lines without having
//    to REINIT.  This allows for much more graceful Plug and Play.
//
//    This sample just changes the number of devices available and can use
//    it next time a call is made.  It also tells the "Dial" dialog.
//
//

void DoLineCreate(
    DWORD dwDevice, DWORD dwMessage, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3)
{
    // dwParam1 is the Device ID of the new line.
    // Add one to get the number of total devices.
    if (g_dwNumDevs <= dwParam1)
        g_dwNumDevs = (DWORD)dwParam1+1;
    if (g_hDialog)
        PostMessage(g_hDialog, WM_COMMAND, IDC_LINECREATE, 0);

}


//
//  FUNCTION: DoLineCallState(..)
//
//  PURPOSE: Handle LINE_CALLSTATE asynchronous messages.
//
//  PARAMETERS:
//    dwDevice  - Handle to Call who's state is changing.
//    dwMsg     - Should always be LINE_CALLSTATE.
//    dwCallbackInstance - Unused by this sample.
//    dwParam1  - LINECALLSTATE constant specifying state change.
//    dwParam2  - Specific to dwParam1.
//    dwParam3  - LINECALLPRIVILEGE change, if any.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    This message is received whenever a call changes state.  Lots of
//    things we do, ranging from notifying the user to closing the line
//    to actually connecting to the target of our phone call.
//
//    What we do is usually obvious based on the call state change.
//

void DoLineCallState(
    DWORD dwDevice, DWORD dwMessage, DWORD_PTR dwCallbackInstance,
    DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwParam3)
{
    LONG    lErr;


    // This sets the global g_dwCallState variable so if we are waiting
    // for a specific call state change, we will know when it happens.
    g_dwCallState = (DWORD)dwParam1;
    g_bCallStateReceived = TRUE;

    // dwParam3 contains changes to LINECALLPRIVILEGE, if there are any.
    switch (dwParam3)
    {
        case 0:
            break; // no change to call state

         // close line if we are made monitor.  Shouldn't happen!
    case LINECALLPRIVILEGE_MONITOR:

            TSHELL_INFO(TEXT("line given monitor privilege; closing"));

            HangupCall(__LINE__);
            return;

         // close line if we are made owner.  Shouldn't happen!
    case LINECALLPRIVILEGE_OWNER:

            TSHELL_INFO(TEXT("line given owner privilege; Ready to Answer"));

            break;

    default: // Shouldn't happen!  All cases handled.

            TSHELL_INFO(TEXT("Unknown LINECALLPRIVILEGE message: closing"));

            HangupCall(__LINE__);
            return;
    }

    // dwParam1 is the specific CALLSTATE change that is occurring.
    switch (dwParam1)
    {
    case LINECALLSTATE_OFFERING:

            TSHELL_INFO(TEXT("Line Offered"));

            g_hCall = (HCALL) dwDevice;
            lErr = lineAccept(g_hCall, NULL, 0);

#ifdef DEBUG
            if (lErr < 0)
                TSHELL_INFO(TEXT("lineAccept in Offering failed."));
#endif

            lErr = lineAnswer(g_hCall, NULL, 0);

#ifdef DEBUG
            if (lErr < 0)
                TSHELL_INFO(TEXT("lineAnswer in Offering failed."));
#endif

            break;

    case LINECALLSTATE_ACCEPTED:

            TSHELL_INFO(TEXT("Line Accepted"));

            g_hCall = (HCALL) dwDevice;
            lErr = lineAnswer(g_hCall, NULL, 0);
#ifdef DEBUG
            if (lErr < 0)
                TSHELL_INFO(TEXT("lineAnswer in Accepted failed."));
#endif

            break;

    case LINECALLSTATE_DIALTONE:

            TSHELL_INFO(TEXT("Dial Tone"));

            break;

    case LINECALLSTATE_DIALING:

            TSHELL_INFO(TEXT("Dialing"));

            break;

    case LINECALLSTATE_PROCEEDING:

            TSHELL_INFO(TEXT("Proceeding"));

            break;

    case LINECALLSTATE_RINGBACK:

            TSHELL_INFO(TEXT("RingBack"));

            break;

    case LINECALLSTATE_BUSY:

            TSHELL_INFO(TEXT("Line busy, shutting down"));

            HangupCall(__LINE__);
            if (g_hConnectionEvent)
                SetEvent(g_hConnectionEvent);
            break;

    case LINECALLSTATE_IDLE:

            TSHELL_INFO(TEXT("Line idle"));

            HangupCall(__LINE__);
            if (g_hConnectionEvent)
                SetEvent(g_hConnectionEvent);
            break;

    case LINECALLSTATE_SPECIALINFO:

            TSHELL_INFO(
                TEXT("Special Info, probably couldn't dial number"));

            HangupCall(__LINE__);
            if (g_hConnectionEvent)
                SetEvent(g_hConnectionEvent);
            break;

        case LINECALLSTATE_DISCONNECTED:
        {
            LPTSTR pszReasonDisconnected;

            switch (dwParam2)
            {
                case LINEDISCONNECTMODE_NORMAL:
                    pszReasonDisconnected = TEXT("Remote Party Disconnected");
                    break;

                case LINEDISCONNECTMODE_UNKNOWN:
                    pszReasonDisconnected = TEXT("Disconnected: Unknown reason");
                    break;

                case LINEDISCONNECTMODE_REJECT:
                    pszReasonDisconnected = TEXT("Remote Party rejected call");
                    break;

                case LINEDISCONNECTMODE_PICKUP:
                    pszReasonDisconnected =
                        TEXT("Disconnected: Local phone picked up");
                    break;

                case LINEDISCONNECTMODE_FORWARDED:
                    pszReasonDisconnected = TEXT("Disconnected: Forwarded");
                    break;

                case LINEDISCONNECTMODE_BUSY:
                    pszReasonDisconnected = TEXT("Disconnected: Busy");
                    break;

                case LINEDISCONNECTMODE_NOANSWER:
                    pszReasonDisconnected = TEXT("Disconnected: No Answer");
                    break;

                case LINEDISCONNECTMODE_BADADDRESS:
                    pszReasonDisconnected = TEXT("Disconnected: Bad Address");
                    break;

                case LINEDISCONNECTMODE_UNREACHABLE:
                    pszReasonDisconnected = TEXT("Disconnected: Unreachable");
                    break;

                case LINEDISCONNECTMODE_CONGESTION:
                    pszReasonDisconnected = TEXT("Disconnected: Congestion");
                    break;

                case LINEDISCONNECTMODE_INCOMPATIBLE:
                    pszReasonDisconnected = TEXT("Disconnected: Incompatible");
                    break;

                case LINEDISCONNECTMODE_UNAVAIL:
                    pszReasonDisconnected = TEXT("Disconnected: Unavail");
                    break;

                case LINEDISCONNECTMODE_NODIALTONE:
                    pszReasonDisconnected = TEXT("Disconnected: No Dial Tone");
                    break;

                default:
                    pszReasonDisconnected =
                        TEXT("Disconnected: LINECALLSTATE; Bad Reason");
                    break;

            }


            TSHELL_INFO(pszReasonDisconnected);

            PostHangupCall();
            if (g_hConnectionEvent)
                SetEvent(g_hConnectionEvent);
            break;
        }


        case LINECALLSTATE_CONNECTED:  // CONNECTED!!!
        {
            LPVARSTRING lpVarString = NULL;
            DWORD dwSizeofVarString = sizeof(VARSTRING) + 128;
            HANDLE hCommFile = NULL;
            long lReturn;

            // Very first, make sure this isn't a duplicated message.
            // A CALLSTATE message can be sent whenever there is a
            // change to the capabilities of a line, meaning that it is
            // possible to receive multiple CONNECTED messages per call.
            // The CONNECTED CALLSTATE message is the only one in TapiComm
            // where it would cause problems if it where sent more
            // than once.

            if (g_bConnected)
                break;

            g_bConnected = TRUE;

            // Get the handle to the comm port from the driver so we can start
            // communicating.  This is returned in a LPVARSTRING structure.
            do
            {
                // Allocate the VARSTRING structure
                lpVarString = (LPVARSTRING) CheckAndReAllocBuffer((LPVOID) lpVarString,
                    dwSizeofVarString, TEXT("lineGetID: "));

                if (lpVarString == NULL)
                    goto ErrorConnecting;

                // Fill the VARSTRING structure
                lReturn = lineGetID(0, 0, g_hCall, LINECALLSELECT_CALL,
                    lpVarString, "comm/datamodem");

                if (HandleLineErr(lReturn))
                    ; // Still need to check if structure was big enough.
                else
                {
                    DBG_INFO((DBGARG, TEXT("lineGetID unhandled error: %x"), lReturn));

                    goto ErrorConnecting;
                }

                // If the VARSTRING wasn't big enough, loop again.
                if ((lpVarString -> dwNeededSize) > (lpVarString -> dwTotalSize))
                {
                    dwSizeofVarString = lpVarString -> dwNeededSize;
                    lReturn = -1; // Lets loop again.
                }
            }
            while(lReturn != SUCCESS);


            TSHELL_INFO(TEXT("Connected!  Starting communications!"));


            // Again, the handle to the comm port is contained in a
            // LPVARSTRING structure.  Thus, the handle is the very first
            // thing after the end of the structure.  Note that the name of
            // the comm port is right after the handle, but I don't want it.
            hCommFile =
                *((LPHANDLE)((LPBYTE)lpVarString +
                    lpVarString -> dwStringOffset));

            // Started communications!
            LPLINECALLINFO lpCInfo;

            lpCInfo = NULL;
            lpCInfo = I_lineGetCallInfo(lpCInfo);
            if (lpCInfo)
            {
                g_dwRate = lpCInfo->dwRate;
                LocalFree(lpCInfo);
            }
            if (StartComm(hCommFile, g_hConnectionEvent))
            {
                LocalFree(lpVarString);
                break;
            }

            // Couldn't start communications.  Clean up instead.
          ErrorConnecting:

            // Its very important that we close all Win32 handles.
            // The CommCode module is responsible for closing the hCommFile
            // handle if it succeeds in starting communications.
            if (hCommFile)
                CloseHandle(hCommFile);

            HangupCall(__LINE__);

            if (lpVarString)
                LocalFree(lpVarString);

            break;
        }

            default:

            TSHELL_INFO(TEXT("Unhandled LINECALLSTATE message"));

            break;
    }
}

//**************************************************
// line API Wrapper Functions.
//**************************************************


//
//  FUNCTION: LPVOID CheckAndReAllocBuffer(LPVOID, size_t, LPCSTR)
//
//  PURPOSE: Checks and ReAllocates a buffer if necessary.
//
//  PARAMETERS:
//    lpBuffer          - Pointer to buffer to be checked.  Can be NULL.
//    sizeBufferMinimum - Minimum size that lpBuffer should be.
//    szApiPhrase       - Phrase to print if an error occurs.
//
//  RETURN VALUE:
//    Returns a pointer to a valid buffer that is guarenteed to be
//    at least sizeBufferMinimum size.
//    Returns NULL if an error occured.
//
//  COMMENTS:
//
//    This function is a helper function intended to make all of the
//    line API Wrapper Functions much simplier.  It allocates (or
//    reallocates) a buffer of the requested size.
//
//    The returned pointer has been allocated with LocalAlloc,
//    so LocalFree has to be called on it when you're finished with it,
//    or there will be a memory leak.
//
//    Similarly, if a pointer is passed in, it *must* have been allocated
//    with LocalAlloc and it could potentially be LocalFree()d.
//
//    If lpBuffer == NULL, then a new buffer is allocated.  It is
//    normal to pass in NULL for this parameter the first time and only
//    pass in a pointer if the buffer needs to be reallocated.
//
//    szApiPhrase is used only for debugging purposes.
//
//    It is assumed that the buffer returned from this function will be used
//    to contain a variable sized structure.  Thus, the dwTotalSize field
//    is always filled in before returning the pointer.
//
//

LPVOID CheckAndReAllocBuffer(
    LPVOID lpBuffer, size_t sizeBufferMinimum, LPTCH szApiPhrase)
{
    size_t sizeBuffer;

    if (lpBuffer == NULL)  // Allocate the buffer if necessary.
    {
        sizeBuffer = sizeBufferMinimum;
        lpBuffer = (LPVOID) LocalAlloc(LPTR, sizeBuffer);

        if (lpBuffer == NULL)
        {
            DBG_INFO((DBGARG, TEXT("%s, LocalAlloc : %x"), szApiPhrase, GetLastError()));
            HandleNoMem();
            return NULL;
        }
    }
    else // If the structure already exists, make sure its good.
    {
        sizeBuffer = LocalSize((HLOCAL) lpBuffer);

        if (sizeBuffer == 0) // Bad pointer?
        {
            DBG_INFO((DBGARG, TEXT("%s, LocalSize : %x"), szApiPhrase, GetLastError()));
            return NULL;
        }

        // Was the buffer big enough for the structure?
        if (sizeBuffer < sizeBufferMinimum)
        {
            DBG_INFO((DBGARG, TEXT("%s, Reallocating structure"), szApiPhrase));
            LocalFree(lpBuffer);
            return CheckAndReAllocBuffer(NULL, sizeBufferMinimum, szApiPhrase);
        }

        // Lets zero the buffer out.
        memset(lpBuffer, 0, sizeBuffer);
    }

    ((LPVARSTRING) lpBuffer ) -> dwTotalSize = (DWORD) sizeBuffer;
    return lpBuffer;
}



//
//  FUNCTION: DWORD I_lineNegotiateAPIVersion(DWORD)
//
//  PURPOSE: Negotiate an API Version to use for a specific device.
//
//  PARAMETERS:
//    dwDeviceID - device to negotiate an API Version for.
//
//  RETURN VALUE:
//    Returns the API Version to use for this line if successful.
//    Returns 0 if negotiations fall through.
//
//  COMMENTS:
//
//    This wrapper function not only negotiates the API, but handles
//    LINEERR errors that can occur while negotiating.
//
//

DWORD I_lineNegotiateAPIVersion(DWORD dwDeviceID)
{
    LINEEXTENSIONID LineExtensionID;
    long lReturn;
    DWORD dwLocalAPIVersion;

    do
    {
        lReturn = lineNegotiateAPIVersion(g_hLineApp, dwDeviceID,
            SAMPLE_TAPI_VERSION, SAMPLE_TAPI_VERSION,
            &dwLocalAPIVersion, &LineExtensionID);

        // DBG_INFO((DBGARG, TEXT("version %8x, %8x, %8x %8x return %8x"),
        //    SAMPLE_TAPI_VERSION, SAMPLE_TAPI_VERSION,
        //    dwLocalAPIVersion, LineExtensionID, lReturn));

        if (lReturn == LINEERR_INCOMPATIBLEAPIVERSION)
        {

            TSHELL_INFO(TEXT("lineNegotiateAPIVersion, INCOMPATIBLEAPIVERSION."));

            return 0;
        }

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineNegotiateAPIVersion unhandled error: %x"), lReturn));
            return 0;
        }
    }
    while(lReturn != SUCCESS);

    return dwLocalAPIVersion;
}


//
//  FUNCTION: I_lineGetDevCaps(LPLINEDEVCAPS, DWORD , DWORD)
//
//  PURPOSE: Retrieve a LINEDEVCAPS structure for the specified line.
//
//  PARAMETERS:
//    lpLineDevCaps - Pointer to a LINEDEVCAPS structure to use.
//    dwDeviceID    - device to get the DevCaps for.
//    dwAPIVersion  - API Version to use while getting DevCaps.
//
//  RETURN VALUE:
//    Returns a pointer to a LINEDEVCAPS structure if successful.
//    Returns NULL if unsuccessful.
//
//  COMMENTS:
//
//    This function is a wrapper around lineGetDevCaps to make it easy
//    to handle the variable sized structure and any errors received.
//
//    The returned structure has been allocated with LocalAlloc,
//    so LocalFree has to be called on it when you're finished with it,
//    or there will be a memory leak.
//
//    Similarly, if a lpLineDevCaps structure is passed in, it *must*
//    have been allocated with LocalAlloc and it could potentially be
//    LocalFree()d.
//
//    If lpLineDevCaps == NULL, then a new structure is allocated.  It is
//    normal to pass in NULL for this parameter unless you want to use a
//    lpLineDevCaps that has been returned by a previous I_lineGetDevCaps
//    call.
//
//

LPLINECALLINFO I_lineGetCallInfo(LPLINECALLINFO lpLineCallInfo)
{
    size_t sizeofLineCallInfo = sizeof(LINECALLINFO) + 128;
    long lReturn;

    // Continue this loop until the structure is big enough.
    while(TRUE)
    {
        // Make sure the buffer exists, is valid and big enough.
        lpLineCallInfo =
            (LPLINECALLINFO) CheckAndReAllocBuffer(
                (LPVOID) lpLineCallInfo, // Pointer to existing buffer, if any
                sizeofLineCallInfo,      // Minimum size the buffer should be
                TEXT("lineCallInfo"));      // Phrase to tag errors, if any.

        if (lpLineCallInfo == NULL)
            return NULL;

        // Make the call to fill the structure.
        do
        {
            lReturn =
                lineGetCallInfo(g_hCall, lpLineCallInfo);

            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineGetCallInfo unhandled error: %x"), lReturn));

                LocalFree(lpLineCallInfo);
                return NULL;
            }
        }
        while (lReturn != SUCCESS);

        // If the buffer was big enough, then succeed.
        if ((lpLineCallInfo -> dwNeededSize) <= (lpLineCallInfo -> dwTotalSize))
            return lpLineCallInfo;

        // Buffer wasn't big enough.  Make it bigger and try again.
        sizeofLineCallInfo = lpLineCallInfo->dwNeededSize;
    }
}


LPLINEDEVCAPS I_lineGetDevCaps(
    LPLINEDEVCAPS lpLineDevCaps,
    DWORD dwDeviceID, DWORD dwAPIVersion)
{
    size_t sizeofLineDevCaps = sizeof(LINEDEVCAPS) + 128;
    long lReturn;

    // Continue this loop until the structure is big enough.
    while(TRUE)
    {
        // Make sure the buffer exists, is valid and big enough.
        lpLineDevCaps =
            (LPLINEDEVCAPS) CheckAndReAllocBuffer(
                (LPVOID) lpLineDevCaps, // Pointer to existing buffer, if any
                sizeofLineDevCaps,      // Minimum size the buffer should be
                TEXT("lineGetDevCaps"));      // Phrase to tag errors, if any.

        if (lpLineDevCaps == NULL)
            return NULL;

        // Make the call to fill the structure.
        do
        {
            lReturn =
                lineGetDevCaps(g_hLineApp,
                    dwDeviceID, dwAPIVersion, 0, lpLineDevCaps);

            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineGetDevCaps unhandled error: %x"), lReturn));

                LocalFree(lpLineDevCaps);
                return NULL;
            }
        }
        while (lReturn != SUCCESS);

        // If the buffer was big enough, then succeed.
        if ((lpLineDevCaps -> dwNeededSize) <= (lpLineDevCaps -> dwTotalSize))
            return lpLineDevCaps;

        // Buffer wasn't big enough.  Make it bigger and try again.
        sizeofLineDevCaps = lpLineDevCaps -> dwNeededSize;
    }
}



//
//  FUNCTION: I_lineGetAddressStatus(LPLINEADDRESSSTATUS, HLINE, DWORD)
//
//  PURPOSE: Retrieve a LINEADDRESSSTATUS structure for the specified line.


//
//  PARAMETERS:
//    lpLineAddressStatus - Pointer to a LINEADDRESSSTATUS structure to use.
//    hLine       - Handle of line to get the AddressStatus of.
//    dwAddressID - Address ID on the hLine to be used.
//
//  RETURN VALUE:
//    Returns a pointer to a LINEADDRESSSTATUS structure if successful.
//    Returns NULL if unsuccessful.
//
//  COMMENTS:
//
//    This function is a wrapper around lineGetAddressStatus to make it easy
//    to handle the variable sized structure and any errors received.
//
//    The returned structure has been allocated with LocalAlloc,
//    so LocalFree has to be called on it when you're finished with it,
//    or there will be a memory leak.
//
//    Similarly, if a lpLineAddressStatus structure is passed in, it *must*
//    have been allocated with LocalAlloc and it could potentially be
//    LocalFree()d.
//
//    If lpLineAddressStatus == NULL, then a new structure is allocated.  It
//    is normal to pass in NULL for this parameter unless you want to use a
//    lpLineAddressStatus that has been returned by previous
//    I_lineGetAddressStatus call.
//
//

LPLINEADDRESSSTATUS I_lineGetAddressStatus(
    LPLINEADDRESSSTATUS lpLineAddressStatus,
    HLINE hLine, DWORD dwAddressID)
{
    size_t sizeofLineAddressStatus = sizeof(LINEADDRESSSTATUS) + 128;
    long lReturn;

    // Continue this loop until the structure is big enough.
    while(TRUE)
    {
        // Make sure the buffer exists, is valid and big enough.
        lpLineAddressStatus =
            (LPLINEADDRESSSTATUS) CheckAndReAllocBuffer(
                (LPVOID) lpLineAddressStatus,
                sizeofLineAddressStatus,
                TEXT("lineGetAddressStatus"));

        if (lpLineAddressStatus == NULL)
            return NULL;

        // Make the call to fill the structure.
        do
        {
            lReturn =
                lineGetAddressStatus(hLine, dwAddressID, lpLineAddressStatus);

            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineGetAddressStatus unhandled error: %x"), lReturn));

                LocalFree(lpLineAddressStatus);
                return NULL;
            }
        }
        while (lReturn != SUCCESS);

        // If the buffer was big enough, then succeed.
        if ((lpLineAddressStatus -> dwNeededSize) <=
            (lpLineAddressStatus -> dwTotalSize))
        {
            return lpLineAddressStatus;
        }

        // Buffer wasn't big enough.  Make it bigger and try again.
        sizeofLineAddressStatus = lpLineAddressStatus -> dwNeededSize;
    }
}


//
//  FUNCTION: I_lineGetCallStatus(LPLINECALLSTATUS, HCALL)
//
//  PURPOSE: Retrieve a LINECALLSTATUS structure for the specified line.
//
//  PARAMETERS:
//    lpLineCallStatus - Pointer to a LINECALLSTATUS structure to use.
//    hCall - Handle of call to get the CallStatus of.
//
//  RETURN VALUE:
//    Returns a pointer to a LINECALLSTATUS structure if successful.
//    Returns NULL if unsuccessful.
//
//  COMMENTS:
//
//    This function is a wrapper around lineGetCallStatus to make it easy
//    to handle the variable sized structure and any errors received.
//
//    The returned structure has been allocated with LocalAlloc,
//    so LocalFree has to be called on it when you're finished with it,
//    or there will be a memory leak.
//
//    Similarly, if a lpLineCallStatus structure is passed in, it *must*
//    have been allocated with LocalAlloc and it could potentially be
//    LocalFree()d.
//
//    If lpLineCallStatus == NULL, then a new structure is allocated.  It
//    is normal to pass in NULL for this parameter unless you want to use a
//    lpLineCallStatus that has been returned by previous I_lineGetCallStatus
//    call.
//
//

LPLINECALLSTATUS I_lineGetCallStatus(
    LPLINECALLSTATUS lpLineCallStatus,
    HCALL hCall)
{
    size_t sizeofLineCallStatus = sizeof(LINECALLSTATUS) + 128;
    long lReturn;

    // Continue this loop until the structure is big enough.
    while(TRUE)
    {
        // Make sure the buffer exists, is valid and big enough.
        lpLineCallStatus =
            (LPLINECALLSTATUS) CheckAndReAllocBuffer(
                (LPVOID) lpLineCallStatus,
                sizeofLineCallStatus,
                TEXT("lineGetCallStatus"));

        if (lpLineCallStatus == NULL)
            return NULL;

        // Make the call to fill the structure.
        do
        {
            lReturn =
                lineGetCallStatus(hCall, lpLineCallStatus);

            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineGetCallStatus unhandled error: %x"), lReturn));
                LocalFree(lpLineCallStatus);
                return NULL;
            }
        }
        while (lReturn != SUCCESS);

        // If the buffer was big enough, then succeed.
        if ((lpLineCallStatus -> dwNeededSize) <=
            (lpLineCallStatus -> dwTotalSize))
        {
            return lpLineCallStatus;
        }

        // Buffer wasn't big enough.  Make it bigger and try again.
        sizeofLineCallStatus = lpLineCallStatus -> dwNeededSize;
    }
}


//
//  FUNCTION: I_lineTranslateAddress
//              (LPLINETRANSLATEOUTPUT, DWORD, DWORD, LPCSTR)
//
//  PURPOSE: Retrieve a LINECALLSTATUS structure for the specified line.
//
//  PARAMETERS:
//    lpLineTranslateOutput - Pointer to a LINETRANSLATEOUTPUT structure.
//    dwDeviceID      - Device that we're translating for.
//    dwAPIVersion    - API Version to use.
//    lpszDialAddress - pointer to the DialAddress string to translate.
//
//  RETURN VALUE:
//    Returns a pointer to a LINETRANSLATEOUTPUT structure if successful.
//    Returns NULL if unsuccessful.
//
//  COMMENTS:
//
//    This function is a wrapper around lineGetTranslateOutput to make it
//    easy to handle the variable sized structure and any errors received.
//
//    The returned structure has been allocated with LocalAlloc,
//    so LocalFree has to be called on it when you're finished with it,
//    or there will be a memory leak.
//
//    Similarly, if a lpLineTranslateOutput structure is passed in, it
//    *must* have been allocated with LocalAlloc and it could potentially be
//    LocalFree()d.
//
//    If lpLineTranslateOutput == NULL, then a new structure is allocated.
//    It is normal to pass in NULL for this parameter unless you want to use
//    a lpLineTranslateOutput that has been returned by previous
//    I_lineTranslateOutput call.
//
//

LPLINETRANSLATEOUTPUT I_lineTranslateAddress(
    LPLINETRANSLATEOUTPUT lpLineTranslateOutput,
    DWORD dwDeviceID, DWORD dwAPIVersion,
    LPCSTR lpszDialAddress)
{
    size_t sizeofLineTranslateOutput = sizeof(LINETRANSLATEOUTPUT) + 128;
    long lReturn;

    // Continue this loop until the structure is big enough.
    while(TRUE)
    {
        // Make sure the buffer exists, is valid and big enough.
        lpLineTranslateOutput =
            (LPLINETRANSLATEOUTPUT) CheckAndReAllocBuffer(
                (LPVOID) lpLineTranslateOutput,
                sizeofLineTranslateOutput,
                TEXT("lineTranslateOutput"));

        if (lpLineTranslateOutput == NULL)
            return NULL;

        // Make the call to fill the structure.
        do
        {
            // Note that CALLWAITING is disabled
            // (assuming the service provider can disable it)
            lReturn =
                lineTranslateAddress(g_hLineApp, dwDeviceID, dwAPIVersion,
                    lpszDialAddress, 0,
                    LINETRANSLATEOPTION_CANCELCALLWAITING,
                    lpLineTranslateOutput);

            // If the address isn't translatable, notify the user.
            if (lReturn == LINEERR_INVALADDRESS)
            {
                TCHAR   achTitle[MAX_PATH];
                TCHAR   achMsg[MAX_PATH];

                LoadString(hInst, IDS_WARNING, achTitle, MAX_PATH);
                LoadString(hInst, IDS_BADTRANSLATE, achMsg, MAX_PATH);

                MessageBox(g_hDlgParentWindow, achMsg, achTitle, MB_OK);
            }

            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineTranslateOutput unhandled error: %x"), lReturn));
                LocalFree(lpLineTranslateOutput);
                return NULL;
            }
        }
        while (lReturn != SUCCESS);

        // If the buffer was big enough, then succeed.
        if ((lpLineTranslateOutput -> dwNeededSize) <=
            (lpLineTranslateOutput -> dwTotalSize))
        {
            return lpLineTranslateOutput;
        }

        // Buffer wasn't big enough.  Make it bigger and try again.
        sizeofLineTranslateOutput = lpLineTranslateOutput -> dwNeededSize;
    }
}


//
//  FUNCTION: I_lineGetAddressCaps(LPLINEADDRESSCAPS, ..)
//
//  PURPOSE: Retrieve a LINEADDRESSCAPS structure for the specified line.
//
//  PARAMETERS:
//    lpLineAddressCaps - Pointer to a LINEADDRESSCAPS, or NULL.
//    dwDeviceID        - Device to get the address caps for.
//    dwAddressID       - This sample always assumes the first address.
//    dwAPIVersion      - API version negotiated for the device.
//    dwExtVersion      - Always 0 for this sample.
//
//  RETURN VALUE:
//    Returns a pointer to a LINEADDRESSCAPS structure if successful.
//    Returns NULL if unsuccessful.
//
//  COMMENTS:
//
//    This function is a wrapper around lineGetAddressCaps to make it easy
//    to handle the variable sized structure and any errors received.
//
//    The returned structure has been allocated with LocalAlloc,
//    so LocalFree has to be called on it when you're finished with it,
//    or there will be a memory leak.
//
//    Similarly, if a lpLineAddressCaps structure is passed in, it *must*
//    have been allocated with LocalAlloc and it could potentially be
//    LocalFree()d.  It also *must* have the dwTotalSize field set.
//
//    If lpLineAddressCaps == NULL, then a new structure is allocated.  It
//    is normal to pass in NULL for this parameter unless you want to use a
//    lpLineCallStatus that has been returned by previous I_lineGetAddressCaps
//    call.
//
//

LPLINEADDRESSCAPS I_lineGetAddressCaps (
    LPLINEADDRESSCAPS lpLineAddressCaps,
    DWORD dwDeviceID, DWORD dwAddressID,
    DWORD dwAPIVersion, DWORD dwExtVersion)
{
    size_t sizeofLineAddressCaps = sizeof(LINEADDRESSCAPS) + 128;
    long lReturn;

    // Continue this loop until the structure is big enough.
    while(TRUE)
    {
        // Make sure the buffer exists, is valid and big enough.
        lpLineAddressCaps =
            (LPLINEADDRESSCAPS) CheckAndReAllocBuffer(
                (LPVOID) lpLineAddressCaps,
                sizeofLineAddressCaps,
                TEXT("lineGetAddressCaps"));

        if (lpLineAddressCaps == NULL)
            return NULL;

        // Make the call to fill the structure.
        do
        {
            lReturn =
                lineGetAddressCaps(g_hLineApp,
                    dwDeviceID, dwAddressID, dwAPIVersion, dwExtVersion,
                    lpLineAddressCaps);

            if (HandleLineErr(lReturn))
                continue;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineGetAddressCaps unhandled error: %x"), lReturn));
                LocalFree(lpLineAddressCaps);
                return NULL;
            }
        }
        while (lReturn != SUCCESS);

        // If the buffer was big enough, then succeed.
        if ((lpLineAddressCaps -> dwNeededSize) <=
            (lpLineAddressCaps -> dwTotalSize))
        {
            return lpLineAddressCaps;
        }

        // Buffer wasn't big enough.  Make it bigger and try again.
        sizeofLineAddressCaps = lpLineAddressCaps -> dwNeededSize;
    }
}



//**************************************************
// LINEERR Error Handlers
//**************************************************


//
//  FUNCTION: HandleLineErr(long)
//
//  PURPOSE: Handle several standard LINEERR errors
//
//  PARAMETERS:
//    lLineErr - Error code to be handled.
//
//  RETURN VALUE:
//    Return TRUE if lLineErr wasn't an error, or if the
//      error was successfully handled and cleared up.
//    Return FALSE if lLineErr was an unhandled error.
//
//  COMMENTS:
//
//    This is the main error handler for all TAPI line APIs.
//    It handles (by correcting or just notifying the user)
//    most of the errors that can occur while using TAPI line APIs.
//
//    Note that many errors still return FALSE (unhandled) even
//    if a dialog is displayed.  Often, the dialog is just notifying
//    the user why the action was canceled.
//
//
//

BOOL HandleLineErr(long lLineErr)
{
    // lLineErr is really an async request ID, not an error.
    if (lLineErr > SUCCESS)
        return FALSE;

    // All we do is dispatch the correct error handler.
    switch(lLineErr)
    {
        case SUCCESS:
            return TRUE;

        case LINEERR_INVALCARD:
        case LINEERR_INVALLOCATION:
        case LINEERR_INIFILECORRUPT:
            return HandleIniFileCorrupt();

        case LINEERR_NODRIVER:
            return HandleNoDriver();

        case LINEERR_REINIT:
            return HandleReInit();

        case LINEERR_NOMULTIPLEINSTANCE:
            return HandleNoMultipleInstance();

        case LINEERR_NOMEM:
            return HandleNoMem();

        case LINEERR_OPERATIONFAILED:
            return HandleOperationFailed();

        case LINEERR_RESOURCEUNAVAIL:
            return HandleResourceUnavail();

        // Unhandled errors fail.
        default:
            return FALSE;
    }
}



//
//  FUNCTION: HandleIniFileCorrupt
//
//  PURPOSE: Handle INIFILECORRUPT error.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//
//    This error shouldn't happen under Windows 95 anymore.  The TAPI.DLL
//    takes care of correcting this problem.  If it does happen, just
//    notify the user.
//

BOOL HandleIniFileCorrupt()
{
    lineTranslateDialog(g_hLineApp, 0, SAMPLE_TAPI_VERSION,
        g_hDlgParentWindow, NULL);

    return TRUE;
}


//
//  FUNCTION: HandleNoDriver
//
//  PURPOSE: Handle NODRIVER error.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//
//

BOOL HandleNoDriver()
{
    return FALSE;
}


//
//  FUNCTION: HandleNoMultipleInstance
//
//  PURPOSE: Handle NOMULTIPLEINSTANCE error.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//
//

BOOL HandleNoMultipleInstance()
{
    return FALSE;
}


//
//  FUNCTION: HandleReInit
//
//  PURPOSE: Handle REINIT error.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//
//

BOOL HandleReInit()
{
    ShutdownTAPI();
    return FALSE;
}


//
//  FUNCTION: HandleNoMem
//
//  PURPOSE: Handle NOMEM error.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//    This is also called if I run out of memory for LocalAlloc()s
//
//

BOOL HandleNoMem()
{
    return FALSE;
}


//
//  FUNCTION: HandleOperationFailed
//
//  PURPOSE: Handle OPERATIONFAILED error.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//
//

BOOL HandleOperationFailed()
{
    TSHELL_INFO(TEXT("TAPI Operation Failed for unknown reasons."));
    return FALSE;
}


//
//  FUNCTION: HandleResourceUnavail
//
//  PURPOSE: Handle RESOURCEUNAVAIL error.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//
//

BOOL HandleResourceUnavail()
{
    return FALSE;
}


//
//  FUNCTION: HandleNoDevicesInstalled
//
//  PURPOSE: Handle cases when we know NODEVICE error
//    is returned because there are no devices installed.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - error was corrected.
//    FALSE - error was not corrected.
//
//  COMMENTS:
//
//    This function is not part of standard error handling
//    but is only used when we know that the NODEVICE error
//    means that no devices are installed.
//
//

BOOL HandleNoDevicesInstalled()
{
    if (LaunchModemControlPanelAdd())
        return TRUE;

    return FALSE;
}


//
//  FUNCTION: LaunchModemControlPanelAdd
//
//  PURPOSE: Launch Add Modem Control Panel applet.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE  - Control Panel launched successfully.
//    FALSE - It didn't.
//
//  COMMENTS:
//
//

BOOL LaunchModemControlPanelAdd()
{
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartupInfo;

    siStartupInfo.cb = sizeof(STARTUPINFO);
    siStartupInfo.lpReserved = NULL;
    siStartupInfo.lpDesktop = NULL;
    siStartupInfo.lpTitle = NULL;
    siStartupInfo.dwFlags = STARTF_USESHOWWINDOW;
    siStartupInfo.wShowWindow = SW_SHOWNORMAL;
    siStartupInfo.cbReserved2 = 0;
    siStartupInfo.lpReserved2 = NULL;

    // The string to launch the modem control panel is *VERY* likely
    // to change on NT.  If nothing else, this is 'contrl32' on NT
    // instead of 'control'.
    if (CreateProcess(
            NULL,
            "CONTROL.EXE MODEM.CPL,,ADD",
            NULL, NULL, FALSE,
            NORMAL_PRIORITY_CLASS,
            NULL, NULL,
            &siStartupInfo,
            &piProcInfo))
    {
        CloseHandle(piProcInfo.hThread);


        // Control panel 'Add New Modem' has been launched.  Now we should
        // wait for it to go away before continueing.

        // If we WaitForSingleObject for the control panel to exit, then we
        // get into a deadlock situation if we need to respond to any messages
        // from the control panel.

        // If we use a PeekMessage loop to wait, we run into
        // message re-entrancy problems.  (The user can get back to our UI
        // and click 'dial' again).

        // Instead, we take the easy way out and return FALSE to abort
        // the current operation.

        CloseHandle(piProcInfo.hProcess);
    }
    else
    {
        DBG_INFO((DBGARG, TEXT("Unable to LaunchModemControlPanelAdd: %x"), GetLastError()));

    }

    return FALSE;
}


//**************************************************
//
// All the functions from this point on are used solely by the "Dial" dialog.
// This dialog is used to get both the 'phone number' address,
// the line device to be used as well as allow the user to configure
// dialing properties and the line device.
//
//**************************************************

//
//  FUNCTION: DWORD I_lineNegotiateLegacyAPIVersion(DWORD)
//
//  PURPOSE: Negotiate an API Version to use for a specific device.
//
//  PARAMETERS:
//    dwDeviceID - device to negotiate an API Version for.
//
//  RETURN VALUE:
//    Returns the API Version to use for this line if successful.
//    Returns 0 if negotiations fall through.
//
//  COMMENTS:
//
//    This wrapper is slightly different from the I_lineNegotiateAPIVersion.
//    This wrapper allows TapiComm to negotiate an API version between
//    1.3 and SAMPLE_TAPI_VERSION.  Normally, this sample is specific to
//    API Version SAMPLE_TAPI_VERSION.  However, there are a few times when
//    TapiComm needs to get information from a service provider, but also knows
//    that a lower API Version would be ok.  This allows TapiComm to recognize
//    legacy service providers even though it can't use them.  1.3 is the
//    lowest API Version a legacy service provider should support.
//
//

DWORD I_lineNegotiateLegacyAPIVersion(DWORD dwDeviceID)
{
    LINEEXTENSIONID LineExtensionID;
    long lReturn;
    DWORD dwLocalAPIVersion;

    do
    {
        lReturn = lineNegotiateAPIVersion(g_hLineApp, dwDeviceID,
            0x00010003, SAMPLE_TAPI_VERSION,
            &dwLocalAPIVersion, &LineExtensionID);

        if (lReturn == LINEERR_INCOMPATIBLEAPIVERSION)
        {

            TSHELL_INFO(TEXT("INCOMPATIBLEAPIVERSION in Dial Dialog."));

            return 0;
        }

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineNegotiateAPIVersion in Dial Dialog unhandled error: %x"), lReturn));
            return 0;
        }
    }
    while(lReturn != SUCCESS);

    return dwLocalAPIVersion;
}


//
//  FUNCTION: long VerifyUsableLine(DWORD)
//
//  PURPOSE: Verifies that a specific line device is useable by TapiComm.
//
//  PARAMETERS:
//    dwDeviceID - The ID of the line device to be verified
//
//  RETURN VALUE:
//    Returns SUCCESS if dwDeviceID is a usable line device.
//    Returns a LINENOTUSEABLE_ constant otherwise.
//
//  COMMENTS:
//
//    VerifyUsableLine takes the give device ID and verifies step by step
//    that the device supports all the features that TapiComm requires.
//
//

long VerifyUsableLine(DWORD dwDeviceID)
{
    LPLINEDEVCAPS lpLineDevCaps = NULL;
    LPLINEADDRESSSTATUS lpLineAddressStatus = NULL;
    LPVARSTRING lpVarString = NULL;
    DWORD dwAPIVersion;
    long lReturn;
    long lUsableLine = SUCCESS;
    HLINE hLine = 0;

    DBG_INFO((DBGARG, TEXT("Testing Line ID '0x%lx'"),dwDeviceID));

    // The line device must support an API Version that TapiComm does.
    dwAPIVersion = I_lineNegotiateAPIVersion(dwDeviceID);
    if (dwAPIVersion == 0)
        return LINENOTUSEABLE_ERROR;

    lpLineDevCaps = I_lineGetDevCaps(lpLineDevCaps,
        dwDeviceID, dwAPIVersion);

    if (lpLineDevCaps == NULL)
        return LINENOTUSEABLE_ERROR;

    // Must support LINEBEARERMODE_VOICE
    if (!(lpLineDevCaps->dwBearerModes & LINEBEARERMODE_VOICE ))
    {
        lUsableLine = LINENOTUSEABLE_NOVOICE;

        TSHELL_INFO(TEXT("LINEBEARERMODE_VOICE not supported"));

        goto DeleteBuffers;
    }

    // Must support LINEMEDIAMODE_DATAMODEM
    if (!(lpLineDevCaps->dwMediaModes & LINEMEDIAMODE_DATAMODEM))
    {
        lUsableLine = LINENOTUSEABLE_NODATAMODEM;

        TSHELL_INFO(TEXT("LINEMEDIAMODE_DATAMODEM not supported"));

        goto DeleteBuffers;
    }

    // Must be able to make calls
    if (!(lpLineDevCaps->dwLineFeatures & LINEFEATURE_MAKECALL))
    {
        lUsableLine = LINENOTUSEABLE_NOMAKECALL;

        TSHELL_INFO(TEXT("LINEFEATURE_MAKECALL not supported"));

        goto DeleteBuffers;
    }

    // It is necessary to open the line so we can check if
    // there are any call appearances available.  Other TAPI
    // applications could be using all call appearances.
    // Opening the line also checks for other possible problems.
    do
    {
        lReturn = lineOpen(g_hLineApp, dwDeviceID, &hLine,
            dwAPIVersion, 0, 0,
            LINECALLPRIVILEGE_NONE, LINEMEDIAMODE_DATAMODEM,
            0);

        if(lReturn == LINEERR_ALLOCATED)
        {

            TSHELL_INFO(TEXT("Line is already in use by a non-TAPI app or another Service Provider."));

            lUsableLine = LINENOTUSEABLE_ALLOCATED;
            goto DeleteBuffers;
        }

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineOpen unhandled error: %x"), lReturn));
            lUsableLine = LINENOTUSEABLE_ERROR;
            goto DeleteBuffers;
        }
    }
    while(lReturn != SUCCESS);

    // Get LineAddressStatus to make sure the line isn't already in use.
    lpLineAddressStatus =
        I_lineGetAddressStatus(lpLineAddressStatus, hLine, 0);

    if (lpLineAddressStatus == NULL)
    {
        lUsableLine = LINENOTUSEABLE_ERROR;
        goto DeleteBuffers;
    }

    // Are there any available call appearances (ie: is it in use)?
    if ( !((lpLineAddressStatus -> dwAddressFeatures) &
           LINEADDRFEATURE_MAKECALL) )
    {

        TSHELL_INFO(TEXT("LINEADDRFEATURE_MAKECALL not available"));

        lUsableLine = LINENOTUSEABLE_INUSE;
        goto DeleteBuffers;
    }

    // Make sure the "comm/datamodem" device class is supported
    // Note that we don't want any of the 'extra' information
    // normally returned in the VARSTRING structure.  All we care
    // about is if lineGetID succeeds.
    do
    {
        lpVarString = (LPVARSTRING) CheckAndReAllocBuffer((LPVOID) lpVarString,
            sizeof(VARSTRING),TEXT("VerifyUsableLine:lineGetID: "));

        if (lpVarString == NULL)
        {
            lUsableLine = LINENOTUSEABLE_ERROR;
            goto DeleteBuffers;
        }

        lReturn = lineGetID(hLine, 0, 0, LINECALLSELECT_LINE,
            lpVarString, "comm/datamodem");

        if (HandleLineErr(lReturn))
            continue;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineGetID unhandled error: %x"), lReturn));
            lUsableLine = LINENOTUSEABLE_NOCOMMDATAMODEM;
            goto DeleteBuffers;
        }
    }
    while(lReturn != SUCCESS);


    TSHELL_INFO(TEXT("Line is suitable and available for use."));


  DeleteBuffers:

    if (hLine)
        lineClose(hLine);
    if (lpLineAddressStatus)
        LocalFree(lpLineAddressStatus);
    if (lpLineDevCaps)
        LocalFree(lpLineDevCaps);
    if (lpVarString)
        LocalFree(lpVarString);

    hLine = NULL;
    lpLineAddressStatus = NULL;
    lpLineDevCaps = NULL;
    lpVarString = NULL;
    return lUsableLine;
}


//
//  FUNCTION: void FillTAPILine(HWND)
//
//  PURPOSE: Fills the 'TAPI Line' control with the available line devices.
//
//  PARAMETERS:
//    hwndDlg - handle to the current "Dial" dialog
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    This function enumerates through all the TAPI line devices and
//    queries each for the device name.  The device name is then put into
//    the 'TAPI Line' control.  These device names are kept in order rather
//    than sorted.  This allows "Dial" to know which device ID the user
//    selected just by the knowing the index of the selected string.
//
//    There are default values if there isn't a device name, if there is
//    an error on the device, or if the device name is an empty string.
//    The device name is also checked to make sure it is null terminated.
//
//    Note that a Legacy API Version is negotiated.  Since the fields in
//    the LINEDEVCAPS structure that we are interested in haven't moved, we
//    can negotiate a lower API Version than this sample is designed for
//    and still be able to access the necessary structure members.
//
//    The first line that is usable by TapiComm is selected as the 'default'
//    line.  Also note that if there was a previously selected line, this
//    remains the default line.  This would likely only occur if this
//    function is called after the dialog has initialized once; for example,
//    if a new line is added.
//
//

void FillTAPILine(HWND hwndDlg)
{
    DWORD dwDeviceID;
    DWORD dwAPIVersion;
    LPLINEDEVCAPS lpLineDevCaps = NULL;
    char szLineUnavail[] = "Line Unavailable";
    char szLineUnnamed[] = "Line Unnamed";
    char szLineNameEmpty[] = "Line Name is Empty";
    LPSTR lpszLineName;
    long lReturn;
    DWORD dwDefaultDevice = MAXDWORD;

    // Make sure the control is empty.  If it isn't,
    // hold onto the currently selected ID and then reset it.
    if (SendDlgItemMessage(hwndDlg, IDC_TAPILINE, CB_GETCOUNT, 0, 0))
    {
        dwDefaultDevice = (DWORD)SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
            CB_GETCURSEL, 0, 0);
        SendDlgItemMessage(hwndDlg, IDC_TAPILINE, CB_RESETCONTENT, 0, 0);
    }

    for (dwDeviceID = 0; dwDeviceID < g_dwNumDevs; dwDeviceID ++)
    {
        dwAPIVersion = I_lineNegotiateLegacyAPIVersion(dwDeviceID);
        if (dwAPIVersion)
        {
            lpLineDevCaps = I_lineGetDevCaps(lpLineDevCaps,
                dwDeviceID, dwAPIVersion);
            if (lpLineDevCaps)
            {
                if ((lpLineDevCaps -> dwLineNameSize) &&
                    (lpLineDevCaps -> dwLineNameOffset) &&
                    (lpLineDevCaps -> dwStringFormat == STRINGFORMAT_ASCII))
                {
                    // This is the name of the device.
                    lpszLineName = ((char *) lpLineDevCaps) +
                        lpLineDevCaps -> dwLineNameOffset;

                    if (lpszLineName[0] != '\0')
                    {
        // Reverse indented to make this fit

        // Make sure the device name is null terminated.
        if (lpszLineName[lpLineDevCaps->dwLineNameSize -1] != '\0')
        {
            // If the device name is not null terminated, null
            // terminate it.  Yes, this looses the end character.
            // Its a bug in the service provider.
            lpszLineName[lpLineDevCaps->dwLineNameSize-1] = '\0';

            DBG_INFO((DBGARG, TEXT("Device name for device 0x%lx is not null terminated."),
                dwDeviceID));
        }
                    }
                    else // Line name started with a NULL.
                        lpszLineName = szLineNameEmpty;
                }
                else  // DevCaps doesn't have a valid line name.  Unnamed.
                    lpszLineName = szLineUnnamed;
            }
            else  // Couldn't GetDevCaps.  Line is unavail.
                lpszLineName = szLineUnavail;
        }
        else  // Couldn't NegotiateAPIVersion.  Line is unavail.
            lpszLineName = szLineUnavail;

        // Put the device name into the control
        lReturn = (long)SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
            CB_ADDSTRING, 0, (LPARAM) (LPCTSTR) lpszLineName);

        // If this line is usable and we don't have a default initial
        // line yet, make this the initial line.
        if ((lpszLineName != szLineUnavail) &&
            (dwDefaultDevice == MAXDWORD) &&
            (VerifyUsableLine(dwDeviceID) == SUCCESS))
        {
            dwDefaultDevice = dwDeviceID;
        }
    }

    if (lpLineDevCaps)
        LocalFree(lpLineDevCaps);

    if (dwDefaultDevice == MAXDWORD)
        dwDefaultDevice = 0;

    // Set the initial default line
    SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
        CB_SETCURSEL, dwDefaultDevice, 0);
}


//
//  FUNCTION: BOOL VerifyAndWarnUsableLine(HWND)
//
//  PURPOSE: Verifies the line device selected by the user.
//
//  PARAMETERS:
//    hwndDlg - The handle to the current "Dial" dialog.
//
//  RETURN VALUE:
//    Returns TRUE if the currently selected line device is useable
//      by TapiComm.  Returns FALSE if it isn't.
//
//  COMMENTS:
//
//    This function is very specific to the "Dial" dialog.  It gets
//    the device selected by the user from the 'TAPI Line' control and
//    VerifyUsableLine to make sure this line device is usable.  If the
//    line isn't useable, it notifies the user and disables the 'Dial'
//    button so that the user can't initiate a call with this line.
//
//    This function is also responsible for filling in the line specific
//    icon found on the "Dial" dialog.
//
//

BOOL VerifyAndWarnUsableLine(HWND hwndDlg)
{
    DWORD dwDeviceID;
    long lReturn;
    HICON hIcon = 0;
    HWND hControlWnd;

    // Get the selected line device.
    dwDeviceID = (DWORD)SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
                        CB_GETCURSEL, 0, 0);

    // Get the "comm" device icon associated with this line device.
    lReturn = lineGetIcon(dwDeviceID, "comm", &hIcon);

    if (lReturn == SUCCESS)
        SendDlgItemMessage(hwndDlg, IDC_LINEICON, STM_SETICON,
            (WPARAM) hIcon, 0);
    else
        // Any failure to get an icon makes us use the default icon.
        SendDlgItemMessage(hwndDlg, IDC_LINEICON, WM_SETTEXT,
            0, (LPARAM) (LPCTSTR) "TapiComm");

/*  // It turns out that TAPI will always return an icon, even if
    // the device class isn't supported by the TSP or even if the TSP
    // doesn't return any icons at all.  This code is unnecessary.
    // The only reason lineGetIcon would fail is due to resource problems.

    else
    {
        // If the line doesn't have a "comm" device icon, use its default one.
        lReturn = lineGetIcon(dwDeviceID, NULL, &hIcon);
        if (lReturn == SUCCESS)
        {

            TSHELL_INFO(TEXT("Line doesn't support a \"comm\" icon."));

            SendDlgItemMessage(hwndDlg, IDC_LINEICON, STM_SETICON,
                (WPARAM) hIcon, 0);
        }
        else
        {
            // If lineGetIcon fails, just use TapiComms icon.
            DBG_INFO((DBGARG, TEXT("lineGetIcon: %x"), lReturn));

            SendDlgItemMessage(hwndDlg, IDC_LINEICON, WM_SETTEXT,
                0, (LPARAM) (LPCTSTR) "TapiComm");
        }
    }
*/

    // Verify if the device is usable by TapiComm.
    lReturn = VerifyUsableLine(dwDeviceID);

    // Enable or disable the 'Dial' button, depending on if the line is ok.
    // Make sure there is a number to dial before enabling the button.
    hControlWnd = GetDlgItem(hwndDlg, IDC_DIAL);

    //
    // Store Canon
    //
    if (g_szTranslatedNumber[0] = 0x00)
    {
        EnableWindow(hControlWnd, FALSE);
    }
    else
        EnableWindow(hControlWnd, (lReturn == SUCCESS));

    // Any errors on this line prevent us from configuring it
    // or using dialing properties.
    if (lReturn == LINENOTUSEABLE_ERROR)
    {
        EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGURELINE), FALSE);
        EnableWindow(GetDlgItem(hwndDlg, IDC_DIALINGPROPERTIES), FALSE);
    }
    else
    {
        EnableWindow(GetDlgItem(hwndDlg, IDC_CONFIGURELINE), TRUE);
        if (SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES, BM_GETCHECK, 0, 0))
            EnableWindow(GetDlgItem(hwndDlg, IDC_DIALINGPROPERTIES), TRUE);
    }

    switch(lReturn)
    {
        case SUCCESS:
            g_dwDeviceID = dwDeviceID;
            return TRUE;

        case LINENOTUSEABLE_ERROR:
            TSHELL_INFO(TEXT("The selected line is incompatible with DirectPlay"));
            break;
        case LINENOTUSEABLE_NOVOICE:
            TSHELL_INFO(TEXT("The selected line doesn't support VOICE capabilities",));
            break;
        case LINENOTUSEABLE_NODATAMODEM:
            TSHELL_INFO(TEXT("The selected line doesn't support DATAMODEM capabilities",));
            break;
        case LINENOTUSEABLE_NOMAKECALL:
            TSHELL_INFO(TEXT("The selected line doesn't support MAKECALL capabilities",));
            break;
        case LINENOTUSEABLE_ALLOCATED:
            TSHELL_INFO(TEXT("The selected line is already in use by a non-TAPI application",));
            break;
        case LINENOTUSEABLE_INUSE:
            TSHELL_INFO(TEXT("The selected line is already in use by a TAPI application",));
            break;

        case LINENOTUSEABLE_NOCOMMDATAMODEM:
            TSHELL_INFO(TEXT("The selected line doesn't support the COMM/DATAMODEM device class",));
            break;
    }

    // g_dwDeviceID == MAXDWORD mean the selected device isn't usable.
    g_dwDeviceID = MAXDWORD;
    return FALSE;
}


//
//  FUNCTION: void FillCountryCodeList(HWND, DWORD)
//
//  PURPOSE: Fill the 'Country Code' control
//
//  PARAMETERS:
//    hwndDlg - handle to the current "Dial" dialog
//    dwDefaultCountryID - ID of the 'default' country to be selected
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    This function fills the 'Country Code' control with country names.
//    The country code is appended to the end of the name and the names
//    are added to the control sorted.  Because the country code is
//    embedded in the string along with the country name, there is no need
//    for any of the country information structures to be kept around.  The
//    country code can be extracted from the selected string at any time.
//
//

void FillCountryCodeList(HWND hwndDlg, DWORD dwDefaultCountryID)
{
    LPLINECOUNTRYLIST lpLineCountryList = NULL;
    DWORD dwSizeofCountryList = sizeof(LINECOUNTRYLIST);
    long lReturn;
    DWORD dwCountry;
    LPLINECOUNTRYENTRY lpLineCountryEntries;
    char szRenamedCountry[256];

    // Get the country information stored in TAPI
    do
    {
        lpLineCountryList = (LPLINECOUNTRYLIST) CheckAndReAllocBuffer(
            (LPVOID) lpLineCountryList, dwSizeofCountryList,
            TEXT("FillCountryCodeList"));

        if (lpLineCountryList == NULL)
            return;

        lReturn = lineGetCountry (0, SAMPLE_TAPI_VERSION, lpLineCountryList);

        if (HandleLineErr(lReturn))
            ;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineGetCountry unhandled error: %x"), lReturn));
            LocalFree(lpLineCountryList);
            return;
        }

        if ((lpLineCountryList -> dwNeededSize) >
            (lpLineCountryList -> dwTotalSize))
        {
            dwSizeofCountryList = lpLineCountryList ->dwNeededSize;
            lReturn = -1; // Lets loop again.
        }
    }
    while (lReturn != SUCCESS);

    // Find the first country entry
    lpLineCountryEntries = (LPLINECOUNTRYENTRY)
        (((LPBYTE) lpLineCountryList)
         + lpLineCountryList -> dwCountryListOffset);

    // Now enumerate through all the countries
    for (dwCountry = 0;
         dwCountry < lpLineCountryList -> dwNumCountries;
         dwCountry++)
    {
        // append the country code to the country name
        wsprintf(szRenamedCountry,"%s (%lu)",
            (((LPSTR) lpLineCountryList) +
                lpLineCountryEntries[dwCountry].dwCountryNameOffset),
            lpLineCountryEntries[dwCountry].dwCountryCode);

        // Now put this country name / code string into the combobox
        lReturn = (long)SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE, CB_ADDSTRING,
                    0, (LPARAM) (LPCTSTR) szRenamedCountry);

        // If this country is the default country, select it.
        if (lpLineCountryEntries[dwCountry].dwCountryID
            == dwDefaultCountryID)
        {
            SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE, CB_SETCURSEL, lReturn, 0);
        }
    }

    LocalFree(lpLineCountryList);
    return;
}


//
//  FUNCTION: void FillLocationInfo(HWND, LPSTR, LPDWORD, LPSTR)
//
//  PURPOSE: Fill (or refill) the 'Your Location' control
//
//  PARAMETERS:
//    hwndDlg - handle to the current "Dial" dialog
//    lpszCurrentLocation - Name of current location, or NULL
//    lpdwCountryID - location to store the current country ID or NULL
//    lpszAreaCode - location to store the current area code or NULL
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    This function is moderately multipurpose.
//
//    If lpszCurrentLocation is NULL, then the 'Your Location' control
//    is filled with all the locations stored in TAPI and the TAPI 'default'
//    location is selected.  This is done during initialization and
//    also after the 'Dialing Properties' dialog has been displayed.
//    This last is done because the user can change the current location
//    or add and delete locations while in the 'Dialing Properties' dialog.
//
//    If lpszCurrentLocation is a valid string pointer, then it is assumed
//    that the 'Your Location' control is already filled and that the user
//    is selecting a specific location.  In this case, all of the existing
//    TAPI locations are enumerated until the specified location is found.
//    At this point, the specified location is set to the current location.
//
//    In either case, if lpdwCountryID is not NULL, it is filled with the
//    country ID for the current location.  If lpszAreaCode is not NULL, it
//    is filled with the area code defined for the current location.  These
//    values can be used later to initialize other "Dial" controls.
//
//    This function also fills the 'Calling Card' control based on
//    the information stored in the current location.
//
//

void FillLocationInfo(HWND hwndDlg, LPSTR lpszCurrentLocation,
    LPDWORD lpdwCountryID, LPSTR lpszAreaCode)
{
    LPLINETRANSLATECAPS lpTranslateCaps = NULL;
    DWORD dwSizeofTranslateCaps = sizeof(LINETRANSLATECAPS);
    long lReturn;
    DWORD dwCounter;
    LPLINELOCATIONENTRY lpLocationEntry;
    LPLINECARDENTRY lpLineCardEntry = NULL;
    DWORD dwPreferredCardID = MAXDWORD;
    TCHAR   achMsg[MAX_PATH];

    // First, get the TRANSLATECAPS
    do
    {
        lpTranslateCaps = (LPLINETRANSLATECAPS) CheckAndReAllocBuffer(
            (LPVOID) lpTranslateCaps, dwSizeofTranslateCaps,
            TEXT(TEXT("FillLocationInfo")));

        if (lpTranslateCaps == NULL)
            return;

        lReturn = lineGetTranslateCaps(g_hLineApp, SAMPLE_TAPI_VERSION,
                    lpTranslateCaps);

        if (HandleLineErr(lReturn))
            ;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineGetTranslateCaps unhandled error: %x"), lReturn));
            LocalFree(lpTranslateCaps);
            return;
        }

        if ((lpTranslateCaps -> dwNeededSize) >
            (lpTranslateCaps -> dwTotalSize))
        {
            dwSizeofTranslateCaps = lpTranslateCaps ->dwNeededSize;
            lReturn = -1; // Lets loop again.
        }
    }
    while(lReturn != SUCCESS);

    // Find the location information in the TRANSLATECAPS
    lpLocationEntry = (LPLINELOCATIONENTRY)
        (((LPBYTE) lpTranslateCaps) + lpTranslateCaps->dwLocationListOffset);

    // If lpszCurrentLocation, then make that location 'current'
    if (lpszCurrentLocation)
    {
        // loop through all locations, looking for a location match
        for(dwCounter = 0;
            dwCounter < lpTranslateCaps -> dwNumLocations;
            dwCounter++)
        {
            if (strcmp((((LPSTR) lpTranslateCaps) +
                            lpLocationEntry[dwCounter].dwLocationNameOffset),
                        lpszCurrentLocation)
                == 0)
            {
                // Found it!  Set the current location.
                lineSetCurrentLocation(g_hLineApp,
                    lpLocationEntry[dwCounter].dwPermanentLocationID);

                // Set the return values.
                if (lpdwCountryID)
                    *lpdwCountryID = lpLocationEntry[dwCounter].dwCountryID;

                if (lpszAreaCode)
                    strcpy(lpszAreaCode, (((LPSTR) lpTranslateCaps) +
                            lpLocationEntry[dwCounter].dwCityCodeOffset));

                // Store the preferred card ID for later use.
                dwPreferredCardID = lpLocationEntry[dwCounter].dwPreferredCardID;
                break;
            }
        }

        // Was a match for lpszCurrentLocation found?
        if (dwPreferredCardID == MAXDWORD)
        {

            TSHELL_INFO(TEXT("lpszCurrentLocation not found"));

            LoadString( hInst, IDS_LOCATIONERR, achMsg, sizeof(achMsg));

            SendDlgItemMessage(hwndDlg, IDC_CALLINGCARD, WM_SETTEXT, 0,
                (LPARAM) achMsg);
            LocalFree(lpTranslateCaps);
            return;
        }
    }
    else // fill the combobox and use the TAPI 'current' location.
    {
        // First empty the combobox
        SendDlgItemMessage(hwndDlg, IDC_LOCATION, CB_RESETCONTENT, 0, 0);

        // enumerate all the locations
        for(dwCounter = 0;
            dwCounter < lpTranslateCaps -> dwNumLocations;
            dwCounter++)
        {
            // Put each one into the combobox
            lReturn = (long)SendDlgItemMessage(hwndDlg, IDC_LOCATION, CB_ADDSTRING,
                0, (LPARAM) (((LPBYTE) lpTranslateCaps) +
                    lpLocationEntry[dwCounter].dwLocationNameOffset));

            // Is this location the 'current' location?
            if (lpLocationEntry[dwCounter].dwPermanentLocationID ==
                lpTranslateCaps->dwCurrentLocationID)
            {
                // Return the requested information
                if (lpdwCountryID)
                    *lpdwCountryID = lpLocationEntry[dwCounter].dwCountryID;

                if (lpszAreaCode)
                    strcpy(lpszAreaCode, (((LPSTR) lpTranslateCaps) +
                            lpLocationEntry[dwCounter].dwCityCodeOffset));

                // Set this to be the active location.
                SendDlgItemMessage(hwndDlg, IDC_LOCATION, CB_SETCURSEL, lReturn, 0);
                dwPreferredCardID = lpLocationEntry[dwCounter].dwPreferredCardID;
            }
        }
    }

    // Now locate the prefered card and display it.

    lpLineCardEntry = (LPLINECARDENTRY)
        (((LPBYTE) lpTranslateCaps) + lpTranslateCaps->dwCardListOffset);

    for(dwCounter = 0;
        dwCounter < lpTranslateCaps -> dwNumCards;
        dwCounter++)
    {
        if (lpLineCardEntry[dwCounter].dwPermanentCardID == dwPreferredCardID)
        {
            SendDlgItemMessage(hwndDlg, IDC_CALLINGCARD, WM_SETTEXT, 0,
                (LPARAM) (((LPBYTE) lpTranslateCaps) +
                    lpLineCardEntry[dwCounter].dwCardNameOffset));
            break;
        }
    }

    LocalFree(lpTranslateCaps);
}



//
//  FUNCTION: void UseDialingRules(HWND)
//
//  PURPOSE: Enable/disable Dialing Rule controls
//
//  PARAMETERS:
//    hwndDlg - handle to the current "Dial" dialog
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    The sole purpose of this function is to enable or disable
//    the controls that apply to dialing rules if the
//    "Use Country Code and Area Code" checkbox is checked or unchecked,
//    as appropriate.
//
//

void UseDialingRules(HWND hwndDlg)
{
    HWND hControl;
    BOOL bEnableWindow;

    bEnableWindow = (BOOL)SendDlgItemMessage(hwndDlg,
        IDC_USEDIALINGRULES, BM_GETCHECK, 0, 0);

    hControl = GetDlgItem(hwndDlg, IDC_STATICCOUNTRYCODE);
    EnableWindow(hControl, bEnableWindow);

    hControl = GetDlgItem(hwndDlg, IDC_COUNTRYCODE);
    EnableWindow(hControl, bEnableWindow);

    hControl = GetDlgItem(hwndDlg, IDC_STATICAREACODE);
    EnableWindow(hControl, bEnableWindow);

    hControl = GetDlgItem(hwndDlg, IDC_AREACODE);
    EnableWindow(hControl, bEnableWindow);

    hControl = GetDlgItem(hwndDlg, IDC_STATICLOCATION);
    EnableWindow(hControl, bEnableWindow);

    hControl = GetDlgItem(hwndDlg, IDC_LOCATION);
    EnableWindow(hControl, bEnableWindow);

    hControl = GetDlgItem(hwndDlg, IDC_STATICCALLINGCARD);
    EnableWindow(hControl, bEnableWindow);

    hControl = GetDlgItem(hwndDlg, IDC_CALLINGCARD);
    EnableWindow(hControl, bEnableWindow);

    if (IsWindowEnabled(GetDlgItem(hwndDlg, IDC_CONFIGURELINE)))
    {
        hControl = GetDlgItem(hwndDlg, IDC_DIALINGPROPERTIES);
        EnableWindow(hControl, bEnableWindow);
    }
}


//
//  FUNCTION: void DisplayPhoneNumber(HWND)
//
//  PURPOSE: Create, Translate and Display the Phone Number
//
//  PARAMETERS:
//    hwndDlg - handle to the current "Dial" dialog
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    This function uses the information stored in many other controls
//    to build the phone number, translate it, and display it.  Also
//    makes sure the Dial button is enabled or disabled, based on if the
//    number can be dialed or not.
//
//    There are actually three phone numbers generated during this
//    process:  canonical, dialable and displayable.  Normally, only the
//    displayable number is shown to the user; the other two numbers are
//    to be used by the program internally.  However, for demonstration
//    purposes (and because it is cool for developers to see these numbers),
//    all three numbers are displayed.
//

void DisplayPhoneNumber(HWND hwndDlg)
{
    char szPreTranslatedNumber[128] = "";
    int  nPreTranslatedSize = 0;
    char szTempBuffer[512];
    int  i;
    DWORD dwDeviceID;
    LPLINETRANSLATEOUTPUT lpLineTranslateOutput = NULL;

    // Disable the 'dial' button if there isn't a number to dial
    if (0 == SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER,
            WM_GETTEXTLENGTH, 0, 0))
    {
        EnableWindow(GetDlgItem(hwndDlg, IDC_DIAL), FALSE);
        return;
    }

    // If we use the dialing rules, lets make canonical format.
    // Canonical format is explained in the TAPI documentation and the
    // string format needs to be followed very strictly.
    if (SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES,
        BM_GETCHECK, 0, 0))
    {
        // First character *has* to be the plus sign.
        szPreTranslatedNumber[0] = '+';
        nPreTranslatedSize = 1;

        // The country code *has* to be next.
        // Country code was stored in the string with the country
        // name and needs to be extracted at this point.
        i = (int)SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE,
                CB_GETCURSEL, 0, 0);
        SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE,
            CB_GETLBTEXT, (WPARAM) i, (LPARAM) (LPCTSTR) szTempBuffer);

        // Country code is at the end of the string, surounded by parens.
        // This makes it easy to identify the country code.
        i = strlen(szTempBuffer);
        while(szTempBuffer[--i] != '(');

        while(szTempBuffer[++i] != ')')
            szPreTranslatedNumber[nPreTranslatedSize++] = szTempBuffer[i];

        // Next is the area code.
        i = (int)SendDlgItemMessage(hwndDlg, IDC_AREACODE, WM_GETTEXT,
                510, (LPARAM) (LPCTSTR) szTempBuffer);

        // Note that the area code is optional.  If it is included,
        // then it has to be preceeded by *exactly* one space and it
        // *has* to be surrounded by parens.
        if (i)
            nPreTranslatedSize +=
                wsprintf(&szPreTranslatedNumber[nPreTranslatedSize],
                    " (%s)", szTempBuffer);

        // There has to be *exactly* one space before the rest of the number.
        szPreTranslatedNumber[nPreTranslatedSize++] = ' ';

        // At this point, the phone number is appended to the
        // canonical number.  The next step is the same whether canonical
        // format is used or not; just the prepended area code and
        // country code are different.
    }

    SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER, WM_GETTEXT,
        510, (LPARAM) (LPCTSTR) szTempBuffer);

    strcat(&szPreTranslatedNumber[nPreTranslatedSize], szTempBuffer);

    dwDeviceID = (DWORD)SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
                        CB_GETCURSEL, 0, 0);

    // Translate the address!
    lpLineTranslateOutput = I_lineTranslateAddress(
        lpLineTranslateOutput, dwDeviceID, SAMPLE_TAPI_VERSION,
        szPreTranslatedNumber);

    // Unable to translate it?
    if (lpLineTranslateOutput == NULL)
    {
        g_szTranslatedNumber[0]   = 0x00;
        g_szDisplayableAddress[0] = 0x00;
        g_szDialableAddress[0]    = 0x00;

        EnableWindow(GetDlgItem(hwndDlg, IDC_DIAL), FALSE);
        return;
    }

    // Is the selected device useable with TapiComm?
    if (g_dwDeviceID != MAXDWORD)
        EnableWindow(GetDlgItem(hwndDlg, IDC_DIAL), TRUE);

    // Fill the appropriate phone number controls.
    strcpy( g_szTranslatedNumber, szPreTranslatedNumber);
    strcpy( g_szDialableAddress, ((LPSTR) lpLineTranslateOutput +
            lpLineTranslateOutput -> dwDialableStringOffset));

    strcpy( g_szDisplayableAddress, ((LPSTR) lpLineTranslateOutput +
            lpLineTranslateOutput -> dwDisplayableStringOffset));

    LocalFree(lpLineTranslateOutput);

}


//
//  FUNCTION: void PreConfigureDevice(HWND, DWORD)
//
//  PURPOSE:
//
//  PARAMETERS:
//    hwndDlg - handle to the current "Dial" dialog
//    dwDeviceID - line device to be configured
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
//    At one point, PreConfigureDevice used lineConfigDialog to
//    configure the device.  This has the unfortunate effect of configuring
//    the device immediately, even if it is in use by another TAPI app.
//    This can be really bad if data communications are already in
//    progress (like with RAS).
//
//    Now, PreConfigureDevice uses lineConfigDialogEdit to give the
//    user the configuration UI, but it doesn't actually do anything to
//    the line device.  TapiComm stores the configuration information so
//    that it can be set later, just before making the call.
//
//

void PreConfigureDevice(HWND hwndDlg, DWORD dwDeviceID)
{
    long lReturn;
    LPVARSTRING lpVarString = NULL;
    DWORD dwSizeofVarString = sizeof(VARSTRING);

    // If there isn't already any device configuration information,
    // then we need to get some.
    if (g_lpDeviceConfig == NULL)
    {
        do
        {
            lpVarString = (LPVARSTRING) CheckAndReAllocBuffer(
                (LPVOID) lpVarString, dwSizeofVarString,
                TEXT("PreConfigureDevice - lineGetDevConfig: "));

            if (lpVarString == NULL)
                return;

            lReturn = lineGetDevConfig(dwDeviceID, lpVarString,
                "comm/datamodem");

            if (HandleLineErr(lReturn))
                ;
            else
            {
                DBG_INFO((DBGARG, TEXT("lineGetDevCaps unhandled error: %x"), lReturn));
                LocalFree(lpVarString);
                return;
            }

            if ((lpVarString -> dwNeededSize) > (lpVarString -> dwTotalSize))
            {
                dwSizeofVarString = lpVarString -> dwNeededSize;
                lReturn = -1; // Lets loop again.
            }
        }
        while (lReturn != SUCCESS);

        g_dwSizeDeviceConfig = lpVarString -> dwStringSize;

        // The extra byte allocated is in case dwStringSize is 0.
        g_lpDeviceConfig = CheckAndReAllocBuffer(
                g_lpDeviceConfig, g_dwSizeDeviceConfig+1,
                TEXT("PreConfigureDevice - Allocate device config: "));

        if (!g_lpDeviceConfig)
        {
            LocalFree(lpVarString);
            return;
        }

        memcpy(g_lpDeviceConfig,
            ((LPBYTE) lpVarString + lpVarString -> dwStringOffset),
            g_dwSizeDeviceConfig);
    }

    // Next make the lineConfigDialogEdit call.

    // Note that we determine the initial size of the VARSTRING
    // structure based on the known size of the existing configuration
    // information.  I make the assumption that this configuration
    // information is very unlikely to grow by more than 5K or by
    // more than 5 times.  This is a *very* conservative number.
    // We do *not* want lineConfigDialogEdit to fail just because there
    // wasn't enough room to stored the data.  This would require the user
    // to go through configuration again and that would be annoying.

    dwSizeofVarString = 5 * g_dwSizeDeviceConfig + 5000;

    do
    {
        lpVarString = (LPVARSTRING) CheckAndReAllocBuffer(
            (LPVOID) lpVarString, dwSizeofVarString,
            TEXT("PreConfigureDevice - lineConfigDialogEdit: "));

        if (lpVarString == NULL)
            return;

        lReturn = lineConfigDialogEdit(dwDeviceID, hwndDlg, "comm/datamodem",
            g_lpDeviceConfig, g_dwSizeDeviceConfig, lpVarString);

        if (HandleLineErr(lReturn))
            ;
        else
        {
            DBG_INFO((DBGARG, TEXT("lineConfigDialogEdit unhandled error: %x"), lReturn));
            LocalFree(lpVarString);
            return;
        }

        if ((lpVarString -> dwNeededSize) > (lpVarString -> dwTotalSize))
        {
            // We had been conservative about making sure the structure was
            // big enough.  Unfortunately, not conservative enough.  Hopefully,
            // this will not happen a second time because we are *DOUBLING*
            // the NeededSize.
            dwSizeofVarString = (lpVarString -> dwNeededSize) * 2;
            lReturn = -1; // Lets loop again.
        }
    }
    while (lReturn != SUCCESS);

    // Store the configuration information into a global structure
    // so it can be set at a later time.
    g_dwSizeDeviceConfig = lpVarString -> dwStringSize;
    g_lpDeviceConfig = CheckAndReAllocBuffer(
            g_lpDeviceConfig, g_dwSizeDeviceConfig+1,
            TEXT("PreConfigureDevice - Reallocate device config: "));

    if (!g_lpDeviceConfig)
    {
        LocalFree(lpVarString);
        return;
    }

    memcpy(g_lpDeviceConfig,
        ((LPBYTE) lpVarString + lpVarString -> dwStringOffset),
        g_dwSizeDeviceConfig);

    LocalFree(lpVarString);
}


//
//  FUNCTION: BOOL GetAddressToDial
//
//  PURPOSE: Get an address to dial from the user.
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    TRUE if a valid device and phone number have been entered by
//    the user.  FALSE if the user canceled the dialing process.
//
//  COMMENTS:
//
//    All this function does is launch the "Dial" dialog.
//
//

BOOL GetAddressToDial()
{
    BOOL bRet;

    bRet = (BOOL)DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_DIALDIALOG), g_hDlgParentWindow,
                                DialDialogProc, 0);
    g_hDialog = NULL;
    g_hDlgParentWindow = g_hWndMainWindow;


    return bRet;
}


//
//  FUNCTION: DialDialogProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Dialog callback procedure for the dialing dialog
//
//  PARAMETERS:
//    hwndDlg - Dialog calling the callback.
//    uMsg    - Dialog message.
//    wParam  - uMsg specific.
//    lParam  - uMsg specific.
//
//  RETURN VALUE:
//    returns 0 - command handled.
//    returns non-0 - command unhandled
//
//  COMMENTS:
//
//    This is the dialog to get the phone number and line device
//    from the user.  All the relavent information is stored in global
//    variables to be used later if the dialog returns successfully.
//
//


INT_PTR CALLBACK DialDialogProc(
    HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    // Static variables to store the information from last time the
    // "Dial" dialog was displayed.  That way the phone number can be
    // typed once but used several times.

    static TCHAR szCountryName[512]  = TEXT("");
    static TCHAR szAreaCode[256]     = TEXT("");
    static TCHAR szPhoneNumber[512]  = TEXT("");
    static DWORD dwUsedDeviceID     = MAXDWORD;
    static BOOL bUsedCountryAndArea = FALSE;
    static BOOL bHistoryValid       = FALSE;

    switch(uMsg)
    {
        case WM_INITDIALOG:
        {
            DWORD dwCountryID = 0;


            // Store the Dialog Window so it can be dismissed if necessary
            g_hDialog = hwndDlg;

            // This dialog should be parent to all dialogs.
            g_hDlgParentWindow = hwndDlg;

            // Initialize the Dialog Box. Lots to do here.

            FillTAPILine(hwndDlg);
            if (g_lpDeviceConfig)
            {
                LocalFree(g_lpDeviceConfig);
                g_lpDeviceConfig = NULL;
            }

            // If there is a valid history, use it to initialize the controls.
            if (bHistoryValid)
            {
                FillLocationInfo(hwndDlg, NULL, NULL, NULL);
                FillCountryCodeList(hwndDlg, 0);

                SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE, CB_SELECTSTRING,
                    (WPARAM) -1, (LPARAM) (LPCTSTR) szCountryName);

                SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER, WM_SETTEXT, 0,
                    (LPARAM) (LPCTSTR) szPhoneNumber);

                SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES,
                    BM_SETCHECK, (WPARAM) bUsedCountryAndArea, 0);

                SendDlgItemMessage(hwndDlg, IDC_TAPILINE, CB_SETCURSEL,
                    g_dwDeviceID, 0);
            }
            else
            {
                FillLocationInfo(hwndDlg, NULL, &dwCountryID, szAreaCode);
                FillCountryCodeList(hwndDlg, dwCountryID);
                SendDlgItemMessage(hwndDlg, IDC_USEDIALINGRULES,
                    BM_SETCHECK, 1, 0);
            }

            SendDlgItemMessage(hwndDlg, IDC_AREACODE, WM_SETTEXT,
                0, (LPARAM) (LPCTSTR) szAreaCode);

            UseDialingRules(hwndDlg);
            DisplayPhoneNumber(hwndDlg);
            VerifyAndWarnUsableLine(hwndDlg);

            return TRUE;
        }

        case WM_COMMAND:
        {
            switch(LOWORD(wParam))
            {
                case IDC_TAPILINE:
                    if (HIWORD(wParam) == CBN_SELENDOK)
                    {
                        if (g_lpDeviceConfig)
                        {
                            LocalFree(g_lpDeviceConfig);
                            g_lpDeviceConfig = NULL;
                        }
                        DisplayPhoneNumber(hwndDlg);
                        VerifyAndWarnUsableLine(hwndDlg);
                    }
                    return TRUE;

                case IDC_CONFIGURELINE:
                {
                    DWORD dwDeviceID;
                    dwDeviceID = (DWORD)SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
                        CB_GETCURSEL, 0, 0);
                    PreConfigureDevice(hwndDlg, dwDeviceID);
                    DisplayPhoneNumber(hwndDlg);
                    return TRUE;
                }

                case IDC_COUNTRYCODE:
                    if (HIWORD(wParam) == CBN_SELENDOK)
                        DisplayPhoneNumber(hwndDlg);
                    return TRUE;

                case IDC_AREACODE:
                case IDC_PHONENUMBER:
                    if (HIWORD(wParam) == EN_CHANGE)
                        DisplayPhoneNumber(hwndDlg);
                    return TRUE;

                case IDC_USEDIALINGRULES:
                    if (HIWORD(wParam) == BN_CLICKED)
                    {
                        UseDialingRules(hwndDlg);
                        DisplayPhoneNumber(hwndDlg);
                    }
                    return TRUE;

                case IDC_LOCATION:
                    if (HIWORD(wParam) == CBN_CLOSEUP)
                    {
                        char szCurrentLocation[128];
                        int nCurrentSelection;

                        nCurrentSelection = (int)SendDlgItemMessage(hwndDlg,
                            IDC_LOCATION, CB_GETCURSEL, 0, 0);
                        SendDlgItemMessage(hwndDlg, IDC_LOCATION,
                            CB_GETLBTEXT, nCurrentSelection,
                            (LPARAM) (LPCTSTR) szCurrentLocation);

                        // If the user selected a 'location', make it current.
                        FillLocationInfo(hwndDlg, szCurrentLocation, NULL, NULL);
                        DisplayPhoneNumber(hwndDlg);
                    }
                    return TRUE;

                case IDC_DIALINGPROPERTIES:
                {
                    DWORD dwDeviceID;
                    long lReturn;

                    dwDeviceID = (DWORD)SendDlgItemMessage(hwndDlg, IDC_TAPILINE,
                            CB_GETCURSEL, 0, 0);

                    lReturn = lineTranslateDialog(g_hLineApp, dwDeviceID,
                        SAMPLE_TAPI_VERSION, hwndDlg, g_szTranslatedNumber);

#ifdef DEBUG
                    if (lReturn != SUCCESS)
                        DBG_INFO((DBGARG, TEXT("lineTranslateDialog: %x"), lReturn));
#endif

                    // The user could have changed the default location, or
                    // added or removed a location while in the 'Dialing
                    // Properties' dialog.  Refill the Location Info.
                    FillLocationInfo(hwndDlg, NULL, NULL, NULL);
                    DisplayPhoneNumber(hwndDlg);

                    return TRUE;
                }

                case IDCANCEL:
                    EndDialog(hwndDlg, FALSE);
                    return TRUE;

                case IDC_DIAL:
                {
                    // The Dial button has to be enabled and the line has
                    // to be currently usable to continue.
                    if (!(IsWindowEnabled((HWND)lParam) &&
                          VerifyAndWarnUsableLine(hwndDlg)))
                        return TRUE;

                    DisplayPhoneNumber(hwndDlg);

                    // Store all the relavent information in static
                    // variables so they will be available the next time a
                    // number is dialed.
                    SendDlgItemMessage(hwndDlg, IDC_COUNTRYCODE,
                        WM_GETTEXT, 511, (LPARAM) (LPCTSTR) szCountryName);

                    SendDlgItemMessage(hwndDlg, IDC_AREACODE,
                        WM_GETTEXT, 255, (LPARAM) (LPCTSTR) szAreaCode);

                    SendDlgItemMessage(hwndDlg, IDC_PHONENUMBER,
                        WM_GETTEXT, 511, (LPARAM) (LPCTSTR) szPhoneNumber);

                    bUsedCountryAndArea = (BOOL) SendDlgItemMessage(hwndDlg,
                        IDC_USEDIALINGRULES, BM_GETCHECK, 0, 0);

                    bHistoryValid = TRUE;

                    EndDialog(hwndDlg, TRUE);
                    return TRUE;
                }


                // This message is actually posted to the dialog from the
                // lineCallbackFunc when it receives a
                // LINEDEVSTATE_TRANSLATECHANGE message.  Notify the user and
                // retranslate the number.  Also refill the Location Info
                // since this could have been generated by a location change.
                case IDC_CONFIGURATIONCHANGED:
                {
                    FillLocationInfo(hwndDlg, NULL, NULL, NULL);
                    DisplayPhoneNumber(hwndDlg);

                    return TRUE;
                }

                // If we get a LINE_CREATE message, all that needs to be done
                // is to reset this controls contents.  The selected line
                // won't change and no lines will be removed.
                case IDC_LINECREATE:
                {
                    FillTAPILine(hwndDlg);
                    return TRUE;
                }

                default:
                    break;
            }

            break;
        }

        default:
            break;
    }

    return FALSE;
}