//
//  SimpleTapi.cpp
//
//  Source code from:
//
//  Serial Communications: A C++ Developer's Guide, 2nd 
//  Edition by Mark Nelson, IDG Books, 1999
//
//  Please see the book for information on usage.
//
// This file contains the complete implementation of 
// the SimpleTapi class. SimpleTapi wraps up a limited
// subset of TAPI 1.4, using the SDK only for its modem
// control and call setup capabilities. This class
// contains several pure virtual functions that are needed
// for notification. Before you can use SimpleTapi, you will
// need to create a derived class that implements those
// functions. A simple example used in the book is
// MySimpleTapi, used in the Chapter 15 example.
//
#include "SimpleTapi.h"

//
// The static m_Trace object is shared by all extant 
// SimpleTapi devices. It provices a way to display trace
// messages. Calling the Open() and Close() method of this
// object will open and close a console window that displays 
// messages.
//

ConStream SimpleTapi::m_Trace;

//
// The SimpleTapi constructor has to do a couple of obvious 
// things, such as clearing the internal handles and setting
// up other variables. One of the things it does that isn't
// quite as obvious is to enumerate a list of modems that
// can be used with this class. That list is stored in the
// m_Devices vector, which is suitable for insertion into 
// a drop down list for selection by a user.
//

SimpleTapi::SimpleTapi()
{
    //
    // No line is active, no call is up
    //
    m_hLine = 0;
    m_hCall = 0;
    m_bCallHandleValid = false;
    DWORD version = TAPI_CURRENT_VERSION;
    //
    // Note that Microsoft categorizes this as an obsolete
    // function, but for a class like this that is only 
    // using TAPI for first part call control (setting up
    // and tearing down our own calls) it is entirely 
    // adequate. It registers our app and gets an app handle
    // in m_hLineApp, and lets TAPI know that we expect to
    // see all notification returned via our static function
    // SimpleTapi::CallBack()
    //
    DWORD device_count;
    LONG result = lineInitialize( &m_hLineApp,
                                  GetModuleHandle( NULL ),
                                  Callback,
                                  "TAPI Application",
                                  &device_count );
    m_Trace << "SimpleTapi::SimpleTapi() "
            << "lineInitialize() returned "
            << hex << result
            << dec << "\n";
    //
    // If error return, no devices, no app handle, not going
    // to do anything useful.
    //
    if ( result != 0 ) {
        device_count = 0;
        m_hLineApp = NULL;
    }
    //
    // We are going to add all data modems to the list of
    // useful devices. That list is a vector that contains
    // object of type TapiDevices, a little class used here
    // to hold a device name and tapi id. 
    //
    // One thing you see here in the call to lineGetDevCaps()
    // is a recurrent them in Microsoft SDKs. Instead of 
    // just telling us how much space we will need to get the
    // information, we're expected to make one call just 
    // to find out how much space is needed, then another
    // to get the actual data. I buck the system in this 
    // case by grossly overestimating the space needed and
    // attempting to do it all in one call.
    //
    m_Devices.resize( device_count );
    int j = 0;
    for ( int i = 0 ; i < (int) device_count ; i++ ) {
        char temp[ 4096 ];
        LINEDEVCAPS *dev_caps = (LINEDEVCAPS *) temp;
        dev_caps->dwTotalSize  = 4096;
        result = lineGetDevCaps( m_hLineApp,
                                 i,
                                 version,
                                 0,
                                 dev_caps );
        m_Trace << "SimpleTapi::SimpleTapi() "
                << "lineGetDevCaps("
                << i
                << ") returned "
                << hex
                << result
                << dec
                << "\n";
        //
        // To qualify as a useful device, I have to get
        // a successful return from lineGetDevCaps(), the
        // device has to be a data modem, and I have to have
        // a valid device name, as denoted by
        // dwLineNameOffset. If those conditions are met, I
        // add the device to the list of devices. Note that
        // the name is stored as a text string that may not
        // be null terminated. Fortunately there is a string
        // constructor that easily acommodates this.
        //
        if ( result == 0 &&
             dev_caps->dwLineNameOffset != 0 &&
             ( dev_caps->dwMediaModes & LINEMEDIAMODE_DATAMODEM ) )
        {
            m_Devices[ j ].m_sName
               = string( temp + dev_caps->dwLineNameOffset,
                         dev_caps->dwLineNameSize );
            m_Trace << "Device name = "
                    << m_Devices[ j ].m_sName
                    << "\n";
            m_Devices[ j++ ].m_iDeviceNumber = i;
        }
    }
    m_Devices.resize( j );
    m_ReplyAction = NOTHING;
    m_dwPendingReplyCode = -1;
}

//
// The destructor shuts down TAPI by calling lineShutdown, 
// passing the app handle. At that point the TAPI DLL may
// be unloaded and its internal resources given back to the
// system.
//
SimpleTapi::~SimpleTapi()
{
    LONG result = -1;
    if ( m_hLineApp != NULL )
        result = lineShutdown( m_hLineApp );
    m_Trace << "lineShutdown() result = "
            << hex
            << result 
            << dec
            << "\n";
}

//
// Many things that we ask TAPI to do take long amounts
// of time to accomplish. When we kick off one of these
// asynchronous events, our call to TAPI normally returns
// immediately. Later, as TAPI makes progress on our 
// request, it sends notification to us via this callback
// function.
//
// Unfortunately for C++ programmers, callback functions
// in Windows are C functions with no concept of a this 
// pointer. This means that the callback function has to
// be a static C++ member function. The good news is that
// TAPI makes provision for the callback function to carry
// around an untyped pointer that the user provides. In our
// case, that pointer is to a SimpleTapi object, giving us
// immediate access to the SimpleTapi object that is actually
// the source or target of the action.
//
void PASCAL SimpleTapi::Callback( DWORD hDevice,
                                  DWORD dwMsg,
                                  DWORD dwCallbackInstance,
                                  DWORD dwParam1,
                                  DWORD dwParam2,
                                  DWORD dwParam3 )
{
    SimpleTapi *tapi = (SimpleTapi *) dwCallbackInstance;
    //
    // Like most Windows callbacks, we have to examine the 
    // message type to decide what to do. SimpleTapi only
    // responds to two different message types: LINE_REPLY
    // and LINE_CALLSTATE. LINE_REPLY sends responses to
    // any of the three asynchronous commands: Make Call,
    // Drop Call, and Answer. LINE_CALLSTATE keeps us
    // up with the changes in a call state. It is where we
    // get the two important events used in the Chapter
    // 15 demo program, the connected and disconnected 
    // events.
    //
    switch ( dwMsg )
    {
    case LINE_REPLY :
        m_Trace << "LINE_REPLY, request = "
                   << hex 
                   << dwParam1
                   << ", result = "
                   << dwParam2
                   << dec
                   << "\n";
        //
        // If the user of this class made one of the three 
        // asynchronous function calls, it is presumably 
        // hanging around and waiting for an answer. We
        // supply the answer by calling one of the three
        // notification functions. These three notification
        // functions aren't defined in the base class, they
        // have to be implemented in a derived class that
        // is designed to work with a specific application.
        //
        // Note that in order for this section of code to
        // work, we need to only be waiting for one pending
        // action at a time. The pending action is stored
        // in the m_ReplyAction member of the object at the
        // time the asynchronous request is made, using an
        // enumerated type defined specifically in this 
        // class. We don't do anything with the result code
        // that TAPI passes in here, we just pass it along
        // to whoever is at the other end of the notification
        // call.
        //
        if ( dwParam1 == tapi->m_dwPendingReplyCode ) {
            switch ( tapi->m_ReplyAction ) {
            case NOTHING: 
                break;
            case HANDLE_MAKE_CALL_RESULT : 
                tapi->HandleMakeCallResult( dwParam2 ); 
                break;
            case HANDLE_DROP_RESULT      : 
                tapi->HandleDropResult( dwParam2 );     
                break;
            case HANDLE_ANSWER_RESULT    : 
                tapi->HandleAnswerResult( dwParam2 );
                break;
            }
            tapi->m_ReplyAction = NOTHING;
        }
        break;
    //
    // Things are really only interesting in TAPI while a 
    // call is in progress. When that is the case, we get
    // periodic updates from TAPI telling us how things are
    // going. As we progress through these actions, we 
    // generate notification function calls that allow the
    // owner of the TAPI object to do the appropriate things.
    // There is a general notification function that always
    // gets called, then specific notification routines for
    // the all important connected and disconnected events.
    //
    case LINE_CALLSTATE :
        tapi->NotifyCallStateChange( dwParam1 );
        //
        // if dwParam3 is non-zero for the LINE_CALLSATE
        // event, it is being used to inform us of our 
        // privilege state for the given line. For the type
        // of call control we are performing here, we should
        // always be the owner of the call.
        //
        switch ( dwParam3 ) {
		case LINECALLPRIVILEGE_OWNER :
			m_Trace << "SimpleTapi is now the owner of handle = "
                    << hex << hDevice
                    << dec << "\n";
			break;
		}
        //
        // When the LINE_CALLSTATE message is received, 
        // dwParam1 contains the new state of the call.
        // A few of these states are particularly exciting,
        // and lead to notification messages. Most of those
        // cases should be pretty obvious. The TAPI docs 
        // show a few more states than are shown here, and
        // a full-featured class might implement more
        // notification messages to deal with them. The ones
        // used here are adequate for a simple dialing and
        // connecting app.
        //
        switch ( dwParam1 ) {
            //
            // A connected state can occur after either side
            // decides to answer a call. The host app surely
            // needs to be notified by this callback routine.
            //
        case LINECALLSTATE_CONNECTED:
            tapi->ConnectedEvent();
            break;
        case LINECALLSTATE_IDLE:
        case LINECALLSTATE_DISCONNECTED:
            //
            // Note that when the call is dropped I call 
            // TAPI to free up the m_hCall handle that was
            // associated with this call way back when it
            // was first initiated.
            //
            if ( tapi->m_hCall ) {
                HCALL temp_hcall = tapi->m_hCall;
                tapi->m_hCall = 0;
                tapi->DisconnectedEvent();
                m_Trace << "Call was dropped\n";
                lineDeallocateCall( temp_hcall );
                tapi->m_bCallHandleValid = 0;
                if ( tapi->m_ReplyAction == HANDLE_DROP_RESULT ) { //hack, I never see it o/w
                    tapi->HandleDropResult( 0 );
                    tapi->m_ReplyAction = NOTHING;
                }
            }
            break;
            //
            // In SimpleTapi, we will always answer an 
            // incoming call that is being offered if the
            // line is open. So if you don't want to answer
            // an incoming call, don't open the line. Note 
            // that this creates the m_hCall handle, which
            // will be used for the duration of the call.
            // The call to lineAnswer() will almost always 
            // be given asynchronously, with a result code
            // of > 0.
            //
        case LINECALLSTATE_OFFERING :
            {
                tapi->m_hCall = (HCALL) hDevice;
                LONG result = lineAnswer( tapi->m_hCall, NULL, 0 );
                if ( result < 0 ) {
                    tapi->Error( "Error returned from lineAnswer : ", result );
                } else if ( result == 0 ) {
                    m_Trace << "lineAnswer returned : "
                            << result
                            << "\n";
                    tapi->HandleAnswerResult( 0 );
                } else {
                    tapi->m_ReplyAction = HANDLE_ANSWER_RESULT;
                    tapi->m_dwPendingReplyCode = result;
                }
            }
            break;
        }
        tapi->m_lCallState = dwParam1;
    }
}

//
// This static member function simply transates one of the
// seemingly infinite TAPI error status codes to a string.
// This makes the trace information easier to read.
//
string SimpleTapi::TranslateTapiError( LONG code )
{
    switch (code ) {
    case LINEERR_ALLOCATED :
        return "LINEERR_ALLOCATED";
    case LINEERR_BADDEVICEID :
        return "LINEERR_BADDEVICEID";
    case LINEERR_BEARERMODEUNAVAIL:
        return "LINEERR_BEARERMODEUNAVAIL";
    case LINEERR_CALLUNAVAIL:
        return "LINEERR_CALLUNAVAIL";
    case LINEERR_COMPLETIONOVERRUN:
        return "LINEERR_COMPLETIONOVERRUN";
    case LINEERR_CONFERENCEFULL:
        return "LINEERR_CONFERENCEFULL";
    case LINEERR_DIALBILLING:
        return "LINEERR_DIALBILLING";
    case LINEERR_DIALDIALTONE:
        return "LINEERR_DIALDIALTONE";
    case LINEERR_DIALPROMPT:
        return "LINEERR_DIALPROMPT";
    case LINEERR_DIALQUIET:
        return "LINEERR_DIALQUIET";
    case LINEERR_INCOMPATIBLEAPIVERSION:
        return "LINEERR_INCOMPATIBLEAPIVERSION";
    case LINEERR_INCOMPATIBLEEXTVERSION:
        return "LINEERR_INCOMPATIBLEEXTVERSION";
    case LINEERR_INIFILECORRUPT:
        return "LINEERR_INIFILECORRUPT";
    case LINEERR_INUSE:
        return "LINEERR_INUSE";
    case LINEERR_INVALADDRESS:
        return "LINEERR_INVALADDRESS";
    case LINEERR_INVALADDRESSID:
        return "LINEERR_INVALADDRESSID";
    case LINEERR_INVALADDRESSMODE:
        return "LINEERR_INVALADDRESSMODE";
    case LINEERR_INVALADDRESSSTATE:
        return "LINEERR_INVALADDRESSSTATE";
    case LINEERR_INVALAPPHANDLE:
        return "LINEERR_INVALAPPHANDLE";
    case LINEERR_INVALAPPNAME:
        return "LINEERR_INVALAPPNAME";
    case LINEERR_INVALBEARERMODE:
        return "LINEERR_INVALBEARERMODE";
    case LINEERR_INVALCALLCOMPLMODE:
        return "LINEERR_INVALCALLCOMPLMODE";
    case LINEERR_INVALCALLHANDLE:
        return "LINEERR_INVALCALLHANDLE";
    case LINEERR_INVALCALLPARAMS:
        return "LINEERR_INVALCALLPARAMS";
    case LINEERR_INVALCALLPRIVILEGE:
        return "LINEERR_INVALCALLPRIVILEGE";
    case LINEERR_INVALCALLSELECT:
        return "LINEERR_INVALCALLSELECT";
    case LINEERR_INVALCALLSTATE:
        return "LINEERR_INVALCALLSTATE";
    case LINEERR_INVALCALLSTATELIST:
        return "LINEERR_INVALCALLSTATELIST";
    case LINEERR_INVALCARD:
        return "LINEERR_INVALCARD";
    case LINEERR_INVALCOMPLETIONID:
        return "LINEERR_INVALCOMPLETIONID";
    case LINEERR_INVALCONFCALLHANDLE:
        return "LINEERR_INVALCONFCALLHANDLE";
    case LINEERR_INVALCONSULTCALLHANDLE:
        return "LINEERR_INVALCONSULTCALLHANDLE";
    case LINEERR_INVALCOUNTRYCODE:
        return "LINEERR_INVALCOUNTRYCODE";
    case LINEERR_INVALDEVICECLASS:
        return "LINEERR_INVALDEVICECLASS";
    case LINEERR_INVALDEVICEHANDLE:
        return "LINEERR_INVALDEVICEHANDLE";
    case LINEERR_INVALDIALPARAMS:
        return "LINEERR_INVALDIALPARAMS";
    case LINEERR_INVALDIGITLIST:
        return "LINEERR_INVALDIGITLIST";
    case LINEERR_INVALDIGITMODE:
        return "LINEERR_INVALDIGITMODE";
    case LINEERR_INVALDIGITS:
        return "LINEERR_INVALDIGITS";
    case LINEERR_INVALEXTVERSION:
        return "LINEERR_INVALEXTVERSION";
    case LINEERR_INVALGROUPID:
        return "LINEERR_INVALGROUPID";
    case LINEERR_INVALLINEHANDLE:
        return "LINEERR_INVALLINEHANDLE";
    case LINEERR_INVALLINESTATE:
        return "LINEERR_INVALLINESTATE";
    case LINEERR_INVALLOCATION:
        return "LINEERR_INVALLOCATION";
    case LINEERR_INVALMEDIALIST:
        return "LINEERR_INVALMEDIALIST";
    case LINEERR_INVALMEDIAMODE:
        return "LINEERR_INVALMEDIAMODE";
    case LINEERR_INVALMESSAGEID:
        return "LINEERR_INVALMESSAGEID";
    case LINEERR_INVALPARAM:
        return "LINEERR_INVALPARAM";
    case LINEERR_INVALPARKID:
        return "LINEERR_INVALPARKID";
    case LINEERR_INVALPARKMODE:
        return "LINEERR_INVALPARKMODE";
    case LINEERR_INVALPOINTER:
        return "LINEERR_INVALPOINTER";
    case LINEERR_INVALPRIVSELECT:
        return "LINEERR_INVALPRIVSELECT";
    case LINEERR_INVALRATE:
        return "LINEERR_INVALRATE";
    case LINEERR_INVALREQUESTMODE:
        return "LINEERR_INVALREQUESTMODE";
    case LINEERR_INVALTERMINALID:
        return "LINEERR_INVALTERMINALID";
    case LINEERR_INVALTERMINALMODE:
        return "LINEERR_INVALTERMINALMODE";
    case LINEERR_INVALTIMEOUT:
        return "LINEERR_INVALTIMEOUT";
    case LINEERR_INVALTONE:
        return "LINEERR_INVALTONE";
    case LINEERR_INVALTONELIST:
        return "LINEERR_INVALTONELIST";
    case LINEERR_INVALTONEMODE:
        return "LINEERR_INVALTONEMODE";
    case LINEERR_INVALTRANSFERMODE:
        return "LINEERR_INVALTRANSFERMODE";
    case LINEERR_LINEMAPPERFAILED:
        return "LINEERR_LINEMAPPERFAILED";
    case LINEERR_NOCONFERENCE:
        return "LINEERR_NOCONFERENCE";
    case LINEERR_NODEVICE:
        return "LINEERR_NODEVICE";
    case LINEERR_NODRIVER:
        return "LINEERR_NODRIVER";
    case LINEERR_NOMEM:
        return "LINEERR_NOMEM";
    case LINEERR_NOREQUEST:
        return "LINEERR_NOREQUEST";
    case LINEERR_NOTOWNER:
        return "LINEERR_NOTOWNER";
    case LINEERR_NOTREGISTERED:
        return "LINEERR_NOTREGISTERED";
    case LINEERR_OPERATIONFAILED:
        return "LINEERR_OPERATIONFAILED";
    case LINEERR_OPERATIONUNAVAIL:
        return "LINEERR_OPERATIONUNAVAIL";
    case LINEERR_RATEUNAVAIL:
        return "LINEERR_RATEUNAVAIL";
    case LINEERR_RESOURCEUNAVAIL:
        return "LINEERR_RESOURCEUNAVAIL";
    case LINEERR_REQUESTOVERRUN:
        return "LINEERR_REQUESTOVERRUN";
    case LINEERR_STRUCTURETOOSMALL:
        return "LINEERR_STRUCTURETOOSMALL";
    case LINEERR_TARGETNOTFOUND:
        return "LINEERR_TARGETNOTFOUND";
    case LINEERR_TARGETSELF:
        return "LINEERR_TARGETSELF";
    case LINEERR_UNINITIALIZED:
        return "LINEERR_UNINITIALIZED";
    case LINEERR_USERUSERINFOTOOBIG:
        return "LINEERR_USERUSERINFOTOOBIG";
    case LINEERR_REINIT:
        return "LINEERR_REINIT";
    case LINEERR_ADDRESSBLOCKED:
        return "LINEERR_ADDRESSBLOCKED";
    case LINEERR_BILLINGREJECTED:
        return "LINEERR_BILLINGREJECTED";
    case LINEERR_INVALFEATURE:
        return "LINEERR_INVALFEATURE";
    case LINEERR_NOMULTIPLEINSTANCE:
        return "LINEERR_NOMULTIPLEINSTANCE";
    }
    char buf[ 128 ];
    wsprintf( buf, "Unknown error code: %08lx", code );
    return buf;
}

//
// This static member function simply transates one of the
// TAPI call state codes to a string. This makes the trace 
// information easier to read.
//
string SimpleTapi::TranslateCallState( LONG state )
{
    switch ( state ) {
    case LINECALLSTATE_IDLE:
        return "LINECALLSTATE_IDLE";
    case LINECALLSTATE_OFFERING:
        return "LINECALLSTATE_OFFERING";
    case LINECALLSTATE_ACCEPTED:
        return "LINECALLSTATE_ACCEPTED";
    case LINECALLSTATE_DIALTONE:
        return "LINECALLSTATE_DIALTONE";
    case LINECALLSTATE_DIALING:
        return "LINECALLSTATE_DIALING";
    case LINECALLSTATE_RINGBACK:
        return "LINECALLSTATE_RINGBACK";
    case LINECALLSTATE_BUSY:
        return "LINECALLSTATE_BUSY";
    case LINECALLSTATE_SPECIALINFO:
        return "LINECALLSTATE_SPECIALINFO";
    case LINECALLSTATE_CONNECTED:
        return "LINECALLSTATE_CONNECTED";
    case LINECALLSTATE_PROCEEDING:
        return "LINECALLSTATE_PROCEEDING";
    case LINECALLSTATE_ONHOLD:
        return "LINECALLSTATE_ONHOLD";
    case LINECALLSTATE_CONFERENCED:
        return "LINECALLSTATE_CONFERENCED";
    case LINECALLSTATE_ONHOLDPENDCONF:
        return "LINECALLSTATE_ONHOLDPENDCONF";
    case LINECALLSTATE_ONHOLDPENDTRANSFER:
        return "LINECALLSTATE_ONHOLDPENDTRANSFER";
    case LINECALLSTATE_DISCONNECTED:
        return "LINECALLSTATE_DISCONNECTED";
    case LINECALLSTATE_UNKNOWN:
        return "LINECALLSTATE_UNKNOWN";
    case -1:
        return "";
    }
    char buf[ 128 ];
    wsprintf( buf, "Unknown call state: %08lx", state );
    return buf;
}

//
// TAPI is kind enough to provide a thorough configuration
// dialog for the devices it supports, relieving programmers
// of the horrible prospect of writing customized dialogs 
// for any device a user might care to employ. All we have
// to do to invoke this ID is to come up with a device ID 
// number and a parent window handle.
//
void SimpleTapi::ConfigureDevice( int index )
{
    int dev_id = m_Devices[ index ].m_iDeviceNumber;
    LONG result = lineConfigDialog( dev_id,
                                    GetWindow(),
                                    "comm/datamodem" );
    if ( result != 0 )
        ::MessageBox( GetWindow(),
                      "Something bad happened",
                      "SimpleTapi Message",
                      MB_OK );
}

//
// Configuring phone calls is another fairly difficult task 
// that TAPI takes on for us. All we have to do is pass in
// the ID of the device and the phone number to be called.
// TAPI then creates a dialog that takes care of thinking 
// about all sorts of dialing issues.
//
void SimpleTapi::ConfigureCall( int index, const string &number )
{
    int dev_id = m_Devices[ index ].m_iDeviceNumber;
    LONG result = lineTranslateDialog( m_hLineApp,
                                       dev_id,
                                       TAPI_CURRENT_VERSION,
                                       GetWindow(),
                                       number.c_str() );
    if ( result != 0 )
        Error( "Error returned from lineTranslateDialog : ",
               result );
}

//
// If a line is open and doesn't have a call in progress, we 
// can use this routine to initiate a call. Looking down into
// the call, you can see that you need to have a valid m_hLine
// handle to make the call, along with a number and a device
// id. The first part of the function calls 
// lineTranslateAddress() to mangle the digit string into
// something that can be used to make the call. It needs the
// device ID and the phone number to do that.
//
// Once the phone number has been properly mangled, we set up
// a LINECALLPARMS structure and make a call to lineMakeCall.
// That function can do one of three things. It might fail
// with an error, It might return immediately with a valid 
// result. But most likely, it will tell me that it doesn't 
// have a result yet, and I'll just have to wait. When that 
// happens, I set up the m_ReplyAction so that I know I'm
// expecting something, and I return.
//
// Note that when you call this function, or the other 
// asynchronous functions in SimpleTapi, you never get 
// anything useful back, which is why these guys are all of
// type void. You wait for response to come back from the
// callback notification routines.
//

void SimpleTapi::MakeCall( const string &number )
{
    //
    // This first call is another one of those with an
    // indeterminate requirement for the size of the 
    // data structure. The Redmond way is to call it twice,
    // the first time you will fail but find out how much
    // space you need. The second time you call it with the
    // correct amount of space. I thwart this strategy by
    // allocating a hugely excessive amount of space so 
    // that I'll never fail. probably.
    //
    char buf[ 4096 ];
    LINETRANSLATEOUTPUT *lto = (LINETRANSLATEOUTPUT *) buf;
    lto->dwTotalSize = 4096;
    LONG result = lineTranslateAddress( m_hLineApp,
                                        m_iDeviceId,
                                        TAPI_CURRENT_VERSION,
                                        number.c_str(),
                                        0,
                                        0,
                                        lto );
    if ( result != 0 ) {
        Error( "Error returned from lineTranslateAddress : ", result );
        return;
    }
    m_sNumber = string( buf + lto->dwDialableStringOffset, 
                        lto->dwDialableStringSize );
    m_Trace << "Dial number = "
            << m_sNumber
            << "\n";
    LINECALLPARAMS lcp;
    memset( &lcp, 0, sizeof( LINECALLPARAMS ) );
    lcp.dwTotalSize = sizeof( LINECALLPARAMS );
    lcp.dwBearerMode = LINEBEARERMODE_VOICE;
    lcp.dwMediaMode = LINEMEDIAMODE_DATAMODEM;
    lcp.dwCallParamFlags = LINECALLPARAMFLAGS_IDLE;
    lcp.dwAddressMode = LINEADDRESSMODE_ADDRESSID;

    result = lineMakeCall( m_hLine,
                           &m_hCall,
                           m_sNumber.c_str(),
                           0,
                           &lcp );

    if ( result < 0 ) {
        Error( "Error returned from lineMakeCall : ", result );
        return;
    } else if ( result == 0 ) {
        m_Trace << "lineMakeCall returned : "
                << TranslateTapiError( result )
                << "\n";
        HandleMakeCallResult( 0 );
    } else {
        m_ReplyAction = HANDLE_MAKE_CALL_RESULT;
        m_dwPendingReplyCode = result;
    }
}

//
// Dropping a call is easier than making one, but it still
// has to be dealt with asynchronously. All we need to 
// get the whole thing going is a call handle, which we
// pass to lineDrop(). It might generate an eerror, or an
// immediate response, but most likely it will let us 
// know that it will get back to us later via notification
// We make a note of that by setting the m_ReplyAction
// enumerated value to signify that we are waiting for a 
// drop result.
//
void SimpleTapi::DropCall()
{
    LONG result = lineDrop( m_hCall,
                            NULL,
                            0 );
    if ( result < 0 ) {
        Error( "Error returned from lineDrop : ", result );
        return;
    } else if ( result == 0 ) {
        m_Trace << "lineDrop returned : "
                << dec << result
                << "\n";
        HandleDropResult( 0 );
    } else {
        m_ReplyAction = HANDLE_DROP_RESULT;
        m_dwPendingReplyCode = result;
    }
}

//
// These three functions are all fundamentally the same.
// Each of them is called when a delayed response comes
// in to a previous action, which is either a request to
// make a call, answer a call, or drop a call.
//
// Each of the functions does a small amount of internal
// class maintenance. Two of the functions then call a
// user notification function. The two notification functions
// are pure virtual, so they must be defined in a derived
// class.
//
// Why doesn't the Answer handler have a notification 
// routine? SimpleTapi doesn't have an Answer call that the
// user of the class can call. Instead, it automatically 
// answers any incoming call. So the user doesn't ever have
// to worry about a response to an Answer call. A more 
// completel TAPI class wouldn't automatically answer calls;
// it would notify users of incoming offering states and
// let the user make some sort of answer response.
//
void SimpleTapi::HandleMakeCallResult( DWORD result )
{
    m_Trace << "lineMakeCall returned (delayed) : "
            << TranslateTapiError( result )
            << "\n";
    if ( result == 0 )
        m_bCallHandleValid = true;
    else {
        Error( "Delayed error returned from lineMakeCall : ", result );
        m_bCallHandleValid = false;
    }
    MakeCallResult( m_bCallHandleValid );
}

void SimpleTapi::HandleAnswerResult( DWORD result )
{
    m_Trace << "lineAnswer returned (delayed) : " 
            << result
            << "\n";
    if ( result == 0 )
        m_bCallHandleValid = true;
    else {
        Error( "Delayed error returned from lineAnswer : ", result );
        m_bCallHandleValid = false;
    }
}

void SimpleTapi::HandleDropResult( DWORD result )
{
    m_Trace << "lineDrop returned (delayed) : "
            << result
            << "\n";
    if ( result != 0 ) 
        Error( "Delayed error returned from lineDrop : ", result );
    DropCallResult( result == 0 );
}

//
// After creating the TAPI object, the user has to open a 
// line to do antyhing useful at all. This is done with a
// call to lineOpen(). That call expects a valid device
// id, which we get from the caller, and an app handle,
// which was created when the object was first built.
//
// The device ID is retrieved from the m_Devices[] vector,
// which got copies of all device names and IDs when we
// iterated through the list at creation time.
// 
// If we opne the line succesfully, the m_hLine member will
// then have a valid handle, and we are ready to make and
// accept calls.
//
int SimpleTapi::OpenLine( int index )
{
    m_iDeviceId = m_Devices[ index ].m_iDeviceNumber;
    LONG result = lineOpen( m_hLineApp,
                            m_iDeviceId,
                            &m_hLine,
                            TAPI_CURRENT_VERSION,
                            0,
                            (DWORD) this,
                            LINECALLPRIVILEGE_OWNER,
                            LINEMEDIAMODE_DATAMODEM,
                            NULL );
    if ( result != 0 )
        Error( "Error returned in WaitFoprCall from lineOpen : ", result );
    else
        NotifyCallStateChange( LINECALLSTATE_IDLE );
    return result;
}

//
// Before shutting down we really need to close the line.
// This is also a good way to make sure we don't accidentally
// answer any incoming calls. TAPI function lineClose() 
// takes care of this immediately.
int SimpleTapi::CloseLine()
{
    LONG result = lineClose( m_hLine );
    m_hLine = 0;
    if ( result < 0 )
        Error( "Error returned from lineClose : ", result );
    else {
        m_Trace << "lineClose returned : "
                << result
                << "\n";
        NotifyCallStateChange( -1 );
    }
    return result;
}

//
// This function is a little deceptive. After a call is put
// through to the connected state, we can take the resulting
// handle and treat it as a port handle, using it for normal
// serial port functions. In the Chapter 15 example we take 
// the resulting port handle and turn it into a Win32Port, 
// and proceed to use it as a terminal emulator.
//
// Once we get this port handle, we bear a certain measure 
// of responsibility for it. When we are done using it, we
// have to specifically close it with a call to 
// CloseHandle(), or the books in the O/S won't balance.
//
HANDLE SimpleTapi::GetPortHandle()
{
    //
    // This is another one of those calls that take an
    // unknown number of byte. I drastically overestimate 
    // the amount in hopes that I won't have to deal with
    // possibility of failure.
    //
    char temp[ 4096 ];
    VARSTRING *vs = (VARSTRING *) temp;
    vs->dwTotalSize = 4096;

    LONG result = lineGetID( 0,                    //hLine
                             0,                    //dwAddressID
                             m_hCall,              //hCall
                             LINECALLSELECT_CALL,  //dwSelect
                             vs,
                             "comm/datamodem" );
    if ( result < 0 ) {
        Error( "Error returned from lineGetID : ", result );
        return 0;
    }
    HANDLE *p = (HANDLE *) ((LPSTR) vs + vs->dwStringOffset);
    return *p;
}

//EOF SimpleTapi.cpp
