// tDaemonDlg.cpp : implementation file
//

#include "stdafx.h"
#include "tDaemon.h"
#include "tDaemonDlg.h"

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

/////////////////////////////////////////////////////////////////////////////
// CtDaemonDlg dialog

CtDaemonDlg::CtDaemonDlg(CWnd* pParent /*=NULL*/)
:   CDialog(CtDaemonDlg::IDD, pParent),
    m_call(&m_line)
{
	//{{AFX_DATA_INIT(CtDaemonDlg)
	m_nAreaEnd = 0;
	m_nAreaStart = 0;
	m_nCountryEnd = 0;
	m_nCountryStart = 0;
	m_nPhoneNoEnd = 0;
	m_nPhoneNoStart = 0;
	//}}AFX_DATA_INIT
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CtDaemonDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CtDaemonDlg)
	DDX_Control(pDX, IDC_LOG, m_editLog);
	DDX_Control(pDX, IDC_STOP, m_btnStop);
	DDX_Control(pDX, IDC_START, m_btnStart);
	DDX_Text(pDX, IDC_AREA_END, m_nAreaEnd);
	DDX_Text(pDX, IDC_AREA_START, m_nAreaStart);
	DDX_Text(pDX, IDC_COUNTRY_END, m_nCountryEnd);
	DDX_Text(pDX, IDC_COUNTRY_START, m_nCountryStart);
	DDX_Text(pDX, IDC_NO_END, m_nPhoneNoEnd);
	DDX_Text(pDX, IDC_NO_START, m_nPhoneNoStart);
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CtDaemonDlg, CDialog)
	//{{AFX_MSG_MAP(CtDaemonDlg)
	ON_WM_SYSCOMMAND()
	ON_BN_CLICKED(IDC_START, OnStart)
	ON_BN_CLICKED(IDC_STOP, OnStop)
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CtDaemonDlg message handlers

bool CtDaemonDlg::OpenValidLine()
{
    DWORD   nLines = ::TfxGetNumLines();
    for( DWORD nLineID = 0; nLineID < nLines; nLineID++ )
    {
        CtLineDevCaps   ldc;
        if( TSUCCEEDED(ldc.GetDevCaps(nLineID)) &&
            (ldc.GetBearerModes() & LINEBEARERMODE_VOICE) &&
            (ldc.GetMediaModes() & LINEMEDIAMODE_DATAMODEM) &&
            (ldc.GetLineFeatures() & LINEFEATURE_MAKECALL) &&
            TSUCCEEDED(m_line.Open(nLineID, this)) )    // Outbound calls only
        {
            return true;
        }
    }

    return false;
}

BOOL CtDaemonDlg::OnInitDialog()
{
    // Check for a suitable line and open it
    if( !OpenValidLine() )
    {
        AfxMessageBox(IDS_NO_MODEM);
        EndDialog(IDABORT);
        return FALSE;
    }

    // Initialize current country and area code
    CtPhoneNo   pno;
    pno.ResetToLocation();
    m_nCountryStart = m_nCountryEnd = pno.GetCountryCodeNum();
    m_nAreaStart = m_nAreaEnd = atol(pno.GetAreaCode());

    // Set suggested phone number range
    m_nPhoneNoStart = 1000000;
    m_nPhoneNoEnd = 9999999;

	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);
		}
	}

	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon
	
	// TODO: Add extra initialization here
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CtDaemonDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
        CDialog(IDD_ABOUT).DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

void CtDaemonDlg::OnStart() 
{
    m_btnStart.EnableWindow(FALSE);
    m_btnStop.EnableWindow(TRUE);

    if( UpdateData(TRUE) )
    {
        m_nCountry = m_nCountryStart;
        m_nArea = m_nAreaStart;
        m_nPhoneNo = m_nPhoneNoStart;

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

void CtDaemonDlg::OnStop() 
{
    if( m_call.GetHandle() )
    {
        m_nPhoneNo = m_nPhoneNoEnd;
        m_nArea = m_nAreaEnd;
        m_nCountry = m_nCountryEnd;
        if( TPENDING(m_call.Drop()) ) return;
    }

    m_btnStart.EnableWindow(TRUE);
    m_btnStop.EnableWindow(FALSE);
}

void CtDaemonDlg::DialNext()
{
    m_nPhoneNo++;
    if( m_nPhoneNo > m_nPhoneNoEnd )
    {
        m_nPhoneNo = m_nPhoneNoStart;
        m_nArea++;

        if( m_nArea > m_nAreaEnd )
        {
            m_nArea = m_nAreaStart;
            m_nCountry++;
            if( m_nCountry > m_nCountryEnd )
            {
                // We're done
                OnStop();
                return;
            }
        }
    }

    Dial();
}

// 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;
}

void CtDaemonDlg::Dial()
{
    CString     sCountry; sCountry.Format("%d", m_nCountry);
    CString     sArea;    sArea.Format("%d", m_nArea);
    CString     sPhoneNo; sPhoneNo.Format("%d", m_nPhoneNo);
    CtPhoneNo   pno(sCountry, sArea, sPhoneNo);

    CtTranslateOutput   to;
    TRESULT             tr;
    tr = to.TranslateAddress(m_line.GetDeviceID(),
                             pno.GetTranslatable(0),
                             0,
                             // Don't let incoming calls interrupt
                             LINETRANSLATEOPTION_CANCELCALLWAITING);
    if( TSUCCEEDED(tr) )
    {
        CString sDisplayable = to.GetDisplayableString();
        CString sDialable = to.GetDialableString();

        // Allocate a LINECALLPARAMS structure
        LINECALLPARAMS* pCallParams = AllocateCallParams(sDisplayable);
        if( pCallParams )
        {
            pCallParams->dwBearerMode = LINEBEARERMODE_VOICE;
            pCallParams->dwMediaMode = LINEMEDIAMODE_DATAMODEM;
        
            if( TPENDING(m_call.MakeCall(sDialable,
                                         pno.GetCountryCodeNum(),
                                         this, pCallParams)) )
            {
                const UINT  nTimeOut = 30000;   // 30 seconds
                m_nTimer = SetTimer(1, nTimeOut, 0);
                LogStatus("Placing a call to '%s'...\r\n", (LPCSTR)sDisplayable);
            }

            delete[] pCallParams;
        }
    }
}

void CtDaemonDlg::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);
}

// Telephony Events

void CtDaemonDlg::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_CONNECTED:
        OnConnected();
    break;

    case LINECALLSTATE_DISCONNECTED:
        m_call.Drop();
    break;

    case LINECALLSTATE_IDLE:
        m_call.Deallocate();
        DialNext();
    break;
    }
}

void CtDaemonDlg::ReadData(HANDLE hComm)
{
    // Temporarily boost thread priority
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

    // Cause read operation to return immediately with
    // the characters that have already been received,
    // even if no characters have been received"
    COMMTIMEOUTS    cto = { 0 };
    cto.ReadIntervalTimeout = MAXDWORD;
    SetCommTimeouts(hComm, &cto);

    const DWORD nTicks = 4000;  // 4 seconds
    const DWORD nTickStart = GetTickCount();
    OVERLAPPED  ol = { 0 };

    while( GetTickCount() - nTickStart < nTicks )
    {
        char    sz[1024];
        DWORD   nBytes;
        if( ReadFile(hComm, sz, sizeof(sz)-1, &nBytes, &ol) && nBytes )
        {
            sz[nBytes] = 0;
            LogStatus(sz);
        }
    }

    // Clear pending bytes and reset thread priority
    PurgeComm(hComm, PURGE_RXCLEAR);
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
    LogStatus("\r\n");
}

void CtDaemonDlg::OnConnected()
{
    LogStatus("Got one! Output follows:\r\n");
    
    // Kill the timer
    KillTimer(m_nTimer); m_nTimer = 0;
    
    // Read data
    CtDeviceID  did;
    if( TSUCCEEDED(did.GetIDFromCall("comm/datamodem", m_call.GetHandle())) )
    {
        HANDLE  hComm;
        did.GetHandleAndString(&hComm);

        if( hComm )
        {
            ReadData(hComm);
            CloseHandle(hComm);
        }
    }

    // Drop the call (and dial the next one)
    m_call.Drop();
}


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

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

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

void CtDaemonDlg::OnTimer(UINT nIDEvent) 
{
    // Drop the call
    if( m_call.GetHandle() )
    {
        LogStatus("Connection timed out.\r\n");
        m_call.Drop();
    }
}

// TODO: Remove
void ReadData2(HANDLE hComm)
{
#if 0
            // Temporarily boost thread priority
            SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

            const DWORD nTicks = 4000;  // 4 seconds
            const DWORD nTickStart = GetTickCount();
            OVERLAPPED  ol = { 0, 0, 0, 0, CreateEvent(0, TRUE, FALSE, 0) };
            //OVERLAPPED  ol = { 0 };
            int n = 0; // TODO: Remove

            char    sz[1024];
            DWORD   nBytes;
            if( ReadFile(hComm, sz, sizeof(sz)-1, &nBytes, &ol) )
            {
                sz[nBytes] = 0;
                LogStatus(sz);
            }
            else if( GetLastError() == ERROR_IO_PENDING )
            {
                if( WaitForSingleObject(ol.hEvent, nTicks) == WAIT_OBJECT_0 &&
                    GetOverlappedResult(hComm, &ol, &nBytes, FALSE) )
                {
                    sz[nBytes] = 0;
                    LogStatus(sz);
                }
            }
#endif
#if 0
            while( GetTickCount() - nTickStart < nTicks )
            {
                TRACE("loops: %d\n", ++n);
                /*
                // Read one byte
                char    ch;
                DWORD   nBytes = 0;
                if( (!ReadFile(hComm, &ch, 1, &nBytes, &ol) &&
                     (GetLastError() != ERROR_IO_PENDING)) ||
                     (!GetOverlappedResult(hComm, &ol, &nBytes, FALSE) &&
                      (GetLastError() != ERROR_IO_INCOMPLETE)) )
                {
                    break;
                }

                if( nBytes )
                {
                    LogStatus("%c", ch);
                }
                */
                /*
                char    ch;
                DWORD   nBytes;

                // Read waiting bytes (synchronously)
                while( ReadFile(hComm, &ch, 1, &nBytes, &ol) )
                {
                    //LogStatus("sync: '%c'\n", ch);
                    TRACE("sync: '%c'\n", ch);
                }

                // Handling asynch. I/O
                if( GetLastError() == ERROR_IO_PENDING )
                {
                    // Wait for I/O to complete
                    DWORD nTimeToWait = GetTickCount() - nTickStart - nTicks;
                    //if( WaitForSingleObject(hComm, nTimeToWait) == WAIT_OBJECT_0 )
                    if( WaitForSingleObject(ol.hEvent, nTimeToWait) == WAIT_OBJECT_0 )
                    {
                        //LogStatus("async: '%c'\n", ch);
                        TRACE("async: '%c'\n", ch);
                    }
                }
                // Stop reading in the event of an error
                else
                {
                    break;
                }
                */

            }

            SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
            LogStatus("\r\n");
            CloseHandle(ol.hEvent);
            CloseHandle(hComm);
#endif
}
