// VIBUpdateDlg.cpp : implementation file
//

#include "stdafx.h"
#include <io.h>
#include "VIBFinder.h"
#include "VIBUpdateDlg.h"


// VIBUpdateDlg dialog

IMPLEMENT_DYNAMIC(VIBUpdateDlg, CDialog)

VIBUpdateDlg::VIBUpdateDlg(CWnd* pParent, unsigned short port)
	: CDialog(VIBUpdateDlg::IDD, pParent)
{
	m_hLoadVbuThread = NULL;
	m_hUpdateThread = NULL;
	m_port = port;
	m_pLogBuffer = NULL;
	m_socket = INVALID_SOCKET;
}

VIBUpdateDlg::~VIBUpdateDlg()
{
	if (m_pLogBuffer)
		free(m_pLogBuffer);
	if (m_socket != INVALID_SOCKET)
		closesocket(m_socket);
}

void VIBUpdateDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}


/////////////////////////////////////////////////////////////////////////////
int VIBUpdateDlg::ChangeFile()
/////////////////////////////////////////////////////////////////////////////
{
	CFileDialog fileDlg(true, "vbu", "*.vbu", OFN_FILEMUSTEXIST|OFN_HIDEREADONLY, "VisionBox Firmware Update Files (*.vbu)", this);

	GetDlgItem(IDC_UPDATE_START)->EnableWindow(false);
	GetDlgItem(IDC_UPDATE_SAVELOG)->EnableWindow(false);
	if (fileDlg.DoModal() != IDOK)
	{
		GetDlgItem(IDC_UPDATE_FILE)->SetWindowTextA("Update File: ---");
		return -1;
	}
	m_pathName = fileDlg.GetPathName();
//	GetDlgItem(IDC_UPDATE_START)->EnableWindow(true);
	CString string = "Update File: '";
	string.Append(fileDlg.GetFileName());
	string.AppendChar('\'');
	GetDlgItem(IDC_UPDATE_FILE)->SetWindowTextA(string);

	m_hLoadVbuThread = CreateThread(NULL, 0, &LoadVbuThread, this, 0, NULL);
	if (m_hLoadVbuThread == NULL)
		AfxMessageBox("Failed to create update thread.", MB_OK | MB_ICONEXCLAMATION);

	return 0;
}


/////////////////////////////////////////////////////////////////////////////
BOOL VIBUpdateDlg::OnInitDialog()
/////////////////////////////////////////////////////////////////////////////
{
	HFONT fCourier;
	LOGFONT lf;

	CDialog::OnInitDialog();

	// Generate font
	memset(&lf, 0, sizeof(LOGFONT)); // zero out font structure
	lf.lfHeight = 14;
	lf.lfWeight = FW_NORMAL;
	strcpy_s(lf.lfFaceName, 32, "Courier New");
	fCourier = CreateFontIndirect(&lf);
	GetDlgItem(IDC_UPDATE_LOG)->SendMessage(WM_SETFONT, (WPARAM) fCourier, TRUE);

	ChangeFile();
	return TRUE;
}

BEGIN_MESSAGE_MAP(VIBUpdateDlg, CDialog)
	ON_BN_CLICKED(IDC_UPDATE_CHANGE, &VIBUpdateDlg::OnBnClickedUpdateChange)
	ON_BN_CLICKED(IDC_UPDATE_START, &VIBUpdateDlg::OnBnClickedUpdateStart)
	ON_MESSAGE(IDM_UPDATEFINISHED, &VIBUpdateDlg::OnUpdateFinished)
	ON_MESSAGE(IDM_LOGUPDATE, &VIBUpdateDlg::OnLogUpdate)
	ON_BN_CLICKED(IDC_UPDATE_SAVELOG, &VIBUpdateDlg::OnBnClickedUpdateSavelog)
END_MESSAGE_MAP()


// VIBUpdateDlg message handlers

/////////////////////////////////////////////////////////////////////////////
void VIBUpdateDlg::OnCancel()
/////////////////////////////////////////////////////////////////////////////
{
	if (m_hUpdateThread != NULL || m_hLoadVbuThread != NULL)
		AfxMessageBox("Update is in progress.", MB_OK | MB_ICONEXCLAMATION);
	else
		CDialog::OnCancel();
}

/////////////////////////////////////////////////////////////////////////////
void VIBUpdateDlg::OnBnClickedUpdateChange()
/////////////////////////////////////////////////////////////////////////////
{
	ChangeFile();
}

/////////////////////////////////////////////////////////////////////////////
void VIBUpdateDlg::OnBnClickedUpdateStart()
/////////////////////////////////////////////////////////////////////////////
{
	if (AfxMessageBox("Make sure the device doesn't get disconnected from the power supply.\n",
						MB_OKCANCEL | MB_ICONINFORMATION) != IDOK)
		return;

	m_hUpdateThread = CreateThread(NULL, 0, &UpdateThread, this, 0, NULL);
	if (m_hUpdateThread == NULL)
		AfxMessageBox("Failed to create update thread.", MB_OK | MB_ICONEXCLAMATION);
	else
	{
		GetDlgItem(IDC_UPDATE_CHANGE)->EnableWindow(false);
		GetDlgItem(IDC_UPDATE_START)->EnableWindow(false);
		GetDlgItem(IDOK)->EnableWindow(false);
		GetDlgItem(IDC_UPDATE_SAVELOG)->EnableWindow(false);
	}
}

/////////////////////////////////////////////////////////////////////////////
LRESULT VIBUpdateDlg::OnLogUpdate(WPARAM wParam, LPARAM lParam)
/////////////////////////////////////////////////////////////////////////////
{
	CEdit *pLogEdit = (CEdit *)GetDlgItem(IDC_UPDATE_LOG);
	// Set new text
	pLogEdit->SetWindowText(m_LogString);
	// Scroll log to bottom line
	pLogEdit->LineScroll(pLogEdit->GetLineCount());
	return 0;
}

/////////////////////////////////////////////////////////////////////////////
LRESULT VIBUpdateDlg::OnUpdateFinished(WPARAM wParam, LPARAM lParam)
/////////////////////////////////////////////////////////////////////////////
{
	if (lParam == 1)
	{	// Archive transmitted
		if (wParam == 0)
		{ // no error, allow start button
			m_LogString.Append("\r\nPress 'Start' to start the update.\r\n\r\n");
			OnLogUpdate(0, 0);
			GetDlgItem(IDC_UPDATE_START)->EnableWindow(true);
		}
		else
			GetDlgItem(IDC_UPDATE_CHANGE)->EnableWindow(true);
	}
	else if (lParam == 2)
	{	// Update finished
		GetDlgItem(IDC_UPDATE_CHANGE)->EnableWindow(true);
//		if (wParam != 0)
//			GetDlgItem(IDC_UPDATE_START)->EnableWindow(true);
		if (m_pLogBuffer)
			GetDlgItem(IDC_UPDATE_SAVELOG)->EnableWindow(true);
	}
	GetDlgItem(IDOK)->EnableWindow(true);
	return 0;
}

/////////////////////////////////////////////////////////////////////////////
DWORD WINAPI VIBUpdateDlg::LoadVbuThread(LPVOID lpParameter)
/////////////////////////////////////////////////////////////////////////////
{
	void *pFileBuffer = NULL;
	int len;
	FILE *pFile = NULL;
//	SOCKET sockTcp = NULL;
	struct sockaddr_in sa;
	unsigned int msg[2];
	int result = -1, deviceResult, recvResult;
	VIBUpdateDlg *pUpdateDlg = (VIBUpdateDlg *)lpParameter;

	if (pUpdateDlg->m_pLogBuffer)
		free(pUpdateDlg->m_pLogBuffer);
	pUpdateDlg->m_pLogBuffer = NULL;
	if (pUpdateDlg->m_socket != INVALID_SOCKET)
	{
		closesocket(pUpdateDlg->m_socket);
		pUpdateDlg->m_socket = INVALID_SOCKET;
	}

	do
	{
		pUpdateDlg->m_LogString = "Loading file '" + pUpdateDlg->m_pathName + "'...\r\n";
		::PostMessage(pUpdateDlg->m_hWnd, IDM_LOGUPDATE, 0, 0);
		if (fopen_s(&pFile, pUpdateDlg->m_pathName, "rb") != 0)
		{
			AfxMessageBox("Failed to open file.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}
		len = _filelength(_fileno(pFile));
		pFileBuffer = malloc(len);
		if (pFileBuffer == NULL)
		{
			AfxMessageBox("Out of memory.", MB_OK | MB_ICONEXCLAMATION);
			fclose(pFile);
			break;
		}
		if (fread(pFileBuffer, 1, len, pFile) != len)
		{
			AfxMessageBox("Error reading file.", MB_OK | MB_ICONEXCLAMATION);
			fclose(pFile);
			break;
		}

		// Create TCP socket
		pUpdateDlg->m_LogString.Append("Connecting to '" + pUpdateDlg->m_IP + "'...\r\n");
		::PostMessage(pUpdateDlg->m_hWnd, IDM_LOGUPDATE, 0, 0);
		pUpdateDlg->m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (pUpdateDlg->m_socket == INVALID_SOCKET)
		{
			AfxMessageBox("Failed to create socket.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}
		memset(&sa, 0, sizeof(sockaddr_in));
		sa.sin_family = AF_INET;
		sa.sin_addr.s_addr = inet_addr(pUpdateDlg->m_IP);
		sa.sin_port = htons(pUpdateDlg->m_port);
		if (connect(pUpdateDlg->m_socket, (SOCKADDR*)&sa, sizeof(sockaddr_in)) == SOCKET_ERROR)
		{
			AfxMessageBox("Failed to connect to device.\n"
				"Make sure the device is reachable in your network.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}

		// Send update archive
		pUpdateDlg->m_LogString.Append("Sending file...\r\n");
		::PostMessage(pUpdateDlg->m_hWnd, IDM_LOGUPDATE, 0, 0);
		msg[0] = 1;	// Command: Transmit Update Archive
		if (send(pUpdateDlg->m_socket, (char *)msg, 4, 0) != 4)
		{
			AfxMessageBox("Failed to send file.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}
		msg[0] = len;
		if (send(pUpdateDlg->m_socket, (char *)msg, 4, 0) != 4)
		{
			AfxMessageBox("Failed to send file.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}
		if (send(pUpdateDlg->m_socket, (char *)pFileBuffer, len, 0) != len)
		{
			AfxMessageBox("Failed to send file.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}

		// Wait for reply
		pUpdateDlg->m_LogString.Append("Receiving reply...\r\n\r\n");
		if (recv(pUpdateDlg->m_socket, (char *)msg, 8, 0) != 8)
		{
			AfxMessageBox("Error waiting for respnse.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}
		deviceResult = msg[0];
		len = msg[1];
		if (len > 0)
		{	// Receive reply text
			char *pLogBuffer = (char *)malloc(len);
			if (pLogBuffer == NULL)
			{
				AfxMessageBox("Out of memory.", MB_OK | MB_ICONEXCLAMATION);
				break;
			}
			for (int i=0; i<len; i+=recvResult)
			{
				recvResult = recv(pUpdateDlg->m_socket, pLogBuffer+i, len-i, 0);
				if (recvResult <= 0)
				{
					AfxMessageBox("Unable to receive reply message.", MB_OK | MB_ICONEXCLAMATION);
					break;
				}
			}
//			pUpdateDlg->m_pLogBuffer[len] = '\0';
//			pUpdateDlg->m_LogString.Append("\r\nLog file:\r\n");
			pUpdateDlg->m_LogString.Append(pLogBuffer);
			free(pLogBuffer);
			::PostMessage(pUpdateDlg->m_hWnd, IDM_LOGUPDATE, 0, 0);
		}
		if (deviceResult == 0)
		{
			result = 0;
		}
		else
		{
			AfxMessageBox("Error occured.", MB_OK | MB_ICONEXCLAMATION);
//			pUpdateDlg->m_LogString.Append("Error starting update.\r\n\r\n");
//			::PostMessage(pUpdateDlg->m_hWnd, IDM_LOGUPDATE, 0, 0);
		}
	} while(0);

	if (pFileBuffer)
		free(pFileBuffer);
	if (pFile)
		fclose(pFile);
	if (result < 0)
	{
		closesocket(pUpdateDlg->m_socket);
		pUpdateDlg->m_socket = INVALID_SOCKET;
	}
	::PostMessage(pUpdateDlg->m_hWnd, IDM_UPDATEFINISHED, result, 1);

	pUpdateDlg->m_hLoadVbuThread = NULL;

	return 0;
}

/////////////////////////////////////////////////////////////////////////////
DWORD WINAPI VIBUpdateDlg::UpdateThread(LPVOID lpParameter)
/////////////////////////////////////////////////////////////////////////////
{
	void *pFileBuffer = NULL;
	int len;
	unsigned int msg[2];
	int result = -1, deviceResult, recvResult;
	VIBUpdateDlg *pUpdateDlg = (VIBUpdateDlg *)lpParameter;

	if (pUpdateDlg->m_pLogBuffer)
		free(pUpdateDlg->m_pLogBuffer);
	pUpdateDlg->m_pLogBuffer = NULL;
	if (pUpdateDlg->m_socket == INVALID_SOCKET)
		return -1;

	do
	{
		msg[0] = 2;	// Command: Transmit Update Archive
		if (send(pUpdateDlg->m_socket, (char *)msg, 4, 0) != 4)
		{
			AfxMessageBox("Failed to start update.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}

		pUpdateDlg->m_LogString.Append("Waiting for update to complete (this may take a while)...\r\n\r\n");
		::PostMessage(pUpdateDlg->m_hWnd, IDM_LOGUPDATE, 0, 0);

		// Wait for reply
		if (recv(pUpdateDlg->m_socket, (char *)msg, 8, 0) != 8)
		{
			AfxMessageBox("Error waiting for respnse.", MB_OK | MB_ICONEXCLAMATION);
			break;
		}
		deviceResult = msg[0];
		len = msg[1];
		if (len > 0)
		{	// Receive reply text
			pUpdateDlg->m_pLogBuffer = (char *)malloc(len);
			if (pUpdateDlg->m_pLogBuffer == NULL)
			{
				AfxMessageBox("Out of memory.", MB_OK | MB_ICONEXCLAMATION);
				break;
			}
			for (int i=0; i<len; i+=recvResult)
			{
				recvResult = recv(pUpdateDlg->m_socket, pUpdateDlg->m_pLogBuffer+i, len-i, 0);
				if (recvResult <= 0)
				{
					AfxMessageBox("Unable to receive reply message.", MB_OK | MB_ICONEXCLAMATION);
					break;
				}
			}
			pUpdateDlg->m_LogString.Append(pUpdateDlg->m_pLogBuffer);
			::PostMessage(pUpdateDlg->m_hWnd, IDM_LOGUPDATE, 0, 0);
		}
		if (deviceResult == 0)
		{
			result = 0;
		}
		else
		{
			AfxMessageBox("Error during update process.", MB_OK | MB_ICONEXCLAMATION);
		}
	} while(0);

	if (pUpdateDlg->m_socket != INVALID_SOCKET)
	{
		closesocket(pUpdateDlg->m_socket);
		pUpdateDlg->m_socket = INVALID_SOCKET;
	}

	::PostMessage(pUpdateDlg->m_hWnd, IDM_UPDATEFINISHED, result, 2);

	pUpdateDlg->m_hUpdateThread = NULL;

	return 0;
}

void VIBUpdateDlg::OnBnClickedUpdateSavelog()
{
	FILE *pFile;

	if (!m_pLogBuffer)
		return;

	CFileDialog fileDlg(false, "txt", "*.txt", OFN_FILEMUSTEXIST|OFN_HIDEREADONLY, "Text Files (*.txt)|*.txt|All Files (*.*)|*.*||", this);

	if (fileDlg.DoModal() != IDOK)
		return;

	if (fopen_s(&pFile, fileDlg.GetPathName(), "wb") != 0)
	{
		AfxMessageBox("Failed to open log file.", MB_OK | MB_ICONEXCLAMATION);
		return;
	}
	fwrite(m_pLogBuffer, 1, strlen(m_pLogBuffer), pFile);
	fclose(pFile);
}
