// This code is a part of the Telephony Framework C++ Library.
// Copyright (C) 1997 Chris Sells. All rights reserved.
// tPhone.cpp: CtPhone class implementation

#include "stdafx.h"
#include "tPhone.h"
#include "TfxUtil.h"

#define PHONESTATE_ALL          0x00FFFFFF
#define PHONEBUTTONMODE_ALL     0x0000003F
#define PHONEBUTTONSTATE_ALL    0x0000000F

///////////////////////////////////////////////////////////////////
// Special Win95 Recovery

#if defined(_DEBUG) && TAPI_CURRENT_VERSION == 0x00010004
static PhoneTapiRecover  g_phoneRecover;
#define RecoverPreInitialize() g_phoneRecover.PreInitialize()
#define RecoverInitialize(happ) g_phoneRecover.Initialize((void*)happ)
#define RecoverShutdown() g_phoneRecover.Shutdown()
#else
#define RecoverPreInitialize()
#define RecoverInitialize(happ)
#define RecoverShutdown()
#endif

/////////////////////////////////////////////////////////////////////////////
// Phone Operations

CtPhone::CtPhone()  // = 0
    :
    m_hPhone(0),
    m_nPhoneID(DWORD(-1))
{
    // Add to static list of Phones
    AddToPhones(this);
}

CtPhone::~CtPhone()
{
    Close();

    // Remove from static list of Phones
    RemoveFromPhones(this);
}

HPHONE CtPhone::GetHandle() const
{
    return m_hPhone;
}

DWORD CtPhone::GetDeviceID() const
{
    return m_nPhoneID;
}

// If it's not already in the list, add it
void CtPhone::AddSink(CtPhoneSink* pSink)
{
    if( pSink )
    {
        int nEmpty = -1;

        for( int i = 0; i < m_rgSinks.size(); i++ )
        {
            // Found
            if( m_rgSinks[i] == pSink )
            {
                return;
            }
            // Found first empty spot
            else if( nEmpty == -1 && m_rgSinks[i] == 0 )
            {
                nEmpty = i;
            }
        }

        // Add it to the list in the empty spot
        if( nEmpty != -1 )
        {
            m_rgSinks[nEmpty] = pSink;
        }
        // Create a new spot and add it to the list
        else
        {
            m_rgSinks.push_back(pSink);
        }
    }
}

void CtPhone::RemoveSink(CtPhoneSink* pSink)
{
    assert(pSink);

    // If it's in the list, remove it
    for( int i = 0; i < m_rgSinks.size(); i++ )
    {
        if( m_rgSinks[i] == pSink )
        {
            m_rgSinks[i] = 0;
            break;
        }
    }
}

BOOL CtPhone::IsRequestPending(
    TREQUEST    nRequestID,
    DWORD*      pnRequestType) const    // = 0
{
    return m_listRequests.IsRequestPending(nRequestID, pnRequestType);
}

BOOL CtPhone::IsRequestTypePending(
    DWORD   nRequestType) const
{
    return m_listRequests.IsRequestTypePending(nRequestType);
}

/////////////////////////////////////////////////////////////////////////////
// Static Phone Operations

void CtPhone::AddToPhones(
    CtPhone* pPhone)
{
    assert(pPhone);
    s_listPhones.push_back( pPhone );  //.AddTail(pPhone);
}

void CtPhone::RemoveFromPhones(
    CtPhone* pPhone)
{
    assert(pPhone);
    s_listPhones.remove( pPhone );  //.RemoveAt(s_listPhones.Find(pPhone));
}

CtPhone* CtPhone::FromHandle(
    HPHONE   hPhone)
{
    assert(hPhone);

    CtPhone*     pPhone = 0;
	CPointerList::iterator iter;
	for( iter = s_listPhones.begin(); iter != s_listPhones.end(); ++iter )
	{
		pPhone = (CtPhone*)(*iter);
		if( pPhone->GetHandle() == hPhone )
		{
			return pPhone;
		}
	}

    return 0;
}

DWORD CtPhone::GetNumDevs()
{
    return s_nNumDevs;
}

HPHONEAPP CtPhone::GetAppHandle()
{
    return s_hPhoneApp;
}

/////////////////////////////////////////////////////////////////////////////
// Static Phone Wrappers

HPHONEAPP   CtPhone::s_hPhoneApp = 0;
DWORD       CtPhone::s_dwLoVersion =  0x00010003;    // TAPI v1.3
DWORD       CtPhone::s_dwHiVersion = TAPI_CURRENT_VERSION;
CPointerList    CtPhone::s_listPhones;
DWORD       CtPhone::s_nNumDevs = 0;
CtAppSink*  CtPhone::s_pAppSink = 0;
DWORD*      CtPhone::s_aApiVersions = 0;

void CtPhone::NegotiateApiVersions()
{
    assert(!s_aApiVersions);

    if( s_nNumDevs &&
        (s_aApiVersions = new DWORD[s_nNumDevs]) )
    {
        PHONEEXTENSIONID    dummy;
        for( DWORD nPhoneID = 0; nPhoneID < s_nNumDevs; nPhoneID++ )
        {
            if( TFAILED(::TfxTapiFunc(::phoneNegotiateAPIVersion(s_hPhoneApp,
                                                                 nPhoneID,
                                                                 s_dwLoVersion,
                                                                 s_dwHiVersion,
                                                                 s_aApiVersions + nPhoneID,
                                                                 &dummy),
                                      TDBSTR("phoneNegotiateAPIVersion"))) )
            {
                s_aApiVersions[nPhoneID] = 0;
            }
        }
    }
}

void CtPhone::SetAppVersion(
    DWORD   dwLoVersion,
    DWORD   dwHiVersion)
{
    s_dwLoVersion = dwLoVersion;
    s_dwHiVersion = dwHiVersion;
}

DWORD CtPhone::GetAppVersion()
{
	return s_dwHiVersion;
}

DWORD CtPhone::GetApiVersion(
    DWORD   nPhoneID)
{
    assert(nPhoneID < GetNumDevs());

    if( s_aApiVersions )
    {
        return s_aApiVersions[nPhoneID];
    }
    else
    {
        return 0;
    }
}

TRESULT CtPhone::Initialize(
    CtAppSink*  pAppSink,
    LPCSTR      szAppName,
    HINSTANCE   hInst)
{
    // Updated to support console apps, too
    // (as per Valery Arkhangorodsky [valerya@balisoft.com])
    //assert(hInst);
    if( !hInst ) hInst = GetModuleHandle(0);

    RecoverPreInitialize();

    if( !s_hPhoneApp )
    {
#if TAPI_CURRENT_VERSION < 0x00020000
        TRESULT tr = ::TfxTapiFunc(::phoneInitialize(&s_hPhoneApp,
                                                     hInst,
                                                     TfxPhoneCallback,
                                                     szAppName,
                                                     &s_nNumDevs),
                                    TDBSTR("phoneInitialize"));
#else
        DWORD   dwAPIVersion = s_dwHiVersion;
        PHONEINITIALIZEEXPARAMS params = { sizeof(PHONEINITIALIZEEXPARAMS) };
        params.dwOptions = PHONEINITIALIZEEXOPTION_USEHIDDENWINDOW;
        TRESULT tr = ::TfxTapiFunc(::phoneInitializeEx(&s_hPhoneApp,
                                                       hInst,
                                                       TfxPhoneCallback,
                                                       szAppName,
                                                       &s_nNumDevs,
                                                       &dwAPIVersion,
                                                       &params),
                                    TDBSTR("phoneInitializeEx"));
#endif
        if( TSUCCEEDED(tr) )
        {
            // Save the sink
            s_pAppSink = pAppSink;

            // Negotiate the API versions
            // (Necessary to get proper notifications)
            NegotiateApiVersions();

            RecoverInitialize(s_hPhoneApp);
        }
        return tr;
    }
    else
    {
		char buf[256] = {'\0'};
        sprintf( buf, "CtPhone::Initialize()  Cannot re-initialize\n");
		OutputDebugString( buf );
        return PHONEERR_OPERATIONFAILED;
    }
}

TRESULT CtPhone::Shutdown()
{
    s_pAppSink = 0;
    delete [] s_aApiVersions;
    s_aApiVersions = 0;

    if( s_hPhoneApp )
    {
        TRESULT tr = ::TfxTapiFunc(::phoneShutdown(s_hPhoneApp), TDBSTR("phoneShutdown"));
        s_hPhoneApp = 0;
        RecoverShutdown();
        return tr;
    }
    else
    {
		char buf[256] = {'\0'};
        sprintf( buf, "CtPhone::Shutdown()  Never initialized\n");
		OutputDebugString( buf );
        return PHONEERR_OPERATIONFAILED;
    }
}

TRESULT CtPhone::GetIcon(
    DWORD   nPhoneID,
    LPHICON phicon,
    LPCSTR  pszDeviceClass)
{
    assert(nPhoneID < GetNumDevs());
    assert(phicon);

    return ::TfxTapiFunc(::phoneGetIcon(nPhoneID, pszDeviceClass, phicon),
                         TDBSTR("phoneGetIcon"));
}

/////////////////////////////////////////////////////////////////////////////
// Phone Wrappers

TRESULT CtPhone::Open(
    DWORD           nPhoneID,
    CtPhoneSink*    pInitialSink,   // = 0
    DWORD           dwPriviledges)  // = PHONEPRIVILEGE_OWNER
{
    assert(s_hPhoneApp);
    assert(nPhoneID < GetNumDevs());
    assert(!m_hPhone);

    TRESULT tr = ::TfxTapiFunc(::phoneOpen(s_hPhoneApp, nPhoneID,
                                           &m_hPhone, s_aApiVersions[nPhoneID],
                                           0, (DWORD)this, dwPriviledges),
                                TDBSTR("phoneOpen"));

    if( TSUCCEEDED(tr) )
    {
        // Cache the device ID and the sink
        m_nPhoneID = nPhoneID;
        AddSink(pInitialSink);

        assert(m_hPhone);

        // Set all status messages
        ::TfxTapiFunc(::phoneSetStatusMessages(m_hPhone, PHONESTATE_ALL, PHONEBUTTONMODE_ALL, PHONEBUTTONSTATE_ALL),
                      TDBSTR("phoneSetStatusMessages"));
    }
    else
    {
        // NT sets the m_hPhone even if we fail!
        m_hPhone = 0;
    }

    return tr;
}

TRESULT CtPhone::Close()
{
    TRESULT tr = 0;

    if( m_hPhone )
    {
        tr = ::TfxTapiFunc(::phoneClose(m_hPhone), TDBSTR("phoneClose"));
        RemoveAllRequests();

        m_hPhone = 0;
        m_nPhoneID = DWORD(-1);
    }
    return tr;
}

TRESULT CtPhone::SetHookSwitch(
    DWORD   dwHookSwitchDevs,
    DWORD   nHookSwitchMode)
{
    assert(m_hPhone);
    assert(dwHookSwitchDevs && nHookSwitchMode);

    return ::TfxTapiFunc(::phoneSetHookSwitch(m_hPhone, dwHookSwitchDevs, nHookSwitchMode),
                         TDBSTR("phoneSetHookSwitch"));
}

/////////////////////////////////////////////////////////////////////////////
// Phone Event Handling

void CALLBACK
TfxPhoneCallback(
    DWORD   dwDevice,
    DWORD   nMsg,
    DWORD   dwInstance,
    DWORD   dwParam1,
    DWORD   dwParam2,
    DWORD   dwParam3)
{
    TETRACE(dwDevice, nMsg, dwParam1, dwParam2, dwParam3);

    switch( nMsg )
    {
    case PHONE_CREATE:
        //TRACE0(TEXT("PHONE_CREATE\n"));
        CtPhone::OnCreate(dwParam1);
    break;

    default:
        CtPhone*   pPhone = (CtPhone*)dwInstance;
        if( pPhone )
        {
            pPhone->OnEvent(dwDevice, nMsg, dwParam1, dwParam2, dwParam3);
        }
        else
        {
			char buf[256] = {'\0'};
            sprintf( buf, __TEXT("Unhandled Phone event: %luL  %lu, %lu, %lu\n"), nMsg,
                  dwParam1, dwParam2, dwParam3);
				OutputDebugString( buf );
        }
    break;
    }
}

void CtPhone::OnCreate(
    DWORD   dwDeviceID)
{
    if( s_pAppSink ) s_pAppSink->OnPhoneCreate(dwDeviceID);
}

void CtPhone::OnEvent(
    DWORD   dwDevice,
    DWORD   nMsg,
    DWORD   dwParam1,
    DWORD   dwParam2,
    DWORD   dwParam3)
{
    switch( nMsg )
    {
    case PHONE_BUTTON:
        //TRACE0(TEXT("PHONE_BUTTON\n"));
        assert(m_hPhone == (HPHONE)dwDevice);
        OnButton(dwParam1, dwParam2, dwParam3);
    break;

    case PHONE_CLOSE:
        //TRACE0(TEXT("PHONE_CLOSE\n"));
        assert(m_hPhone == (HPHONE)dwDevice);
        OnClose();
    break;

    case PHONE_DEVSPECIFIC:
        //TRACE0(TEXT("PHONE_DEVSPECIFIC\n"));
        assert(m_hPhone == (HPHONE)dwDevice);
        OnDevSpecific(dwDevice, dwParam1, dwParam2, dwParam3);
    break;

    case PHONE_REPLY:
    {
        //TRACE0(TEXT("PHONE_REPLY: "));
        // If a reply target can be found for this request,
        // let the target handle the reply
        CtReplyTarget*  pTarget = 0;
        DWORD           dwRequest;
        if( m_listRequests.RemoveRequest(dwParam1, &pTarget, &dwRequest) )
        {
            assert(pTarget);
            assert(dwRequest != PHONEREQUEST_UNKNOWN);
            pTarget->OnReply(dwParam1, dwParam2, dwRequest);
        }
        // If a reply target can't be found for this request,
        // let the Phone handle it as an unhandled reply.
        // This allows app-specific reply handling for Phones made
        // directly to TAPI via the handle.
        else
        {
            OnReply(dwParam1, dwParam2, PHONEREQUEST_UNKNOWN);
        }
    }
    break;

    case PHONE_STATE:
        //TRACE0(TEXT("PHONE_STATE\n"));
        OnState(dwParam1, dwParam2);
    break;

#if (TAPI_CURRENT_VERSION >= 0x00020000)
    case PHONE_REMOVE: // TAPI v2.0
        OnRemove(dwParam1);
    break;
#endif

    default:
		char buf[256] = {'\0'};
        sprintf( buf, __TEXT("CtPhone::OnEvent()  Unhandled nMsg= %d\n"), nMsg);
		OutputDebugString( buf );
        assert(FALSE);
    break;
    }
}

void CtPhone::OnButton(
    DWORD   nButtonOrLampID,
    DWORD   nButtonMode,
    DWORD   nButtonState)
{
    for( int i = 0; i < m_rgSinks.size(); i++ )
    {
        if( m_rgSinks[i] )
        {
            ((CtPhoneSink*)m_rgSinks[i])->OnPhoneButton(this, nButtonOrLampID, nButtonMode, nButtonState);
        }
    }
}

void CtPhone::OnClose()
{
    for( int i = 0; i < m_rgSinks.size(); i++ )
    {
        if( m_rgSinks[i] )
        {
            ((CtPhoneSink*)m_rgSinks[i])->OnPhoneClose(this);
        }
    }
}

void CtPhone::OnDevSpecific(
    DWORD   dwDevice,
    DWORD   dwParam1,
    DWORD   dwParam2,
    DWORD   dwParam3)
{
    for( int i = 0; i < m_rgSinks.size(); i++ )
    {
        if( m_rgSinks[i] )
        {
            ((CtPhoneSink*)m_rgSinks[i])->OnPhoneDevSpecific(this, dwDevice, dwParam1, dwParam2, dwParam3);
        }
    }
}

void CtPhone::OnReply(
    TREQUEST    nRequestID,
    TRESULT     nResult,
    DWORD       nRequestType)
{
#ifdef _DEBUG
    static LPCSTR   aszReplyName[] = {  "PHONEREQUEST_UNKNOWN", };
    assert(nRequestType <= DIM(aszReplyName));
    ::TfxTapiReply(nRequestID, nResult, aszReplyName[nRequestType]);
#endif

    for( int i = 0; i < m_rgSinks.size(); i++ )
    {
        if( m_rgSinks[i] )
        {
            ((CtPhoneSink*)m_rgSinks[i])->OnPhoneReply(this, nRequestID, nResult, nRequestType);
        }
    }
}

void CtPhone::OnState(
    DWORD   dwPhoneStates,
    DWORD   dwPhoneStateDetails)
{
    for( int i = 0; i < m_rgSinks.size(); i++ )
    {
        if( m_rgSinks[i] )
        {
            ((CtPhoneSink*)m_rgSinks[i])->OnPhoneState(this, dwPhoneStates, dwPhoneStateDetails);
        }
    }
}

#if (TAPI_CURRENT_VERSION >= 0x00020000)

void CtPhone::OnRemove(
    DWORD dwDeviceID)
{
    for( int i = 0; i < m_rgSinks.size(); i++ )
    {
        if( m_rgSinks[i] )
        {
            ((CtPhoneSink*)m_rgSinks[i])->OnPhoneRemove(this, dwDeviceID);
        }
    }
}

#endif

void CtPhone::AddRequest(
    TREQUEST        nRequestID,
    CtReplyTarget*  pTarget,
    DWORD           dwRequestType)
{
    m_listRequests.AddRequest(nRequestID, pTarget, dwRequestType);
}

void CtPhone::RemoveAllRequests(
    CtReplyTarget*  pTarget)
{
    m_listRequests.RemoveAllRequests(pTarget);
}
