// tDialDlg.cpp : implementation file
//

#include "stdafx.h"
#include "tDial.h"
#include "tDialDlg.h"
#include "AboutDlg.h"
#include "OptionsDlg.h" // COptionsDlg
#include "PhoneNoDlg.h" // CtPhoneNoDlg
#include <stdarg.h>     // Variable number of arguments for LogStatus()
#include <ctype.h>      // isalpha() and toupper()

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define DIM(a) (sizeof(a)/sizeof(*a))

/////////////////////////////////////////////////////////////////////////////
// CtDialDlg dialog

CtDialDlg::CtDialDlg(CWnd* pParent /*=NULL*/)
:
    CDialog(CtDialDlg::IDD, pParent),
    m_bCallUnanswered(FALSE),
    m_rgbValidLines(0),
    m_rgpLines(0),
    m_pCall(0)
{
	//{{AFX_DATA_INIT(CtDialDlg)
	//}}AFX_DATA_INIT
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CtDialDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CtDialDlg)
	DDX_Control(pDX, IDC_ANSWER, m_btnAnswer);
	DDX_Control(pDX, IDC_HANG_UP, m_btnHangUp);
	DDX_Control(pDX, IDC_LOG, m_editLog);
	DDX_Control(pDX, IDC_DIAL, m_btnDial);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CtDialDlg, CDialog)
	//{{AFX_MSG_MAP(CtDialDlg)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_ANSWER, OnAnswer)
	ON_BN_CLICKED(IDC_DIAL, OnDial)
	ON_BN_CLICKED(IDC_HANG_UP, OnHangUp)
	ON_WM_DESTROY()
	ON_BN_CLICKED(IDC_ABOUT, OnAbout)
	ON_WM_SYSCOMMAND()
	ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CtDialDlg message handlers

BOOL CtDialDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// TODO: Add extra initialization here

    // Open valid lines
    OpenValidLines();

    // Update controls
    UpdateStatus();

    // DONE
    // Update request recipient status
    UpdateRequestRecipientStatus();

	return TRUE;  // return TRUE  unless you set the focus to a control
}

// DONE
void CtDialDlg::UpdateRequestRecipientStatus()
{
    DWORD   nPriority;
    if( TSUCCEEDED(::TfxGetAppPriority(0, &nPriority)) &&
        nPriority > 0 )
    {
        ::TfxRegisterRequestRecipient();
    }
    else
    {
        ::TfxUnregisterRequestRecipient();
    }
}

// DONE
void CtDialDlg::OnOptions() 
{
    COptionsDlg dlg;
    DWORD       nPriority = 0;

    ::TfxGetAppPriority(0, &nPriority);
    dlg.m_bRequestRecipient = (nPriority == 1 ? TRUE : FALSE);

    if( dlg.DoModal() == IDOK )
    {
        ::TfxSetAppPriority(0, dlg.m_bRequestRecipient ? 1 : 0);
        UpdateRequestRecipientStatus();
    }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CtDialDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

HCURSOR CtDialDlg::OnQueryDragIcon()
{
	return (HCURSOR)m_hIcon;
}

void CtDialDlg::OnSysCommand(UINT nID, LPARAM lParam) 
{
	if( (nID & 0xFFF0) == IDM_ABOUTBOX )
	{
		OnAbout();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

void CtDialDlg::OnAbout() 
{
	CAboutDlg().DoModal();
}

void CtDialDlg::OpenValidLines()
{
    ASSERT(m_rgbValidLines == 0);

    // Get and open valid lines
    DWORD   nLines = ::TfxGetNumLines();
    DWORD   nOpenedLines = 0;

    if( (m_rgbValidLines = new BOOL[nLines]) &&
        (m_rgpLines = new CtLine*[nLines]) )
    {
        // Assume we can't use any line
        ZeroMemory(m_rgbValidLines, nLines * sizeof(BOOL));
        ZeroMemory(m_rgpLines, nLines * sizeof(CtLine*));

        for( DWORD nLineID = 0; nLineID < nLines; nLineID++ )
        {
            CtLineDevCaps   ldc;
            if( TSUCCEEDED(ldc.GetDevCaps(nLineID)) &&
                (ldc.GetBearerModes() & LINEBEARERMODE_VOICE) &&
                (ldc.GetMediaModes() & LINEMEDIAMODE_INTERACTIVEVOICE) &&
                (ldc.GetLineFeatures() & LINEFEATURE_MAKECALL) )
            {
                if( (m_rgpLines[nLineID] = new CtLine) &&
                    TSUCCEEDED(m_rgpLines[nLineID]->Open(nLineID, this,
                                                         LINECALLPRIVILEGE_OWNER,
                                                         LINEMEDIAMODE_INTERACTIVEVOICE | LINEMEDIAMODE_UNKNOWN)) )
                {
                    m_rgbValidLines[nLineID] = TRUE;
                    nOpenedLines++;
                }
                else
                {
                    delete m_rgpLines[nLineID]; m_rgpLines[nLineID] = 0;
                    LogStatus("Unable to use line %d for answering voice calls.\r\n", nLineID);
                }
            }
            else
            {
                LogStatus("Line %d doesn't support voice calls.\r\n", nLineID);
            }
        }
    }

    if( !nOpenedLines )
    {
        delete[] m_rgbValidLines; m_rgbValidLines = 0;
        delete[] m_rgpLines; m_rgpLines = 0;

        LogStatus("Unabled to open any lines.\r\n");
    }
}

void CtDialDlg::OnDial() 
{
    // Only handling one call at a time
    ASSERT(!m_pCall);

    m_editLog.SetWindowText("");
    Dial();
}

void CtDialDlg::OnAnswer()
{
    ASSERT(m_pCall);

    // Can't answer a call we've already answered
    ASSERT(m_bCallUnanswered);

    m_bCallUnanswered = FALSE;
    if( TPENDING(m_pCall->Answer()) )
    {
        LogStatus("Answering call...\r\n");
    }
    else
    {
        LogStatus("Can't answer call.\r\n");
    }

    UpdateStatus();
}

void CtDialDlg::OnHangUp() 
{
    // Can't hang up a call w/o a handle 'cuz
    // there's no way to cancel a pending TAPI request.
    ASSERT(m_pCall && m_pCall->GetHandle());

    // Can't drop a call before its answered...<<TODO?>>
    ASSERT(!m_pCall->IsRequestTypePending(CALLREQUEST_ANSWER));

    if( TPENDING(m_pCall->Drop()) )
    {
        LogStatus("Call dropping...\r\n");
    }
    else
    {
        LogStatus("Can't drop call.\r\n");
    }

    UpdateStatus();
}

void CtDialDlg::UpdateStatus()
{
    // If we don't have a call, we can only dial
    if( !m_pCall )
    {
        m_btnDial.EnableWindow(TRUE);
        m_btnHangUp.EnableWindow(FALSE);
        m_btnAnswer.EnableWindow(FALSE);
    }
    // We can't do anything to the call while an operation is pending
    else if( m_pCall->IsRequestPending() )
    {
        m_btnDial.EnableWindow(FALSE);
        m_btnHangUp.EnableWindow(FALSE);
        m_btnAnswer.EnableWindow(FALSE);
    }
    // If there is a call,
    else
    {
        // we can't dial a new one
        m_btnDial.EnableWindow(FALSE);

        // we can hang it up if we've answered it (or placed it)
        m_btnHangUp.EnableWindow(!m_bCallUnanswered);

        // we can answer it if we haven't already and it's incoming
        m_btnAnswer.EnableWindow(m_bCallUnanswered);
    }
}

void CtDialDlg::LogStatus(LPCSTR pszFormat, ...)
{
    char    szOutput[256];
    va_list argList;

    va_start(argList, pszFormat);
    ::wvsprintf(szOutput, pszFormat, argList);
    va_end(argList);

    m_editLog.SetSel(0xffffffff);
    m_editLog.ReplaceSel(szOutput);
}

void CtDialDlg::OnDestroy() 
{
    if( m_rgbValidLines )
    {
        delete[] m_rgbValidLines;
        m_rgbValidLines = 0;
    }

    if( m_pCall )
    {
        m_pCall->Drop();
        delete m_pCall;
        m_pCall = 0;
    }

    if( m_rgpLines )
    {
        DWORD   nLines = ::TfxGetNumLines();
        for( DWORD nLineID = 0; nLineID < nLines; nLineID++ )
        {
            if( m_rgpLines[nLineID] && m_rgpLines[nLineID]->GetHandle() )
            {
                m_rgpLines[nLineID]->Close();
                delete m_rgpLines[nLineID];
            }
        }

        delete[] m_rgpLines;
        m_rgpLines = 0;
    }

	CDialog::OnDestroy();
}

// Telephony handlers
void CtDialDlg::Dial(
    LPCSTR          pszPhoneNo, // = 0
    LINECALLPARAMS* pCallParams)// = 0
{
    ASSERT(m_rgpLines);
    ASSERT(!m_pCall);

    // Show the dialog
    CPhoneNoDlg dlg;
    dlg.m_pno.SetWholePhoneNo(pszPhoneNo);
    dlg.m_rgbShouldShowLines = m_rgbValidLines;

    if( dlg.DoModal() == IDOK )
    {
        // DONE: Use the dialog's address
        if( pCallParams )
        {
            pCallParams->dwAddressMode = LINEADDRESSMODE_ADDRESSID;
            pCallParams->dwAddressID = dlg.m_nAddressID;
        }

        if( (m_pCall = new CtCall(m_rgpLines[dlg.m_nLineID])) &&
            TPENDING(m_pCall->MakeCall(dlg.m_sDialable,
                                       dlg.m_pno.GetCountryCodeNum(),
                                       this, pCallParams)) )
        {
            LogStatus("Placing a call to '%s'...\r\n", dlg.m_pno.GetDisplayable());
            UpdateStatus();
        }
        else
        {
            delete m_pCall; m_pCall = 0;
            ::AfxMessageBox(IDS_CANT_MAKE_CALL);
        }
    }
}

void CtDialDlg::OnLineNewCall(
    CtLine* pLine,
    HCALL   hCall,
    DWORD   nAddressID,
    DWORD   nCallPriviledge)
{
    LogStatus("New call on line %d (address %d)\r\n", pLine->GetDeviceID(), nAddressID);

    // If we're not already handling a call,
    if( !m_pCall )
    {
        // we've got a new call
        m_pCall = new CtCall(pLine, hCall, this);

        // Check to see if call needs to be answered
        m_bCallUnanswered = FALSE;
        if( m_pCall )
        {
            CtCallStatus    cs;
            if( TSUCCEEDED(cs.GetCallStatus(m_pCall)) )
            {
                if( cs.GetCallFeatures() & LINECALLFEATURE_ANSWER )
                {
                    m_bCallUnanswered = TRUE;
                }
            }
        }

        UpdateStatus();
    }
}

void CtDialDlg::OnCallState(
    CtCall* pCall,
    DWORD   nCallState,
    DWORD   dwParam2,
    DWORD   nCallPriviledge)
{
    struct FlagMap { DWORD nFlag; LPCSTR pszFlag; };
    static FlagMap rgFlags[] =
    {
        {LINECALLSTATE_IDLE,        "idle"},
        {LINECALLSTATE_ACCEPTED,    "accepted"},
        {LINECALLSTATE_DIALTONE,    "dial tone detected"},
        {LINECALLSTATE_DIALING,     "dialing"},
        {LINECALLSTATE_RINGBACK,    "ring-back detected"},
        {LINECALLSTATE_BUSY,        "busy detected"},
        {LINECALLSTATE_SPECIALINFO, "error detected"},
        {LINECALLSTATE_CONNECTED,   "connected"},
        {LINECALLSTATE_PROCEEDING,  "proceeding"},
        {LINECALLSTATE_DISCONNECTED,"disconnected"},
    };

    for( int i = 0; i < DIM(rgFlags); i++ )
    {
        if( rgFlags[i].nFlag == nCallState )
        {
            LogStatus("Call %s.\r\n", rgFlags[i].pszFlag);
            break;
        }
    }

    switch( nCallState )
    {
    case LINECALLSTATE_DISCONNECTED:
        OnHangUp();
    break;

    case LINECALLSTATE_IDLE:
        delete m_pCall; m_pCall = 0;
    break;
    }

    UpdateStatus();
}

void CtDialDlg::OnCallReply(
    CtCall*     pCall,
    TREQUEST    nRequestID,
    TRESULT     nResult,
    DWORD       nRequestType)
{
    switch( nRequestType )
    {
    case CALLREQUEST_MAKECALL:
        if( TSUCCEEDED(nResult) )
        {
            LogStatus("Call placed.\r\n");
        }
        else
        {
            delete m_pCall; m_pCall = 0;
            LogStatus("Call cannot be placed.\r\n");
        }
    break;

    case CALLREQUEST_DROP:
        if( TSUCCEEDED(nResult) )
        {
            LogStatus("Call dropped.\r\n");
        }
        else
        {
            LogStatus("Call cannot be dropped.\r\n");
        }
    break;

    case CALLREQUEST_ANSWER:
        if( TSUCCEEDED(nResult) )
        {
            LogStatus("Call answered.\r\n");
        }
        else
        {
            LogStatus("Call cannot be answered.\r\n");
        }
    break;
    }

    UpdateStatus();
}

void CtDialDlg::OnLineClose(CtLine* pLine)
{
    LogStatus("Line %d closed.\r\n", pLine->GetDeviceID());

    if( m_pCall )
    {
        delete m_pCall; m_pCall = 0;
    }

    DWORD   nLineID = pLine->GetDeviceID();
    delete m_rgpLines[nLineID];
    m_rgpLines[nLineID] = 0;
}

// DONE
// Caller responsible for setting flags (if they are other than zero)
// and calling delete[] on result when buffer is no longer needed.
LINECALLPARAMS* AllocateCallParams(
    LPCSTR pszAddress = 0,
    LPCSTR pszCalledParty = 0,
    LPCSTR pszComment = 0)
{
    // Calculate LINECALLPARAMS sizes
    size_t  cbAddress     = (pszAddress && *pszAddress ? strlen(pszAddress) + 1 : 0);
    size_t  cbCalledParty = (pszCalledParty && *pszCalledParty ? strlen(pszCalledParty) + 1 : 0);
    size_t  cbComment     = (pszComment && *pszComment ? strlen(pszComment) + 1 : 0);
    size_t  cbCallParams  = sizeof(LINECALLPARAMS) + cbAddress + cbCalledParty + cbComment;

    // Allocate LINECALLPARAMS structure
    LINECALLPARAMS* pCallParams = (LINECALLPARAMS*)(new BYTE[cbCallParams]);
    if( pCallParams )
    {
        ZeroMemory(pCallParams, cbCallParams);
        pCallParams->dwTotalSize = cbCallParams;

        // Fill in a LINECALLPARAMS structure

        // pszAddress
        pCallParams->dwDisplayableAddressSize = cbAddress;
        if( cbAddress )
        {
            pCallParams->dwDisplayableAddressOffset = sizeof(LINECALLPARAMS);
            char*   psz = (char*)((BYTE*)pCallParams + pCallParams->dwDisplayableAddressOffset);
            strcpy(psz, pszAddress);
        }
        else
        {
            pCallParams->dwDisplayableAddressOffset = 0;
        }

        // pszCalledParty
        pCallParams->dwCalledPartySize = cbCalledParty;
        if( cbCalledParty )
        {
            pCallParams->dwCalledPartyOffset = sizeof(LINECALLPARAMS) + cbAddress;
            char*   psz = (char*)((BYTE*)pCallParams + pCallParams->dwCalledPartyOffset);
            strcpy(psz, pszCalledParty);
        }
        else
        {
            pCallParams->dwCalledPartyOffset = 0;
        }

        // pszComment
        pCallParams->dwCommentSize = cbComment;
        if( cbComment )
        {
            pCallParams->dwCommentOffset = sizeof(LINECALLPARAMS) + cbAddress + cbCalledParty;
            char*   psz = (char*)((BYTE*)pCallParams + pCallParams->dwCommentOffset);
            strcpy(psz, pszComment);
        }
        else
        {
            pCallParams->dwCommentOffset = 0;
        }
    }

    return pCallParams;
}

// DONE
void CtDialDlg::OnLineRequest(DWORD nRequestMode, HWND hRequestWnd, TREQUEST nRequestID)
{
    ASSERT(nRequestMode == LINEREQUESTMODE_MAKECALL);

    LINEREQMAKECALL lmc;
    if( TSUCCEEDED(::TfxGetMakeCallRequest(&lmc)) )
    {
        UpdateData(FALSE);

        m_editLog.SetWindowText("");
        LogStatus("Request to call");
        if( lmc.szCalledParty[0] )
        {
            LogStatus(" '%s'", lmc.szCalledParty);
        }

        if( lmc.szAppName[0] )
        {
            LogStatus(" by '%s'", lmc.szAppName);
        }

        if( lmc.szComment[0] )
        {
            LogStatus(": '%s'", lmc.szComment);
        }

        LogStatus("...\r\n");
        UpdateStatus();

        // TAPI starts us minimized
        ShowWindow(SW_RESTORE);
        SetForegroundWindow();

        // Fill in a LINECALLPARAMS structure
        LINECALLPARAMS* pCallParams = AllocateCallParams(lmc.szDestAddress, lmc.szCalledParty, lmc.szComment);
        if( pCallParams )
        {
            pCallParams->dwBearerMode = LINEBEARERMODE_VOICE;
            pCallParams->dwMediaMode = LINEMEDIAMODE_INTERACTIVEVOICE;
        }

        Dial(lmc.szDestAddress, pCallParams);
        delete[] pCallParams;
    }
}

