// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 1995  Microsoft Corporation.  All Rights Reserved.
//
//  MODULE: TapiInfo.c
//
//  PURPOSE: Handles all Pretty Printing functions for the TapiComm sample. 
//
//  EXPORTED FUNCTIONS:  These functions are for use by other modules.
//
//      All of these pretty print to the debugging output.
//    OutputDebugLineCallback       - Calls FormatLineCallback.
//    OutputDebugLineError          - Calls OutputDebugLineErrorFileLine.
//    OutputDebugLastError          - Calls OutputDebugLineErrorFileLine.
//    OutputDebugPrintf             - Calls wsprintf
//    OutputDebugLineErrorFileLine  - Calls FormatLineError
//    OutputDebugLastErrorFileLine  - Calls FormatLastError
//
//      All of these functions pretty print to a string buffer.
//    FormatLineError               - Prints a LINEERR
//    FormatLastError               - Prints a GetLastError error.
//    FormatLineCallback            - Prints a lineCallbackFunc message.
//
//  INTERNAL FUNCTION:  These functions are for this module only.
//    strBinaryArrayAppend          - prints a binary flag array to a buffer.

#include <windows.h>
#include <tapi.h>
#include "TapiInfo.h"

// Maximum length of all internal string buffers.
#define MAXOUTPUTSTRINGLENGTH 4096

// define to make accessing arrays easy.
#define sizeofArray(pArray) (sizeof(pArray) / sizeof((pArray)[0]))


//*****************************************
// Internal prototypes.
//*****************************************

static long strBinaryArrayAppend(LPSTR pszOutputBuffer, DWORD dwFlags,
     LPSTR szStringArray[], DWORD dwSizeofStringArray);



//*****************************************
// Global arrays for interpreting TAPI constants.
//*****************************************

LPSTR pszLineErrorNameArray[] = 
{
"",
"LINEERR_ALLOCATED",
"LINEERR_BADDEVICEID",
"LINEERR_BEARERMODEUNAVAIL",
"LINEERR Unused constant, ERROR!!",
"LINEERR_CALLUNAVAIL",
"LINEERR_COMPLETIONOVERRUN",
"LINEERR_CONFERENCEFULL",
"LINEERR_DIALBILLING",
"LINEERR_DIALDIALTONE",
"LINEERR_DIALPROMPT",
"LINEERR_DIALQUIET",
"LINEERR_INCOMPATIBLEAPIVERSION",
"LINEERR_INCOMPATIBLEEXTVERSION",
"LINEERR_INIFILECORRUPT",
"LINEERR_INUSE",
"LINEERR_INVALADDRESS",
"LINEERR_INVALADDRESSID",
"LINEERR_INVALADDRESSMODE",
"LINEERR_INVALADDRESSSTATE",
"LINEERR_INVALAPPHANDLE",
"LINEERR_INVALAPPNAME",
"LINEERR_INVALBEARERMODE",
"LINEERR_INVALCALLCOMPLMODE",
"LINEERR_INVALCALLHANDLE",
"LINEERR_INVALCALLPARAMS",
"LINEERR_INVALCALLPRIVILEGE",
"LINEERR_INVALCALLSELECT",
"LINEERR_INVALCALLSTATE",
"LINEERR_INVALCALLSTATELIST",
"LINEERR_INVALCARD",
"LINEERR_INVALCOMPLETIONID",
"LINEERR_INVALCONFCALLHANDLE",
"LINEERR_INVALCONSULTCALLHANDLE",
"LINEERR_INVALCOUNTRYCODE",
"LINEERR_INVALDEVICECLASS",
"LINEERR_INVALDEVICEHANDLE",
"LINEERR_INVALDIALPARAMS",
"LINEERR_INVALDIGITLIST",
"LINEERR_INVALDIGITMODE",
"LINEERR_INVALDIGITS",
"LINEERR_INVALEXTVERSION",
"LINEERR_INVALGROUPID",
"LINEERR_INVALLINEHANDLE",
"LINEERR_INVALLINESTATE",
"LINEERR_INVALLOCATION",
"LINEERR_INVALMEDIALIST",
"LINEERR_INVALMEDIAMODE",
"LINEERR_INVALMESSAGEID",
"LINEERR Unused constant, ERROR!!",
"LINEERR_INVALPARAM",
"LINEERR_INVALPARKID",
"LINEERR_INVALPARKMODE",
"LINEERR_INVALPOINTER",
"LINEERR_INVALPRIVSELECT",
"LINEERR_INVALRATE",
"LINEERR_INVALREQUESTMODE",
"LINEERR_INVALTERMINALID",
"LINEERR_INVALTERMINALMODE",
"LINEERR_INVALTIMEOUT",
"LINEERR_INVALTONE",
"LINEERR_INVALTONELIST",
"LINEERR_INVALTONEMODE",
"LINEERR_INVALTRANSFERMODE",
"LINEERR_LINEMAPPERFAILED",
"LINEERR_NOCONFERENCE",
"LINEERR_NODEVICE",
"LINEERR_NODRIVER",
"LINEERR_NOMEM",
"LINEERR_NOREQUEST",
"LINEERR_NOTOWNER",
"LINEERR_NOTREGISTERED",
"LINEERR_OPERATIONFAILED",
"LINEERR_OPERATIONUNAVAIL",
"LINEERR_RATEUNAVAIL",
"LINEERR_RESOURCEUNAVAIL",
"LINEERR_REQUESTOVERRUN",
"LINEERR_STRUCTURETOOSMALL",
"LINEERR_TARGETNOTFOUND",
"LINEERR_TARGETSELF",
"LINEERR_UNINITIALIZED",
"LINEERR_USERUSERINFOTOOBIG",
"LINEERR_REINIT",
"LINEERR_ADDRESSBLOCKED",
"LINEERR_BILLINGREJECTED",
"LINEERR_INVALFEATURE",
"LINEERR_NOMULTIPLEINSTANCE"
};


LPSTR psz_dwMsg[] = {
    "LINE_ADDRESSSTATE",
    "LINE_CALLINFO",
    "LINE_CALLSTATE",
    "LINE_CLOSE",
    "LINE_DEVSPECIFIC",
    "LINE_DEVSPECIFICFEATURE",
    "LINE_GATHERDIGITS",
    "LINE_GENERATE",
    "LINE_LINEDEVSTATE",
    "LINE_MONITORDIGITS",
    "LINE_MONITORMEDIA",
    "LINE_MONITORTONE",
    "LINE_REPLY",
    "LINE_REQUEST",
    "PHONE_BUTTON",
    "PHONE_CLOSE",
    "PHONE_DEVSPECIFIC",
    "PHONE_REPLY",
    "PHONE_STATE",
    "LINE_CREATE",
    "PHONE_CREATE"
};


LPSTR pszfLINEADDRESSSTATE[] = 
{
    "Unknown LINEADDRESSSTATE information",
    "LINEADDRESSSTATE_OTHER",
    "LINEADDRESSSTATE_DEVSPECIFIC",
    "LINEADDRESSSTATE_INUSEZERO",
    "LINEADDRESSSTATE_INUSEONE",
    "LINEADDRESSSTATE_INUSEMANY",
    "LINEADDRESSSTATE_NUMCALLS",
    "LINEADDRESSSTATE_FORWARD",
    "LINEADDRESSSTATE_TERMINALS",
    "LINEADDRESSSTATE_CAPSCHANGE"
};


LPSTR pszfLINECALLINFOSTATE[] = 
{
    "Unknown LINECALLINFOSTATE state",
    "LINECALLINFOSTATE_OTHER",
    "LINECALLINFOSTATE_DEVSPECIFIC",
    "LINECALLINFOSTATE_BEARERMODE",
    "LINECALLINFOSTATE_RATE",
    "LINECALLINFOSTATE_MEDIAMODE",
    "LINECALLINFOSTATE_APPSPECIFIC",
    "LINECALLINFOSTATE_CALLID",
    "LINECALLINFOSTATE_RELATEDCALLID",
    "LINECALLINFOSTATE_ORIGIN",
    "LINECALLINFOSTATE_REASON",
    "LINECALLINFOSTATE_COMPLETIONID",
    "LINECALLINFOSTATE_NUMOWNERINCR",
    "LINECALLINFOSTATE_NUMOWNERDECR",
    "LINECALLINFOSTATE_NUMMONITORS",
    "LINECALLINFOSTATE_TRUNK",
    "LINECALLINFOSTATE_CALLERID",
    "LINECALLINFOSTATE_CALLEDID",
    "LINECALLINFOSTATE_CONNECTEDID",
    "LINECALLINFOSTATE_REDIRECTIONID",
    "LINECALLINFOSTATE_REDIRECTINGID",
    "LINECALLINFOSTATE_DISPLAY",
    "LINECALLINFOSTATE_USERUSERINFO",
    "LINECALLINFOSTATE_HIGHLEVELCOMP",
    "LINECALLINFOSTATE_LOWLEVELCOMP",
    "LINECALLINFOSTATE_CHARGINGINFO",
    "LINECALLINFOSTATE_TERMINAL",
    "LINECALLINFOSTATE_DIALPARAMS",
    "LINECALLINFOSTATE_MONITORMODES"
};


LPSTR pszfLINECALLSTATE[] = 
{
    "Unknown LINECALLSTATE state",
    "LINECALLSTATE_IDLE",
    "LINECALLSTATE_OFFERING",
    "LINECALLSTATE_ACCEPTED",
    "LINECALLSTATE_DIALTONE",
    "LINECALLSTATE_DIALING",
    "LINECALLSTATE_RINGBACK",
    "LINECALLSTATE_BUSY",
    "LINECALLSTATE_SPECIALINFO",
    "LINECALLSTATE_CONNECTED",
    "LINECALLSTATE_PROCEEDING",
    "LINECALLSTATE_ONHOLD",
    "LINECALLSTATE_CONFERENCED",
    "LINECALLSTATE_ONHOLDPENDCONF",
    "LINECALLSTATE_ONHOLDPENDTRANSFER",
    "LINECALLSTATE_DISCONNECTED",
    "LINECALLSTATE_UNKNOWN"
};


LPSTR pszfLINEDIALTONEMODE[] =
{
    "Unknown LINEDIALTONE information",
    "LINEDIALTONEMODE_NORMAL",
    "LINEDIALTONEMODE_SPECIAL",
    "LINEDIALTONEMODE_INTERNAL",
    "LINEDIALTONEMODE_EXTERNAL",
    "LINEDIALTONEMODE_UNKNOWN",
    "LINEDIALTONEMODE_UNAVAIL"
};


LPSTR pszfLINEBUSYMODE[] =
{
    "Unknown LINEBUSYMODE information",
    "LINEBUSYMODE_STATION",
    "LINEBUSYMODE_TRUNK",
    "LINEBUSYMODE_UNKNOWN",
    "LINEBUSYMODE_UNAVAIL"
};


LPSTR pszfLINESPECIALINFO[] =
{
    "Unknown LINESPECIALINFO information",
    "LINESPECIALINFO_NOCIRCUIT",
    "LINESPECIALINFO_CUSTIRREG",
    "LINESPECIALINFO_REORDER",
    "LINESPECIALINFO_UNKNOWN",
    "LINESPECIALINFO_UNAVAIL"
};


LPSTR pszfLINEDISCONNECTED[] =
{
    "Unknown LINEDISCONNECTED information",
    "LINEDISCONNECTMODE_NORMAL",
    "LINEDISCONNECTMODE_UNKNOWN",
    "LINEDISCONNECTMODE_REJECT",
    "LINEDISCONNECTMODE_PICKUP",
    "LINEDISCONNECTMODE_FORWARDED",
    "LINEDISCONNECTMODE_BUSY",
    "LINEDISCONNECTMODE_NOANSWER",
    "LINEDISCONNECTMODE_BADADDRESS",
    "LINEDISCONNECTMODE_UNREACHABLE",
    "LINEDISCONNECTMODE_CONGESTION",
    "LINEDISCONNECTMODE_INCOMPATIBLE",
    "LINEDISCONNECTMODE_UNAVAIL",
    "LINEDISCONNECTMODE_NODIALTONE"
};


LPSTR pszfLINECALLPRIVILEGE[] =
{
    "No change to LINECALLPRIVILEGE",
    "LINECALLPRIVILEGE_NONE",
    "LINECALLPRIVILEGE_MONITOR",
    "LINECALLPRIVILEGE_OWNER"
};


LPSTR pszfLINEGATHERTERM[] =
{
    "Unknown LINEGATHERTERM message",
    "LINEGATHERTERM_BUFFERFULL",
    "LINEGATHERTERM_TERMDIGIT",
    "LINEGATHERTERM_FIRSTTIMEOUT",
    "LINEGATHERTERM_INTERTIMEOUT",
    "LINEGATHERTERM_CANCEL"
};


LPSTR pszfLINEGENERATETERM[] = 
{
    "Unknown LINEGENERATETERM message",
    "LINEGENERATETERM_DONE",
    "LINEGENERATETERM_CANCEL"
};


LPSTR pszfLINEDEVSTATE[] =
{    
    "Unknown LINEDEVESTATE state",
    "LINEDEVSTATE_OTHER",
    "LINEDEVSTATE_RINGING",
    "LINEDEVSTATE_CONNECTED",
    "LINEDEVSTATE_DISCONNECTED",
    "LINEDEVSTATE_MSGWAITON",
    "LINEDEVSTATE_MSGWAITOFF",
    "LINEDEVSTATE_INSERVICE",
    "LINEDEVSTATE_OUTOFSERVICE",
    "LINEDEVSTATE_MAINTENANCE",
    "LINEDEVSTATE_OPEN",
    "LINEDEVSTATE_CLOSE",
    "LINEDEVSTATE_NUMCALLS",
    "LINEDEVSTATE_NUMCOMPLETIONS",
    "LINEDEVSTATE_TERMINALS",
    "LINEDEVSTATE_ROAMMODE",
    "LINEDEVSTATE_BATTERY",
    "LINEDEVSTATE_SIGNAL",
    "LINEDEVSTATE_DEVSPECIFIC",
    "LINEDEVSTATE_REINIT",
    "LINEDEVSTATE_LOCK",
    "LINEDEVSTATE_CAPSCHANGE",
    "LINEDEVSTATE_CONFIGCHANGE",
    "LINEDEVSTATE_TRANSLATECHANGE",
    "LINEDEVSTATE_COMPLCANCEL",
    "LINEDEVSTATE_REMOVED"
};


LPSTR pszfLINEDIGITMODE[] =
{
    "Unknown LINEDIGITMODE mode",
    "LINEDIGITMODE_PULSE",
    "LINEDIGITMODE_DTMF",
    "LINEDIGITMODE_DTMFEND"
};
    

LPSTR pszfLINEMEDIAMODE[] =
{
    "Unknown LINEMEDIAMODE mode",
    "UnUsed LINEMEDIAMODE mode, ERROR!!",
    "LINEMEDIAMODE_UNKNOWN",
    "LINEMEDIAMODE_INTERACTIVEVOICE",
    "LINEMEDIAMODE_AUTOMATEDVOICE",
    "LINEMEDIAMODE_DATAMODEM",
    "LINEMEDIAMODE_G3FAX",
    "LINEMEDIAMODE_TDD",
    "LINEMEDIAMODE_G4FAX",
    "LINEMEDIAMODE_DIGITALDATA",
    "LINEMEDIAMODE_TELETEX",
    "LINEMEDIAMODE_VIDEOTEX",
    "LINEMEDIAMODE_TELEX",
    "LINEMEDIAMODE_MIXED",
    "LINEMEDIAMODE_ADSI",
    "LINEMEDIAMODE_VOICEVIEW"
};


LPSTR pszfLINEREQUESTMODE[] =
{
    "Unknown LINEREQUESTMODE message",
    "LINEREQUESTMODE_MAKECALL",
    "LINEREQUESTMODE_MEDIACALL",
    "LINEREQUESTMODE_DROP"
};


//
//  MACRO: OutputDebugLineError(long, LPSTR)
//
//  PURPOSE: Pretty print a line error to the debugging output.
//
//  PARAMETERS:
//    lLineError - Actual error code to decipher.
//    pszPrefix  - String to prepend to the printed message.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    This macro is actually defined in the .h file.
//    It will take a LINEERR error, turn it into a human
//    readable string, prepend pszPrefix (so you
//    can tag your errors), append __FILE__ and __LINE__
//    and print it to the debugging output.

//    This macro is just a wrapper around OutputDebugLineErrorFileLine
//    that is necessary to get proper values for __FILE__ and __LINE__.
//
//


/*
#define OuputDebugLineError(lLineError, pszPrefix) \
    OutputDebugLineErrorFileLine(lLineError, pszPrefix,\
        __FILE__, __LINE__)
*/

//
//  FUNCTION: OutputDebugLineErrorFileLine(..)
//
//  PURPOSE: Pretty print a line error to the debugging output.
//
//  PARAMETERS:
//    lLineError  - Actual error code to decipher.
//    pszPrefix   - String to prepend to the printed message.
//    szFileName  - Filename the error occured in.
//    nLineNumber - Line number the error occured at.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    This is the actual function that OutputDebugLineError
//    expands to.  Its not likely to be usefull except
//    through the OutputDebugLineError macro, or to print
//    errors without line and file information.
//
//    If szFileName == NULL, then the File and Line are not printed.
//   
//    Note that there is an internal string length limit of
//    MAXOUTPUTSTRINGLENGTH.  If this length is exceeded,
//    the behavior will be the same as wsprintf, although
//    it will be undetectable.  *KEEP szPrefix SHORT!*
//
//

void OutputDebugLineErrorFileLine(
    long lLineError, LPSTR szPrefix, 
    LPSTR szFileName, DWORD nLineNumber)
{
    LPSTR szLineError;
    char szOutputLineError[MAXOUTPUTSTRINGLENGTH];

    if (szPrefix == NULL)
        szPrefix = "";

    // Pretty print the error message.
    szLineError = FormatLineError(lLineError, NULL, 0);

    // The only reason FormatLineError should fail is "Out of memory".
    if (szLineError == NULL)
    {
        if (szFileName == NULL)
            wsprintf(szOutputLineError, "%sOut of memory", szPrefix);
        else
            wsprintf(szOutputLineError, 
                "%sOut of memory in file %s, line %d\r\n",
                szPrefix, szFileName, nLineNumber);

        OutputDebugString(szOutputLineError);

        return;
    }

    // If szFileName, then use it; else don't.
    if (szFileName != NULL)
    {
        wsprintf(szOutputLineError,
            "%sTapi Line Error: \"%s\" in File \"%s\", Line %d\r\n",
            szPrefix, szLineError, szFileName, nLineNumber);
    }
    else
    {
        wsprintf(szOutputLineError,
            "%sTapi Line Error: \"%s\"\r\n",
            szPrefix, szLineError);
    }

    // Pointer returned from FormatLineError *must* be freed!
    LocalFree(szLineError);

    // Print it!
    OutputDebugString(szOutputLineError);

    return;
}


//
//  FUNCTION: FormatLineError(long, LPSTR, DWORD)
//
//  PURPOSE: Pretty print a line error to a string.
//
//  PARAMETERS:
//    lLineError           - Actual error code to decipher.
//    szOutputBuffer       - String buffer to pretty print to.
//    dwSizeofOutputBuffer - Size of String buffer.
//
//  RETURN VALUE:
//    Returns the buffer printed to.
//
//  COMMENTS:
//    If szOutputBuffer isn't big enough to hold the whole string,
//    then the string gets truncated to fit the buffer.
//
//    If szOutputBuffer == NULL, then dwSizeofOutputBuffer
//    is ignored, a buffer 'big enough' is LocalAlloc()d and
//    a pointer to it is returned.  However, its *very* important
//    that this pointer be LocalFree()d by the calling application.
//
//

LPSTR FormatLineError(long lLineError,
    LPSTR szOutputBuffer, DWORD dwSizeofOutputBuffer)
{
    char szUnknownLineError[256];
    LPSTR szLineError;
    int nSizeofLineError;
    long lErrorIndex;
    DWORD * pdwLineError;

    // Strip off the high bit to make the error code positive.
    pdwLineError = &lLineError;
    lErrorIndex = (long) (0x7FFFFFFF & *pdwLineError);

    // Is it an unknown error?
    if ((lErrorIndex >= sizeofArray(pszLineErrorNameArray)) ||
        (lErrorIndex < 0))
    {
        nSizeofLineError = 
            wsprintf(szUnknownLineError, "Unknown TAPI line error code: 0x%lx",
                lLineError);
        szLineError = szUnknownLineError;
    }
    else
    {
        szLineError = pszLineErrorNameArray[lErrorIndex];
        nSizeofLineError = strlen(szLineError);
    }

    // allocate a buffer if necessary
    if (szOutputBuffer == NULL)
    {
        szOutputBuffer = (LPSTR) LocalAlloc(LPTR, nSizeofLineError + 1);
        if (szOutputBuffer == NULL)
            return NULL;
    }
    else // truncate string if it won't fit in the specified buffer.
    {
        if ((DWORD) nSizeofLineError >= dwSizeofOutputBuffer)
            nSizeofLineError = dwSizeofOutputBuffer - 1;
    }

    // Put the string into the buffer and null terminate.
    memcpy(szOutputBuffer, szLineError, nSizeofLineError);
    szOutputBuffer[nSizeofLineError] = '\0';

    return szOutputBuffer;
}


//
//  MACRO: OutputDebugLastError(DWORD, LPSTR)
//
//  PURPOSE: Pretty print a system error to the debugging output.
//
//  PARAMETERS:
//    dwLastError - Actual error code to decipher.
//    pszPrefix   - String to prepend to the printed message.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    This macro is actually defined in the .h file.
//    It will take an error that was retrieved by GetLastError(),
//    turn it into a human readable string, prepend pszPrefix
//    (so you can tag your errors), append __FILE__ and __LINE__
//    and print it to the debugging output.
//
//    This macro is just a wrapper around OutputDebugLastErrorFileLine
//    that is necessary to get proper values for __FILE__ and __LINE__.
//
//

/*
#define OuputDebugLastError(dwLastError, pszPrefix) \
    OutputDebugLastErrorFileLine(dwLastError, pszPrefix,\
        __FILE__, __LINE__)
*/


//
//  FUNCTION: OutputDebugLastErrorFileLine(..)
//
//  PURPOSE: Pretty print a line error to the debugging output.
//
//  PARAMETERS:
//    dwLastError - Actual error code to decipher.
//    pszPrefix   - String to prepend to the printed message.
//    szFileName  - Filename the error occured in.
//    nLineNumber - Line number the error occured at.
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    This is the actual function that OutputDebugLastError
//    expands to.  Its not likely to be usefull except
//    through the OutputDebugLastError macro or to print
//    errors without line and file information.
//
//    If szFileName == NULL, then the File and Line are not printed.
//   
//    Note that there is an internal string length limit of
//    MAXOUTPUTSTRINGLENGTH.  If this length is exceeded,
//    the behavior will be the same as wsprintf, although
//    it will be undetectable.  *KEEP szPrefix SHORT!*
//
//

void OutputDebugLastErrorFileLine(
    DWORD dwLastError, LPSTR szPrefix, 
    LPSTR szFileName, DWORD nLineNumber)
{
    LPSTR szLastError;
    char szOutputLastError[MAXOUTPUTSTRINGLENGTH];

    if (szPrefix == NULL)
        szPrefix = "";

    // Pretty print the error.
    szLastError = FormatLastError(dwLastError, NULL, 0);

    // The only reason FormatLastError should fail is "Out of memory".
    if (szLastError == NULL)
    {
        if (szFileName == NULL)
            wsprintf(szOutputLastError, "%sOut of memory\r\n", szPrefix);
        else
            wsprintf(szOutputLastError, "%sOut of memory in file %s, line %d\r\n",
                szPrefix, szFileName, nLineNumber);

        OutputDebugString(szOutputLastError);

        return;
    }

    // If szFileName, then use it; else don't.
    if (szFileName != NULL)
    {
        wsprintf(szOutputLastError,
            "%sGetLastError returned: \"%s\" in File \"%s\", Line %d\r\n",
            szPrefix, szLastError, szFileName, nLineNumber);
    }
    else
    {
        wsprintf(szOutputLastError,
            "%sGetLastError returned: \"%s\"\r\n",
            szPrefix, szLastError);
    }

    // Pointer returned from FormatLineError *must* be freed!
    LocalFree(szLastError);

    // Print it!
    OutputDebugString(szOutputLastError);
    return;
}


//
//  FUNCTION: FormatLastError(DWORD, LPSTR, DWORD)
//
//  PURPOSE: Pretty print a system error to a string.
//
//  PARAMETERS:
//    dwLastError          - Actual error code to decipher.
//    szOutputBuffer       - String buffer to pretty print to.
//    dwSizeofOutputBuffer - Size of String buffer.
//
//  RETURN VALUE:
//    Returns the buffer printed to.
//
//  COMMENTS:
//    If szOutputBuffer isn't big enough to hold the whole string,
//    then the string gets truncated to fit the buffer.
//
//    If szOutputBuffer == NULL, then dwSizeofOutputBuffer
//    is ignored, a buffer 'big enough' is LocalAlloc()d and
//    a pointer to it is returned.  However, its *very* important
//    that this pointer be LocalFree()d by the calling application.
//
//

LPSTR FormatLastError(DWORD dwLastError,
    LPSTR szOutputBuffer, DWORD dwSizeofOutputBuffer)
{
    DWORD dwRetFM;
    DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM;

    // Should we allocate a buffer?
    if (szOutputBuffer == NULL)
    {
        // Actually, we make FormatMessage allocate the buffer, if needed.
        dwFlags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;

        // minimum size FormatMessage should allocate.
        dwSizeofOutputBuffer = 1;  
    }

    // Make FormatMessage pretty print the system error.
    dwRetFM = FormatMessage(
        dwFlags, NULL, dwLastError,
        MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
        (LPTSTR) &szOutputBuffer, dwSizeofOutputBuffer,
        NULL);

    // FormatMessage failed to print the error.
    if (dwRetFM == 0)
    {
        DWORD dwGetLastError;
        LPSTR szFormatMessageError;

        dwGetLastError = GetLastError();

        // If we asked FormatMessage to allocate a buffer, then it
        // might have allocated one.  Lets be safe and LocalFree it.
        if (dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER)
        {
            __try
            {
                LocalFree(szOutputBuffer);
            }
            __except(EXCEPTION_EXECUTE_HANDLER)
            {
                // Actually, we do nothing for this fault.  If
                // there was a fault, it meant the buffer wasn't
                // allocated, and the LocalFree was unnecessary.
                ;
            }

            szOutputBuffer = LocalAlloc(LPTR, MAXOUTPUTSTRINGLENGTH);
            dwSizeofOutputBuffer = MAXOUTPUTSTRINGLENGTH;

            if (szOutputBuffer == NULL)
            {
                OutputDebugString("Out of memory trying to FormatLastError\r\n");
                return NULL;
            }
        }

        szFormatMessageError = 
            FormatLastError(dwGetLastError, NULL, 0);

        if (szFormatMessageError == NULL)
            return NULL;

        wsprintf(szOutputBuffer, 
            "FormatMessage failed on error 0x%lx for the following reason: %s",
            dwLastError, szFormatMessageError);

        LocalFree(szFormatMessageError);
    }

    return szOutputBuffer;
}


//
//  FUNCTION: OutputDebugLineCallback(...)
//
//  PURPOSE: Pretty print a message passed into a lineCallbackFunc.
//
//  PARAMETERS:
//    Standard lineCallbackFunc parameters.
//
//  RETURN VALUE:
//    none.
//
//  COMMENTS:
//
//    This function takes all of the parameters passed into a
//    lineCallbackFunc callback function, and pretty prints the
//    meaning of the message.  It then prints the result to
//    the debugging output.
//
//

void OutputDebugLineCallback(
    DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance, 
    DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
    char szOutputBuff[MAXOUTPUTSTRINGLENGTH];

    FormatLineCallback(szOutputBuff, 
        dwDevice, dwMsg, dwCallbackInstance, 
        dwParam1, dwParam2, dwParam3);

    strcat(szOutputBuff,"\r\n");

    OutputDebugString(szOutputBuff);
}


//
//  FUNCTION: FormatLineCallback(...)
//
//  PURPOSE: Pretty prints into a buffer a lineCallbackFunc message.
//
//  PARAMETERS:
//    Standard lineCallbackFunc parameters.
//
//  RETURN VALUE:
//    The pointer to the buffer that has the resulting string.
//
//  COMMENTS:
//
//    This function takes all of the parameters passed into a
//    lineCallbackFunc callback function, and pretty prints the
//    meaning of the message.  It then returns the pointer to
//    the buffer containing this string.
//
//    If szOutputBuffer == NULL, then a buffer is LocalAlloc()d
//    and returned.  However, it is *very* important that this buffer
//    is LocalFree()d.
//

LPSTR FormatLineCallback(LPSTR szOutputBuffer,
    DWORD dwDevice, DWORD dwMsg, DWORD dwCallbackInstance, 
    DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
    long lBufferIndex = 0;

    // Allocate the buffer if necessary.
    if (szOutputBuffer == NULL)
    {
        szOutputBuffer = (LPSTR) LocalAlloc(LPTR, MAXOUTPUTSTRINGLENGTH);

        if (szOutputBuffer == NULL)
            return NULL;
    }

    // Is this a known message?
    if (dwMsg >= sizeofArray(psz_dwMsg))
    {
        wsprintf(szOutputBuffer, "lineCallback: Unknown dwMsg: '0x%lx', "
            "dwDevice: '0x%lx', dwCallbackInstance: '0x%lx', "
            "dwParam1: '0x%lx', dwParam2: '0x%lx', dwParam3: '0x%lx'", dwMsg, 
            dwDevice, dwCallbackInstance, dwParam1, dwParam2, dwParam3);
        return szOutputBuffer;
    }

    // Lets start pretty printing.
    lBufferIndex +=
        wsprintf(szOutputBuffer, "lineCallback: %s; dwDevice: '0x%lx'; ",
            psz_dwMsg[dwMsg], dwDevice);

    // Which message was it?  And start decoding it!
    // How the message is decoded depends entirely on the message.
    // READ THE HELP FILES if you more information on this.
    switch(dwMsg)
    {
        case LINE_ADDRESSSTATE:
        {
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    "Address ID: 0x%lx, Address State: ", dwParam1);

            lBufferIndex +=
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam2,
                    pszfLINEADDRESSSTATE, sizeofArray(pszfLINEADDRESSSTATE));

            break;
        }

        case LINE_CALLINFO:
        {
            lBufferIndex +=
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam1,
                    pszfLINECALLINFOSTATE, sizeofArray(pszfLINECALLINFOSTATE));

            break;
        }

        case LINE_CALLSTATE:
        {

            lBufferIndex +=
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam3,
                    pszfLINECALLPRIVILEGE, sizeofArray(pszfLINECALLPRIVILEGE));
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    "; ");

            lBufferIndex += 
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam1,
                    pszfLINECALLSTATE, sizeofArray(pszfLINECALLSTATE));
    
            switch(dwParam1)
            {
                case LINECALLSTATE_DIALTONE:
                {
                    lBufferIndex +=
                        wsprintf(&(szOutputBuffer[lBufferIndex]),
                            ": ");

                    lBufferIndex += 
                        strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                        dwParam2,
                        pszfLINEDIALTONEMODE, sizeofArray(pszfLINEDIALTONEMODE));

                    break;
                }

                case LINECALLSTATE_BUSY:
                {
                    lBufferIndex +=
                        wsprintf(&(szOutputBuffer[lBufferIndex]),
                            ": ");

                    lBufferIndex += 
                        strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                        dwParam2,
                        pszfLINEBUSYMODE, sizeofArray(pszfLINEBUSYMODE));

                    break;
                }

                case LINECALLSTATE_SPECIALINFO:
                {
                    lBufferIndex +=
                        wsprintf(&(szOutputBuffer[lBufferIndex]),
                            ": ");

                    lBufferIndex += 
                        strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                        dwParam2,
                        pszfLINESPECIALINFO, sizeofArray(pszfLINESPECIALINFO));

                    break;
                }

                case LINECALLSTATE_DISCONNECTED:
                {
                    lBufferIndex +=
                        wsprintf(&(szOutputBuffer[lBufferIndex]),
                            ": ");

                    lBufferIndex += 
                        strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                        dwParam2,
                        pszfLINEDISCONNECTED, sizeofArray(pszfLINEDISCONNECTED));

                    break;
                }

                case LINECALLSTATE_CONFERENCED:
                {
                    lBufferIndex +=
                        wsprintf(&(szOutputBuffer[lBufferIndex]),
                            ": Parent conference call handle: 0x%lx", dwParam2);

                    break;
                }
            }

            break;
        }

        case LINE_CLOSE:
            break;

        case LINE_DEVSPECIFIC:
            break;

        case LINE_DEVSPECIFICFEATURE:
            break;

        case LINE_GATHERDIGITS:
        {
            lBufferIndex +=
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam1,
                    pszfLINEGATHERTERM, sizeofArray(pszfLINEGATHERTERM));

            break;
        }

        case LINE_GENERATE:
        {
            lBufferIndex +=
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam1,
                    pszfLINEGENERATETERM, sizeofArray(pszfLINEGENERATETERM));

            break;
        }

        case LINE_LINEDEVSTATE:
        {
            lBufferIndex +=
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam1,
                    pszfLINEDEVSTATE, sizeofArray(pszfLINEDEVSTATE));

            switch(dwParam1)
            {
                case LINEDEVSTATE_RINGING:
                    lBufferIndex +=
                        wsprintf(&(szOutputBuffer[lBufferIndex]),
                            "; Ring Mode: 0x%lx, Ring Count: %lu"
                            ,dwParam2, dwParam3);
                    break;

                case LINEDEVSTATE_REINIT:
                {
                    switch(dwParam2)
                    {
                        case LINE_CREATE:
                            lBufferIndex +=
                                wsprintf(&(szOutputBuffer[lBufferIndex]),
                                    "; ReInit reason: LINE_CREATE, "
                                        "New Line Device ID '0x%lx'"
                                    , dwParam3);
                            break;
                            
                        case LINE_LINEDEVSTATE:
                            lBufferIndex +=
                                wsprintf(&(szOutputBuffer[lBufferIndex]),
                                    "; ReInit reason: LINE_LINEDEVSTATE, ");

                            lBufferIndex +=
                                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                                    dwParam3,
                                    pszfLINEDEVSTATE, sizeofArray(pszfLINEDEVSTATE));

                            break;
                        
                        case 0:
                            break;
                        default:
                            lBufferIndex +=
                                wsprintf(&(szOutputBuffer[lBufferIndex]),
                                    "; ReInit reason: %s, dwParam3: 0x%lx"
                                    ,psz_dwMsg[dwParam2], dwParam3);
                            break;

                    }

                    break;
                }

                default:
                    break;
            }

            break;
        }

        case LINE_MONITORDIGITS:
        {
            lBufferIndex += 
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                dwParam2,
                pszfLINEDIGITMODE, sizeofArray(pszfLINEDIGITMODE));

            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    ", Received: '%c'", LOBYTE(LOWORD(dwParam1)));
            
            break;
        }

        case LINE_MONITORMEDIA:
        {
            lBufferIndex +=
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                    dwParam1,
                    pszfLINEMEDIAMODE, sizeofArray(pszfLINEMEDIAMODE));

            break;
        }

        case LINE_MONITORTONE:
        {
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    "AppSpecific tone '%lu'", dwParam1);
            break;
        }

        case LINE_REPLY:
        {
            if (dwParam2 == 0)
            {
                lBufferIndex +=
                    wsprintf(&(szOutputBuffer[lBufferIndex]),
                        "Request ID: 0x%lx; Successful reply!", dwParam1);
            }
            else
            {
                char szTmpBuff[256];

                FormatLineError((long) dwParam2, szTmpBuff, 255);

                lBufferIndex +=
                    wsprintf(&(szOutputBuffer[lBufferIndex]),
                        "Request ID: 0x%lx; UnSuccessful reply; %s",
                        dwParam1, szTmpBuff);
            }

            break;
        }

        case LINE_REQUEST:
        {
            lBufferIndex += 
                strBinaryArrayAppend(&(szOutputBuffer[lBufferIndex]),
                dwParam1,
                pszfLINEREQUESTMODE, sizeofArray(pszfLINEREQUESTMODE));

            switch(dwParam1)
            {
                case LINEREQUESTMODE_DROP:
                {
                    char szHwndName[1024];

                    SendMessage((HWND) dwParam2, WM_GETTEXT, 1024, (long) szHwndName);

                    lBufferIndex +=
                        wsprintf(&(szOutputBuffer[lBufferIndex]),
                            ": hwnd dropping = 0x%lx, \"%s\"; wRequestID = %u",
                            dwParam2, szHwndName, LOWORD(dwParam3));
                    break;
                }
                default:
                    break;
            }

            break;
        }

        case LINE_CREATE:
        {
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    "New Line Device ID '0x%lx'", dwParam1);
            break;
        }

        case PHONE_CREATE:
        {
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    "New Phone Device ID '0x%lx'", dwParam1);
            break;
        }

        default:
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    "dwParam1: 0x%lx , dwParam2: 0x%lx , dwParam3: 0x%lx",
                    dwParam1, dwParam2, dwParam3);
            break;

    } // End switch(dwMsg)

    // return that pointer!
    return szOutputBuffer;

}


//
//  FUNCTION: strBinaryArrayAppend(LPSTR, DWORD, LPSTR *, DWORD)
//
//  PURPOSE: Takes a bitmapped DWORD, an array representing that
//    binary mapping, and pretty prints it to a buffer.
//
//  PARAMETERS:
//    szOutputBuffer      - Buffer to print to.
//    dwFlags             - Binary mapped flags to interpret.
//    szStringArray       - Array of strings.
//    dwSizeofStringArray - number of elements in szStringArray.

//
//  RETURN VALUE:
//    The number of characters printed into szOutputBuffer.
//
//  COMMENTS:
//
//    This function takes dwFlags and checks each bit.  If the
//    bit is set, the appropriate string (taken from szStringArray)
//    is printed to the szOutputBuffer string buffer.  If there were
//    more bits set in the string than elements in the array, and error
//    is also tagged on the end.
//
//    This function is intended to be used only within the TapiInfo module.
//

static long strBinaryArrayAppend(LPSTR szOutputBuffer, DWORD dwFlags,
     LPSTR szStringArray[], DWORD dwSizeofStringArray)
{
    DWORD dwIndex = 1, dwPower = 1;
    long lBufferIndex = 0;
    BOOL bFirst = TRUE;

    // The zeroth element in every bitmapped array is the "unknown" or
    // "unchanged" message.
    if (dwFlags == 0)
    {
        lBufferIndex =
            wsprintf(szOutputBuffer, "%s", szStringArray[0]);
        return lBufferIndex;
    }

    // iterate through the flags and check each one.
    while(dwIndex < dwSizeofStringArray)
    {
        // If we find one, print it.
        if (dwFlags & dwPower)
            // Seporate each printing with a ", " except the first one.
            if (bFirst)
            {
                lBufferIndex +=
                    wsprintf(&(szOutputBuffer[lBufferIndex]),
                        "%s", szStringArray[dwIndex]);
                bFirst = FALSE;
            }
            else
                lBufferIndex +=
                    wsprintf(&(szOutputBuffer[lBufferIndex]),
                        ", %s", szStringArray[dwIndex]);

        dwIndex ++;
        dwFlags &= ~dwPower;  // clear it so we know we checked it.
        dwPower *= 2;
    }

    // If there are any flags left, they were not in the array.
    if (dwFlags)
    {
        if (bFirst)
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    "Unknown flags '0x%lx'", dwFlags);
        else
            lBufferIndex +=
                wsprintf(&(szOutputBuffer[lBufferIndex]),
                    ", Unknown flags '0x%lx'", dwFlags);
    }

    // how many did we print into the buffer?
    return lBufferIndex;
}



//
//  FUNCTION: OutputDebugPrintf(LPCSTR, ...)
//
//  PURPOSE: wsprintf to the debugging output.
//
//  PARAMETERS:
//    Exactly the same as wsprintf.
//
//  RETURN VALUE:
//    none.
//
//  COMMENTS:
//
//    This function takes exactly the same parameters as wsprintf and
//    prints the results to the debugging output.
//

void __cdecl OutputDebugPrintf(LPCSTR lpszFormat, ...)
{
    char szOutput[MAXOUTPUTSTRINGLENGTH];
    va_list v1;
    DWORD dwSize;

    va_start(v1, lpszFormat);

    dwSize = wvsprintf(szOutput, lpszFormat, v1);

    if (szOutput[dwSize-1] != '\n')
        strcat(szOutput, "\r\n");

    OutputDebugString(szOutput);
}
