/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY                          *
 *                                                                            *
 * This program is free software; you can redistribute it and/or modify       *
 * it under the terms of the GNU General Public Liense as published by        *
 * the Free Software Foundation, either version 2 of the License, or (at      * 
 * your option) any later version.                                            *
 *                                                                            *
 * The ITX package is distributed in the hope that it will be useful, but     *
 * WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY *
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License   *
 * for more details.                                                          * 
 *                                                                            *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.                                                   *
 *                                                                            * 
 * Contact information:                                                       *
 * Donna Bergmark                                                             *
 * 484 Rhodes Hall                                                            *
 * Cornell University                                                         *
 * Ithaca, NY 14853-3801                                                      *
 * bergmark@cs.cornell.edu                                                    *
 ******************************************************************************/


// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "itxcemfc.h"

#include "MainFrm.h"
#include "DatagramSocket.h"

#include "ConnDialog.h"
#include "ceprotocolconstants.h"

#include "CallDialog.h"

#include <malloc.h>
#include <math.h>

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

#define BUFFERSIZE 2000
#define NUMBUFFERS 3
#define INBUFFERSIZE 8192//256
#define NUMINBUFFERS 2

const DWORD dwAdornmentFlags = 0; // exit button

/////////////////////////////////////////////////////////////////////////////
// CMainFrame

IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
	//{{AFX_MSG_MAP(CMainFrame)
	ON_WM_CREATE()
	ON_WM_SETFOCUS()
	ON_MESSAGE(MM_WOM_OPEN, OnWaveOutOpen)
	ON_MESSAGE(MM_WOM_CLOSE, OnWaveOutClose)
	ON_MESSAGE(MM_WOM_DONE, OnWaveOutDone)
	ON_MESSAGE(MM_WIM_OPEN, OnWaveInOpen)
	ON_MESSAGE(MM_WIM_DATA, OnWaveInData)
	ON_MESSAGE(MM_WIM_CLOSE, OnWaveInClose)
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_COMMAND(ID_DISCONNECT, OnDisconnect)
	ON_COMMAND(ID_CONNECT, OnConnect)
	ON_UPDATE_COMMAND_UI(ID_CONNECT, OnUpdateConnect)
	ON_UPDATE_COMMAND_UI(ID_DISCONNECT, OnUpdateDisconnect)
	ON_COMMAND(ID_PHONE_CALL, OnPhoneCall)
	ON_COMMAND(ID_PHONE_HANGUP, OnPhoneHangup)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

static UINT indicators[] =
{
	ID_SEPARATOR,           // status line indicator
	ID_INDICATOR_CAPS
};

/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
	// TODO: add member initialization code here
	m_bPlaying = false;
	m_bShutoff = false;
	m_bRecording = false;
	m_bBIn1Free = true;
	m_bBIn2Free = true;
	m_bWaveOutOpened = false;
	m_nNumBytesReceived = 0;
	m_nNumBytesDropped = 0;
	m_nBufferNum = 0;
	m_nNumPlaying = 0;
	
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
		return -1;
	// create a view to occupy the client area of the frame
	if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
		CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL))
	{
		TRACE0("Failed to create view window\n");
		return -1;
	}
	
	if(!m_wndCommandBar.Create(this) ||
	   !m_wndCommandBar.InsertMenuBar(IDR_MAINFRAME) ||
	   !m_wndCommandBar.AddAdornments(dwAdornmentFlags))
	{
		TRACE0("Failed to create CommandBar\n");
		return -1;      // fail to create
	}

	m_wndCommandBar.SetBarStyle(m_wndCommandBar.GetBarStyle() |
		CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED);

	if (!m_wndStatusBar.Create(this) ||
		!m_wndStatusBar.SetIndicators(indicators,
		  sizeof(indicators)/sizeof(UINT)))
	{
		TRACE0("Failed to create status bar\n");
		return -1;      // fail to create
	}

	if(!(m_pDataSock = new CDatagramSocket(this))) {
		TRACE0("Cannot Instantiate Data Socket\n");
		return -1;
	}
	if(!m_pDataSock->Create()) {
		TRACE0("Cannot Create Data Socket\n");
		return -1;
	}

	WAVEFORMATEX waveform;
	
	waveform.wFormatTag = WAVE_FORMAT_PCM;
	waveform.nChannels = 1;
	waveform.nSamplesPerSec = 8000;
	waveform.nAvgBytesPerSec = 8000;
	waveform.nBlockAlign = 1;
	waveform.wBitsPerSample = 8;
	waveform.cbSize = 0;
	
	// open waveaudio for input
	if(MMRESULT mmr = waveInOpen(&m_hWaveIn, WAVE_MAPPER, &waveform, (DWORD) m_hWnd, 0, CALLBACK_WINDOW)) {
		TRACE0("Cannot open wavein device\n");
		return -1;
	}

	waveform.wFormatTag = WAVE_FORMAT_PCM;
	waveform.nChannels = 1;
	waveform.nSamplesPerSec = 8000;
	waveform.nAvgBytesPerSec = 8000;
	waveform.nBlockAlign = 1;
	waveform.wBitsPerSample = 8;
	waveform.cbSize = 0;
	
	// open waveaudio for output
	if(MMRESULT mmr = waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &waveform, (DWORD) m_hWnd, 0, CALLBACK_WINDOW)) {
		TRACE0("Cannot open waveout device\n");
		return -1;
	}

	// initialize output sound buffers
	OutBuffers = new SOUNDBUFFER[NUMBUFFERS];

	if(!OutBuffers) {
		TRACE0("Memory error while trying to allocate OutBuffers\n");
		return -1;
	}

	for(int i = 0; i<NUMBUFFERS; i++) {
		OutBuffers[i].buffer = new BYTE[BUFFERSIZE];
		OutBuffers[i].length = BUFFERSIZE;
		OutBuffers[i].used = false;
		OutBuffers[i].index = 0;
		OutBuffers[i].wavehdr = new WAVEHDR;
		OutBuffers[i].wavehdr->lpData = (char*)OutBuffers[i].buffer;
		OutBuffers[i].wavehdr->dwBufferLength = BUFFERSIZE;
		OutBuffers[i].wavehdr->dwBytesRecorded = 0;
		OutBuffers[i].wavehdr->dwUser = i;
		OutBuffers[i].wavehdr->dwFlags = 0;
		OutBuffers[i].wavehdr->dwLoops = 1;
		OutBuffers[i].wavehdr->lpNext = NULL;
		OutBuffers[i].wavehdr->reserved = 0;
		waveOutPrepareHeader(m_hWaveOut, OutBuffers[i].wavehdr, sizeof(WAVEHDR));
	}
	
	// initialize input sound buffers
	InBuffers = new SOUNDBUFFER[NUMINBUFFERS];

	if(!InBuffers) {
		TRACE0("Memory error while trying to allocate InBuffers\n");
		return -1;
	}

	for(i = 0; i<NUMINBUFFERS; i++) {
		InBuffers[i].buffer = new BYTE[INBUFFERSIZE/];
		InBuffers[i].length = INBUFFERSIZE;
		InBuffers[i].wavehdr = new WAVEHDR;
		InBuffers[i].wavehdr->lpData = (char*)InBuffers[i].buffer;
		InBuffers[i].wavehdr->dwBufferLength = INBUFFERSIZE;
		InBuffers[i].wavehdr->dwBytesRecorded = 0;
		InBuffers[i].wavehdr->dwUser = i;
		InBuffers[i].wavehdr->dwFlags = 0;
		InBuffers[i].wavehdr->dwLoops = 1;
		InBuffers[i].wavehdr->lpNext = NULL;
		InBuffers[i].wavehdr->reserved = 0;
		waveInPrepareHeader(m_hWaveIn, InBuffers[i].wavehdr, sizeof(WAVEHDR));
	}

	// start the thread to send recorded data
	CWinThread* m_pSenderThread = AfxBeginThread(SenderThread, (LPVOID)this);
	if(!m_pSenderThread) return FALSE;

	return 0;
}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )
		return FALSE;
	// TODO: Modify the Window class or styles here by modifying
	//  the CREATESTRUCT cs


	cs.lpszClass = AfxRegisterWndClass(0);
	return TRUE;
}


/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics

#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
	CFrameWnd::AssertValid();
}

void CMainFrame::Dump(CDumpContext& dc) const
{
	CFrameWnd::Dump(dc);
}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers
void CMainFrame::OnSetFocus(CWnd* pOldWnd)
{
	// forward focus to the view window
//	m_wndView.SetFocus();
}

BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
	// let the view have first crack at the command
	if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
		return TRUE;

	// otherwise, do default handling
	return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

// fill the buffers on opening the wave out
LRESULT CMainFrame::OnWaveOutOpen(WPARAM wParam, LPARAM lParam) {
	TRACE0("In Handle WaveOutOpen Message\n");
	for(int i = 0; i<NUMBUFFERS; i++) {
		OutBuffers[i].used = TRUE;
		waveOutWrite(m_hWaveOut, OutBuffers[i].wavehdr, sizeof(WAVEHDR));
	}
	m_nNumPlaying = NUMBUFFERS;
	m_nBufferNum = -1;
	m_bWaveOutOpened = true;
	m_bPlaying = true;
	m_bRecording = false;
	return TRUE;
}


LRESULT CMainFrame::OnWaveOutDone(WPARAM wParam, LPARAM lParam) {
//	TRACE0("In WaveOutDone\n");
	TRACE1("Number of Buffers playing = %d\n", m_nNumPlaying);
	m_nNumPlaying--;
	PWAVEHDR tWave = ((PWAVEHDR)lParam);
	OutBuffers[tWave->dwUser].index = 0;
	OutBuffers[tWave->dwUser].used = false;
	if(m_nBufferNum == -1) m_nBufferNum = tWave->dwUser;
	if(m_bShutoff) {
		waveOutClose(m_hWaveOut);
		return TRUE;
	}
//	if(tWave->dwBytesRecorded != 400)
//		TRACE2("Buffer %d played %d bytes\n", tWave->dwUser, tWave->dwBufferLength);
	for(int i = 0; i<NUMBUFFERS; i++) {
		if(OutBuffers[i].used == TRUE) TRACE0("* ");
		else TRACE0("O ");
	}
	TRACE0("\n");
	return TRUE;
}

LRESULT CMainFrame::OnWaveOutClose(WPARAM wParam, LPARAM lParam) {
	TRACE0("In Handle WaveOutClose Message\n");
	waveInUnprepareHeader(m_hWaveIn, m_pWaveHdrIn1, sizeof(WAVEHDR));
	waveInUnprepareHeader(m_hWaveIn, m_pWaveHdrIn2, sizeof(WAVEHDR));
	
	m_bRecording = false;
	m_bPlaying = false;
	m_bShutoff = false;

	return TRUE;
}

LRESULT CMainFrame::OnWaveInOpen(WPARAM wParam, LPARAM lParam) {
	TRACE0("In Handle WaveInOpen Message\n");
	return TRUE;
}

LRESULT CMainFrame::OnWaveInData(WPARAM wParam, LPARAM lParam) {
	if(m_bShutoff) {
		waveInClose(m_hWaveIn);
		return TRUE;
	}
	PWAVEHDR tWave = (PWAVEHDR)lParam;
	TRACE1("In Handle WaveInData Message - %d\n", tWave->dwUser);
	if(m_bRecording) {
		InBuffers[tWave->dwUser].used = true;
	}
	return TRUE;
}

LRESULT CMainFrame::OnWaveInClose(WPARAM wParam, LPARAM lParam) {
	TRACE0("In Handle WaveInClose Message\n");
	for(int i=0; i<NUMBUFFERS; i++) {
		waveOutUnprepareHeader(m_hWaveOut, OutBuffers[i].wavehdr, sizeof(WAVEHDR));
	}
	return TRUE;
}

BOOL CMainFrame::DestroyWindow() 
{
	m_bShutoff = true;
	waveOutReset(m_hWaveOut);
	waveOutClose(m_hWaveOut);
	waveInReset(m_hWaveIn);
	waveInClose(m_hWaveIn);
	if(m_pSignalSocket != NULL)
		OnDisconnect();
	delete m_pDataSock;
	return CFrameWnd::DestroyWindow();
}

// the sound device is half-duplex, so we record when the CANCEL key
// is held down
void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if(nChar == VK_F24) {
		SetMessageText(TEXT("Speak"));
		// switch from play to record
		m_bPlaying = false;
		m_bRecording = true;
		waveOutReset(m_hWaveOut);
		waveOutPause(m_hWaveOut);
		for(int i = 0; i<NUMINBUFFERS; i++) {
			waveInAddBuffer(m_hWaveIn, InBuffers[i].wavehdr, sizeof(WAVEHDR));
		}
//		m_pWaveFile = CreateFile(TEXT("message.rad"), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
		waveInStart(m_hWaveIn);
	return;
	}
	
	CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}


void CMainFrame::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if(nChar == VK_F24) {
		SetMessageText(TEXT("Listen"));
		// switch from record to play
		waveInStop(m_hWaveIn);
		waveInReset(m_hWaveIn);
//		CloseHandle(m_pWaveFile);
		waveOutRestart(m_hWaveOut);
		for(int i = 0; i<NUMBUFFERS; i++) {
			OutBuffers[i].used = TRUE;
			waveOutWrite(m_hWaveOut, OutBuffers[i].wavehdr, sizeof(WAVEHDR));
		}
		m_nBufferNum = -1;
		m_nNumPlaying = NUMBUFFERS;
		m_bRecording = false;
		m_bPlaying = true;
		return;
	}
	CFrameWnd::OnKeyUp(nChar, nRepCnt, nFlags);
}


// called by CDatagramSocket when a datagram packet is received
void CMainFrame::ReceivedData()
{
	if(!m_bWaveOutOpened) return;
//	TRACE1("m_nBufferNum = %d\n", m_nBufferNum);
	int numRecvd = 0;
	while(1) {
		// if we're playing and there's enough buffers to play to, keep receiving until there no more data
		if(m_bPlaying &&(m_nBufferNum != -1)) {
			numRecvd = recv(m_pDataSock->m_socket, (char*)OutBuffers[m_nBufferNum].buffer+OutBuffers[m_nBufferNum].index, OutBuffers[m_nBufferNum].length-OutBuffers[m_nBufferNum].index, 0);
			if(numRecvd == -1) break;
			m_nNumBytesReceived++;
//			TRACE1("Packet size %d\n", numRecvd);
			OutBuffers[m_nBufferNum].index += numRecvd;
			// if the buffer is full enough, play it out
			if(((OutBuffers[m_nBufferNum].index + numRecvd)>OutBuffers[m_nBufferNum].length)) {
				m_nNumPlaying++;
				OutBuffers[m_nBufferNum].used = true;
				OutBuffers[m_nBufferNum].wavehdr->dwBufferLength = OutBuffers[m_nBufferNum].index;
				waveOutWrite(m_hWaveOut, OutBuffers[m_nBufferNum].wavehdr, sizeof(WAVEHDR));
				for(int i = 0; i<NUMBUFFERS; i++) {
					if(!OutBuffers[i].used) {
						m_nBufferNum = i;
						return;
					}
				}
				m_nBufferNum = -1;
			}
		} else {
			break;
		}
	}
	m_nNumBytesDropped++;
	BYTE Buffer[1024];
	numRecvd = recv(m_pDataSock->m_socket, (char*)Buffer, 1024, 0);
//	TRACE2("Dropped %d out of %d packets\n", m_nNumBytesDropped, m_nNumBytesReceived);
}

// send a disconnect packet to the proxy
void CMainFrame::OnDisconnect() 
{
	struct COMMANDPACKET packet;
	packet.id = CEP_DISCONNECT;
	m_pSignalSocket->Send((char*)&packet, sizeof(COMMANDPACKET));
	m_pSignalSocket->Close();
	delete m_pSignalSocket;
	m_pSignalSocket = NULL;
}

// display the connect to proxy dialog
void CMainFrame::OnConnect() 
{
	USES_CONVERSION;

	CConnDialog dlg = new CConnDialog;
	if(dlg.DoModal() == IDOK) {
		struct COMMANDPACKET packet;
		m_pSignalSocket = new CCeDataSocket(this);
		if(!m_pSignalSocket) return;
		if(!m_pSignalSocket->Create(0)) {
			delete m_pSignalSocket;
			m_pSignalSocket = NULL;
			return;
		}
		if(!m_pSignalSocket->Connect(dlg.m_sIpAddress, SIGNALPROXYPORT)) {
			delete m_pSignalSocket;
			m_pSignalSocket = NULL;
			return;
		}
		packet.id = m_pDataSock->GetPort();
		packet.length1 = dlg.m_sName.GetLength();
		packet.length2 = dlg.m_sPassword.GetLength();
		memcpy(packet.data1, wce_T2CA(dlg.m_sName.GetBuffer(0)), packet.length1);
		memcpy(packet.data2, wce_T2CA(dlg.m_sPassword.GetBuffer(0)), packet.length2);
		m_pSignalSocket->Send((char*)&packet, sizeof(COMMANDPACKET));
	}
}

void CMainFrame::ReceivedCommand()
{
	// parses packets received from the proxy
	struct COMMANDPACKET packet;
	m_pSignalSocket->Receive(&packet, sizeof(COMMANDPACKET));
	switch(packet.id) {
	case CEP_SETPEERADDR:
		TRACE0("Got SETPEERADDR\n");
		packet.data1[packet.length1] = '\0';
		packet.data2[packet.length2] = '\0';
		m_pDataSock->SetPeerAddr(packet.data1, atoi(packet.data2));
		break;
	case CEP_INVITATION:
		TRACE0("Got INVITATION\n");
		packet.id = CEP_ACCEPT;
		m_pSignalSocket->Send((char*)&packet, sizeof(COMMANDPACKET));
		break;
	case CEP_HANGUP:
		TRACE0("Got HANGUP\n");
		break;
	default:
		TRACE0("Got Unknown\n");
		break;
	}
}

// this thread receives a handle to the main frame through pvParams
// it basically waits for the wave in device to advance (ie record enough data)
// when this is done it sends the sound samples over the network
// we play a little trick here, we don't wait until the record buffers are
// returned to us before we send them out, (since the record buffers are around
// 8k, this would translate to a second of delay)
unsigned int AFX_CDECL SenderThread( LPVOID pvParams )
{						  
	CMainFrame *frame = (CMainFrame*)pvParams;
	double filter[7] = {-0.0625, 0, 0.5625, 1.0, 0.5625, 0, -0.0625};
	int index = 0;
	int bufferIndex = 0;
	int lastPosition = 0;
	MMTIME mmtime;
	mmtime.wType = TIME_BYTES;
	while(1) {
		if(frame->m_bShutoff) break;
		Sleep(30);
		if(!frame->m_bRecording) {
			index = 0;
			bufferIndex = 0;
			lastPosition = 0;
			continue;
		}
		waveInGetPosition(frame->m_hWaveIn, &mmtime, sizeof(MMTIME));
		// get the position of the record device and see if it has advanced enough
		int currPosition = mmtime.u.cb;
		if((currPosition - lastPosition)>64) {
			while((lastPosition+64)<currPosition) {
				frame->m_pDataSock->Send((char*)frame->InBuffers[index].buffer+bufferIndex, 64);
				lastPosition += 64;
				bufferIndex += 64;
				if(bufferIndex >= INBUFFERSIZE) {
					bufferIndex = 0;
					while(!frame->InBuffers[index].used){
						Sleep(10);
					}
					frame->InBuffers[index].used = false;
					waveInAddBuffer(frame->m_hWaveIn, frame->InBuffers[index].wavehdr, sizeof(WAVEHDR));
					index = (index+1)%NUMINBUFFERS;
				}
			}
		}
	}
	return TRUE;
}


void CMainFrame::OnUpdateConnect(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_pSignalSocket==NULL);	
}

void CMainFrame::OnUpdateDisconnect(CCmdUI* pCmdUI) 
{
	pCmdUI->Enable(m_pSignalSocket!=NULL);	
}

void CMainFrame::OnPhoneCall() 
{
	// display the call dialog and make the call if necessary
	CCallDialog dlg = new CCallDialog;
	if(dlg.DoModal() == IDOK) {
		struct COMMANDPACKET packet;
		packet.id = CEP_INVITATION;
		packet.length1 = dlg.m_sCallee.GetLength();
		memcpy(packet.data1, wce_T2CA(dlg.m_sCallee.GetBuffer(0)), packet.length1);
		m_pSignalSocket->Send((char*)&packet, sizeof(COMMANDPACKET));
	}
}

void CMainFrame::OnPhoneHangup() 
{
	// send a hangup packet
	struct COMMANDPACKET packet;
	packet.id = CEP_HANGUP;
	m_pSignalSocket->Send((char*)&packet, sizeof(COMMANDPACKET));
}
