// 
// 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 1993 Microsoft Corporation, all rights reserved.
// 
// 
/****************************************************************************/
/*
    Dialer ---- Windows TAPI sample application created as an illustration of the usage of Windows TAPI
    
    Dialer does the following 3 things :
    
    (1) initiates/drops calls
    (2) handles simple TAPI requests for other applications initiating/dropping calls on their behalf
    (3) monitors incoming/outgoing calls and keeps a call log based on the user's request.
    
    tapifu.c : contains dialer's TAPI code.
*/

/****************************************************************************/
/* include files */

#include <windows.h>
#include <windowsx.h>
#include <tapi.h>
#include <string.h>
#include <time.h>
#include <malloc.h>

#include "dialer.h"
#include "dialerrc.h"

/****************************************************************************/
/* constant definitions */

#define tapiVersionCur                          (MAKELONG(1,1))

#define lcbLineDevCapsInitial                   (sizeof(LINEDEVCAPS)+1000)
#define lcbAddrDevCapsInitial                   (sizeof(LINEADDRESSCAPS)+1000) 
#define lcbLineTransCapsInitial                 (sizeof(LINETRANSLATECAPS)+1000)
#define lcbLineTransOutputInitial               (sizeof(LINETRANSLATEOUTPUT)+1000)

#define cchLineNameMac                          128 
#define cchAddrNameMac                          128
#define cchLocationNameMac                      128 
#define cchCardNameMac                          128

#define cstrrMax                                 256

/****************************************************************************/
/* struct definitions and the corresponding global declarations */

typedef struct tagLINEINFO                  /* info we keep around for all available lines */
    {
    DWORD cAddr;                            /* number of available addresses on the line */
    BOOL fIsVoiceLine;                      /* is it a voice line or not? */
    DWORD dwAPIVersion;                     /* API version the line supports */
    HLINE hLine;                            /* handle to the line as returned by lineOpen */
    char  szLineName[cchLineNameMac];       /* the line's name */
    } LINEINFO,FAR *LPLINEINFO;

typedef enum tagMNS                         /* call MoNitoring State */
    {
    mnsIdle = 0,                            /* no call being monitored */
    mnsUnknown,                             /* don't know whether it is an incoming or outgoing call yet */
    mnsIncoming,                            /* call being monitored is incoming */
    mnsOutgoing                             /* call being monitored is outgoing */
    } MNS;

typedef struct tagDMR                       /* Dialer Monitor Record */
    {
    HCALL   hcall;                          /* handle to the call being monitored */
    MNS     mns;                            /* state of the call being monitored */
    time_t  tBegin;                         /* beginning time of the call */
    time_t  tEnd;                           /* ending time of the call */
    } DMR,FAR *LPDMR;


/***************************************************/
    
typedef struct tagDTS                               /* Dialer Tapi State */ 
    {
    BOOL            fLineInited;                    /* whether lineInitialize succeeded */
    BOOL            fSTapiMakeCallRegistered;       /* whether we are registered for being STAPI MakeCall recipient */
    BOOL            fSTapiMediaCallRegistered;      /* whether we are registered for being STAPI MediaCall recipient */
    BOOL            fRefusing;                      /* This flag is set when exiting to refuse new requests */
    BOOL            fTapiAddrDLLPresent;            /* the address translation module is present */
    BOOL            fReInitTapi;                    /* set to TRUE when telephon.ini has changed and we should re init */
    BOOL            fCheckMakeCallRequest;          /* on idle, check TAPI queue to see if there are any STAPI MakeCall reuests pending */
    BOOL            fCheckMediaCallRequest;         /* on idle, check TAPI queue to see if there are any STAPI MediaCall reuests pending */
    BOOL            fAbortCall;                     
	
    HLINEAPP        hApp;                           /* instance handle TAPI gives back to us through lineInitialize */
    DWORD           cLines;                         /* number of lines available */ 
    LPLINEINFO      lprgLineInfo;                   /* info on all lines */
    
    DWORD           iLineCur;                       /* the line selected by the user */
    DWORD           iAddrCur;                       /* the address selected by the user */
    DWORD           dwCardCur;                      /* the calling card selected by the user */

    LPDMR           lprgdmr;                        /* array of DMRs being monitored */
    size_t          idmrMax;                        /* size of the *lprgdmr */
    size_t          idmrMac;                        /* number of calls currently being monitored */
	
    char szLocation[cchLocationNameMac];            /* the location name selected by the user */ 
    char szCallingCard[cchCardNameMac];             /* the calling card name selected by the user */
    char szAreaCode[4];                             /* area code of the currently selected location */
    } DTS;


static DTS vdts = {FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,NULL,0,NULL,0,0,(DWORD)-1,NULL,0,0};

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

typedef enum tagMCS                         /* These are states that we progress through on the way to establishing call */
    {
    mcsIdle,                                /* no call in progress */
    mcsMaking,                              /* just called lineMakeCall and waiting for its LINE_REPLY */
    mcsStopMaking,                          /* waiting for the LINE_REPLY for lineMakeCall so that the call can be dropped as requested by the user */
    mcsDropping,                            /* just called lineDrop and waiting for its LINE_REPLY */
    mcsMade                                 /* lineMakeCall succeeded and the call is connected */
    } MCS;

typedef struct tagDCR                       /* Dialer Call Record */
    {
    MCS mcs;                                /* state of the call as tracked by the Dialer */
    DWORD lcs;                              /* Line Call State as trakced by TAPI about the call */
    DWORD requestID;                                                /* as returned by assync TAPI functions lineMakeCall/lineDropCall */
    
    DWORD iLine;                            /* the line ID on which the call is made */
    DWORD iAddr;                            /* the originating address ID of the call being made */
    HLINE hLine;                            /* handle to the line */
    HCALL hCall;                            /* handle to the call */
    
    time_t tBegin;
    time_t tEnd;

    char szDialString[TAPIMAXDESTADDRESSSIZE];          /* phone number being called */
    char dialStringDisplayable[TAPIMAXDESTADDRESSSIZE];
    DWORD dwTranslateResults;
    
    /*  fIsProxy is TRUE iff the call is initiated through tapiRequestMediaCall
    */
    BOOL fIsProxy;                                                  
    BOOL fIsSecure;
    HWND hwndProxy;
    WORD requestIDProxy;
    BOOL fDisconnectSent;
    
    /* the following fields are used as the parameter block passed into lineMakeCall.
       this is due to the fact that lineMakeCall is assync and we need to keep the parameter
       block around till we get the corresponding LINE_REPLY message and it's much easier to
       just use a global block instead of allocating/freeing a memory block 
    */
    LINECALLPARAMS lcp;                                     /* Line Call Parameters */
    char szCalledParty[TAPIMAXCALLEDPARTYSIZE];             /* name of the party being called */
    } DCR,FAR *LPDCR;


static DCR vdcr = {mcsIdle,LINECALLSTATE_IDLE};

/****************************************************************************/ 
/* function declarations */

/* calling/dropping core functions */

static void TranslateAddress(LPDCR lpdcr);
static BOOL FLocalInitiateCall(LPCSTR lpszDialString,LPCSTR lpszCalledParty,LPDCR lpdcr);
static BOOL FDropCall(LPDCR lpdcr);
static VOID DoLineClose(LPDCR lpdcr);

/* STAPI functions for calling/dropping on other apps behalf */ 

static void DoMakeCallRequest(void);
static void DoMediaCallRequest(void);
static void DoDropCallRequest(HWND hwndCall,WORD wRequestID);
static void SendProxyMessage(LPDCR lpdcr,LPARAM lParam);

/* call logging functions */

static void LogUsingCall(HCALL,BOOL,time_t,time_t);

/* call monitoring functions */

static LPDMR LpdmrAddMonitor(HCALL);
static LPDMR LpdmrFindMonitor(HCALL);
static VOID RemoveMonitor(LPDMR);

/* call state transition related functions */

static VOID NewCallState(DWORD newState,DWORD callbackInstance,DWORD privilege,HCALL call,DWORD dwParam2);
void FAR PASCAL _export LineCallBackProc(DWORD hDevice,DWORD dwMessage,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2,DWORD dwParam3);

/****************************************************************************/
/* TAPI init/clean functions */
/****************************************************************************/
/* %%Function:ErrStartTapi */
/*  
    initializes TAPI by calling lineInitialize. enumerates all the available lines 
    to set up vdts.lprgLineInfo. also opens up each available line for monitoring.
    sets up vdts.iLineCur and vdts.iAddrCur by checking the preferred line/address
    name stored in the ini file  against the available line/address names.
    
    returns errNone if success and the corresponding error code otherwise.
*/

DWORD ErrStartTapi(HANDLE hInst,LPCSTR szAppName)
{   
    LONG lResult;
    DWORD iLine, iAddr, cAddr, iLineVoiceFirst = (DWORD)-1;
    LPLINEDEVCAPS lpDevCaps = NULL;
    LPLINEADDRESSCAPS lpAddrCaps = NULL;
    char szPreferedLine[cchLineNameMac];
    char szPreferedAddress[cchAddrNameMac];
    
    /***************************************************/
    /* initialize tapi */   
    lResult = lineInitialize(&(vdts.hApp),hInst,(LINECALLBACK)MakeProcInstance((FARPROC)LineCallBackProc,hInst),szAppName,&(vdts.cLines));
    if (lResult != errNone)
	return lResult;
    vdts.fLineInited = TRUE;
    if (vdts.cLines == 0)
	return errNoVoiceLine;

    /***************************************************/ 
    /* initialize vdts.lpgrLineInfo and open each available line for monitoring */
    
    /* get the name of the preferred line from ini file */
    CchGetDialerProfileString(hInst,ikszSecPreference,ikszFieldPreferedLine,ikszPreferedLineDefault,szPreferedLine,sizeof(szPreferedLine));
    vdts.iLineCur = 0;
   
    /* allocate buffer for storing LINEINFO for all the available lines */
    vdts.lprgLineInfo = (LPLINEINFO) _fmalloc(sizeof(LINEINFO)*(int)vdts.cLines);
    if (vdts.lprgLineInfo == NULL)
	return LINEERR_NOMEM;
    _fmemset(vdts.lprgLineInfo,0,sizeof(LINEINFO)*(int)vdts.cLines);
    
    /* allocate buffer for the lineGetDevCaps calls */
    if ((lpDevCaps = (LPLINEDEVCAPS) _fmalloc(lcbLineDevCapsInitial)) == NULL)
	return LINEERR_NOMEM;
    lpDevCaps->dwTotalSize = lcbLineDevCapsInitial;

    /* init vdts.lprgLineInfo and open each line for monitoring */ 
    for (iLine = 0; iLine < vdts.cLines; ++iLine) 
	{
	LINEEXTENSIONID lineExtensionID;
	char szLineName[cchLineNameMac];
	LPSTR lpszLineName;
	
	/* negotiate API version for each line */
	lResult = lineNegotiateAPIVersion(vdts.hApp,iLine,tapiVersionCur,
					tapiVersionCur,&(vdts.lprgLineInfo[iLine].dwAPIVersion),&lineExtensionID);
	if (lResult != errNone)
	    goto LDone;
	
	/* get line capability info */
	lResult = lineGetDevCaps(vdts.hApp,iLine,vdts.lprgLineInfo[iLine].dwAPIVersion,0,lpDevCaps);
	if (lResult != errNone)
	    goto LDone;
	
	/* reallocate buffer if not big enough */       
	while (lpDevCaps->dwNeededSize > lpDevCaps->dwTotalSize) 
	    {
	    DWORD lcbNeeded = lpDevCaps->dwNeededSize; 
	
	    _ffree(lpDevCaps); 
	    if ((lpDevCaps = (LPLINEDEVCAPS) _fmalloc((size_t)lcbNeeded)) == NULL)
		{
		lResult = LINEERR_NOMEM; 
		goto LDone;
		} /* if */
	    lpDevCaps->dwTotalSize = lcbNeeded; 
	    
	    /* try it one more time */
	    lResult = lineGetDevCaps(vdts.hApp,iLine,vdts.lprgLineInfo[iLine].dwAPIVersion,0,lpDevCaps);
	    if (lResult != errNone)
		goto LDone;
	    } /* while */
    
	vdts.lprgLineInfo[iLine].cAddr = lpDevCaps->dwNumAddresses;
	vdts.lprgLineInfo[iLine].fIsVoiceLine = ((lpDevCaps->dwMediaModes & LINEMEDIAMODE_INTERACTIVEVOICE) != 0);
	
	if (lpDevCaps->dwLineNameSize > 0)
	    lpszLineName = (LPSTR)(lpDevCaps)+lpDevCaps->dwLineNameOffset;
	else 
	    {/* use default name */
	    char szLineFormat[32];
    
	    LoadString(hInst,ikszCallLogLineName,szLineFormat,sizeof(szLineFormat));    
	    wsprintf(szLineName,szLineFormat,iLine);
	    lpszLineName = (LPSTR)szLineName;
	    } /* else */
	if (lstrlen(lpszLineName) > cchLineNameMac - 1)
	    lpszLineName[cchLineNameMac - 1] = 0;
	lstrcpyn(vdts.lprgLineInfo[iLine].szLineName,lpszLineName,sizeof(vdts.lprgLineInfo[iLine].szLineName));
	vdts.lprgLineInfo[iLine].szLineName[sizeof(vdts.lprgLineInfo[iLine].szLineName) - 1] = 0; 
	
	if (iLineVoiceFirst == (DWORD)-1 && vdts.lprgLineInfo[iLine].fIsVoiceLine)
	    iLineVoiceFirst = iLine;
		
	/* check if it's the current line */
	if (lstrcmpi(lpszLineName,szPreferedLine) == 0 && vdts.lprgLineInfo[iLine].fIsVoiceLine)
	    vdts.iLineCur = iLine;

	/* open each line for monitoring */     
// UNIMODEM M5 WORKAROUND - OPEN ONLY INTERACTIVE_VOICE LINES
	if (vdts.lprgLineInfo[iLine].fIsVoiceLine)
				lResult = lineOpen(vdts.hApp,iLine,&(vdts.lprgLineInfo[iLine].hLine),vdts.lprgLineInfo[iLine].dwAPIVersion,
		0,(DWORD)(LPDMR)NULL,LINECALLPRIVILEGE_MONITOR,LINEMEDIAMODE_INTERACTIVEVOICE,NULL);

	if (lResult != errNone)
	    {
	    vdts.lprgLineInfo[iLine].dwAPIVersion = 0;
	    goto LDone;
	    } /* if */ 
	
	} /* for */
    
    if (vdts.iLineCur == 0)
	if (iLineVoiceFirst == (DWORD)-1)
	    {/* no voice line, too bad */
	    lResult = errNoVoiceLine;
	    goto LDone;
	    } /* if */
	else
	    vdts.iLineCur = iLineVoiceFirst;       
	
    /***************************************************/
    /* init vdts.iAddrCur */ 

    /* get the name of the preferred address from ini file */
    CchGetDialerProfileString(hInst,ikszSecPreference,ikszFieldPreferedAddress,ikszPreferedAddressDefault,szPreferedAddress,sizeof(szPreferedAddress));
    vdts.iAddrCur = 0;

    /* allocate buffer for the lineGetAddressCaps calls */
    if ((lpAddrCaps = (LPLINEADDRESSCAPS)_fmalloc(lcbAddrDevCapsInitial)) == NULL) 
	{
	lResult = LINEERR_NOMEM; 
	goto LDone;
	} /* if */
    lpAddrCaps->dwTotalSize = lcbAddrDevCapsInitial;

    /* enumerate all the available addresses to match szPreferedAddress with an address name */     
    cAddr = vdts.lprgLineInfo[vdts.iLineCur].cAddr;
    for (iAddr = 0; iAddr < cAddr; ++iAddr) 
	{
	char szAddrName[cchAddrNameMac];
	LPSTR lpszAddrName;

    /* get address capability info */
	lResult = lineGetAddressCaps(vdts.hApp,vdts.iLineCur,iAddr,tapiVersionCur,0,lpAddrCaps);
	if (lResult != errNone)
	    goto LDone;
	    
	/* reallocate buffer if not big enough */       
	while (lpAddrCaps->dwNeededSize > lpAddrCaps->dwTotalSize) 
	    {
	    DWORD lcbNeeded = lpAddrCaps->dwNeededSize; 
	    
	    _ffree(lpAddrCaps);                     
	    if ((lpAddrCaps = (LPLINEADDRESSCAPS)_fmalloc((size_t)lcbNeeded)) == NULL)
		{
		lResult = LINEERR_NOMEM; 
		goto LDone;
		} /* if */
	    lpAddrCaps->dwTotalSize = lcbNeeded; 
	    
	    /* try it one more time */
	    lResult = lineGetAddressCaps(vdts.hApp,vdts.iLineCur,iAddr,tapiVersionCur,0,lpAddrCaps);
	    if (lResult != errNone)
		goto LDone;
	    } /* while */
	
	/* get the address's name */     
	if (lpAddrCaps->dwAddressSize > 0)
	    lpszAddrName = (LPSTR)(lpAddrCaps)+lpAddrCaps->dwAddressOffset;
	else 
	    {/* use default name */
	    char szAddrFormat[32];

	    LoadString(hInst,ikszCallLogAddrName,szAddrFormat,sizeof(szAddrFormat));    
	    wsprintf(szAddrName,szAddrFormat,iAddr);
	    lpszAddrName = (LPSTR)szAddrName;
	    } /* else */

	if (lstrcmpi(lpszAddrName,szPreferedAddress) == 0)
	    {
	    vdts.iAddrCur = iAddr;
	    break;
	    } /* if */
	} /* while */
	 
    lResult = errNone;

LDone:
    /* free up memory allocated */  
    if (lpDevCaps)
	_ffree(lpDevCaps);
    if (lpAddrCaps)
	_ffree(lpAddrCaps);
	
    return lResult;
       
} /* ErrStartTapi */

/****************************************************************************/
/* %%Function:TapiDone */
/*
    frees up the memory allocated, closes all the lines we have opened for monitoring
    and calls lineShutDown to disconnect from TAPI.
*/
    
void TapiDone()
{ 
    /* never mind if lineInitialize failed in the first place */
    if (!vdts.fLineInited)
	return;
    
    /* unregister STAPI */
    FRegisterSimpleTapi(FALSE);
    
    /* closes all the open lines and free vdts.lprgLineInfo */
    if (vdts.lprgLineInfo) 
	{ 
	DWORD iLine;
    
	for (iLine = 0; iLine < vdts.cLines; ++iLine) 
	    {
	    if (vdts.lprgLineInfo[iLine].dwAPIVersion == 0)
		break;
	    lineClose(vdts.lprgLineInfo[iLine].hLine);
	    } /* for */

	_ffree(vdts.lprgLineInfo);
	} /* if */ 
	
    /* disconnect from TAPI */
    lineShutdown(vdts.hApp);
    
    /* free the rest of the memory allocated */     
    if (vdts.lprgdmr)
	_ffree(vdts.lprgdmr);
	
} /* TapiDone */

/****************************************************************************/
/* %%Function:FTapiAddrDLLPresent */
/* 
    returns vdts.fTapiAddrDLLPresent.
*/

BOOL FTapiAddrDLLPresent()

{ 
    return vdts.fTapiAddrDLLPresent;
    
} /* FTapiAddrDLLPresent */

/****************************************************************************/
/* %%Function:LpLineTransCapsGetLineTransCaps */
/*
   allocates a big enough buffer, calls lineGetTranslateCaps and returns the buffer.
   it's the caller's responsibility to free the buffer.
*/

static LPLINETRANSLATECAPS LpLineTransCapsGetLineTransCaps(DWORD *perrCode);
static LPLINETRANSLATECAPS LpLineTransCapsGetLineTransCaps(DWORD *perrCode)

{  
    LPLINETRANSLATECAPS lpLineTransCaps;
    
    /* allocate buffer */
    if ((lpLineTransCaps = (LPLINETRANSLATECAPS)_fmalloc(lcbLineTransCapsInitial)) == NULL)
	{
	*perrCode = LINEERR_NOMEM;
	return NULL;
	} /* if */
    lpLineTransCaps->dwTotalSize = lcbLineTransCapsInitial;

    /* try to get TranslateCaps */
    if ((*perrCode = lineGetTranslateCaps(vdts.hApp,vdts.lprgLineInfo[vdts.iLineCur].dwAPIVersion,lpLineTransCaps)) != errNone)
	goto LDone;
	    
    /* reallocate buffer if not big enough */       
    while (lpLineTransCaps->dwNeededSize > lpLineTransCaps->dwTotalSize) 
	{
	DWORD lcbNeeded = lpLineTransCaps->dwNeededSize; 
	    
	_ffree(lpLineTransCaps);                        
	if ((lpLineTransCaps = (LPLINETRANSLATECAPS)_fmalloc((size_t)lcbNeeded)) == NULL)
	    {
	    *perrCode = LINEERR_NOMEM;
	    return NULL;
	    } /* if */
	lpLineTransCaps->dwTotalSize = lcbNeeded;
	 
	/* try one more time */ 
	if ((*perrCode = lineGetTranslateCaps(vdts.hApp,vdts.lprgLineInfo[vdts.iLineCur].dwAPIVersion,lpLineTransCaps)) != errNone)
	    goto LDone;
	} /* while */
	
    return lpLineTransCaps;

LDone:
    if (lpLineTransCaps != NULL)
	_ffree(lpLineTransCaps);
    return NULL; 
    
} /* LpLineTransCapsGetLineTransCaps */

/****************************************************************************/
/* %%Function:lpLineLocationEntryFromLocationID */
/*
    given locID and the array of LINELOCATIONENTRY. it scans the array to find the entry
    whose dwPermanentLocationID matches dwLocID. we are assuming that such an entry exists.
*/

static LPLINELOCATIONENTRY lpLineLocationEntryFromLocationID(LPLINELOCATIONENTRY lprgLineLocationEntry,DWORD dwLocID)

{
    LPLINELOCATIONENTRY lplle = lprgLineLocationEntry;
    
    while (lplle->dwPermanentLocationID != dwLocID)
	++lplle;
    return lplle;
	
} /* lpLineLocationEntryFromLocationID */

/****************************************************************************/
/* %%Function:ErrInitCallingCardInfo */
/*
    initialize vdts.dwCardCur according to the user preference in the ini file.
    enumerate all the calling cards available to find the calling card that 
    is the same as stored in the ini file.
*/

DWORD ErrInitCallingCardInfo(HANDLE hInst)

{   
    DWORD errCode;
    LPLINETRANSLATECAPS lpLineTransCaps = LpLineTransCapsGetLineTransCaps(&errCode); 
    LPLINELOCATIONENTRY lprgLineLocationEntry, lplleCur;
    LPLINECARDENTRY lprgLineCardEntry;
    DWORD iCard, cCard, iCardCur; 
    
    if (lpLineTransCaps == NULL || lpLineTransCaps->dwNumCards == 0)
	{
	/* initialize to default value */
	LoadString(hInst,ikszLocationUnknown,vdts.szLocation,sizeof(vdts.szLocation));
	LoadString(hInst,ikszCardUnknown,vdts.szCallingCard,sizeof(vdts.szCallingCard));
	return errCode;
	} /* if */
	
    /* get the name and area code of the current location */ 
    lprgLineLocationEntry = (LPLINELOCATIONENTRY)((LPSTR)(lpLineTransCaps) + lpLineTransCaps->dwLocationListOffset);
    lplleCur = lpLineLocationEntryFromLocationID(lprgLineLocationEntry,lpLineTransCaps->dwCurrentLocationID);
    lstrcpyn(vdts.szLocation,(LPSTR)(lpLineTransCaps) + lplleCur->dwLocationNameOffset,sizeof(vdts.szLocation));
    vdts.szLocation[sizeof(vdts.szLocation)-1] = 0;
    lstrcpyn(vdts.szAreaCode,(LPSTR)(lpLineTransCaps) + lplleCur->dwCityCodeOffset,sizeof(vdts.szAreaCode));
    vdts.szAreaCode[sizeof(vdts.szAreaCode)-1] = 0;
     
    /* get the name of the preferred calling card from ini file */
    CchGetDialerProfileString(hInst,ikszSecPreference,ikszFieldPreferedCallingCard,ikszPreferedCallingCardDefault,vdts.szCallingCard,sizeof(vdts.szCallingCard));
    iCardCur = 0;
    
    cCard = lpLineTransCaps->dwNumCards; 
    lprgLineCardEntry = (LPLINECARDENTRY)((LPSTR)(lpLineTransCaps) + lpLineTransCaps->dwCardListOffset);
    
    /* enumerates all locations and put their names into the location list box */
    for (iCard = 0; iCard < cCard; ++iCard)
    {
	LPSTR lpszCardName;
	
	lpszCardName = (LPSTR)(lpLineTransCaps) + lprgLineCardEntry[iCard].dwCardNameOffset;
	if (lstrcmpi(lpszCardName,vdts.szCallingCard) == 0)
	    {
	    iCardCur = iCard; 
	    break;
	    } /* if */
    } /* for */
    lstrcpyn(vdts.szCallingCard,(LPSTR)(lpLineTransCaps) + lprgLineCardEntry[iCardCur].dwCardNameOffset,sizeof(vdts.szCallingCard));
    vdts.szCallingCard[sizeof(vdts.szCallingCard)-1] = 0;
    vdts.dwCardCur = lprgLineCardEntry[iCardCur].dwPermanentCardID;
   
    _ffree(lpLineTransCaps); 
    vdts.fTapiAddrDLLPresent = TRUE;
    return errNone;

} /* ErrInitCallingCardInfo */

/****************************************************************************/
/* %%Function:FRegisterSimpleTapi */
/*  
    register/unregister for simple tapi requests: makecall and mediacall/drop.
    sets vdts.fSTapiMakeCallRegistered and vdts.fSTapiMediaCallRegistered accordingly.
*/

BOOL FRegisterSimpleTapi(BOOL fRegister)
{
    DWORD lResult;
    
    /* (un)register make call */
    if (fRegister != vdts.fSTapiMakeCallRegistered)
	{
    lResult = lineRegisterRequestRecipient(vdts.hApp,0,LINEREQUESTMODE_MAKECALL,fRegister);
    if (lResult != errNone)
	return FALSE;
	vdts.fSTapiMakeCallRegistered = fRegister;
	} /* if */
    
    /* (un)register media call */
    if (fRegister != vdts.fSTapiMediaCallRegistered)
	{
    lResult = lineRegisterRequestRecipient(vdts.hApp,0,LINEREQUESTMODE_MEDIACALL,fRegister);
    if (lResult != errNone)
	return FALSE;
	vdts.fSTapiMediaCallRegistered = fRegister;
	} /* if */
    
    return TRUE;

} /* FRegisterSimpleTapi */

/****************************************************************************/
/* Phone/Change Options... dialogs related functions */
/****************************************************************************/
/* %%Function:FInitLineLBox */
/*
   initialize the available line list box for the "Phone Options..." dialog.
   also selects the currently selected line. 
*/

BOOL FAR FInitLineLBox(HWND hdlgDialingOption)
{   
    HWND  hwndLBoxLine = GetDlgItem(hdlgDialingOption,didDialingOptionLBoxLine);
    DWORD iLine, iItemCur = (DWORD)-1;
    
    /* enumerate each line device available */
    iLine = 0;
    for (iLine = 0; iLine < vdts.cLines; ++iLine) 
	{
	DWORD iItem;
	
	if (!vdts.lprgLineInfo[iLine].fIsVoiceLine)
	    continue;
	    
	iItem = SendMessage(hwndLBoxLine,CB_ADDSTRING,0,(LPARAM)(LPCSTR)(vdts.lprgLineInfo[iLine].szLineName));
	if (iItem == LB_ERR || iItem == LB_ERRSPACE)
	    return FALSE;  
	/* the listbox is a sorted list box, so use ITEMDATA to store the device id */
	SendMessage(hwndLBoxLine,CB_SETITEMDATA,(WPARAM)iItem,(LPARAM)iLine);
	if (iLine == vdts.iLineCur)
	    iItemCur = iItem;
	else if (iItemCur != -1 && iItem <= iItemCur)
	    ++iItemCur;
	} /* for */
    
    /* selected the current line */ 
    SendMessage(hwndLBoxLine,CB_SETCURSEL,(WPARAM)iItemCur,0);
    
    return TRUE;
    
} /* FInitLineLBox */

/****************************************************************************/
/* %%Function:FInitAddressLBox */
/*
   initialize the available address list box for the "Phone Options..." dialog.
   also selects the first address if the current selection in the line list box
   is not the same line as the current line. Otherwise, it selects the current 
   address. 
*/

BOOL FAR FInitAddressLBox(HANDLE hInst,HWND hdlgDialingOption)
{
    WORD cAddr;
    WORD iAddr;
    WORD iLineCur;
    DWORD iItemCur = (DWORD)-1;
    LPLINEADDRESSCAPS lpAddrCaps;
    BOOL fSuccess = FALSE;
    HWND hwndLineLBox = GetDlgItem(hdlgDialingOption,didDialingOptionLBoxLine);
    HWND hwndAddrLBox = GetDlgItem(hdlgDialingOption,didDialingOptionLBoxAddress);
    
    iLineCur = (WORD)SendMessage(hwndLineLBox,CB_GETITEMDATA,(WORD)SendMessage(hwndLineLBox,CB_GETCURSEL,0,0),0);
    cAddr = (WORD)vdts.lprgLineInfo[iLineCur].cAddr; 
    SendMessage(hwndAddrLBox,CB_RESETCONTENT,0,0);
    
    /* allocate buffer */
    if ((lpAddrCaps = (LPLINEADDRESSCAPS)_fmalloc(lcbAddrDevCapsInitial)) == NULL)
	return FALSE;
    lpAddrCaps->dwTotalSize = lcbAddrDevCapsInitial;

    /* enumerate all the available addresses */     
    iAddr = 0;
    while (iAddr < cAddr) 
	{
	DWORD iItem;
	LONG lResult;
	char szAddrName[cchAddrNameMac];
	LPSTR lpszAddrName;

    /* get address capability info */
	if ((lResult = lineGetAddressCaps(vdts.hApp,iLineCur,iAddr,tapiVersionCur,0,lpAddrCaps)) != 0)
	    goto LDone;
	    
	/* reallocate buffer if not big enough */       
	while (lpAddrCaps->dwNeededSize > lpAddrCaps->dwTotalSize) 
	    {
	    DWORD lcbNeeded = lpAddrCaps->dwNeededSize; 
	    
	    _ffree(lpAddrCaps);                     
	    if ((lpAddrCaps = (LPLINEADDRESSCAPS)_fmalloc((size_t)lcbNeeded)) == NULL)
		goto LDone;
	    lpAddrCaps->dwTotalSize = lcbNeeded; 
	    continue;
	    } /* while */
	
	/* get the address's name */     
	if (lpAddrCaps->dwAddressSize > 0)
	    lpszAddrName = (LPSTR)(lpAddrCaps)+lpAddrCaps->dwAddressOffset;
	else 
	    {/* use default name */
	    char szAddrFormat[32];

	    LoadString(hInst,ikszCallLogAddrName,szAddrFormat,sizeof(szAddrFormat));    
	    wsprintf(szAddrName,szAddrFormat,iAddr);
	    lpszAddrName = (LPSTR)szAddrName;
	    }
	    
	/* insert the name into the list box */
	iItem = SendMessage(hwndAddrLBox,CB_ADDSTRING,0,(LPARAM)(LPCSTR)lpszAddrName);
	if (iItem == LB_ERR || iItem == LB_ERRSPACE)
	    goto LDone;
	      
	/* the listbox is a sorted list box, so use ITEMDATA to store the device id */
	SendMessage(hwndAddrLBox,CB_SETITEMDATA,(WPARAM)iItem,(LPARAM)iAddr);
	if (iLineCur == vdts.iLineCur)
	    if (iAddr == vdts.iAddrCur)
		iItemCur = iItem;
	    else if (iItemCur != -1 && iItem <= iItemCur)
		++iItemCur;

	iAddr++;
	} /* while */
    
    SendMessage(hwndAddrLBox,CB_SETCURSEL,(iLineCur == vdts.iLineCur) ? ((WORD)iItemCur) : 0,0);
    fSuccess = TRUE;
	
LDone:
    if (lpAddrCaps != NULL)
	_ffree(lpAddrCaps);
    return fSuccess; 
    
} /* FInitAddressLBox */

/****************************************************************************/
/* %%Function:FInitLocationLBox */
/*
   initialize the available location list box for the "Phone Options..." dialog.
*/

BOOL FAR FInitLocationLBox(HWND hdlg,WORD didLocation) 

{   
    DWORD errCode;
    HWND  hwndLBoxLocation = GetDlgItem(hdlg,didLocation);
    LPLINETRANSLATECAPS lpLineTransCaps = LpLineTransCapsGetLineTransCaps(&errCode); 
    LPLINELOCATIONENTRY lprgLineLocationEntry;
    DWORD iLocation, cLocation, iLocationCur; 
    BOOL fSuccess = FALSE;
    DWORD iItemCur = (DWORD)-1;
    
    if (lpLineTransCaps == NULL)
	return FALSE;
    
    cLocation = lpLineTransCaps->dwNumLocations; 
    iLocationCur = lpLineTransCaps->dwCurrentLocationID;
    lprgLineLocationEntry = (LPLINELOCATIONENTRY)((LPSTR)(lpLineTransCaps) + lpLineTransCaps->dwLocationListOffset);
 
    /* enumerates all locations and put their names into the location list box */
    for (iLocation = 0; iLocation < cLocation; ++iLocation)
    {
	DWORD iItem; 
	LPSTR lpszLocationName;
	
	lpszLocationName = (LPSTR)(lpLineTransCaps) + lprgLineLocationEntry[iLocation].dwLocationNameOffset;
	iItem = SendMessage(hwndLBoxLocation,CB_ADDSTRING,0,(LPARAM)(LPCSTR)lpszLocationName);
	if (iItem == LB_ERR || iItem == LB_ERRSPACE)
	    goto LDone;
	      
	/* the listbox is a sorted list box, so use ITEMDATA to store the location id and its preferred calling card id */
	SendMessage(hwndLBoxLocation,CB_SETITEMDATA,(WPARAM)iItem,MAKELPARAM((WORD)iLocation,
	    (WORD)((iLocation == iLocationCur) ? vdts.dwCardCur : (lprgLineLocationEntry[iLocation].dwPreferredCardID))));
	if (iLocation == iLocationCur)
	    iItemCur = iItem;
	else if (iItemCur != -1 && iItem <= iItemCur)
	    ++iItemCur;
    } /* for */

    /* selected the current line */ 
    SendMessage(hwndLBoxLocation,CB_SETCURSEL,(WPARAM)iItemCur,0);
    fSuccess = TRUE;

LDone:    
    _ffree(lpLineTransCaps);
    return fSuccess;

} /* FInitLocationLBox */

/****************************************************************************/
/* %%Function:FInitCallingCardLBox */
/*
   initialize the available calling card list box for the "Phone Options..." dialog.
*/

BOOL FAR FInitCallingCardLBox(HWND hdlg,WORD didLocation,WORD didCallingCard)

{   
    DWORD errCode;
    HWND hwndLBoxLocation = GetDlgItem(hdlg,didLocation);
    HWND hwndLBoxLineCard = GetDlgItem(hdlg,didCallingCard);
    LPLINETRANSLATECAPS lpLineTransCaps = LpLineTransCapsGetLineTransCaps(&errCode); 
    LPLINECARDENTRY lprgLineCardEntry;
    DWORD iCard, cCard, dwCardCur; 
    BOOL fSuccess = FALSE;
    DWORD iItemCur = (DWORD)-1;  
    
    if (lpLineTransCaps == NULL)
	return FALSE;
    
    dwCardCur = HIWORD(SendMessage(hwndLBoxLocation,CB_GETITEMDATA,(WORD)SendMessage(hwndLBoxLocation,CB_GETCURSEL,0,0),0));
    
    cCard = lpLineTransCaps->dwNumCards; 
    lprgLineCardEntry = (LPLINECARDENTRY)((LPSTR)(lpLineTransCaps) + lpLineTransCaps->dwCardListOffset);
    
    /* enumerates all locations and puts their names into the location list box */
    for (iCard = 0; iCard < cCard; ++iCard)
    {
	DWORD iItem; 
	LPSTR lpszCardName;
	
	lpszCardName = (LPSTR)(lpLineTransCaps) + lprgLineCardEntry[iCard].dwCardNameOffset;
	iItem = SendMessage(hwndLBoxLineCard,CB_ADDSTRING,0,(LPARAM)(LPCSTR)lpszCardName);
	if (iItem == LB_ERR || iItem == LB_ERRSPACE)
	    goto LDone;
	      
	/* the listbox is a sorted list box, so use ITEMDATA to store the location id */
	SendMessage(hwndLBoxLineCard,CB_SETITEMDATA,(WPARAM)iItem,(LPARAM)(lprgLineCardEntry[iCard].dwPermanentCardID));
	if (lprgLineCardEntry[iCard].dwPermanentCardID == dwCardCur)
	    iItemCur = iItem;
	else if (iItemCur != -1 && iItem <= iItemCur)
	    ++iItemCur;
    } /* for */

    /* selected the current line */ 
    SendMessage(hwndLBoxLineCard,CB_SETCURSEL,(WPARAM)iItemCur,0);
    fSuccess = TRUE;

LDone:    
    _ffree(lpLineTransCaps);
    return fSuccess;

} /* FInitCallingCardLBox */

/****************************************************************************/
/* %%Function:UpdateCallingCardLBoxSelection */
/*
    called when the user selects a different location.
    sets the calling card to the preferred calling card for that location.
*/

extern void FAR UpdateCallingCardLBoxSelection(HWND hdlg,WORD didLocation,WORD didCallingCard)

{                                   
    HWND hwndLBoxLocation = GetDlgItem(hdlg,didLocation);
    HWND hwndLBoxLineCard = GetDlgItem(hdlg,didCallingCard);
    WORD iItem, cItem; 
    DWORD iCardCur;

    iCardCur = HIWORD(SendMessage(hwndLBoxLocation,CB_GETITEMDATA,(WORD)SendMessage(hwndLBoxLocation,CB_GETCURSEL,0,0),0));
    cItem = (WORD)SendMessage(hwndLBoxLineCard,CB_GETCOUNT,0,0);
    for (iItem = 0; iItem < cItem; ++iItem)
	if (iCardCur == (WORD)SendMessage(hwndLBoxLineCard,CB_GETITEMDATA,0,0))
	    break;
    
    SendMessage(hwndLBoxLineCard,CB_SETCURSEL,iItem,0);
	
} /* UpdateCallingCardLBoxSelection */

/****************************************************************************/
/* %%Function:UpdatePreferedCardForLocation */
/*
    called when the user selects a different calling card.
    makes this newly selected card the preferred calling card for the currently
    selected location.
*/

void FAR UpdatePreferedCardForLocation(HWND hdlg,WORD didLocation,WORD didCallingCard)

{      
    HWND hwndLBoxLocation = GetDlgItem(hdlg,didLocation);
    HWND hwndLBoxLineCard = GetDlgItem(hdlg,didCallingCard);
    DWORD dwData;
    WORD iCard, iLocCurSel;

    iCard = (WORD)SendMessage(hwndLBoxLineCard,CB_GETITEMDATA,(WORD)SendMessage(hwndLBoxLineCard,CB_GETCURSEL,0,0),0);
    dwData = SendMessage(hwndLBoxLocation,CB_GETITEMDATA,iLocCurSel = (WORD)SendMessage(hwndLBoxLocation,CB_GETCURSEL,0,0),0);
    SendMessage(hwndLBoxLocation,CB_SETITEMDATA,iLocCurSel,MAKELPARAM(LOWORD(dwData),iCard));
    
    
} /* UpdatePreferedCardForLocation */

/****************************************************************************/
/* %%Function:UpdateCurrentLocation */
/*
    called to extract the location the user has selected from the list box passed in.
*/

void UpdateCurrentLocation(HWND hdlg,WORD didLocation)

{   
    LPLINETRANSLATECAPS lpLineTransCaps; 
    LPLINELOCATIONENTRY lprgLineLocationEntry, lplleCur;
    HWND hwndCtrl;
    WORD iItemCur;
    DWORD errCode; 

    if (!vdts.fTapiAddrDLLPresent)
	return;
    
    hwndCtrl = GetDlgItem(hdlg,didLocation);
    lineSetCurrentLocation(vdts.hApp,LOWORD(SendMessage(hwndCtrl,CB_GETITEMDATA,iItemCur = (WORD)SendMessage(hwndCtrl,CB_GETCURSEL,0,0),0)));
    GetDlgItemText(hdlg,didLocation,vdts.szLocation,sizeof(vdts.szLocation));

    lpLineTransCaps = LpLineTransCapsGetLineTransCaps(&errCode); 
    lprgLineLocationEntry = (LPLINELOCATIONENTRY)((LPSTR)(lpLineTransCaps) + lpLineTransCaps->dwLocationListOffset);
    lplleCur = lpLineLocationEntryFromLocationID(lprgLineLocationEntry,lpLineTransCaps->dwCurrentLocationID);
    lstrcpyn(vdts.szAreaCode,(LPSTR)(lpLineTransCaps) + lplleCur->dwCityCodeOffset,sizeof(vdts.szAreaCode));
    vdts.szAreaCode[sizeof(vdts.szAreaCode)-1] = 0;

} /* UpdateCurrentLocation */

/****************************************************************************/
/* %%Function:UpdateCurrentCallingCard */
/*
    called to extract the calling card info the user has selected from the list box passed in.
*/

void UpdateCurrentCallingCard(HANDLE hInst,HWND hdlg,WORD didCallingCard)

{
    HWND hwndCtrl;
    WORD iItemCur; 

    if (!vdts.fTapiAddrDLLPresent)
	return;

    hwndCtrl = GetDlgItem(hdlg,didCallingCard);
    vdts.dwCardCur = (WORD)SendMessage(hwndCtrl,CB_GETITEMDATA,iItemCur = (WORD)SendMessage(hwndCtrl,CB_GETCURSEL,0,0),0);
    GetDlgItemText(hdlg,didCallingCard,vdts.szCallingCard,sizeof(vdts.szCallingCard));
    SetDialerProfileString(hInst,ikszSecPreference,ikszFieldPreferedCallingCard,vdts.szCallingCard);

} /* UpdateCurrentCallingCard */

/****************************************************************************/
/* %%Function:UpdateDialingOptionSettings */
/*
    called after the user hits "OK" in the "Phone Options..." dialog.
    saves away the user's line and address selection.
*/

void UpdateDialingOptionSettings(HANDLE hInst,HWND hdlgDialingOption)

{   
    HWND hwndCtrl;
    WORD iItemCur; 
    char szBuffer[cchSzMax];
    
    /* update line */       
    hwndCtrl = GetDlgItem(hdlgDialingOption,didDialingOptionLBoxLine);
    vdts.iLineCur = (WORD)SendMessage(hwndCtrl,CB_GETITEMDATA,iItemCur = (WORD)SendMessage(hwndCtrl,CB_GETCURSEL,0,0),0);
    GetDlgItemText(hdlgDialingOption,didDialingOptionLBoxLine,szBuffer,sizeof(szBuffer));
    SetDialerProfileString(hInst,ikszSecPreference,ikszFieldPreferedLine,szBuffer);

    /* update address */    
    hwndCtrl = GetDlgItem(hdlgDialingOption,didDialingOptionLBoxAddress);
    vdts.iAddrCur = (WORD)SendMessage(hwndCtrl,CB_GETITEMDATA,(WORD)SendMessage(hwndCtrl,CB_GETCURSEL,0,0),0);
    GetDlgItemText(hdlgDialingOption,didDialingOptionLBoxAddress,szBuffer,sizeof(szBuffer));
    SetDialerProfileString(hInst,ikszSecPreference,ikszFieldPreferedAddress,szBuffer);
    
    UpdateCurrentLocation(hdlgDialingOption,didDialingOptionLBoxLocation);
    UpdateCurrentCallingCard(hInst,hdlgDialingOption,didDialingOptionLBoxCallingCard);
	    
} /* UpdateDialingOptionSettings */

/****************************************************************************/
/* call status dialog related functions */ 
/****************************************************************************/
/* %%Function:GetCurCallTranslatedNumber */ 
/*  
    returns the translated number of the current call.
*/

void GetCurCallTranslatedNumber(char *szNumber,WORD cchSzNumber,DWORD *pdwTranslateResults)

{
    lstrcpyn(szNumber,vdcr.szDialString,cchSzNumber); 
    szNumber[cchSzNumber-1] = 0;
    *pdwTranslateResults = vdcr.dwTranslateResults;
    
} /* GetCurCallTranslatedNumber */

/****************************************************************************/
/* %%Function:AddRemoveCurCallFromTollList */ 
/* 
    add/remove the phone number in vdcr into/from the toll list.
*/

void AddRemoveCurCallFromTollList()

{   
    DWORD lResult;
    
    lResult = lineSetTollList(vdts.hApp,vdcr.iLine,vdcr.dialStringDisplayable,
	(vdcr.dwTranslateResults & LINETRANSLATERESULT_NOTINTOLLLIST) ? LINETOLLLISTOPTION_ADD : LINETOLLLISTOPTION_REMOVE);
    if (lResult == 0)
	TranslateAddress(&vdcr);
    
} /* AddRemoveCurCallFromTollList */

/****************************************************************************/
/* %%Function:SetCurCallName */ 
/* 
    sets vdcr.szCalledParty to the passed in string.
*/

void SetCurCallName(char *szName)

{ 
    lstrcpyn(vdcr.szCalledParty,szName,sizeof(vdcr.szCalledParty));
    vdcr.szCalledParty[sizeof(vdcr.szCalledParty)-1] = 0;
    
} /* SetCurCallName */

/****************************************************************************/
/* making/dropping calls functions */ 
/****************************************************************************/
/* %%Function:DialerCanonicalNumberTranslation */ 
/*
    this is Dialer's version of canonical number expansion for North America
    phone numbers. szDialString is assumed to be in a buffer of size TAPIMAXDESTADDRESSSIZE.
*/

static void DialerCanonicalNumberTranslation(LPSTR szDialString)

{                                       
    char szDigits[11] = {'0','1','2','3','4','5','6','7','8','9',0};
    char szCNum[TAPIMAXDESTADDRESSSIZE] = {'+','1',' ','('};
    LPSTR lpch, lpch2, lpch3, lpch4, lpch5;
    int cchDigit, ichCNumEnd = 4;

    if (!vdts.fTapiAddrDLLPresent || vdts.szAreaCode[0] == 0
	|| lstrlen(szDialString) >= TAPIMAXDESTADDRESSSIZE - 9)
	return;
	
    /* skip all the leading white space characters */
    lpch = szDialString;
    while (*lpch == ' ')
	++lpch;
    
    /* number is already canonical or bad */
    if (*lpch == '+')
	return;
    
    /* kill all the leading white space */
    if (lpch != szDialString)
	_fmemmove(szDialString,lpch,lstrlen(szDialString) - (lpch - szDialString) + 1);
    
    /* init cchDigit */
    for (cchDigit = 0, lpch = szDialString; *lpch != 0 && *lpch != '$' && *lpch != 'W' && *lpch != '?' && *lpch != '@'; ++lpch)
	if (*lpch >= '0' && *lpch <= '9')
	    ++cchDigit;
    
    if (cchDigit < 7 || cchDigit > 11 || cchDigit == 9)
	return;
	
    /* find the pointer to the fist few digits */
    lpch = _fstrpbrk(szDialString,szDigits);
    lpch2 = _fstrpbrk(lpch + 1,szDigits);
    lpch3 = _fstrpbrk(lpch2 + 1,szDigits);
    lpch4 = _fstrpbrk(lpch3 + 1,szDigits);
    lpch5 = _fstrpbrk(lpch4 + 1,szDigits);
    
    /* don't expand in the following cases */
    if (cchDigit == 7 && (*lpch == '0' || *lpch == '1')
	|| cchDigit == 8 && (*lpch != '0' && *lpch != '1' || *lpch2 == '0' || *lpch2 == '1')
	|| cchDigit == 10 && (*lpch == '0' || *lpch == '1' || *lpch4 == '0' || *lpch4 == '1')
	|| cchDigit == 11 && (*lpch != '0' && *lpch != '1' || *lpch2 == '0' || *lpch2 == '1' || *lpch5 == '0' || *lpch5 == '1'))
	return;
    
    /* add xxx to the end of szCNum */
    if (cchDigit < 10)
	{/* use the area code of the current location */
	lstrcpy(szCNum + ichCNumEnd,vdts.szAreaCode);
	ichCNumEnd += lstrlen(vdts.szAreaCode);
	} /* if */
     else if (cchDigit == 10)
	{
	_fmemmove(szCNum + ichCNumEnd,lpch,lpch4-lpch);
	ichCNumEnd += lpch4-lpch;
	lpch = lpch4;
	} /* else if */
    else
	{
	_fmemmove(szCNum + ichCNumEnd,lpch2,lpch5-lpch2);
	ichCNumEnd += lpch5-lpch2;
	lpch = lpch5;
	} /* else */
	
    szCNum[ichCNumEnd++] = ')';
    szCNum[ichCNumEnd++] = ' ';
    
    /* now put the remainder of szDialString into szCNum */
    _fmemmove(szCNum + ichCNumEnd,lpch,lstrlen(lpch) + 1);
    _fmemmove(szDialString,szCNum,lstrlen(szCNum)+1);
	
} /* DialerCanonicalNumberTranslation */

/****************************************************************************/
/* %%Function:TranslateAddress */
/*
    calls LineTranslateAddress to get the dialString pointed to by lpdcr translated.
    stores the result in lpdcr->dialStringDisplayable while lpdcr->dialString is
    updated with the translated number.
*/

static void TranslateAddress(LPDCR lpdcr)

{   
    LONG lResult;
    LPLINETRANSLATEOUTPUT lpLineTransOutput;
    char szDialStringOld[TAPIMAXDESTADDRESSSIZE];
    
    lstrcpyn(szDialStringOld,lpdcr->szDialString,lstrlen(lpdcr->szDialString) + 1);
    
    lstrcpyn(lpdcr->dialStringDisplayable,lpdcr->szDialString,lstrlen(lpdcr->szDialString) + 1);
    lpdcr->dialStringDisplayable[sizeof(lpdcr->dialStringDisplayable)-1] = 0;
    
    if (GetProfileInt("intl","iCountry",0) == 1)
	{/* translates all the letters in lpdcr->dialString into the corresponding digits */
	int ich = lstrlen(lpdcr->szDialString);
	while (--ich >= 0)
	    { 
	    char ch = lpdcr->szDialString[ich]; 
	    
	    if (ch == 'z' || ch == 'Z')
		ch = '9';
	    if (ch >= 'a' && ch <= 'y' && ch != 'q')
		ch = '2' + (ch - 'a' - (ch > 'q'))/3;
	    else if (ch >= 'A' && ch <= 'Y' && ch != 'Q')
		ch = '2' + (ch - 'A' - (ch > 'Q'))/3;
	    lpdcr->szDialString[ich] = ch;
	    } /* while */ 
	lstrcpyn(szDialStringOld,lpdcr->szDialString,lstrlen(lpdcr->szDialString) + 1);
	    
	DialerCanonicalNumberTranslation(lpdcr->szDialString);
	} /* if */
    
    if (!vdts.fTapiAddrDLLPresent)
	return;
    
    /* allocate buffer */
    if ((lpLineTransOutput = (LPLINETRANSLATEOUTPUT)_fmalloc(lcbLineTransOutputInitial)) == NULL)
	return;
    lpLineTransOutput->dwTotalSize = lcbLineTransOutputInitial;

    /* try to get TranslateOutput */
    if ((lResult = lineTranslateAddress(vdts.hApp,lpdcr->iLine,vdts.lprgLineInfo[0].dwAPIVersion,lpdcr->szDialString,
	vdts.dwCardCur,LINETRANSLATEOPTION_CARDOVERRIDE,lpLineTransOutput)) != errNone)
	goto LDone;
	    
    /* reallocate buffer if not big enough */       
    while (lpLineTransOutput->dwNeededSize > lpLineTransOutput->dwTotalSize) 
	{
	DWORD lcbNeeded = lpLineTransOutput->dwNeededSize; 
	    
	_ffree(lpLineTransOutput);                      
	if ((lpLineTransOutput = (LPLINETRANSLATEOUTPUT)_fmalloc((size_t)lcbNeeded)) == NULL)
	    {
	    lResult = 1;    /* so that the dialstring will be restored */
	    goto LDone;
	    }
	lpLineTransOutput->dwTotalSize = lcbNeeded;
	 
	/* try one more time */ 
	lResult = lineTranslateAddress(vdts.hApp,lpdcr->iLine,vdts.lprgLineInfo[0].dwAPIVersion,lpdcr->szDialString,
	    vdts.dwCardCur,LINETRANSLATEOPTION_CARDOVERRIDE,lpLineTransOutput);                
	if (lResult != errNone)
	    goto LDone;                
	} /* while */
    
    lstrcpyn(lpdcr->szDialString,(LPSTR)(lpLineTransOutput) + lpLineTransOutput->dwDialableStringOffset,sizeof(lpdcr->szDialString)); 
    lpdcr->szDialString[sizeof(lpdcr->szDialString)-1] = 0;
    lpdcr->dwTranslateResults = lpLineTransOutput->dwTranslateResults;

LDone:
    if (lpLineTransOutput != NULL)
	_ffree(lpLineTransOutput);

    if (lResult != errNone) /* restore the dialstring */
	lstrcpyn(lpdcr->szDialString,szDialStringOld,lstrlen(szDialStringOld) + 1);
   
} /* TranslateAddress */

/****************************************************************************/
/* %%Function:AbortTapiCallInProgress */ 
/*  
    turn on the flag vdts.fAbortCall.
*/

void AbortTapiCallInProgress()

{  
    vdts.fAbortCall = TRUE;
    
} /* AbortTapiCallInProgress */

/****************************************************************************/
/* %%Function:DwDialerLineMakeCall */ 
/*
    Dialer's version of lineMakeCall. deals with all the control characters embedded
    inside lpdcr->szDialString.
*/

static LONG DwDialerLineMakeCall(LPDCR lpdcr,LPLINECALLPARAMS lpParams)

{
    LONG lResult;
    BOOL fCallMade;
    LPSTR lpchDialStart;
    char szCtrlCh[2] = {0,0}; 
    
    fCallMade = FALSE;
    lpchDialStart = lpdcr->szDialString;
    while (*lpchDialStart)
	{
	LONG dwLineErrCode;
	int ichCtrl;
	char chSave; 
	    
	/* try to make the call */ 
	if (!fCallMade)
	    lResult = lineMakeCall(lpdcr->hLine,&(lpdcr->hCall),lpchDialStart,0,lpParams);
	else
	    lineDial(lpdcr->hCall,lpchDialStart,0);
	if (lResult > 0) /* async */
	    {/* we succeeded */
	    if (!fCallMade)
		lpdcr->requestID = lResult;
	    return lResult; 
	    } /* if */ 
    
	/* if we fail not due to any control characters, just return */
	if (lResult != LINEERR_DIALBILLING && lResult != LINEERR_DIALDIALTONE
	    && lResult != LINEERR_DIALPROMPT && lResult != LINEERR_DIALQUIET)
	    return lResult;
	
	dwLineErrCode = lResult;
	
	/* find the offending control character and kill it */
	switch (lResult)
	    {
	    case LINEERR_DIALBILLING: 
		{
		szCtrlCh[0] = '$';
		break;
		}
	    case LINEERR_DIALDIALTONE: 
		{
		szCtrlCh[0] = 'W';
		break;
		}
	    case LINEERR_DIALQUIET: 
		{
		szCtrlCh[0] = '@';
		break;
		}
	    case LINEERR_DIALPROMPT: 
		{
		szCtrlCh[0] = '?';
		break;
		}
	    } /* switch */
	ichCtrl = _fstrcspn(lpchDialStart,szCtrlCh);
	lpchDialStart[ichCtrl] = ';';
	chSave = lpchDialStart[ichCtrl + 1];
	lpchDialStart[ichCtrl + 1] = 0;
	
	/* try to make the call again */ 
	if (!fCallMade)
	    lResult = lineMakeCall(lpdcr->hLine,&(lpdcr->hCall),lpchDialStart,0,lpParams);
	else
	    lineDial(lpdcr->hCall,lpchDialStart,0);
	if (lResult < 0)
	    {/* we failed again but shouldn't */
	    lpchDialStart[ichCtrl] = szCtrlCh[0];
	    lpchDialStart[ichCtrl + 1] = chSave;
	    return lResult; 
	    } /* if */

	/* put up the user prompt box */
	vdts.fAbortCall = FALSE;
	DisplayDialingPromptDlg(dwLineErrCode);
	
	/* continue to the next iteration */
	lpchDialStart[ichCtrl] = szCtrlCh[0];
	lpchDialStart[ichCtrl + 1] = chSave;
	lpchDialStart += ichCtrl + 1;
	fCallMade = TRUE; 
	
	if (vdts.fAbortCall)
	    return 1;
	} /* while */
	
} /* DwDialerLineMakeCall */

/****************************************************************************/
/* %%Function:FLocalInitiateCall */ 
/*
    upon entry, expects iLine, iAddr, fIsProxy,and iSecure fields of lpdcr to be
    already set up. copies lpszDialString and lpszCalledParty into the corresponding
    fields in *lpdcr; opens the line for calling; builds the call parameters; and
    calls lineMakeCall to initiate the call. also puts up the call status dialog.
    
    if fIsProxy, the proxy portion of the *lpdcr is also expected to be already set up.
*/
    
static BOOL FLocalInitiateCall(LPCSTR lpszDialString,LPCSTR lpszCalledParty,LPDCR lpdcr)
{
    LONG lResult;
    LPLINECALLPARAMS lpParams;
    WORD cchCalledParty;
    
    /* copy dial string and called party name */
    lstrcpyn(lpdcr->szDialString,lpszDialString,sizeof(lpdcr->szDialString));
    lpdcr->szDialString[sizeof(lpdcr->szDialString)-1] = '\0';
    lstrcpyn(lpdcr->szCalledParty,lpszCalledParty,sizeof(lpdcr->szCalledParty));
    lpdcr->szCalledParty[sizeof(lpdcr->szCalledParty)-1] = '\0';
    cchCalledParty = lstrlen(lpdcr->szCalledParty)+1;
    
    /* open the line specified in lpdcr->iLine for dialing */ 
    lResult = lineOpen(vdts.hApp,lpdcr->iLine,&(lpdcr->hLine),vdts.lprgLineInfo[lpdcr->iLine].dwAPIVersion,
	0,(DWORD)(lpdcr),LINECALLPRIVILEGE_NONE,0,NULL);
    if (lResult != errNone) 
	return FALSE;

    lpdcr->mcs = mcsMaking;
    lpdcr->lcs = LINECALLSTATE_UNKNOWN;
    lpdcr->tBegin = time(&(lpdcr->tBegin)); 

    /* build call parameters */
    lpParams = &(lpdcr->lcp); 
    _fmemset(lpParams,0,sizeof(LINECALLPARAMS));

    lpParams->dwTotalSize = sizeof(lpdcr->lcp) + sizeof(cchCalledParty);
    lpParams->dwBearerMode = LINEBEARERMODE_VOICE;
    if (!lpdcr->fIsProxy) 
	lpParams->dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
    else
	{
	lpParams->dwMediaMode = LINEMEDIAMODE_DATAMODEM;
	lpParams->dwCallParamFlags = lpdcr->fIsSecure ? LINECALLPARAMFLAGS_SECURE: 0;
	} /* else */   
    lpParams->dwAddressMode = LINEADDRESSMODE_ADDRESSID;
    lpParams->dwAddressID = lpdcr->iAddr;
    lpParams->dwCalledPartySize = cchCalledParty;
    lpParams->dwCalledPartyOffset = sizeof(LINECALLPARAMS);
    
    TranslateAddress(lpdcr);
    ShowCallStatusDlg(lpdcr->szCalledParty,lpdcr->dialStringDisplayable,vdts.szLocation,vdts.szCallingCard,lpdcr->szDialString);
    
    /* make the call */
    lResult = DwDialerLineMakeCall(lpdcr,lpParams);
    if (lResult > 0) /* async */
	{
	lpdcr->requestID = lResult;
	return TRUE; 
	} /* if */ 
    
    /* lineMakeCall failed */
    if (lResult == LINEERR_CALLUNAVAIL)
	DisplayLineInUseDlg();
    else
	DialerErrMessageBox(IkszFromErrCode(lResult));
	
    HideCallStatusDlg();
    DoLineClose(lpdcr);
    lpdcr->mcs = mcsIdle;
	
    return FALSE; 
	    
} /* FLocalInitiateCall */  

/****************************************************************************/
/* %%Function:FCallInProgress */
/*  
    returns whether a call is in progress.
*/

BOOL FCallInProgress(void)

{ 
    return !(vdcr.mcs == mcsIdle || vdcr.mcs == mcsDropping);
    
} /* FCallInProgress */

/****************************************************************************/
/* %%Function:FInitiateCall */
/*
    init vdcr and calls FLocalInitiateCall to do the real work.
*/

BOOL FInitiateCall(LPCSTR lpszDialString,LPCSTR lpszCalledParty)

{
    if (vdts.fRefusing)
	return TRUE;
	
    vdcr.iLine = vdts.iLineCur;
    vdcr.iAddr = vdts.iAddrCur;
    vdcr.fIsProxy = FALSE;
    vdcr.fIsSecure = FALSE;
    return FLocalInitiateCall(lpszDialString,lpszCalledParty,&vdcr);
	   
} /* FInitiateCall */

/****************************************************************************/
/* %%Function:FDropCall */
/*  
    calls lineDrop which returns asynchronously to drop the call specified by
    lpdcr. If we are still waiting for the preceding linMakeCall to complete,
    defer the drop by setting lpdcr->mcs to mcsStopMaking.
*/

static BOOL FDropCall(LPDCR lpdcr)

{   
    DWORD lResult;
    
    if (lpdcr->mcs != mcsMade)
	{
	if (lpdcr->mcs == mcsMaking)
	    lpdcr->mcs = mcsStopMaking;
	return TRUE; 
	} /* if */
	    
    lResult = lineDrop(lpdcr->hCall,NULL,0);
    if (lResult > 0) /* assync */
	{
	lpdcr->mcs = mcsDropping;
	lpdcr->requestID = lResult;

	#ifdef _DEBUG    
	{
	HWND hwndSTAPI = FindWindow(NULL,"Test Script For Simple Tapi - Stapi");
	if (hwndSTAPI)
	    PostMessage(hwndSTAPI,WM_USER+900,0,0);
	}       
	#endif /* DEBUG */

	DisableDialerDesktop(TRUE);
	return TRUE;
	} /* if */

    lpdcr->mcs = mcsIdle;
    return FALSE;
    
} /* FDropCall */

/****************************************************************************/
/* %%Function:DoLineClose */
/*  
    close the line as specified by lpdcr.
*/

static VOID DoLineClose(LPDCR lpdcr)
{
    lpdcr->mcs = mcsIdle;
    lpdcr->lcs = LINECALLSTATE_IDLE;
    lineClose(lpdcr->hLine);

} /* DoLineClose */

/****************************************************************************/
/* %%Function:FDropCurrentCall */
/* 
    drops the current call as specified by vdcr. calls FDropCall to do the real work.
*/
BOOL FDropCurrentCall()
{
    return FDropCall(&vdcr); 
    
} /* FDropCurrentCall */

/****************************************************************************/
/* %%Function:TerminateCalls */
/*
    drops the current call (if there is one) and waits until it goes to idle. 
    gives up after 1 min in anycase. 
*/    

void TerminateCalls(HANDLE hInst)
{       
    DWORD dwTick = GetTickCount();
    
    vdts.fRefusing = TRUE;
    if (vdcr.lcs == LINECALLSTATE_IDLE) 
	return;
    
    SetCursor(LoadCursor(hInst,IDC_WAIT));  
    FDropCurrentCall(); 
    while (vdcr.lcs != LINECALLSTATE_IDLE && (GetTickCount() - dwTick) < 60000L)
	;
    SetCursor(LoadCursor(hInst,IDC_ARROW)); 
	    
} /* TerminateCalls */

/****************************************************************************/
/* functions for calling/dropping on other apps behalf */ 
/****************************************************************************/
/* %%Function:lpCallDataFromSTAPIRequest */
/* 
    calls lineGetRequest to retrieve the call data of the first queued request
    of the specified type. it is the caller's responsibility to free up the allocated
    memory. also updates the two global flags vdts.fCheckMakeCallRequest and
    vdts.fCheckMediaCallRequest.
*/

static LPSTR lpCallDataFromSTAPIRequest(DWORD dwRequestMode)

{
    DWORD lResult;
    LPSTR lpData;

    lpData = _fmalloc((dwRequestMode == LINEREQUESTMODE_MAKECALL) ? sizeof(LINEREQMAKECALL) : sizeof(LINEREQMEDIACALL));
    if (lpData == NULL)
	return NULL;
	
    lResult = lineGetRequest(vdts.hApp,dwRequestMode,lpData);
    if (lResult != errNone) 
	{
	if (lResult == LINEERR_NOREQUEST)
	    {/* turn off the flag */
	    if (dwRequestMode == LINEREQUESTMODE_MAKECALL)
		vdts.fCheckMakeCallRequest = FALSE;
	    else 
		vdts.fCheckMediaCallRequest = FALSE;
	    } /* if */
				    
	_ffree(lpData);
	return NULL; 
	} /* if */

    return lpData;
    
} /* lpCallDataFromSTAPIRequest */
 
/****************************************************************************/
/* %%Function:DoMakeCallRequest */ 
/*
    handles tapiRequestMakeCall. should only be called when vdts.mcs == mcsIdle.
*/

static void DoMakeCallRequest()
{   
    LPLINEREQMAKECALL lpMakeCallData = (LPLINEREQMAKECALL)lpCallDataFromSTAPIRequest(LINEREQUESTMODE_MAKECALL);
    
    if (vdts.fRefusing || lpMakeCallData == NULL)
	return;
	
    FDialerInitiateCall(lpMakeCallData->szDestAddress,lpMakeCallData->szCalledParty);
    _ffree(lpMakeCallData);
    
} /* DoMakeCallRequest */

/****************************************************************************/
/* %%Function:FGetCompatibleLine */ 
/*
    determines which line and address to use to support requested device class 
    and device id. return TRUE if such a line/address is found and FALSE otherwise.
*/

static BOOL FGetCompatibleLine(LPSTR szDeviceClass,LPBYTE ucDeviceID,DWORD dwSize,LPDWORD compatLine,LPDWORD compatAddr)
{
    struct tagVSDEVICE 
	{
	VARSTRING vs;
	unsigned char buffer[TAPIMAXDEVICEIDSIZE];
	} vsDevice;

    LONG lResult;
    DWORD iLine,iAddr;
    
    for (iLine = 0; iLine < vdts.cLines; ++iLine) 
	{ 
	LPLINEINFO lpl = &(vdts.lprgLineInfo[iLine]);
	
	for (iAddr = 0; iAddr < lpl->cAddr; ++iAddr) 
	    {
	    vsDevice.vs.dwTotalSize = sizeof(vsDevice);
	    lResult = lineGetID(lpl->hLine,iAddr,NULL,LINECALLSELECT_ADDRESS,&(vsDevice.vs),szDeviceClass);
	    if (lResult == errNone && vsDevice.vs.dwStringSize == dwSize 
		&& _fmemcmp((LPBYTE)(&vsDevice) + vsDevice.vs.dwStringOffset,ucDeviceID,(size_t)dwSize) == 0) 
		{
		*compatLine = iLine;
		*compatAddr = iAddr;
		lResult = 0;
		return TRUE;
		} /* if */
	    } /* for */
	} /* for */
	
    return FALSE;
     
} /* FGetCompatibleLine */

/****************************************************************************/
/* %%Function:DoMediaCallRequest */ 
/*
    handles tapiRequestMediaCall. Note that we only support one owned call at a time.
    As simplification, we only support data modem media calls. NOTE: fDisconnectSent is 
    used to avoid sending redundant TAPI_REPLY messages in process of shutting down call.
    
    should only be called when vdts.mcs == mcsIdle.
*/

static void DoMediaCallRequest()
{   
    LPLINEREQMEDIACALL lpMediaCallData = (LPLINEREQMEDIACALL)lpCallDataFromSTAPIRequest(LINEREQUESTMODE_MEDIACALL);
    
    if (lpMediaCallData == NULL)
	return;
	
    if (vdts.fRefusing) 
	{
	if (IsWindow(lpMediaCallData->hWnd))
	    PostMessage(lpMediaCallData->hWnd,TAPI_REPLY,lpMediaCallData->wRequestID,TAPIERR_REQUESTFAILED); 
	goto LDone;
	} /* if */
    
    if (!FGetCompatibleLine(lpMediaCallData->szDeviceClass,lpMediaCallData->ucDeviceID,lpMediaCallData->dwSize,&(vdcr.iLine),&(vdcr.iAddr))) 
	{
	SendProxyMessage(&vdcr,TAPIERR_DEVICEIDUNAVAIL);
	goto LDone;
	} /* if */
	    
    /* init the proxy portion of vdcr */           
    vdcr.fIsProxy = TRUE;
    vdcr.fIsSecure = lpMediaCallData->dwSecure != 0;
    vdcr.hwndProxy = lpMediaCallData->hWnd;
    vdcr.requestIDProxy = lpMediaCallData->wRequestID;
    vdcr.fDisconnectSent = FALSE;
    
    /* make the call */
    if (!FLocalInitiateCall(lpMediaCallData->szDestAddress,lpMediaCallData->szCalledParty,&vdcr)) 
	{
	SendProxyMessage(&vdcr,TAPIERR_REQUESTFAILED);
	vdcr.fIsProxy = FALSE;
	} /* if */

LDone:
    _ffree(lpMediaCallData);
	
} /* DoMediaCallRequest */

/****************************************************************************/
/* %%Function:DoDropCallRequest */ 
/*
    handles tapiRequestDrop. drops the current call iff it is a proxy call(i.e.
    arose from a tapiRequestMediaCall()) and matches requesting window and request ID.
*/

static void DoDropCallRequest(HWND hwndCall,WORD wRequestID)
{   
    if (vdts.fRefusing) 
	{
LTellUser :        
	if(IsWindow(hwndCall))
	    PostMessage(hwndCall,TAPI_REPLY,wRequestID,TAPIERR_REQUESTFAILED);
	return;
	} /* if */
    
    if (vdcr.fIsProxy && vdcr.hwndProxy != hwndCall || vdcr.requestIDProxy != wRequestID)
	goto LTellUser;
    else
	{/* current call is the one to drop */
	if (!FDropCurrentCall())
	    goto LTellUser; 
	} /* if */

} /* DoDropCallRequest */

/****************************************************************************/
/* %%Function:ProcessNextQueuedSTAPIRequest */ 
/* 
    get the next queued simple tapi message and process it.
*/

void ProcessNextQueuedSTAPIRequest()

{   
    if (vdcr.lcs != LINECALLSTATE_IDLE)
	return;

    if (vdts.fReInitTapi)
	{
	lineShutdown(vdts.hApp);
	DialerReInitTapi();
	vdts.fReInitTapi = FALSE;
	} /* if */
    
    if (vdts.fCheckMakeCallRequest)
	DoMakeCallRequest();
    else if (vdts.fCheckMediaCallRequest)
	DoMediaCallRequest();

    if (vdts.fCheckMakeCallRequest || vdts.fCheckMediaCallRequest)
	/* so that we WILL get call again */
	PostMessage(HwndDialerMain(),WM_USER,0,0);
		
} /* ProcessNextQueuedSTAPIRequest */

/****************************************************************************/
/* %%Function:SendProxyMessage */ 
/*  
    Sends TAPI_REPLY message to requesting app.
*/

static void SendProxyMessage(LPDCR lpdcr,LPARAM lParam)

{
    if (lpdcr->fIsProxy && IsWindow(lpdcr->hwndProxy))
	PostMessage(lpdcr->hwndProxy,TAPI_REPLY,lpdcr->requestIDProxy,lParam);
	
} /* SendProxyMessage */

/****************************************************************************/
/* call logging functions */ 
/****************************************************************************/
/* %%Function:LpCallInfoFromHCall */
/*
    returns lpCallInfo for hcall as returned by lineGetCallInfo.
    returns NULL on failure.
*/
 
static LPLINECALLINFO LpCallInfoFromHCall(HCALL hcall)

{
    LPLINECALLINFO lpCallInfo = NULL;
    LONG lResult = 0;
    size_t lcb;
    
    /* allocate memory for lineGetCallInfo */ 
    lcb = sizeof(LINECALLINFO)+1000;
    lpCallInfo = (LPLINECALLINFO) _fmalloc(lcb);
    if (lpCallInfo == NULL)
	return NULL;
    
    /* try it for the first time */
    lpCallInfo->dwTotalSize = lcb;
    lResult = lineGetCallInfo(hcall,lpCallInfo);
    if (lResult < 0)
	goto LOOM;
    
    /* if buffer not big enough, try it again */
    while (lpCallInfo->dwNeededSize > lpCallInfo->dwTotalSize) 
	{
	DWORD lcbNeeded = lpCallInfo->dwNeededSize; 
	    
	_ffree(lpCallInfo);                        
	lpCallInfo = (LPLINECALLINFO) _fmalloc(lcb);
	if (lpCallInfo == NULL)
	    goto LOOM;
	lpCallInfo->dwTotalSize = lcbNeeded;
	 
	/* try one more time */
	lResult = lineGetCallInfo(hcall,lpCallInfo);
	if (lResult < 0)
	    goto LOOM;
	} /* while */

    return lpCallInfo;

LOOM: 
    if (lpCallInfo != NULL)
	_ffree(lpCallInfo);
    return NULL;
   
} /* LpCallInfoFromHCall */

/****************************************************************************/
/* %%Function:LogUsingCall */ 
/*
    Uses lineGetCallInfo to extract logging info for call. We pass in whether the 
    call is incoming/outgoing, begin and end time. Calls logCall() to put this 
    info into log file and update log window if it is visible.
*/

static void LogUsingCall(HCALL hcall,BOOL fIncoming,time_t tBegin,time_t tEnd)

{
    char szBlank[1] = {0};
    LPLINECALLINFO lpCallInfo = LpCallInfoFromHCall(hcall);
    LPSTR szName;
    LPSTR szDialString;
    
    if (lpCallInfo == NULL)
	return;
    
    /* get info out of lpCallInfo */
    szName = (LPSTR) szBlank; 
    szDialString = (LPSTR) szBlank;
    if (fIncoming) 
	{
	if (lpCallInfo->dwCalledIDFlags == LINECALLPARTYID_NAME) 
	    {
	    if (lpCallInfo->dwCalledIDNameSize)
		szName = ((LPSTR)(lpCallInfo))+lpCallInfo->dwCalledIDNameOffset;  
	    } /* if */
	else if (lpCallInfo->dwCalledIDFlags == LINECALLPARTYID_ADDRESS) 
	    {
	    if (lpCallInfo->dwCalledIDSize)
		szDialString = ((LPSTR)(lpCallInfo))+lpCallInfo->dwCalledIDOffset;      
	    } /* else if */
	} /* if */
    else 
	{/* in the outgoing case, use info in vdcr */
	szDialString = vdcr.dialStringDisplayable;    
	} /* else */
	
    szName = vdcr.szCalledParty; 
    FLogCall(fIncoming,szName,szDialString,tBegin,tEnd);
     
    _ffree(lpCallInfo);
	 
} /* LogUsingCall */

/****************************************************************************/
/* call monitoring functions */ 
/****************************************************************************/
/* %%Function:LpdmrAddMonitor */
/*
    add hcall to the list of calls we are currently monitoring(stored in vdts.lprgdmr).
    returns handle to this call's monitoring record if successful and NULL otherwise.
*/

static LPDMR LpdmrAddMonitor(HCALL hcall)
{
    LPDMR lpdmr;
    LPLINECALLINFO lpCallInfo;
	
    if (vdts.idmrMac == vdts.idmrMax) 
	{/* out of space, reallocate */
	LPDMR lprgdmr;
	size_t idmrMaxNew;
	
	idmrMaxNew = vdts.idmrMax + 32;
	lprgdmr = (LPDMR)_frealloc(vdts.lprgdmr,idmrMaxNew*sizeof(DMR));
	if (lprgdmr == NULL)
	    return NULL;
	    
	vdts.lprgdmr = lprgdmr;
	_fmemset(&((vdts.lprgdmr)[vdts.idmrMax]),mnsIdle,(idmrMaxNew-vdts.idmrMax)*sizeof(DMR));
	vdts.idmrMax = idmrMaxNew;
	} /* if */
    
    /* find the first empty slot */ 
    lpdmr = vdts.lprgdmr;
    while (lpdmr->mns != mnsIdle)
	lpdmr++;
    
    lpdmr->hcall = hcall;
    lpdmr->mns = mnsUnknown;
    if (lpCallInfo = LpCallInfoFromHCall(hcall))
	{
	switch (lpCallInfo->dwOrigin)
	    {
	    case LINECALLORIGIN_OUTBOUND:
		{
		lpdmr->mns = mnsOutgoing;
		break;
		}
	    case LINECALLORIGIN_INTERNAL:
	    case LINECALLORIGIN_EXTERNAL: 
	    case LINECALLORIGIN_UNKNOWN:
		{
		lpdmr->mns = mnsIncoming;
		break;
		} 
	    } /* switch */
	_ffree(lpCallInfo);
	} /* if */
    vdts.idmrMac++;
    return lpdmr; 
    
} /* LpdmrAddMonitor */

/****************************************************************************/
/* %%Function:LpdmrFindMonitor */
/* 
    find the call specified by hcall and returns the corresponding lpdmr.
    NULL is returned if not found.
*/

static LPDMR LpdmrFindMonitor(HCALL hcall)
{
    LPDMR lpdmr;
    LPDMR lpdmrLast;
    DWORD idmrChecked;
    
    for (lpdmr = vdts.lprgdmr, lpdmrLast = lpdmr + vdts.idmrMax - 1, idmrChecked = 0; lpdmr <= lpdmrLast; ++lpdmr, ++idmrChecked)
	{
	if (idmrChecked == vdts.idmrMac)
	    return NULL;
	
	if (lpdmr->mns == mnsIdle)
	    continue;
	    
	if (lpdmr->hcall == hcall)
	    return lpdmr;
	} /* for */
    
    return NULL; 
    
} /* LpdmrFindMonitor */

/****************************************************************************/
/* %%Function:LpdmrFindMonitor */
/* 
    frees the DMR slot occupied by hcall.
*/

static VOID RemoveMonitor(LPDMR lpdmr)
{
    lpdmr->mns = mnsIdle; 
    vdts.idmrMac--;
    
} /* RemoveMonitor */

/****************************************************************************/
/* state transition related functions */
/****************************************************************************/
/* %%Function:TapiDisconnectError */
/* 
    translates disconnectMode into the coresponding TAPIERR.
*/

static LONG TapiDisconnectError(DWORD disconnectMode)
{
    LONG lResult;
    
    switch (disconnectMode) 
	{
	case LINEDISCONNECTMODE_NOANSWER:
	    {
	    lResult = TAPIERR_DESTNOANSWER;
	    break;
	    }
	case LINEDISCONNECTMODE_BUSY:
	case LINEDISCONNECTMODE_CONGESTION:
	    {
	    lResult = TAPIERR_DESTBUSY;
	    break; 
	    }
	case LINEDISCONNECTMODE_BADADDRESS:
	case LINEDISCONNECTMODE_UNREACHEABLE:
	case LINEDISCONNECTMODE_INCOMPATIBLE:
	case LINEDISCONNECTMODE_UNAVAIL:
	    {
	    lResult = TAPIERR_DESTUNAVAIL;
	    break; 
	    }
	default:   
	    {
	    lResult = TAPIERR_DROPPED;
	    break;
	    }
	} /* switch */
	
    return lResult;
    
} /* TapiDisconnectError */

/****************************************************************************/
/* %%Function:NewCallState */
/* 
    handles state transition for both calls we originate and calls we monitor.
*/

static VOID NewCallState(DWORD newState,DWORD callbackInstance,DWORD privilege,HCALL hcall,DWORD dwParam2)
{
    LPDCR lpdcr;
    DWORD lcsOld;
    
    /* monitored call */
    if ((LPDMR)callbackInstance == NULL)
	{
	LPDMR lpdmr;
	
	/* get pointer to the monitoring record */
	if ((privilege & LINECALLPRIVILEGE_MONITOR) || (privilege & LINECALLPRIVILEGE_OWNER)) /* privilege != 0 iff this is new call to monitor */
	    {
	    lpdmr = LpdmrAddMonitor(hcall);
	    if (lpdmr == NULL)
		lineDeallocateCall(hcall);
	    else 
		{/* Default to mnsOutgoing until we see offering or accepted */
		lpdmr->mns = mnsOutgoing;
		lpdmr->tBegin = time(&(lpdmr->tBegin));
		} /* else */
	    } /* if */
	else if (privilege == 0)
	    lpdmr = LpdmrFindMonitor(hcall);  
	    
	if (lpdmr == NULL) /* nothing more we can do */
	    return;
	
	/* perfomr state transition */
	switch (newState) 
	    {
	    case LINECALLSTATE_OFFERING:
	    case LINECALLSTATE_ACCEPTED:
		{
		lpdmr->mns = mnsIncoming;
		lpdmr->tBegin = time(&(lpdmr->tBegin));
		break;
		}
	    case LINECALLSTATE_DIALING:
	    case LINECALLSTATE_DIALTONE:
	    case LINECALLSTATE_PROCEEDING:
		{
		lpdmr->mns = mnsOutgoing;
		lpdmr->tBegin = time(&(lpdmr->tBegin));
		break; 
		}
	    case LINECALLSTATE_IDLE:
		{
		lpdmr->tEnd = time(&(lpdmr->tEnd));
		LogUsingCall(lpdmr->hcall,lpdmr->mns == mnsIncoming,lpdmr->tBegin,lpdmr->tEnd);
		RemoveMonitor(lpdmr);
		lineDeallocateCall(lpdmr->hcall);
		break;
		}
	    } /* switch */
	    
	return;
	} /* if */
	
    /* the call we originated */
    lpdcr = (LPDCR)callbackInstance;
    lcsOld = lpdcr->lcs;
    
    lpdcr->lcs = newState;
    switch (newState) 
	{
	case LINECALLSTATE_CONNECTED: 
	    {
	    UpdateCallStatusDlg(TRUE,lpdcr->szCalledParty,lpdcr->szDialString);
	    SendProxyMessage(lpdcr,TAPIERR_CONNECTED);
	    break;
	    }
	case LINECALLSTATE_DISCONNECTED: 
	    {
	    UpdateCallStatusDlg(FALSE,lpdcr->szCalledParty,lpdcr->szDialString);
	    SendProxyMessage(lpdcr,TapiDisconnectError(dwParam2));
	    lpdcr->fDisconnectSent = TRUE;                 
	    FDropCall(lpdcr);
	    break;
	    }
	case LINECALLSTATE_BUSY:
	    {
	    SendProxyMessage(lpdcr,TAPIERR_DESTBUSY);
	    DisplayLineInUseDlg();
	    lpdcr->fDisconnectSent = TRUE;
	    FDropCall(lpdcr);
	    break;
	    } 
	case LINECALLSTATE_IDLE: 
	    {
	    if (!lpdcr->fDisconnectSent)
		{
		if (lcsOld == LINECALLSTATE_UNKNOWN || lpdcr->fIsProxy)
		    HideCallStatusDlg();
		else
		    UpdateCallStatusDlg(FALSE,lpdcr->szCalledParty,lpdcr->szDialString);
		SendProxyMessage(lpdcr,TAPIERR_DROPPED);
		} /* if */
	    else if (lpdcr->fIsProxy)
		HideCallStatusDlg();   
		
	    lpdcr->tEnd = time(&(lpdcr->tEnd));
	    lineDeallocateCall(lpdcr->hCall);
	    DoLineClose(lpdcr);
	    lpdcr->fIsProxy = FALSE;
	    lpdcr->mcs = mcsIdle;
	    DisableDialerDesktop(FALSE);
	    break;
	    }
	} /* switch */

} /* NewCallState */

/****************************************************************************/
/* %%Function:LineCallBackProc */
/*
    This is our tapi line call back function.
*/

void FAR PASCAL _export LineCallBackProc(DWORD hDevice,DWORD dwMessage,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2,DWORD dwParam3)
{
    switch (dwMessage) 
	{ 
	case LINE_LINEDEVSTATE:
	    {     
	    if (dwParam1 == LINEDEVSTATE_REINIT)
		vdts.fReInitTapi = TRUE;
	    break;
	    } 
	/* process state transition */
	case LINE_CALLSTATE:
	    {
	    NewCallState(dwParam1,dwInstance,dwParam3,(HCALL)hDevice,dwParam2);
	    break;
	    }
	case LINE_CLOSE: 
	    {
	    NewCallState(LINECALLSTATE_IDLE,dwInstance,0,(HCALL)hDevice,0);
	    break; 
	    }
	/* handle simple tapi request. */ 
	case LINE_REQUEST:
	    {
	    if (dwParam1 == LINEREQUESTMODE_MAKECALL)
		vdts.fCheckMakeCallRequest = TRUE;
	    else if (dwParam1 == LINEREQUESTMODE_MEDIACALL)
		vdts.fCheckMediaCallRequest = TRUE;
	    else /* handle the drop request right away */
		DoDropCallRequest((HWND)dwParam2,LOWORD(dwParam3));

	    if (vdts.fCheckMakeCallRequest || vdts.fCheckMediaCallRequest)
		/* so that we WILL get call again */
		PostMessage(HwndDialerMain(),WM_USER,0,0);
	    break;
	    }
	/* handle the assync completion of TAPI functions lineMakeCall/lineDropCall */
	case LINE_REPLY:
	    {
	    if (vdcr.requestID != dwParam1)
		break;
		 
	    if (vdcr.mcs == mcsMaking || vdcr.mcs == mcsStopMaking)
		{/* reply message for lineMakeCall */
		if (dwParam2 != errNone) 
		    {       
		    if (vdcr.fIsProxy)
			SendProxyMessage(&vdcr,TAPIERR_REQUESTFAILED);
		    DoLineClose(&vdcr);
		    vdcr.mcs = mcsIdle;
		    } /* if */ 
		else 
		    {
		    MCS mcsOld = vdcr.mcs;
				
		    vdcr.mcs = mcsMade;
		    if (mcsOld == mcsStopMaking)
			FDropCall(&vdcr);
		    } /* else */
		} /* if */
	    else if (vdcr.mcs == mcsDropping)
		vdcr.mcs = mcsIdle; 
	    break;
	    }
	/* other messages that can be processed */
	case LINE_ADDRESSSTATE:
	    break;
	case LINE_CALLINFO:
	    break;
	case LINE_DEVSPECIFIC:
	    break;
	case LINE_DEVSPECIFICFEATURE:
	    break;
	case LINE_GATHERDIGITS:
	    break;
	case LINE_GENERATE:
	    break;
	 case LINE_MONITORDIGITS:
	    break;
	case LINE_MONITORMEDIA:
	    break;
	case LINE_MONITORTONE:
	    break;
	} /* switch */ 
	
} /* LineCallBackProc */

