// 
// 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.
// 
// 
#include <windows.h>
#include <memory.h>    
#include <stdlib.h>   
#include <string.h>
#include <assert.h>
#include "atsp.h"

int FAR PASCAL __export ConfigDlgProc(HWND, UINT, WPARAM, LPARAM);
int FAR PASCAL __export LineConfDlgProc(HWND, UINT, WPARAM, LPARAM);

void	LoadIniStrings(DWORD);
int	SendModemCommand(int, const void *, int);
int	GetModemReply(int, void *, int);   
int	SerialOpenComms(void);
void	SerialCloseComms(void);
int	DialAndGetState(const char *, BOOL, int);
void	ATSPHelp(DWORD);

#define ATSPMEDIAMODES (LINEMEDIAMODE_INTERACTIVEVOICE | LINEMEDIAMODE_DATAMODEM)

char *lpszCommDevArray[NUMPORTS] = 
{ "COM1", "COM2", "COM3", "COM4" };
char *lpszCommSpeedArray[NUMSPEEDS] = 
{ "300", "1200", "2400", "9600", "19200", "38400", "57600" };

// Various tags in the ini file.

char s_telephon_ini[] = "telephon.ini";
char s_one[]			= "1";
char s_zero[]			= "0";
char s_numlines[]		= "NumLines";
char s_numphones[]	= "NumPhones";
char s_providerx[]	= "Provider%d";

char lpszIniSection[INI_SECTIONSIZE];

char lpszPortEntry[INI_ENTRYSIZE];
char lpszSpeedEntry[INI_ENTRYSIZE];
char lpszInitStrEntry[INI_ENTRYSIZE];
char lpszInitStrPrefEntry[INI_ENTRYSIZE];
char lpszLineNameEntry[INI_ENTRYSIZE];
char lpszLineAddressEntry[INI_ENTRYSIZE];



// The global module handle
HANDLE hInst = NULL;

// Line object.
ATSPLineData line;



///////////////////////////////////////////////////////////
// The required DLL functions
///////////////////////////////////////////////////////////

int FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSegment,
			       WORD wHeapSize, LPSTR lpszCmdLine)
{                      
  if (hInst != NULL)
      return FALSE;

  hInst = hInstance;
  return TRUE;
}

VOID FAR PASCAL WEP (int bSystemExit)
{ 
  hInst = NULL;
  return;
}


///////////////////////////////////////////////////////////
// The Service Provider Basic Configuration Routines
///////////////////////////////////////////////////////////

#ifdef DEBUG
static BOOL initialised = FALSE;
#endif

LONG TSPIAPI TSPI_lineNegotiateTSPIVersion(DWORD dwDeviceID,
					  DWORD dwLowVersion,
					  DWORD dwHighVersion,
					  LPDWORD lpdwTSPIVersion)
{
	DebugMsg ("Entering TSPI_lineNegotiateTSPIVersion");

	// line.lineID will contain garbage before provider_init has
	// been called (ie. first time through). However, we can guarantee
	// that the first call will be with INITIALIZE_NEGOTIATION and that
	// is followed immediately by provider_init. This would be a problem
	// if the line data structure was dynamically allocated !

#ifdef DEBUG
	if (!initialised)
		assert (dwDeviceID == INITIALIZE_NEGOTIATION);
#endif

	if (dwDeviceID == INITIALIZE_NEGOTIATION ||
		 dwDeviceID == line.lineID)			// we support only one line
	{
		*lpdwTSPIVersion = ATSP_VERSION;
		
		if (dwLowVersion  > ATSP_VERSION ||	// the app is too new for us
			 dwHighVersion < ATSP_VERSION)	// we are too new for the app
			return LINEERR_INCOMPATIBLEAPIVERSION;
		else
			return 0;
	}

	return LINEERR_BADDEVICEID;				// The requested device doesn't exist
}


LONG TSPIAPI TSPI_providerInit(
    DWORD             dwTSPIVersion,
    DWORD             dwPermanentProviderID,
    DWORD             dwLineDeviceIDBase,
    DWORD             dwPhoneDeviceIDBase,
    DWORD             dwNumLines,
    DWORD             dwNumPhones,
    ASYNC_COMPLETION  cbCompletionProc)
{
	int i;

	DebugMsg("Entering TSPI_providerInit");

	assert (dwTSPIVersion == ATSP_VERSION);
	assert (dwNumLines    == 1);
	assert (dwNumPhones   == 0);

	_fmemset (&line, 0, sizeof(ATSPLineData));
  
	line.dwPermanentProviderID	= dwPermanentProviderID;
	line.lpfnCompletion			= cbCompletionProc;
	line.lineID						= dwLineDeviceIDBase;
  
	for (i = 0; i < ATSP_CALL_ARRAY_SIZE; i++)
	{
		ATSPCallData	*call		= &line.call[i];
		LINEDIALPARAMS *ldp		= &call->dpDialParams;

		call->dwState				= LINECALLSTATE_IDLE;

		ldp->dwDialPause			= 2000;
		ldp->dwDialSpeed			= 95;
		ldp->dwDigitDuration		= 95;
		ldp->dwWaitForDialtone	= 60000;
	}

	LoadIniStrings (dwPermanentProviderID);

#ifdef DEBUG
	initialised = TRUE;
#endif

	return 0;
}


LONG TSPIAPI TSPI_providerShutdown (DWORD dwTSPIVersion)
{											   
  DebugMsg("Entering TSPI_providerShutdown");
  return 0;
}


///////////////////////////////////////////////////////////
// The Line Specific Calls
///////////////////////////////////////////////////////////

LONG TSPIAPI TSPI_lineConfigDialog(DWORD dwDeviceID,
				  HWND  hwndOwner,
				  LPCSTR const lpszDeviceClass)
{
	LoadIniStrings(line.dwPermanentProviderID);
  
	DialogBox(hInst, MAKEINTRESOURCE(IDD_CFGDLG),
		   hwndOwner, ConfigDlgProc);

  	return 0;
}

LONG TSPIAPI TSPI_lineClose(HDRVLINE hdLine)
{
  int Current;
  
  DebugMsg("Entering TSPI_lineClose");
  
  for(Current = 0; Current < ATSP_CALL_ARRAY_SIZE; Current++)
    {
      if (line.call[Current].bInUse)
	{
	  if(line.call[Current].dwState != LINECALLSTATE_IDLE)
	    {
	      (*(line.lpfnEventProc))(line.htLine, line.call[Current].htCall,
	        LINE_CALLSTATE, LINECALLSTATE_IDLE, 0, 0);
	    }
	  line.call[Current].bInUse = FALSE;
	}
    }
  
  line.calls = 0;
  if (line.bActiveCall)
    {
      SerialCloseComms();
      line.bActiveCall = FALSE;
    }

  line.bLineOpen = FALSE;
  return 0;
}

LONG TSPIAPI TSPI_lineCloseCall(HDRVCALL hdCall)
{
  // At this point, the call has already been dropped,
  // so we only need to deallocate it.
  
  int CurrCall;
  
  DebugMsg("Entering TSPI_lineCloseCall");
  
  for (CurrCall = 0; CurrCall < ATSP_CALL_ARRAY_SIZE; CurrCall++)
    {
      if (hdCall == (HDRVCALL) &line.call[CurrCall])
	{
	  line.call[CurrCall].bInUse = FALSE;
	  line.calls--;
	  return 0;
	}
    }
  
  return 0;
}

LONG CheckDestAddress (LPCSTR const szAddr)
{
	char ch;
	int  i;

	for (i = 0; ch = szAddr[i]; i++)
	{
		if (ch == '?')
			return LINEERR_DIALPROMPT;
		else if (ch == '$')
			return LINEERR_DIALBILLING;
	}

	if (i > MAXDESTADDR)
		return (LINEERR_INVALPOINTER);

	return 0;
}

LONG TSPIAPI TSPI_lineDial(DRV_REQUESTID dwRequestID,
			  HDRVCALL hdCall,
			  LPCSTR const lpszDestAddress,
			  DWORD dwCountryCode)
{
	LONG	res;
	int   call;

	DebugMsg("Entering TSPI_lineDial");
  
	// check for invalid dial string (wierd chars or length)
	// we can assume lpszDestAddress is a valid pointer as TAPI checks it
	if (res = CheckDestAddress (lpszDestAddress))
		return (res);

	// Figure out which call we are dialing.
	for (call = 0; call < ATSP_CALL_ARRAY_SIZE; call++)
	{
		ATSPCallData *thisCall = &line.call[call];

		if (hdCall == (HDRVCALL) thisCall &&
			 (thisCall->dwState == LINECALLSTATE_IDLE ||
			  thisCall->dwState == LINECALLSTATE_DIALTONE))
		{
			_fstrcpy (thisCall->DestAddress, lpszDestAddress);

			// With the second parameter set to TRUE, DialAndGetState
			// will take care of updating the state of the call through
			// the event proc.
  
			if (DialAndGetState (lpszDestAddress, TRUE, call) == CALL_STATE_ERROR)
				res = LINEERR_CALLUNAVAIL;
   
			(*(line.lpfnCompletion)) (dwRequestID, res);
			return dwRequestID;
		}
	}

	return (LINEERR_CALLUNAVAIL);
}

LONG TSPIAPI TSPI_lineDrop(DRV_REQUESTID dwRequestID,
			  HDRVCALL hdCall,
			  LPCSTR const lpsUserUserInfo,
			  DWORD dwSize)
{
  // Transition a call to the IDLE state.
  
  int CurrCall;
  
  DebugMsg("Entering TSPI_lineDrop");
  
  for (CurrCall = 0; CurrCall < ATSP_CALL_ARRAY_SIZE; CurrCall++)
    {
      if (((HDRVCALL) &line.call[CurrCall]) == hdCall)
	{
	  line.call[CurrCall].bInUse = FALSE;
	  line.calls--; 
	  if (line.call[CurrCall].dwState == LINECALLSTATE_IDLE)
	    goto POST_COMPLETION_AND_LEAVE;
	  
	  // It was an active call.
	  line.bActiveCall = FALSE;
	  SerialCloseComms();
	  break;
	}
    }
  
  (*(line.lpfnEventProc))(line.htLine, line.call[CurrCall].htCall, 
    LINE_CALLSTATE, LINECALLSTATE_IDLE, 0, 0);
 POST_COMPLETION_AND_LEAVE:
  (*(line.lpfnCompletion))(dwRequestID, 0);
  return dwRequestID;
}

LONG TSPIAPI TSPI_lineGenerateDigits(HDRVCALL hdCall,
				    DWORD dwEndToEndID,
				    DWORD dwDigitMode,
				    LPCSTR const lpszDigits,
				    DWORD dwDuration)
{ 
  char szDigitBuf[MEDIUMBUFFER];
  int Reply, cbDigitBuf;

  DebugMsg("Entering TSPI_lineGenerateDigits");

  // Peek into the handle at the call state.
  if (((ATSPCallData*)hdCall)->dwState != LINECALLSTATE_BUSY &&
      ((ATSPCallData*)hdCall)->dwState != LINECALLSTATE_DIALTONE &&
      ((ATSPCallData*)hdCall)->dwState != LINECALLSTATE_CONNECTED)
    return LINEERR_INVALCALLSTATE;

  // Set the digit duration.
  if (dwDuration == 0)
    Reply = (int)((ATSPCallData*)hdCall)->dpDialParams.dwDigitDuration;
  else
    Reply = (int)dwDuration;
  
  if(dwDigitMode == LINEDIGITMODE_PULSE)
    cbDigitBuf = wsprintf(szDigitBuf, "ATX0S6=0DP%s;X4", (LPSTR) lpszDigits);
  else
    cbDigitBuf = wsprintf(szDigitBuf, "ATX0S6=0S11=%luDT%s;X4", 
			  dwDuration, (LPSTR) lpszDigits);
  
  // We neglect to check for some errors here to speed up this
  // process.  It's time critical and failure is unimportant in
  // most cases.

  Reply = SendModemCommand(line.uiCommId, szDigitBuf, cbDigitBuf);
  if (Reply >= 0)
    {
      Reply = GetModemReply(line.uiCommId, szDigitBuf, MEDIUMBUFFER);
      if (Reply == SERIAL_OK)
	if (_fstrcmp(szDigitBuf, "OK") == 0)
	  {
	    SendModemCommand(line.uiCommId, INTERNAL_HANGUP,
			     INTERNAL_HANGUP_LEN);
	    return 0;
	  }
    }

  return LINEERR_OPERATIONFAILED;
}


LONG TSPIAPI TSPI_lineGetAddressCaps(DWORD dwDeviceID,
				    DWORD dwAddressID,
				    DWORD dwTSPIVersion,
				    DWORD dwExtVersion,
				    LPLINEADDRESSCAPS lpAddressCaps)

{
  char szLineAddr[LINEADDRESSSIZE];
  int cbLineAddrLen,
      cbAvailMem,
      cbCopy;

  DebugMsg("Entering TSPI_lineGetAddressCaps");

  // We support only one line and one address.
  
  if(dwDeviceID != line.lineID)
    return LINEERR_BADDEVICEID;
  if(dwAddressID != 0)
    return LINEERR_INVALADDRESSID;
  
  // Check to see if we have enough memory in the structure.
  
  cbLineAddrLen = GetPrivateProfileString(lpszIniSection, lpszLineAddressEntry,
    "", szLineAddr, sizeof(szLineAddr), s_telephon_ini);
  // GetPrivateProfileString doesn't count the null terminating character.
  if (cbLineAddrLen != 0)
    cbLineAddrLen++;

  lpAddressCaps->dwUsedSize = sizeof(LINEADDRESSCAPS);

  cbAvailMem = ((int) lpAddressCaps->dwTotalSize - 
		((int) lpAddressCaps->dwUsedSize));
  
  lpAddressCaps->dwNeededSize = cbLineAddrLen + lpAddressCaps->dwUsedSize;

  if (cbAvailMem > 0)
    {
      cbCopy = ((cbAvailMem > cbLineAddrLen) ?
		cbLineAddrLen : cbAvailMem);
      
      _fmemcpy((char far*)lpAddressCaps + lpAddressCaps->dwUsedSize, 
	     szLineAddr, cbCopy);
      lpAddressCaps->dwAddressSize = cbCopy;
      lpAddressCaps->dwAddressOffset = lpAddressCaps->dwUsedSize;
      lpAddressCaps->dwUsedSize += cbCopy;
    }
  
  lpAddressCaps->dwLineDeviceID = line.lineID;

  lpAddressCaps->dwAddressSharing = LINEADDRESSSHARING_PRIVATE;
  lpAddressCaps->dwAddressStates = LINEADDRESSSTATE_OTHER |
                                   LINEADDRESSSTATE_INUSEZERO |
				   LINEADDRESSSTATE_INUSEONE |
				   LINEADDRESSSTATE_NUMCALLS;
  lpAddressCaps->dwCallInfoStates = LINECALLINFOSTATE_OTHER |
                                    LINECALLINFOSTATE_APPSPECIFIC |
				    LINECALLINFOSTATE_NUMOWNERINCR |
				    LINECALLINFOSTATE_NUMOWNERDECR |
				    LINECALLINFOSTATE_NUMMONITORS |
				    LINECALLINFOSTATE_DIALPARAMS;
  lpAddressCaps->dwCallerIDFlags = LINECALLPARTYID_UNAVAIL;
  lpAddressCaps->dwCalledIDFlags = LINECALLPARTYID_UNAVAIL;
  lpAddressCaps->dwConnectedIDFlags = LINECALLPARTYID_UNAVAIL;
  lpAddressCaps->dwRedirectionIDFlags = LINECALLPARTYID_UNAVAIL;
  lpAddressCaps->dwRedirectingIDFlags = LINECALLPARTYID_UNAVAIL;
  lpAddressCaps->dwCallStates = LINECALLSTATE_IDLE |
                                LINECALLSTATE_DIALTONE |
				LINECALLSTATE_DIALING |
				LINECALLSTATE_BUSY |
				LINECALLSTATE_CONNECTED |
				LINECALLSTATE_UNKNOWN;
  lpAddressCaps->dwDialToneModes = LINEDIALTONEMODE_UNAVAIL;
  lpAddressCaps->dwBusyModes = LINEBUSYMODE_UNAVAIL;
  lpAddressCaps->dwSpecialInfo = LINESPECIALINFO_UNAVAIL;
  lpAddressCaps->dwDisconnectModes = LINEDISCONNECTMODE_UNKNOWN;
  lpAddressCaps->dwMaxNumActiveCalls = 1;
  lpAddressCaps->dwAddrCapFlags = LINEADDRCAPFLAGS_BLOCKIDDEFAULT |
				  LINEADDRCAPFLAGS_DIALED |
				  LINEADDRCAPFLAGS_PARTIALDIAL;
  lpAddressCaps->dwCallFeatures = LINECALLFEATURE_DIAL |
                                  LINECALLFEATURE_DROP |
				  LINECALLFEATURE_GENERATEDIGITS |
				  LINECALLFEATURE_SETCALLPARAMS;

  return 0;
}

LONG TSPIAPI TSPI_lineGetAddressID(HDRVLINE hdLine, 
				  LPDWORD lpdwAddressID, 
				  DWORD dwAddressMode,
				  LPCSTR const lpsAddress, 
				  DWORD dwSize)
{
  char szLineAddress[LINEADDRESSSIZE];

  DebugMsg("Entering TSPI_lineGetAddressID");

  switch (dwAddressMode)
    {
    case LINEADDRESSMODE_ADDRESSID:
      
      if ((int) *lpsAddress == 0)
	{
	  *lpdwAddressID = ((DWORD) 0);
	  return 0;
	}
      else
	return LINEERR_INVALADDRESS;
      
    case LINEADDRESSMODE_DIALABLEADDR:
      
      GetPrivateProfileString(lpszIniSection, lpszLineAddressEntry,
			      "", szLineAddress, sizeof(szLineAddress),
			      s_telephon_ini);
      
      if (!_fstrcmp(szLineAddress, lpsAddress))
	{ 
	  *lpdwAddressID = ((DWORD) 0);
	  return 0;
	}
      else
	return LINEERR_INVALADDRESS;
    }

  return 0;
}

LONG TSPIAPI TSPI_lineGetAddressStatus(HDRVLINE hdLine,
				      DWORD dwAddressID,
				      LPLINEADDRESSSTATUS lpAddressStatus)
{
	DebugMsg("Entering TSPI_lineGetAddressStatus");

	if (dwAddressID)
		return LINEERR_INVALADDRESSID;

	lpAddressStatus->dwUsedSize	= sizeof(LINEADDRESSSTATUS);
	lpAddressStatus->dwNeededSize	= sizeof(LINEADDRESSSTATUS);
  
	if (line.bLineOpen)
		lpAddressStatus->dwNumInUse = 1;

	if (line.bActiveCall)
		lpAddressStatus->dwNumActiveCalls = 1;
	else
		lpAddressStatus->dwAddressFeatures = LINEADDRFEATURE_MAKECALL;

	return 0;
}

LONG TSPIAPI TSPI_lineGetCallAddressID(HDRVCALL hdCall,
				      LPDWORD lpdwAddressID)
{
	DebugMsg("Entering TSPI_lineGetCallAddressID");

	// There is but a single address where a call may exist.
	*lpdwAddressID = 0;
  
	return 0;
}

LONG TSPIAPI TSPI_lineGetCallInfo(HDRVCALL hdCall,
				 LPLINECALLINFO lpCallInfo)
{
	int Call;

	DebugMsg("Entering TSPI_lineGetCallInfo");

	for (Call = 0; Call < ATSP_CALL_ARRAY_SIZE; Call++)
	{
		if (hdCall == (HDRVCALL) &line.call[Call])
		{
			int cbNeeded, cbAvail, cbCopy;

			lpCallInfo->dwUsedSize = sizeof(LINECALLINFO);
			cbAvail = (int) (lpCallInfo->dwTotalSize - lpCallInfo->dwUsedSize);
			cbNeeded = _fstrlen(line.call[Call].DestAddress) + 1;
			cbCopy = ((cbAvail > cbNeeded) ? cbNeeded : cbAvail);
			_fmemcpy((char *)lpCallInfo + lpCallInfo->dwUsedSize, 
						line.call[Call].DestAddress, cbCopy);
			
			lpCallInfo->dwNeededSize = lpCallInfo->dwUsedSize + cbNeeded;
			lpCallInfo->dwDisplayableAddressSize = cbCopy;
			lpCallInfo->dwDisplayableAddressOffset = lpCallInfo->dwUsedSize;
			lpCallInfo->dwUsedSize += cbCopy;
	
			lpCallInfo->dwLineDeviceID = line.lineID;
			lpCallInfo->dwBearerMode = LINEBEARERMODE_VOICE;
			lpCallInfo->dwMediaMode = ((ATSPCallData*)hdCall)->dwMediaMode;
			lpCallInfo->dwAppSpecific = ((ATSPCallData*)hdCall)->dwAppSpecific;
			lpCallInfo->dwCallParamFlags = LINECALLPARAMFLAGS_IDLE |
	                               LINECALLPARAMFLAGS_BLOCKID;
			lpCallInfo->dwCallStates = LINECALLSTATE_IDLE |
	                           LINECALLSTATE_DIALTONE |
				   LINECALLSTATE_DIALING |
				   LINECALLSTATE_BUSY |
				   LINECALLSTATE_CONNECTED |
				   LINECALLSTATE_PROCEEDING |
				   LINECALLSTATE_UNKNOWN;

			_fmemcpy (&lpCallInfo->DialParams, 
						 &line.call[Call].dpDialParams, sizeof (LINEDIALPARAMS));

			lpCallInfo->dwOrigin					= LINECALLORIGIN_OUTBOUND;
			lpCallInfo->dwReason					= LINECALLREASON_UNAVAIL;
			lpCallInfo->dwCallerIDFlags		= LINECALLPARTYID_UNAVAIL;
			lpCallInfo->dwCalledIDFlags		= LINECALLPARTYID_UNAVAIL;
			lpCallInfo->dwConnectedIDFlags	= LINECALLPARTYID_UNAVAIL;
			lpCallInfo->dwRedirectionIDFlags	= LINECALLPARTYID_UNAVAIL;
			lpCallInfo->dwRedirectingIDFlags	= LINECALLPARTYID_UNAVAIL;
	
			return 0;
		}
  	}

	return LINEERR_INVALCALLHANDLE;
}

LONG TSPIAPI TSPI_lineGetCallStatus(HDRVCALL hdCall,
				   LPLINECALLSTATUS lpCallStatus)
{
  DebugMsg("Entering TSPI_lineGetCallStatus");

  lpCallStatus->dwCallState = ((ATSPCallData*)(hdCall))->dwState;
  switch(lpCallStatus->dwCallState)
    {
    case LINECALLSTATE_IDLE:
      lpCallStatus->dwCallFeatures = LINECALLFEATURE_SETCALLPARAMS |
	                             LINECALLFEATURE_DIAL |
				     LINECALLFEATURE_DROP;
      break;
    case LINECALLSTATE_DIALTONE:
      lpCallStatus->dwCallStateMode = LINEDIALTONEMODE_UNAVAIL;
      lpCallStatus->dwCallFeatures = LINECALLFEATURE_DIAL |
	                             LINECALLFEATURE_DROP |
                                     LINECALLFEATURE_SETCALLPARAMS;
      break;
    case LINECALLSTATE_DIALING:
      // The LINEDIALINGMODE constants are missing, so we'll skip it for now.
      // lpCallStatus->dwCallStateMode = LINEDIALINGMODE_ADDRESS;
      lpCallStatus->dwCallFeatures = LINECALLFEATURE_DROP;
      break;
    case LINECALLSTATE_BUSY:
      lpCallStatus->dwCallStateMode = LINEBUSYMODE_UNAVAIL;
      lpCallStatus->dwCallFeatures = LINECALLFEATURE_GENERATEDIGITS |
	                             LINECALLFEATURE_DROP |
				     LINECALLFEATURE_SETCALLPARAMS;
      break;
    case LINECALLSTATE_CONNECTED:
      lpCallStatus->dwCallFeatures = LINECALLFEATURE_GENERATEDIGITS |
	                             LINECALLFEATURE_DROP |
				     LINECALLFEATURE_SETCALLPARAMS;
      break;
    case LINECALLSTATE_PROCEEDING:
      lpCallStatus->dwCallFeatures = LINECALLFEATURE_DROP |
	                             LINECALLFEATURE_GENERATEDIGITS;
      break;
    case LINECALLSTATE_UNKNOWN:
      break;
    }

  return 0;
}

LONG TSPIAPI TSPI_lineGetDevCaps (DWORD dwDeviceID,
				DWORD dwTSPIVersion, DWORD dwExtVersion,
				LPLINEDEVCAPS lpLineDevCaps)
{
	char lpszLineName[LINENAMESIZE];
	char lpszProviderInfo[PROVIDERINFOSIZE];
  
	int cbProviderInfoLen;
	int cbLineNameLen;
	int cbAvailMem;
	int cbCopy;

	DebugMsg("Entering TSPI_lineGetDevCaps");

  // Check to see how much memory we'll need.
  
  cbProviderInfoLen = LoadString(hInst, ID_PROVIDER_INFO, lpszProviderInfo, 
				 sizeof(lpszProviderInfo));
  
  cbLineNameLen = GetPrivateProfileString(lpszIniSection, lpszLineNameEntry,
    "", lpszLineName, sizeof(lpszLineName), s_telephon_ini);
  if (cbLineNameLen > 0)
    cbLineNameLen++;

  lpLineDevCaps->dwUsedSize = sizeof(LINEDEVCAPS);

  cbAvailMem = (int) (lpLineDevCaps->dwTotalSize - 
		      lpLineDevCaps->dwUsedSize);
  
  // Enter the size we ideally need.
  lpLineDevCaps->dwNeededSize = cbProviderInfoLen + cbLineNameLen + 
                                lpLineDevCaps->dwUsedSize;
  
  // Copy in the provider info that will fit.
  if (cbAvailMem > 0)
    {
      cbCopy = ((cbAvailMem > cbProviderInfoLen) ? 
		cbProviderInfoLen : cbAvailMem);

      _fmemcpy((char *)lpLineDevCaps + lpLineDevCaps->dwUsedSize, 
	     lpszProviderInfo, cbCopy);
      lpLineDevCaps->dwProviderInfoSize = cbCopy;
      lpLineDevCaps->dwProviderInfoOffset = lpLineDevCaps->dwUsedSize;
      lpLineDevCaps->dwUsedSize += cbCopy;
      cbAvailMem -= cbCopy;
    }
  
  // Copy as much of the name as will fit.
  if (cbAvailMem != 0)
    {
      cbCopy = ((cbAvailMem > cbLineNameLen) ?
		cbLineNameLen : cbAvailMem);

      _fmemcpy((char *)lpLineDevCaps + lpLineDevCaps->dwUsedSize,
	     lpszLineName, cbCopy);
      lpLineDevCaps->dwLineNameSize = cbCopy;
      lpLineDevCaps->dwLineNameOffset = lpLineDevCaps->dwUsedSize;
      lpLineDevCaps->dwUsedSize += cbCopy;
    }
  
  // Get the permanent line id from somewhere.  For now,
  // we hard code it to zero.
  lpLineDevCaps->dwPermanentLineID = 0;
  
  // TAPI.DLL fills in APIVersion and ExtVersion.

  lpLineDevCaps->dwStringFormat = STRINGFORMAT_ASCII;
  lpLineDevCaps->dwNumAddresses = 1;
  lpLineDevCaps->dwBearerModes = LINEBEARERMODE_VOICE;

  // We claim to support the datamodem media mode, but we do not
  // and will not release the connected data set to a media handler.
  // This is just for testing media call handoffs.

  lpLineDevCaps->dwMediaModes = ATSPMEDIAMODES;
  lpLineDevCaps->dwGenerateDigitModes = LINEDIGITMODE_PULSE |
                                        LINEDIGITMODE_DTMF;
  lpLineDevCaps->dwMaxNumActiveCalls = 1;
  lpLineDevCaps->dwLineStates = LINEDEVSTATE_OTHER |
                                LINEDEVSTATE_CONNECTED |
				LINEDEVSTATE_DISCONNECTED |
				LINEDEVSTATE_OPEN |
				LINEDEVSTATE_CLOSE |
				LINEDEVSTATE_NUMCALLS |
				LINEDEVSTATE_REINIT;

	lpLineDevCaps->MinDialParams.dwDialSpeed				= 50;
	lpLineDevCaps->DefaultDialParams.dwDialSpeed			= 95;
	lpLineDevCaps->MaxDialParams.dwDialSpeed				= 255;

	lpLineDevCaps->MinDialParams.dwDigitDuration			= 50;
	lpLineDevCaps->DefaultDialParams.dwDigitDuration	= 95;
	lpLineDevCaps->MaxDialParams.dwDigitDuration			= 255;

	lpLineDevCaps->MinDialParams.dwWaitForDialtone		= 1000;
	lpLineDevCaps->DefaultDialParams.dwWaitForDialtone	= 60000;
	lpLineDevCaps->MaxDialParams.dwWaitForDialtone		= 255000;

	lpLineDevCaps->DefaultDialParams.dwDialPause			= 2000;
	lpLineDevCaps->MaxDialParams.dwDialPause				= 255000;

	// we make an assumption in this sample code that a "stock"
	// AT modem will understand @ and W but not $ modifiers

	lpLineDevCaps->dwDevCapFlags =	LINEDEVCAPFLAGS_DIALQUIET |
												LINEDEVCAPFLAGS_DIALDIALTONE;

	return 0;
}

LONG TSPIAPI TSPI_lineGetID(HDRVLINE hdLine,
			   DWORD dwAddressID,
			   HDRVCALL hdCall,
			   DWORD dwSelect,
			   LPVARSTRING lpDeviceID,
			   LPCSTR const lpszDeviceClass)
{
	DebugMsg("Entering TSPI_lineGetID");

	// Since we have only one device, we don't have to
	// check the location of the line, address, or call.
  
	if (_fstrcmp (lpszDeviceClass, "tapi/line") == 0)
	{
		// tapi checks that the lpDeviceID structure is 
		// at least sizeof(VARSTRING) so the following writes are safe

		lpDeviceID->dwUsedSize		= 
		lpDeviceID->dwNeededSize	= sizeof(VARSTRING) + sizeof(DWORD);
      lpDeviceID->dwStringFormat	= STRINGFORMAT_BINARY;
      lpDeviceID->dwStringSize	= sizeof(DWORD);
      lpDeviceID->dwStringOffset	= sizeof(VARSTRING);
		
		if (lpDeviceID->dwTotalSize < lpDeviceID->dwNeededSize)
			return LINEERR_STRUCTURETOOSMALL;

      *((DWORD *) ((char *) lpDeviceID + sizeof(VARSTRING))) = line.lineID;

      return 0;
    }
  
	if (_fstrcmp (lpszDeviceClass, "comm") == 0)
	{
		int port	  = GetPrivateProfileInt (lpszIniSection, lpszPortEntry, 0, s_telephon_ini);
      int cbport = _fstrlen(lpszCommDevArray[port]) + 1;

      lpDeviceID->dwUsedSize		= 
      lpDeviceID->dwNeededSize	= sizeof(VARSTRING) + cbport;
      lpDeviceID->dwStringFormat = STRINGFORMAT_ASCII;
      lpDeviceID->dwStringSize	= cbport;
      lpDeviceID->dwStringOffset = sizeof(VARSTRING);

      if (lpDeviceID->dwTotalSize < lpDeviceID->dwNeededSize)
			return LINEERR_STRUCTURETOOSMALL;

		_fmemcpy((char *) lpDeviceID + sizeof(VARSTRING), 
						lpszCommDevArray[port], cbport);
		return 0;
	}
  
	return LINEERR_NODEVICE;
}

LONG TSPIAPI TSPI_lineGetLineDevStatus(HDRVLINE hdLine,
				      LPLINEDEVSTATUS lpLineDevStatus)
{
  DebugMsg("Entering TSPI_lineGetLineDevStatus");
  
  lpLineDevStatus->dwUsedSize = sizeof(LINEDEVSTATUS);
  lpLineDevStatus->dwNeededSize = sizeof(LINEDEVSTATUS);
  lpLineDevStatus->dwOpenMediaModes = line.dwLineMediaModes;
  if (line.bActiveCall)
    {
      lpLineDevStatus->dwNumActiveCalls = 1;
      lpLineDevStatus->dwLineFeatures = 0;
    }
  else
    {
      lpLineDevStatus->dwNumActiveCalls = 0;
      lpLineDevStatus->dwLineFeatures = LINEFEATURE_MAKECALL;
    }
	lpLineDevStatus->dwNumOnHoldCalls = 0;
	lpLineDevStatus->dwNumOnHoldPendCalls = 0;
	lpLineDevStatus->dwNumCallCompletions = 0;
	lpLineDevStatus->dwRingMode = 0;
	lpLineDevStatus->dwSignalLevel = 0;
	lpLineDevStatus->dwBatteryLevel = 0;
	lpLineDevStatus->dwRoamMode = LINEROAMMODE_UNAVAIL;
  if (line.bLineOpen)
    lpLineDevStatus->dwDevStatusFlags = LINEDEVSTATUSFLAGS_CONNECTED |
                                        LINEDEVSTATUSFLAGS_INSERVICE;
  else
    lpLineDevStatus->dwDevStatusFlags = 0;
  lpLineDevStatus->dwTerminalModesSize = 0;
  lpLineDevStatus->dwTerminalModesOffset = 0;
  lpLineDevStatus->dwDevSpecificSize = 0;
  lpLineDevStatus->dwDevSpecificOffset = 0;
  
  return 0;
}

LONG TSPIAPI TSPI_lineGetNumAddressIDs(HDRVLINE hdLine,
				      LPDWORD lpNumAddressIDs)
{
  DebugMsg("Entering TSPI_lineGetNumAddressIDs");
  
  // We only support one address.
  *lpNumAddressIDs = 1;
  return 0;
}


LONG TSPIAPI TSPI_lineMakeCall(DRV_REQUESTID dwRequestID,
			      HDRVLINE hdLine,
			      HTAPICALL htCall,
			      LPHDRVCALL lphdCall,
			      LPCSTR const lpszDestAddress,
			      DWORD dwCountryCode,
			      LPLINECALLPARAMS const lpCallParams)
{
	int	numCall;
	LONG	res;
 	DWORD	error = LINEERR_OPERATIONFAILED;


	DebugMsg("Entering TSPI_lineMakeCall");

	// check for invalid dial string (wierd chars or length)
	if (lpszDestAddress && (res = CheckDestAddress (lpszDestAddress)))
		return (res);

	// See if we have a free call struct.
	if (line.calls == ATSP_CALL_ARRAY_SIZE)
		return LINEERR_RESOURCEUNAVAIL;
  
	// Commit the first available call struct.
	for (numCall = 0; numCall < ATSP_CALL_ARRAY_SIZE; numCall++)
	{
		if(line.call[numCall].bInUse == FALSE)
		{
			line.call[numCall].htCall	= htCall;
			line.call[numCall].bInUse	= TRUE;
			line.call[numCall].dwState = LINECALLSTATE_IDLE;
			*lphdCall = (HDRVCALL) &line.call[numCall];

			if (lpszDestAddress != NULL)
				_fstrcpy(line.call[numCall].DestAddress, lpszDestAddress);
			
			line.calls++;
			break;
		}
	}
  
	if (lpCallParams)
	{
		if (lpCallParams->dwBearerMode != LINEBEARERMODE_VOICE)
		{
			error = LINEERR_INVALBEARERMODE;
			goto DEALLOCATE_CALL_AND_FAIL;
		}
      
		if (lpCallParams->dwMediaMode != LINEMEDIAMODE_INTERACTIVEVOICE &&
			 lpCallParams->dwMediaMode != LINEMEDIAMODE_DATAMODEM)
		{
			error = LINEERR_INVALMEDIAMODE;
			goto DEALLOCATE_CALL_AND_FAIL;
		}
		else
			line.call[numCall].dwMediaMode = lpCallParams->dwMediaMode;

		// We support only the IDLE and BLOCKID call param flags.
		if (!(lpCallParams->dwCallParamFlags & 
				(LINECALLPARAMFLAGS_IDLE | LINECALLPARAMFLAGS_BLOCKID)) &&
				lpCallParams->dwCallParamFlags != 0)
		{
			error = LINEERR_INVALCALLPARAMS;
			goto DEALLOCATE_CALL_AND_FAIL;
		}

		// Set the dial params if there are any.
		{	
			LINEDIALPARAMS *ldp   = &lpCallParams->DialParams;
			LINEDIALPARAMS *myldp = &line.call[numCall].dpDialParams;

			if (ldp->dwDialPause)		 
				myldp->dwDialPause = ldp->dwDialPause;
			if (ldp->dwDialSpeed)
				myldp->dwDialSpeed = ldp->dwDialSpeed; 
			if (ldp->dwDigitDuration)	 
				myldp->dwDigitDuration = ldp->dwDigitDuration; 
			if (ldp->dwWaitForDialtone) 
				myldp->dwWaitForDialtone = ldp->dwWaitForDialtone;
		}
    }

	if (lpszDestAddress == NULL)
	{
		if (!(lpCallParams->dwCallParamFlags & LINECALLPARAMFLAGS_IDLE))
		{
			// If the call params IDLE flag is not enabled, then we
			// have to put the call into DIALTONE mode.
	  
			line.call[numCall].dwState = LINECALLSTATE_DIALTONE;
	  
			// We have to mark this as an active call in case we
			// get a generate digits call on it.
			res = SerialOpenComms();
			if (res == SERIAL_FATAL)
				goto DEALLOCATE_CALL_AND_FAIL;

			line.bActiveCall = TRUE;
			(*(line.lpfnCompletion))(dwRequestID, 0);
			(*(line.lpfnEventProc))(line.htLine, line.call[numCall].htCall,
											LINE_CALLSTATE, LINECALLSTATE_DIALTONE, 
											LINEDIALTONEMODE_UNAVAIL, 0);
		}
		else
		{
			line.call[numCall].dwState = LINECALLSTATE_IDLE;
			(*(line.lpfnCompletion))(dwRequestID, 0);
		}

		return dwRequestID;
	}
  
	// We have something to dial, so there should be no active calls.
	if (line.bActiveCall)
	{
		error = LINEERR_INUSE;
		goto DEALLOCATE_CALL_AND_FAIL;
	}
  
	res = DialAndGetState (lpszDestAddress, FALSE, numCall);

	switch (res)
	{
		case CALL_STATE_BUSY:
			DebugMsg("busy");
			(*(line.lpfnCompletion))(dwRequestID, 0);
			(*(line.lpfnEventProc))(line.htLine, line.call[numCall].htCall,
						LINE_CALLSTATE, LINECALLSTATE_BUSY, LINEBUSYMODE_UNAVAIL, 0);
			break;
		case CALL_STATE_DROP:
		case CALL_STATE_HELP:
			DebugMsg("drop");
			(*(line.lpfnCompletion))(dwRequestID, 0);
			(*(line.lpfnEventProc))(line.htLine, line.call[numCall].htCall,
						LINE_CALLSTATE, LINECALLSTATE_IDLE, 0, 0);
			if (res == CALL_STATE_HELP) ATSPHelp(hidTalkDrop);
			break;
		case CALL_STATE_TALK:
			DebugMsg("talk");
			(*(line.lpfnCompletion))(dwRequestID, 0);
			(*(line.lpfnEventProc))(line.htLine, line.call[numCall].htCall,
						LINE_CALLSTATE, LINECALLSTATE_CONNECTED, 0, 0);
			break;
		case CALL_STATE_ERROR:
			goto DEALLOCATE_CALL_AND_FAIL;
	}
  
	return dwRequestID;

DEALLOCATE_CALL_AND_FAIL:

	DebugMsg("Failed in lineMakeCall");

	line.call[numCall].bInUse = FALSE;
	line.calls--;
	return error;
}

LONG TSPIAPI TSPI_lineOpen(DWORD dwDeviceID,
			  HTAPILINE htLine,
			  LPHDRVLINE lphdLine,
			  DWORD dwTSPIVersion,
			  LINEEVENT lineEventProc)
{
  DebugMsg("Entering TSPI_lineOpen");
  
  // Mark the line as open, but don't open the serial
  // device until we need to make a call.
  
  line.bLineOpen = TRUE;
  line.lpfnEventProc = lineEventProc;
  line.htLine = htLine;
  *lphdLine = (HDRVLINE) &line;
  
  return 0;
}


LONG TSPIAPI TSPI_lineSetAppSpecific(HDRVCALL hdCall,
				    DWORD dwAppSpecific)
{
  DebugMsg("Entering TSPI_lineSetAppSpecific");

  ((ATSPCallData*)(hdCall))->dwAppSpecific = dwAppSpecific;
  return 0;
}


LONG TSPIAPI TSPI_lineSetCallParams(DRV_REQUESTID dwRequestID,
				   HDRVCALL hdCall,
				   DWORD dwBearerMode,
				   DWORD dwMinRate,
				   DWORD dwMaxRate,
				   LPLINEDIALPARAMS const lpDialParams)
{
  DebugMsg("Entering TSPI_lineSetCallParams");

  // We only honor dial params requests.
  if (lpDialParams->dwDialPause != 0)
    ((ATSPCallData*)(hdCall))->dpDialParams.dwDialPause =
      lpDialParams->dwDialPause;
  if (lpDialParams->dwDialSpeed != 0)
    ((ATSPCallData*)(hdCall))->dpDialParams.dwDialSpeed =
      lpDialParams->dwDialSpeed; 
  if (lpDialParams->dwDigitDuration != 0)
    ((ATSPCallData*)(hdCall))->dpDialParams.dwDigitDuration =
      lpDialParams->dwDigitDuration; 
  if (lpDialParams->dwWaitForDialtone != 0)
    ((ATSPCallData*)(hdCall))->dpDialParams.dwWaitForDialtone =
      lpDialParams->dwWaitForDialtone;
  
  return 0;
}

LONG TSPIAPI TSPI_lineSetDefaultMediaDetection(HDRVLINE hdLine,
					      DWORD dwMediaModes)
{
  DebugMsg("Entering TSPI_lineSetDefaultMediaDetection");

  if (!(dwMediaModes &  ATSPMEDIAMODES) ||
       (dwMediaModes & !ATSPMEDIAMODES))
    return LINEERR_INVALMEDIAMODE;

  line.dwLineMediaModes = dwMediaModes;
  return 0;
}

LONG TSPIAPI TSPI_lineSetMediaMode(HDRVCALL hdCall,
				  DWORD dwMediaMode)
{ 
  DebugMsg("Entering TSPI_lineSetMediaMode");
  
  if (dwMediaMode != LINEMEDIAMODE_INTERACTIVEVOICE &&
      dwMediaMode != LINEMEDIAMODE_DATAMODEM)
    return LINEERR_INVALMEDIAMODE;
  
  ((ATSPCallData*)(hdCall))->dwMediaMode = dwMediaMode;
  return 0;
}

LONG TSPIAPI TSPI_lineSetStatusMessages(HDRVLINE hdLine,
				       DWORD dwLineStates,
				       DWORD dwAddressStates)
{
  DebugMsg("Entering TSPI_lineSetStatusMessages");

  ((ATSPLineData*)(hdLine))->dwLineStates = dwLineStates;
  ((ATSPLineData*)(hdLine))->dwAddressStates = dwAddressStates;
  return 0;
}


///////////////////////////////////////////////////////////
// The configuration trio
///////////////////////////////////////////////////////////

LONG TSPIAPI TSPI_providerConfig (HWND hwnd, DWORD dwPermanentProviderId)
{
	LoadIniStrings (dwPermanentProviderId);
	return DialogBox (hInst, MAKEINTRESOURCE(IDD_CFGDLG), hwnd, ConfigDlgProc);
}


LONG TSPIAPI TSPI_providerInstall (HWND hwnd, DWORD dwPermanentProviderId)
{
	char szProvider[sizeof (s_providerx) + 5];		// room for 65535

	wsprintf (szProvider, s_providerx, (int) dwPermanentProviderId);
	
	// we support 1 line and 0 phones

	WritePrivateProfileString (szProvider, s_numlines,  s_one,  s_telephon_ini);
	WritePrivateProfileString (szProvider, s_numphones, s_zero, s_telephon_ini);

	// Flush the ini file cache.
	WritePrivateProfileString (0, 0, 0, s_telephon_ini);

	// call the config function
	return TSPI_providerConfig (hwnd, dwPermanentProviderId);
}


LONG TSPIAPI TSPI_providerRemove(HWND hwnd,	DWORD dwPermanentProviderId)
{
  // The control panel removes all of our junk for us
  // when the provider is removed.
  return 0;
}



///////////////////////////////////////////////////////////
// Internal support routines
///////////////////////////////////////////////////////////

void LoadIniStrings(DWORD dwPermanentProviderId)
{
	char			lpszSectionBuf[INI_SECTIONSIZE];
	static BOOL StringResourcesLoaded = FALSE;

	if (StringResourcesLoaded) // Load resources exactly once
		return;  


  LoadString(hInst, ID_INI_SECTION, 
	     lpszSectionBuf,  sizeof(lpszSectionBuf));
  wsprintf(lpszIniSection, "%s%d", lpszSectionBuf, 
	   dwPermanentProviderId);
  LoadString(hInst, ID_INI_PORTENTRY, 
	     lpszPortEntry, sizeof(lpszPortEntry));
  LoadString(hInst, ID_INI_SPEEDENTRY, 
	     lpszSpeedEntry, sizeof(lpszSpeedEntry));
  LoadString(hInst, ID_INI_INITSTRENTRY, 
	     lpszInitStrEntry, sizeof(lpszInitStrEntry));
  LoadString(hInst, ID_INI_INITSTRPREFENTRY,
	     lpszInitStrPrefEntry, sizeof(lpszInitStrPrefEntry));
  LoadString(hInst, ID_INI_LINENAME, 
	     lpszLineNameEntry, sizeof(lpszLineNameEntry)); 
  LoadString(hInst, ID_INI_LINEADDRESS,
	     lpszLineAddressEntry, sizeof(lpszLineAddressEntry));
  
  StringResourcesLoaded = TRUE;

  return;
}

BOOL CALLBACK __export ConfigDlgProc(HWND hDlg, UINT uiMsg, 
				    WPARAM wParam, LPARAM lParam)
{
  int Count,                      // Generic counting variable.
      AvailDevs;                  // Number of available ports.
  static int OrigPort,            // Port set in ini file.
	     OrigSpeed;           // Speed set in ini file.
  DWORD CurrSel,                  // Generic state variable.
	CurrVal;                  // Generic index variable.
  char Buffer[SMALLBUFFER];       // Generic buffer.

  switch (uiMsg) 
    {
    case WM_INITDIALOG:
      
      // Get the current selected port from the ini file.  The valid
      // entries are Port=[0-3] and correspond to the entries of
      // lpszCommDevArray.  The default is COM1.
      
      OrigPort = GetPrivateProfileInt(lpszIniSection, lpszPortEntry, 
				      0, s_telephon_ini);

      AvailDevs = 0;  CurrVal = 0;
      for (Count = 0; Count < NUMPORTS; Count++)
	{
	  // List the port in the combo box.
	  SendDlgItemMessage(hDlg, IDC_COMBO1, CB_ADDSTRING,
	    0, (LPARAM) ((LPSTR) lpszCommDevArray[Count]));
	      
	  // Assign the combo box entry a port constant.
	  SendDlgItemMessage(hDlg, IDC_COMBO1, CB_SETITEMDATA,
	    AvailDevs, (LPARAM) ((DWORD) Count));
	      
	  // If this is the current default, show it as such.
	  if(OrigPort==Count)
	    SendDlgItemMessage(hDlg, IDC_COMBO1, CB_SETCURSEL, AvailDevs, 0);
      
	  // Increment the counter of items in the combo box.
	  AvailDevs++;
	}
      
      // Get the current default speed.  The valid entries are 
      // Speed=[0-6] and the value corresponds to the index into
      // lpszCommSpeedArray.

      OrigSpeed = GetPrivateProfileInt(lpszIniSection, 
		    lpszSpeedEntry, 0, s_telephon_ini);

      for (Count=0; Count < NUMSPEEDS; Count++)
	{
	  SendDlgItemMessage(hDlg, IDC_COMBO2, CB_ADDSTRING,
	    0, (LPARAM) ((LPSTR) lpszCommSpeedArray[Count]));
	}

      SendDlgItemMessage(hDlg, IDC_COMBO2, CB_SETCURSEL, OrigSpeed, 0);
      
      break;

    case WM_COMMAND:
      switch (wParam) 
	{
	case IDOK:

	  // Check to see if the port was changed.
	  CurrSel = SendDlgItemMessage(hDlg, IDC_COMBO1, CB_GETCURSEL, 0, 0);
	  CurrVal = SendDlgItemMessage(hDlg, IDC_COMBO1, CB_GETITEMDATA,
		      (WPARAM) CurrSel, 0);
	  if (CurrVal != (DWORD) OrigPort)
	    {
	      _itoa((int)CurrVal, Buffer, 10);
	      WritePrivateProfileString(lpszIniSection, lpszPortEntry,
		Buffer, s_telephon_ini);
	    }
	  
	  // Check to see if the speed was changed.
	  CurrSel = SendDlgItemMessage(hDlg, IDC_COMBO2, CB_GETCURSEL, 0, 0);
	  if (CurrSel != (DWORD) OrigSpeed)
	    {
	      _itoa((int)CurrSel, Buffer, 10);
	      WritePrivateProfileString(lpszIniSection, lpszSpeedEntry,
		Buffer, s_telephon_ini);
	    }

	  // Flush the ini file cache.
	  WritePrivateProfileString(NULL, NULL, NULL, s_telephon_ini);
	  EndDialog(hDlg, 0);
	  break;
	  
	case IDCANCEL:
	  EndDialog(hDlg, 0);
	  break;
	
	case IDLINECONF:
	  DialogBox (hInst, MAKEINTRESOURCE(IDD_LINE_CONFIG), hDlg, 
		     LineConfDlgProc);
	  break;
	  
	default:
	  break;
	}
      break;
    default:
      return FALSE;
    }
  return TRUE;
}


BOOL CALLBACK __export LineConfDlgProc(HWND hDlg, UINT uiMsg, 
				      WPARAM wParam, LPARAM lParam)
{
  char lpszLineName[LINENAMESIZE],
       lpszLineAddress[LINEADDRESSSIZE],
       lpszInitStr[INITSTRSIZE],
       Buffer[SMALLBUFFER];
  int Count;
  DWORD CurrSel;

  switch (uiMsg)
    {
    case WM_INITDIALOG:
      
      // Load the line name from the ini.
      
      GetPrivateProfileString(lpszIniSection, lpszLineNameEntry,
	"", lpszLineName, sizeof(lpszLineName), s_telephon_ini);
      SendDlgItemMessage(hDlg, IDC_NAME_EDIT, WM_SETTEXT, 0,
			 (LPARAM) (LPSTR) lpszLineName);

      // Load the line address from the ini.

      GetPrivateProfileString(lpszIniSection, lpszLineAddressEntry,
	"", lpszLineAddress, sizeof(lpszLineAddress), s_telephon_ini);
      SendDlgItemMessage(hDlg, IDC_ADDR_EDIT, WM_SETTEXT, 0,
			 (LPARAM) (LPSTR) lpszLineAddress);

      // Load the user modem init string from the ini.
      
      GetPrivateProfileString(lpszIniSection, lpszInitStrEntry, 
	"", lpszInitStr, sizeof(lpszInitStr), s_telephon_ini);
      SendDlgItemMessage(hDlg, IDC_EDIT1, WM_SETTEXT, 0, 
			 (LPARAM) (LPSTR) lpszInitStr);

      // Get the init string pref and set the check box.

      SendDlgItemMessage(hDlg, IDC_CHECK1, BM_SETCHECK,  
	GetPrivateProfileInt(lpszIniSection, lpszInitStrPrefEntry, 
	  0, s_telephon_ini), 0);

      break;

    case WM_COMMAND:

      switch(wParam)
	{
	case IDOK:
	  
	  // Check to see if the line name changed.
	  if(SendDlgItemMessage(hDlg, IDC_NAME_EDIT, EM_GETMODIFY, 0, 0))
	    {
	      // It changed.
	      SendDlgItemMessage(hDlg, IDC_NAME_EDIT, WM_GETTEXT,
		(WPARAM)LINENAMESIZE, (LPARAM) ((LPSTR) lpszLineName));
	      WritePrivateProfileString(lpszIniSection, lpszLineNameEntry,
					lpszLineName, s_telephon_ini);
	    }

	  // Check to see if the line address changed.
	  if(SendDlgItemMessage(hDlg, IDC_ADDR_EDIT, EM_GETMODIFY, 0, 0))
	    {
	      // It changed.
	      SendDlgItemMessage(hDlg, IDC_ADDR_EDIT, WM_GETTEXT,
		(WPARAM)LINEADDRESSSIZE, (LPARAM) ((LPSTR) lpszLineAddress));
	      WritePrivateProfileString(lpszIniSection, lpszLineAddressEntry,
					lpszLineAddress, s_telephon_ini);
	    }

	  // Check to see if the init string was changed.
	  if(SendDlgItemMessage(hDlg, IDC_EDIT1, EM_GETMODIFY, 0, 0))
	    {
	      // It changed.
	      SendDlgItemMessage(hDlg, IDC_EDIT1, WM_GETTEXT, 
		(WPARAM)INITSTRSIZE, (LPARAM) ((LPSTR) lpszInitStr));
	      
	      // Fast, in-place, toupper for ascii text.
	      Count=0;
	      while(lpszInitStr[Count] != '\0')
		{
		  if(lpszInitStr[Count] <= 122 &&
		     lpszInitStr[Count] >= 97)
		    lpszInitStr[Count] = lpszInitStr[Count] - 32;
		  Count++;
		}
	      
	      WritePrivateProfileString(lpszIniSection, lpszInitStrEntry,
		lpszInitStr, s_telephon_ini);
	    }

	  CurrSel = SendDlgItemMessage(hDlg, IDC_CHECK1, BM_GETCHECK, 0, 0);
	  _itoa((int)CurrSel, Buffer, 10);
	  WritePrivateProfileString(lpszIniSection, lpszInitStrPrefEntry,
	    Buffer, s_telephon_ini);
	  
	  // The ini file will be flushed in cfgdlgproc.

	  EndDialog(hDlg, 0);
	  break;

	case IDCANCEL:
	  EndDialog(hDlg, 0);
	  break;

	default:
	  return FALSE;
	}
      
    default:
      return FALSE;
    }
  return TRUE;
}



#ifdef DEBUG
void CDECL SPTrace(LPCSTR pszFormat, ...)
{
  static char szBuffer[512];
  static char fmtBuffer[1024];
  static char szModuleBuffer[_MAX_PATH];
  static char szTemp[_MAX_PATH];
  static char szFName[_MAX_FNAME];
  const char* pszLocalFormat;
  int nBuf, count, localCount;
  va_list args;
  
  pszLocalFormat = pszFormat;
  
  va_start(args, pszFormat);
  
  nBuf = wvsprintf(szBuffer, pszLocalFormat, args);
  
  // Convert formatting to readable format.
  for (count = 0, localCount = 0; count < nBuf; count++, localCount++)
    {
      if (szBuffer[count] == '\r')
	{
	  fmtBuffer[localCount++] = '\\';
	  fmtBuffer[localCount] = 'r';
	}
      else if (szBuffer[count] == '\n')
	{
 	  fmtBuffer[localCount++] = '\\';
	  fmtBuffer[localCount] = 'n';
	}
      else
	fmtBuffer[localCount] = szBuffer[count];
    }
  fmtBuffer[localCount] = '\0';

   GetModuleFileName(hInst, szModuleBuffer, sizeof(szModuleBuffer));
   _splitpath( szModuleBuffer, szTemp, szTemp, szFName, szTemp );
  
  wsprintf(szBuffer, "%s: %s\n\r", (LPSTR) szFName, (LPSTR) fmtBuffer);
  OutputDebugString(szBuffer);
}
#endif
