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

int		 PollComms(int, void far*, int);
int		 CheckSerialError(int);
int		 SerialOpenPort(char far*);

BOOL CALLBACK __export GetTaskWndProc(HWND, LPARAM);
BOOL CALLBACK __export TalkDropProc(HWND, UINT, WPARAM, LPARAM);

extern HANDLE hInst;
extern char s_telephon_ini[13];	/* "telephon.ini" */

extern char lpszIniSection[INI_SECTIONSIZE];

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

extern char *lpszCommDevArray[NUMPORTS];
extern char *lpszCommSpeedArray[NUMSPEEDS];

extern ATSPLineData line;


// The serial transmit and receive queue sizes.
                                                   
#define TXQUEUE 512
#define RXQUEUE 512

// int SendModemCommand(int, const void far*, int)
//
// Arguments:
//
//      int hndComDev - The identifier of the COM port that the
//                      modem command should be sent to.  This is
//                      the identifier returned by OpenComm.
//
//      const void far *lpsBuf - A pointer to the command string to
//                      to be sent to the modem.  This string need
//                      not be null terminated.
//
//      int cbBufsize - The number of bytes in the modem command.
//
// Return Values:
//
//      If the return value is greater than or equal to zero, then
//      the return value is the number of bytes successfully written.
//      Note that the number of characters written will be more than
//      cbBufsize on correct execution since a carriage return must
//      also be sent.
//      
//      SERIAL_ERROR - A data set error occurred.
//
//      SERIAL_FATAL - A fatal error occurred.
//
// Side Effects:
//
//      This function clears the incoming serial queue and resets the
//      PollComms routine before sending its command so that the next
//      series of calls to the PollComms routine will return the reply
//      for this modem command.

int SendModemCommand(int hndComDev, const void far *lpsBuf, int cbBufsize)
{     
  COMSTAT CommStatus;
  int LastErr, cbWritten, Retry;
  char sCommBuf[TXQUEUE+1];
  
  // Quietly truncate strings that are too long.
  if (cbBufsize > TXQUEUE - 1)
    cbBufsize = TXQUEUE - 1;
  
  // We need to send a return in addition to the supplied command.
  cbBufsize++;
  
  // Clear the incoming buffer.
  LastErr = ReadComm(hndComDev, sCommBuf, TXQUEUE);

#ifdef VERBOSE_DEBUG
  // We don't care about errors since the following 
  // GetCommError will clear the device state.
  if (LastErr > 0)
    {
      sCommBuf[LastErr] = '\0';
      SPTrace("Dumped %d chars in SendModemCommand: %s", LastErr, 
	      (LPSTR) sCommBuf);
    }
#endif

  // Reset the reply gatherer.
  PollComms(0, NULL, 0);

  Retry = SERIAL_RETRY;
  do
    {
      // Reset the device and get the transmit queue space.
      LastErr = GetCommError(hndComDev, &CommStatus);
      Retry--;
    }
  while (((TXQUEUE - (int) CommStatus.cbOutQue) < cbBufsize) &&
	 Retry != 0 );

  if(Retry == 0 && ((TXQUEUE - (int) CommStatus.cbOutQue) < cbBufsize))
    // There's wasn't enough space in the queue to send the command.
    return SERIAL_ERROR;
  
  // Do an inline copy and write it.
  Retry = (cbBufsize - 1);
  sCommBuf[Retry--] = '\r';
  while (Retry >= 0)
    {
      sCommBuf[Retry] = ((char *)lpsBuf)[Retry];
      Retry--;
    }
  
  Retry = SERIAL_RETRY;
  do
    {
      Retry--;
      cbWritten = WriteComm(hndComDev, sCommBuf, cbBufsize);
      if(cbWritten  >= 0)
	{
	  // Something was written.  That's about all we can do.
#ifdef DEBUG
	  sCommBuf[cbWritten] = '\0';
	  SPTrace("Sent following %d chars to modem: %s", 
		  cbWritten, (LPSTR) sCommBuf);
#endif
	  return cbWritten;
	}
      else
	{
	  // There was an error, so check it.
	  LastErr = CheckSerialError(hndComDev);
	  switch (LastErr)
	    {
	    case SERIAL_OK:
	      // There wasn't an error.  This typically means that
	      // the comms driver and/or tapi is in an inconsistent
	      // state and should be reset, or that the driver is being
	      // unloaded.  We'll retry until we timeout, just in case 
	      // we can recover.
	      break;
	    case SERIAL_ERROR:
	      // There was an error.  Try to recover.
	      break;
	    case SERIAL_FATAL:
	      // There was an unrecoverable error.
	      return SERIAL_FATAL;
	    }
	}
      // Fall out and run the do loop again.
    }    
  while (Retry != 0);
  
  // If we fall all the way out, then we've failed.
  return SERIAL_ERROR;
}

// int GetModemReply(int, void far*, int)
//
// Arguments:
//
//      int hndComDev - The identifier of the COM port that the
//                      modem reply should be read from.  This is
//                      the identifier returned by OpenComm.
//
//      const void far *lpszReplyBuf - A pointer to the buffer where
//                      the modem reply string should be written.
//
//      int cbReplyBuf - The number of bytes in the reply buffer.
//
// Return Values:
//
//      SERIAL_OK - The routine located the reply string and
//                      wrote it into lpszReplyBuf. 
//
//      SERIAL_NOMEM - The routine is out of memory.
//
//      SERIAL_FATAL - A fatal serial error occurred.
//
//      SERIAL_ERROR - The routine was not able to locate the
//                      reply string because of serial errors.
//
// Side Effects:
// 
//      This function synchronously retrieves the modem reply to the
//      last command.  This should only be used when a quick reply
//      is expected (i.e. initialization, etc.).

int GetModemReply(int hndComDev, void far *lpszReplyBuf, int cbReplyBuf)
{
  int ErrResult, Timeout, Result;
  unsigned long Wait;
  COMSTAT commStat;
#ifdef DEBUG
  int WaitCount = 0;
#endif
  
  Timeout = SERIAL_TIMEOUT;
  
  do
    {
      // Decrement our ttl counter.  
      Timeout--;
      
      // See how much stuff is in the buffer.
      ErrResult = GetCommError(hndComDev, &commStat);
      
      // If there's nothing in the queue, then we busy wait.
      if (commStat.cbInQue == 0) 
      	{
	  // Wait 1/2 second and try again.
	  Wait = GetTickCount();
	  while (GetTickCount() - Wait < 500)
	    ;
#ifdef DEBUG
	  WaitCount++;
#endif
	  continue;
	}
      
      // Get the characters that are waiting for us.
      Result = PollComms(hndComDev, lpszReplyBuf, cbReplyBuf);
      switch (Result)
	{
	case SERIAL_FATAL:
	  return SERIAL_FATAL;
	case SERIAL_NOMEM:
	  return SERIAL_NOMEM;
	case SERIAL_INC:
	  break;
	case SERIAL_OK:
	  return SERIAL_OK;
	}
    }
  while(Timeout != 0);
  
#ifdef DEBUG
  SPTrace("Failed to get modem reply.  Waited %i ticks.", WaitCount);
#endif
  
  return SERIAL_ERROR;
}

// int PollComms(int, void far*, int)
//
// Arguments:
//
//      int hndComDev - The identifier of the COM port that should
//                      be polled.  This is the identifier returned 
//                      by OpenComm.
//
//      void far *lpszReply - A pointer to the buffer where the modem 
//                      reply string should be written.
//
//      int cbReply - The number of bytes in the reply buffer.
//
// Return Values:
//
//      SERIAL_OK - The function has successfully retrieved the reply
//                      from the data set and it is in lpszReply.
//
//      SERIAL_INC - The function has not received enough information
//                      from the data set to determine the reply and
//                      should be called again later.
//
//      SERIAL_NOMEM - The function is out of memory and cannot continue.
//
//      SERIAL_FATAL - A fatal serial error occurred.
//         
// Overview:
//
//      The PollComms routine is used to periodically gather characters
//      from the modem and look for a reply.  The token structure is
//      <command>\r\r\n<reply>\r\n and non-alpha-numeric characters are
//      dropped as random port noise.  The state of PollComms remains
//      consistent through multiple calls and the procedure must be reset
//      when a reply has been received and a new reply is sought.  PollComms
//      is reset by calling it with the arguments (0, NULL, 0).
//
// Caveat:
//
//      This function looks linearly along the token stream to extract
//      its reply and fails on the lineClose call.  

int PollComms(int hndComDev, void far *lpszReply, int cbReply)
{
  static char *sCharBuf, *sTempBuf;
  static int cbCharBuf = 0, cbCurrCharBufSize = 0;

  char szReadBuf[TXQUEUE];
  int cbReadBuf, SerialErr, ErrResult, 
      Retry = SERIAL_RETRY, Count, Copy;
  BOOL FoundCommand = FALSE;
  
  if (lpszReply == NULL)
    {
      // Reset the poll state.
      cbCurrCharBufSize = 0;
      return 0;
    }
  
  // Clear the state of the device.
  ErrResult = GetCommError(hndComDev, NULL);
  
  do
    {
      // Decrement the serial retry counter.
      Retry--;

      SerialErr = SERIAL_OK;

      // Get the serial data that we can.
      cbReadBuf = ReadComm(hndComDev, szReadBuf, TXQUEUE);
      
      if (cbReadBuf > 0)
	{
	  // Copy what we got to the cumulative buffer.
	  for (Count = 0; Count < cbReadBuf; Count++)
	    {
	      if (cbCurrCharBufSize == cbCharBuf)
		{
		  // Resize the buffer.		  
		  sTempBuf = (char *) _fmalloc(cbCharBuf + SERIAL_BUF_INC);
		  if (sTempBuf == NULL)
		    {
		      // We're out of memory, so just give up.
		      
		      if (szReadBuf != NULL)
			_ffree ((void __far *) szReadBuf);
		      if (sCharBuf != NULL)
			_ffree ((void __far *) sCharBuf);
		      
		      return SERIAL_NOMEM;
		    }
		  
		  // Copy to the new buffer.		  
		  for (Copy = 0; Copy < cbCurrCharBufSize; Copy++)
		    sTempBuf[Copy] = sCharBuf[Copy];
		  
		  // Mark the new size.
		  cbCharBuf += SERIAL_BUF_INC;
		  
		  // Free the old buffer and reference the new one.
		  _ffree ((void __far *) sCharBuf);
		  sCharBuf = sTempBuf;
		  sTempBuf = NULL;
		  
		  // When we fall out, we'll do the copy.
		}
	      
	      // Copy the character.
	      sCharBuf[cbCurrCharBufSize++] = 
		szReadBuf[Count];
	    }
	}
      else
	{
	  // A serial error may have occurred, so we have to check 
	  // the error state of the serial device.
	  
	  SerialErr = CheckSerialError(hndComDev);
	  if (SerialErr == SERIAL_OK)
	    {
	      // No error occurred.
	      
	      // We set the SerialErr state to SERIAL_ERROR and time
	      // out the do loop so that the cumulative buffer is not
	      // searched for a response since we haven't changed it.
	      
	      SerialErr = SERIAL_ERROR;
	      Retry = 0;
	    }
	  else if (SerialErr == SERIAL_ERROR)
	    {
	      // There was an error.  When we fall out, our SerialErr 
	      // state will cause us to retry until we timeout.
	      ;
	    }
	  else if (SerialErr == SERIAL_FATAL)
	    {
	      // A fatal error occurred.  Clean up and beat it!
	      
	      _ffree ((void __far *) szReadBuf);
	      _ffree ((void __far *) sCharBuf);
	      
	      return SERIAL_FATAL;
	    }
	}
    }
  while(Retry != 0 && SerialErr == SERIAL_ERROR);

  if (Retry == 0 && SerialErr == SERIAL_ERROR)
    // We timed out.
    return SERIAL_INC;
      
  // Check the cummulative string for a reply.  This parser
  // could use a little work!

#ifdef VERBOSE_DEBUG  
  sCharBuf[cbCurrCharBufSize] = '\0';
  SPTrace("Checking string: %s.", (LPSTR) sCharBuf);
#endif

  Copy = 0; Count = 0;
  while (Count < cbCurrCharBufSize &&
	 Copy < cbReply)
    {
      if (sCharBuf[Count] == '\r')
	{
	  if (sCharBuf[Count + 1] == '\r')
	    {
	      // It's the command part, skip the escape characters.
	      // We'll fall out if we overstep the array.
	      FoundCommand = TRUE;
	      Count += 3;
	      Copy = 0;
	    }
	  else if (sCharBuf[Count + 1] == '\n')
	    {
	      if (!FoundCommand)
		{
		  FoundCommand = TRUE;
		  Count++;
		  continue;
		}
	      else
		{
		  // We've got the reply.
		  ((char *)lpszReply)[Copy] = '\0';
		  _ffree((void __far *) sCharBuf);
#ifdef DEBUG
		  SPTrace("Reply: %s", (LPSTR) lpszReply);
#endif
		  return SERIAL_OK;
		}
	    }
	}
      // There's got to be a better way to do this.
      if ((sCharBuf[Count] >= '0' && sCharBuf[Count] <= '9') ||
	  (sCharBuf[Count] >= 'A' && sCharBuf[Count] <= 'Z') ||
	  (sCharBuf[Count] >= 'a' && sCharBuf[Count] <= 'z') ||
	  sCharBuf[Count] == ' ')
	{
	  // It's just a regular character.
	  ((char *)lpszReply)[Copy++] = sCharBuf[Count++];
	}
      else
	{
	  // It's noise, so we can ignore it.
	  Count++;
	}
    }

  // Nothing yet.
  return SERIAL_INC;
}

// int CheckSerialError(int)
//
// Arguments:
//
//      int hndComDev - The identifier of the COM port that should
//                      checked for an error.  This is the identifier 
//                      returned by OpenComm.
//
// Return Values:
// 
//      SERIAL_OK - No error occurred.
//      SERIAL_ERROR - A recoverable serial error occurred.
//      SERIAL_FATAL - An unrecoverable serial error occurred.
//
// Overview:
//
//      This functions checks the COM port for an error condition,
//      clearing the error condition on the port and returning a
//      classification of the error.  The abstraction exists so that
//      the debug version will be able to print a sensible error string 
//      to the debug window.

int CheckSerialError(int hndComDev)
{
  int ErrResult;
  
  ErrResult = GetCommError(hndComDev, NULL);
  if (ErrResult == 0)
    return SERIAL_OK;

  // Otherwise, check the error.
  
  if (ErrResult & CE_BREAK)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: Hardware detected a break condition.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_CTSTO)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: There was a clear-to-send timeout.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_DNS)
    {
      // This is not a valid error.
#ifdef DEBUG
      SPTrace("SERIAL_FATAL: A parallel device was not selected.");
#endif
      return SERIAL_FATAL;
    }
  if (ErrResult & CE_DSRTO)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: There was a data-set-ready timeout.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_FRAME)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: Hardware detected a framing error.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_IOE)
    {
      // This is not a valid error.
#ifdef DEBUG
      SPTrace("SERIAL_FATAL: There was an I/O error on the parallel device.");
#endif
      return SERIAL_FATAL;
    }
  if (ErrResult & CE_MODE)
    {
      // This error cannot be recovered from.
#ifdef DEBUG
      SPTrace("SERIAL_FATAL: The mode or the descriptor is invalid.");
#endif
      return SERIAL_FATAL;
    }
  if (ErrResult & CE_OOP)
    {
      // This is not a valid error.
#ifdef DEBUG
      SPTrace("SERIAL_FATAL: Apparently, your modem is out of paper.");
#endif
      return SERIAL_FATAL;
    }
  if (ErrResult & CE_OVERRUN)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: There was a serial overrun.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_PTO)
    {
      // This is not a valid error.
#ifdef DEBUG
      SPTrace("SERIAL_FATAL: There was a timeout on the parallel device.");
#endif
      return SERIAL_FATAL;
    }
  if (ErrResult & CE_RLSDTO)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: A receive-line-signal-detect time out occurred.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_RXOVER)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: There was an rxqueue overflow.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_RXPARITY)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: Hardware detected a parity error.");
#endif
      return SERIAL_ERROR;
    }
  if (ErrResult & CE_TXFULL)
    {
#ifdef DEBUG
      SPTrace("SERIAL_ERROR: The txqueue was full.");
#endif
      return SERIAL_ERROR;
    }
}

// int SerialOpenPort(char far*)
//
// Arguments:
//
//      char far *port - The COM port that should be opened.
//
// Return Values:
// 
//      SERIAL_ERROR - A recoverable serial error occurred.
//      SERIAL_NOMEM - There was not enough memory.
//
//      Otherwise, the function returns the handle to the open
//      device for later use.
//
// Overview:
//
//      This function simply opens the com port for use.  The abstraction
//      exists so that sensible error messages are reported to the debug 
//      terminal in the debug version.

int SerialOpenPort(char far* port)
{
  int Reply;
  char errMsg[LARGEBUFFER];

  Reply = OpenComm(port, RXQUEUE, TXQUEUE);
  if(Reply < 0)
    {
      wsprintf(errMsg, "Couldn't open %s: ", (LPSTR) port);
      switch (Reply)
	{
#ifdef DEBUG
	case IE_BADID:
	  _fstrcat(errMsg, "Invalid device id.");
	  SPTrace(errMsg);
	  break;
	case IE_BAUDRATE:
	  _fstrcat(errMsg, "Unsupported baud rate.");
	  SPTrace(errMsg);
	  break;
	case IE_BYTESIZE:
	  _fstrcat(errMsg, "Invalid byte size.");
	  SPTrace(errMsg);
	  break;
	case IE_DEFAULT:
	  _fstrcat(errMsg, "Invalid parameters.");
	  SPTrace(errMsg);
	  break;
	case IE_HARDWARE:
	  _fstrcat(errMsg, "Hardware unavailable.");
	  SPTrace(errMsg);
	  break;
#endif
	case IE_MEMORY:
#ifdef DEBUG
	  _fstrcat(errMsg, "Out of memory.");
	  SPTrace(errMsg);
#endif
	  return SERIAL_NOMEM;
#ifdef DEBUG
	case IE_NOPEN:
	  _fstrcat(errMsg, "Device not open.");
	  SPTrace(errMsg);
	  break;
	case IE_OPEN:
	  _fstrcat(errMsg, "Device already in use.");
	  SPTrace(errMsg);
	  break;
#endif
	}
      return SERIAL_ERROR;
    }
  return Reply;
}

// int SerialOpenComms()
//
// Return Values:
//
//      SERIAL_OK: The port was successfully opened.
//      SERIAL_FATAL: The port was not opened.
//
// Comments:  
//
//      Open the serial port and set it up for use.  This includes 
//      setting the communications state.

int SerialOpenComms()
{
  int Port, Speed, Reply, Retry;
  char szCommDB[40];
  DCB dcbDeviceControl;

  // Get the port, speed, init string, and init string
  // prefereces from the ini file.
  
  Port = GetPrivateProfileInt(lpszIniSection, lpszPortEntry, 0, 
			      s_telephon_ini);
  Speed = GetPrivateProfileInt(lpszIniSection, lpszSpeedEntry, 0,
			       s_telephon_ini);
  
  Reply = SerialOpenPort(lpszCommDevArray[Port]);
  if (Reply < 0)
    return SERIAL_FATAL;
  else
    line.uiCommId = Reply;
  
  wsprintf(szCommDB, "%s:%s,n,8,1", lpszCommDevArray[Port],
	   (LPSTR) lpszCommSpeedArray[Speed]);
  
  Reply = BuildCommDCB(szCommDB, &dcbDeviceControl);
  if(Reply < 0)
    {
#ifdef DEBUG
      SPTrace("Couldn't build comm DCB in SerialOpenComms.");
#endif
      goto LINE_OPEN_FAILURE_RECOVER;
    }
  
  Reply = SetCommState(&dcbDeviceControl);
  if(Reply < 0)
    {
#ifdef DEBUG
      SPTrace("Couldn't set the comm state in SerialOpenComms.");
#endif
      goto LINE_OPEN_FAILURE_RECOVER;
    }
  
#ifdef DEBUG
  SPTrace("Communications port is open.");
#endif
  
  return SERIAL_OK;
  
 LINE_OPEN_FAILURE_RECOVER:
  
  Retry = SERIAL_RETRY;
  do
    {
      Reply = CloseComm(line.uiCommId);
      Retry--;
    }
  while (Reply < 0 && Retry != 0);
  
#ifdef DEBUG
  if (Retry == 0 && Reply < 0)
    SPTrace("Couldn't close COM port after failure in SerialOpenComms.");
#endif
  
  return SERIAL_FATAL;
}

// void SerialCloseComms()
//
// Comments:  
//   
//      This closes a serial port.  The serial port
//      is only open if there is an active call using it.
//      This always succeeds :-).

void SerialCloseComms()
{
  int Result, Retry;  
  LONG lResult;
  char szReply[SMALLBUFFER];

  // Send the onhook string.  If we fail at any
  // point, just toggle DTR and hope for the best.
  
  Result = SendModemCommand(line.uiCommId, INTERNAL_HANGUP, 
			    INTERNAL_HANGUP_LEN);

  if (Result > 0)
    {
      Result = GetModemReply(line.uiCommId, szReply, SMALLBUFFER);
      if (Result == SERIAL_OK)
	// The token structure in PollColls typically breaks
	// at this point, so we don't care what the reply
	// was so long as we got one.
	goto CLOSE_COMM;
    }
  
#ifdef DEBUG
  SPTrace("Toggling DTR to reset modem in SerialCloseComms.");
#endif
  lResult = EscapeCommFunction(line.uiCommId, CLRDTR);
  lResult = EscapeCommFunction(line.uiCommId, SETDTR);
  
 CLOSE_COMM:
  
  // Close the port.
  Retry = SERIAL_RETRY;
  do
    {
      Result = CloseComm(line.uiCommId);
      Retry--;
    }
  while (Result < 0 && Retry != 0);
  
#ifdef DEBUG
  if (Result < 0 && Retry == 0)
    SPTrace("Couldn't close the serial port in SerialCloseComms.");
  else
    SPTrace("Communications port is closed.");
#endif
  
  return;
}

typedef struct hwndList_TAG
{
  struct hwndList_TAG* next;
  HWND hwnd;
} hwndList; 

hwndList *DisableCurrentTask(void);
void		 ReEnableCurrentTask(hwndList*);

hwndList* hwlCurrentList;
hwndList* hwlCurrentItem;
hwndList* hwlAlloced;
HWND hwndActive;
  
char s_dialFormat[] = "ATS11=%luS8=%luS7=%lu%s%sDT%s;";
char s_dialInit[]	 = "X4E1S6=2";
char s_null[]		 = "";

// int DialAndGetState(char *, BOOL, int)
//
// Arguments:
//
//      char far *lpszAddress - The canonical address.
//      BOOL bSendState - This informs us whether or not it is
//               ok to send event callbacks to the app as we
//               progress.  To send callbacks on a call,
//               lineMakeCall must have successfully completed
//               so that the call handle is valid.
//      int Call - The index into the call array of the call that
//               we are currently dialing.
//
// Return Values:
// 
//      CALL_STATE_BUSY - Dialing succeeded and the call state is
//               currently LINECALLSTATE_BUSY.
//      CALL_STATE_DROP - The call was dropped while dialing.
//      CALL_STATE_TALK - Dialing succeeded and the call state is
//               currently LINECALLSTATE_CONNECTED.
//      CALL_STATE_ERROR - We were unable to dial the call.
//		CALL_STATE_HELP - Like drop, but must launch context-s HELP
//
// Comments:
//
//      This function does dial tone detection before dialing the phone.
//

int DialAndGetState(char *lpszAddress, BOOL bSendState, int Call)
{
	int DResult;
	int Result;
	int cbDialString;
	int cbInitStrSize;

	BOOL bSuccess			 = FALSE;
	BOOL bNoInit			 = FALSE;
	BOOL bLeaveCallActive = FALSE;
  
	char szDialString[MAXDESTADDR + 
							sizeof(s_dialFormat) + 
							8*3 + 
							sizeof(s_dialInit) +
							MEDIUMBUFFER];
	char szInitStr[MEDIUMBUFFER];

  // Get the user init params and preferences.
  cbInitStrSize = GetPrivateProfileString(lpszIniSection, 
    lpszInitStrEntry, "", szInitStr, sizeof(szInitStr),
    s_telephon_ini);
  bNoInit = (BOOL) GetPrivateProfileInt(lpszIniSection, 
    lpszInitStrPrefEntry, 0, s_telephon_ini);
  
  // Set up the config tokens.
  if (cbInitStrSize >= 2)
    {
      if (szInitStr[0] == 'A' &&
	  szInitStr[1] == 'T')
	{
	  for (Result = 2; Result < cbInitStrSize; Result++)
	    szInitStr[Result - 2] = szInitStr[Result];
	  cbInitStrSize -= 2;
	  szInitStr[cbInitStrSize] = '\0';
	}
    }

	// Take note that we now have an active call.
	if (line.bActiveCall)
	{
		if (line.call[Call].dwState == LINECALLSTATE_DIALTONE)
		{
			bLeaveCallActive = TRUE;
			goto DETECT_DIALTONE;
		}
		else
			return CALL_STATE_ERROR;
	}
	else
	{
		Result = SerialOpenComms();

		if (Result == SERIAL_FATAL)
			return CALL_STATE_ERROR;

		line.bActiveCall = TRUE;
	}
  
DETECT_DIALTONE:

	hwlAlloced= DisableCurrentTask();

	// We re-enable the current task just before the dialog
	// box goes down.

	// need to change DialogBox to CreateWindow
	//DialogBox(hInst, MAKEINTRESOURCE (IDD_TALKDROP), NULL, TalkDropProc);


#if defined(VERBOSE_MESSAGES)
  // Detect dialtone on the line.
  Result = SendModemCommand(line.uiCommId, "ATD;", 4);
  if (Result > 0)
    {
      Result = GetModemReply(line.uiCommId, szDialString, MEDIUMBUFFER);
      if (Result == SERIAL_OK)
	if (_fstrcmp(szDialString, "OK") == 0)
	  {
	    bSuccess = TRUE;
	    if (bSendState)
	      (*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
		LINE_CALLSTATE, LINECALLSTATE_DIALTONE, 
                LINEDIALTONEMODE_UNAVAIL, 0);
	  }
    }
  if (!bSuccess)
    goto REPORT_FAILED_DIAL;
#endif

  // Set the state to dialing.
  line.call[Call].dwState = LINECALLSTATE_DIALING;
  if (bSendState)
    (*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
			    LINE_CALLSTATE, LINECALLSTATE_DIALING, 0, 0);
  
  // Create a dial string.

  cbDialString = wsprintf(szDialString, s_dialFormat,
    line.call[Call].dpDialParams.dwDigitDuration,
    line.call[Call].dpDialParams.dwDialPause / 1000,
    line.call[Call].dpDialParams.dwWaitForDialtone / 1000,
    (LPSTR) (bNoInit ? s_null : s_dialInit), 
	 szInitStr, lpszAddress);	

  // Dial.
  bSuccess = FALSE;
  Result = SendModemCommand(line.uiCommId, szDialString, cbDialString);
  if (Result > 0)
    {
      Result = GetModemReply(line.uiCommId, szDialString, MEDIUMBUFFER);
      if (Result == SERIAL_OK)
	if (_fstrcmp(szDialString, "OK") == 0)
	  bSuccess = TRUE;
    }
  if (!bSuccess)
    goto REPORT_FAILED_DIAL;
  
  // Set the state to proceeding.
  line.call[Call].dwState = LINECALLSTATE_PROCEEDING;
  if (bSendState)
    (*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
      LINE_CALLSTATE, LINECALLSTATE_PROCEEDING, 0, 0);

  switch(DResult = DialogBox(hInst, MAKEINTRESOURCE(IDD_TALKDROP), 
		   NULL, TalkDropProc))
    {
    case CALL_STATE_BUSY:
      line.call[Call].dwState = LINECALLSTATE_BUSY;
      if (bSendState)
	(*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
	  LINE_CALLSTATE, LINECALLSTATE_BUSY, LINEBUSYMODE_UNAVAIL, 0);
      return CALL_STATE_BUSY;
      break;
    case CALL_STATE_DROP:
    case CALL_STATE_HELP:
      line.call[Call].dwState = LINECALLSTATE_IDLE;
      line.bActiveCall = FALSE;
      SerialCloseComms();
      if (bSendState)
	(*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
	  LINE_CALLSTATE, LINECALLSTATE_IDLE, 0, 0);
      return DResult;
      break;
    case CALL_STATE_TALK:
      line.call[Call].dwState = LINECALLSTATE_CONNECTED;
      if (bSendState)
	(*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
	  LINE_CALLSTATE, LINECALLSTATE_CONNECTED, 0, 0);
      return CALL_STATE_TALK;
      break;
    case CALL_STATE_ERROR:
      // The error is handled in the following section.
      break;
    }

 REPORT_FAILED_DIAL:
  
#ifdef DEBUG
  SPTrace("Dialing failed.");
#endif
  
  if (bLeaveCallActive)
    {
      line.call[Call].dwState = LINECALLSTATE_DIALTONE;
      if (bSendState)
	(*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
	  LINE_CALLSTATE, LINECALLSTATE_DIALTONE, LINEDIALTONEMODE_UNAVAIL, 0);
    }
  else
    {
      line.call[Call].dwState = LINECALLSTATE_IDLE;
      line.bActiveCall = FALSE;
      SerialCloseComms();
      if (bSendState)
	(*(line.lpfnEventProc))(line.htLine, line.call[Call].htCall,
	  LINE_CALLSTATE, LINECALLSTATE_IDLE, 0, 0);
    }
  
  return CALL_STATE_ERROR;
}
 
// void ATSPHelp(DWORD contextID)
//
//	Comment: Does bookkeeping to manage WinHelp().  Call WinHelp()
//	with contextID as context.
void ATSPHelp(DWORD contextID)
{
#ifdef DEBUG
 SPTrace("KillRoy was here; help <Context == %d>!",(int)contextID);
#endif
//	WinHelp(hwndForWinHelp,ATSPHELPFILE,HELP_CONTEXT,contextID);
}


// BOOL CALLBACK GetTaskWndProc(HWND, LPARAM)
//
// Comment:  The EnumTaskWindows callback.

BOOL CALLBACK __export GetTaskWndProc(HWND hwnd, LPARAM lParam)
{
  // Build the list of task windows.
  
  if (hwlCurrentList == NULL)
    {
      hwlCurrentList = _fmalloc(sizeof(hwndList));
      hwlCurrentList->next = NULL;
      hwlCurrentItem = hwlCurrentList;
    }
  
  if (hwnd != NULL && IsWindowEnabled(hwnd) && IsWindowVisible(hwnd))
    {
      hwlCurrentItem->hwnd = hwnd;
      hwlCurrentItem->next = _fmalloc(sizeof(hwndList));
      hwlCurrentItem = hwlCurrentItem->next;
      hwlCurrentItem->next = NULL;
    }
  
  return TRUE;
}

// hwndList* DisableCurrentTask();
//
// Comments:
//
//      This returns a pointer to a window list.  This pointer
//      must be saved and passed to ReEnableCurrentTask().

hwndList* DisableCurrentTask()
{
  HTASK task;

  hwlCurrentList = NULL;
  hwlCurrentItem = NULL;

  hwndActive = GetLastActivePopup(GetActiveWindow());
  task = GetCurrentTask();
  EnumTaskWindows(task, GetTaskWndProc, 0);

  if (hwlCurrentList == NULL)
    {
      // There were no task windows to disable.
      return NULL;
    }

  // Disable the task windows.
  hwlCurrentItem = hwlCurrentList;
  while (hwlCurrentItem->next != NULL)
    {
      EnableWindow(hwlCurrentItem->hwnd, FALSE);
      hwlCurrentItem = hwlCurrentItem->next;
    }
  
  return hwlCurrentList;
}

// void ReEnableCurrentTask(hwndList)
//
// Arguments:
//
//      hwlWinList - A list of task windows created by DisableCurrentTask.
//
// Comments:
//
//      This functions is responsible for restoring task state after
//      a DisableCurrentTask call.  It is also responsible for de-allocating
//      memory.

void ReEnableCurrentTask(hwndList *hwlWinList)
{
  // Re-enable the windows list!
  hwlCurrentItem = hwlCurrentList = hwlWinList;
  while (hwlCurrentList->next != NULL)
    {
      EnableWindow(hwlCurrentList->hwnd, TRUE);
      hwlCurrentList = hwlCurrentList->next;
      _ffree(hwlCurrentItem);
      hwlCurrentItem = hwlCurrentList;
    }
  _ffree(hwlCurrentList);
  if (hwndActive != NULL)
    SetActiveWindow(hwndActive);
  return;
}

// BOOL TalkDropProc(HWND, UINT, WPARAM, LPARAM)
//
// Comment:  This is the standard dialog procedure
//           for the state box.
//
// Status:   This function needs to have it's error handling
//           beefed up.

BOOL CALLBACK __export TalkDropProc(HWND hDlg, UINT uiMsg, WPARAM wParam,
			   LPARAM lParam)
{
  int Reply, BoxHeight, BoxWidth, NewTop, NewLeft;
  char lpszModemReply[RXQUEUE];
  RECT rcDeskTop, rcStateBox;

  switch(uiMsg)
    {
    case WM_INITDIALOG:
      
      // Center the state box.
      GetWindowRect(GetDesktopWindow(), &rcDeskTop);
      GetWindowRect(hDlg, &rcStateBox);
      BoxWidth = rcStateBox.right - rcStateBox.left;
      BoxHeight = rcStateBox.bottom - rcStateBox.top;
      NewTop = (rcDeskTop.bottom - BoxHeight) / 2;
      NewLeft = (rcDeskTop.right - BoxWidth) / 2;
      MoveWindow(hDlg, NewLeft, NewTop, BoxWidth, BoxHeight, TRUE);

      // Setup the comm notification.
      Reply = EnableCommNotification(line.uiCommId, hDlg, 1, -1);
      if (Reply == 0)
	{
#ifdef DEBUG
	  SPTrace("Couldn't enable communications notification.");
#endif
	  EnableCommNotification(line.uiCommId, NULL, -1, -1);
	  ReEnableCurrentTask(hwlAlloced);
	  EndDialog(hDlg, CALL_STATE_ERROR);
	}
      
      // Start busy detection.
      Reply = SendModemCommand(line.uiCommId, "ATD", 3);
      if (Reply <= 0)
	{
#ifdef DEBUG
	  SPTrace("Couldn't start busy detection.");
#endif
	  EnableCommNotification(line.uiCommId, NULL, -1, -1);
	  ReEnableCurrentTask(hwlAlloced);
	  EndDialog(hDlg, CALL_STATE_ERROR);
	}
      
      break;
    case WM_COMMAND:
      
      switch (wParam)
	{
	case IDTALK:
	  // End busy detection, which kicks the modem relay
	  // over again and puts us back in command mode.
	  EnableCommNotification(line.uiCommId, NULL, -1, -1);
	  SendModemCommand(line.uiCommId, "", 0);
	  ReEnableCurrentTask(hwlAlloced);
	  EndDialog(hDlg, CALL_STATE_TALK);
	  break;
	case IDDROP:
	case IDTDHELP:
	  // End busy detection. See above comment.
	  EnableCommNotification(line.uiCommId, NULL, -1, -1);
	  SendModemCommand(line.uiCommId, "", 0);
	  ReEnableCurrentTask(hwlAlloced);
	  EndDialog(hDlg, wParam == IDDROP ? CALL_STATE_DROP:CALL_STATE_HELP);
	  break;
	}
      break;
    case WM_COMMNOTIFY:
      if (LOWORD(lParam) == CN_RECEIVE)
	{
	  Reply = PollComms(line.uiCommId, lpszModemReply, RXQUEUE);
	  if (Reply == 0)
	    {
	      EnableCommNotification(line.uiCommId, NULL, -1, -1);
	      if(_fstrcmp(lpszModemReply, "BUSY") == 0)
		{
		  ReEnableCurrentTask(hwlAlloced);
		  EndDialog(hDlg, CALL_STATE_BUSY);
		}
	      else
		{
		  ReEnableCurrentTask(hwlAlloced);
		  EndDialog(hDlg, CALL_STATE_ERROR);
		}
	    }
	}
      break;
    default:
      return FALSE;
    }
  return TRUE;
}
