/* ============================================================================
Main.cpp is the code module that contains the application startup code for
the SonyVisca driver.

The module includes the primary entry point, WinMain(), called when the
application starts. This function registers the class for the main application
window, and runs the primary message processing loop. The module also has the
callback code used to manage the dialog box shown in the main application
window. Elements in the dilog box show status information from the driver.

The program is an Infodriver External The host 32-bit infodriver should be 
running before this Visca driver is started. The external infodriver and this
module must have the same driver_id value on their startup command lines.

Some of the parameters required for operation, such as the serial 
communications port and the comms parameters, are held in the same dev_XXX.ini
file used by the Infodriver. If these parameters are not present, default 
values are added to the dev_xxx.ini.

============================================================================ */

#include "stdafx.h"

// Define the EXT property, which causes the global variables in SonyVisca.h
// to be initiated in this compile unit, whereas they are only referenced when
// SonyVisca.h  is included in other files.
#define EXT

// As EXT is now defined, include the header with all the global definitions.
#include "SonyVisca.h"
#include "include\BNCS.H"
#include "awCcConfig.h"

//********************************************************************//
// Function templates for inter-module use are defined in SonyVisca.h //
//********************************************************************//

////////////////////////////////////////////////////////////////////////////////
// The function templates below are for functions used only within this
// compilation module.
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);
void InitMyGlobals(void);		// Function that sets gloabl variables to safe default values.
void VersionInfo(void);			// Function to extract version data from resource file
void InitialiseApp(void);		// Implements most of the application specific initialisation
void CloseApp(HWND hWnd);		// Called to provide tidy shutdown of this application
void AssertDebugOnOff(void);	// Enable/Disable the Debug output, set flag to match in dev_xxx.ini
void AssertDebugShow(void);		// Show/hide the debug window
int find_BNCS_paths(void);		// Find file path to BNCS 
void CreateBrushes(void);		// Create colour brushes for dialog box element indications
void ReleaseBrushes(void);		// Release resources used by CreateBrushes
void LoadIniFile(void);			// Reads settings from dex_xxx.ini


////////////////////////////////////////////////////////////////////////////////
// FUNCTION: WinMain(...)
//
// PURPOSE:  Defines the entry point for the application after initial loads of
//           code into memory have been processed.
//
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);	// Stop the compiler complaining we
											// have not used a parameter.

	MSG msg;				// Windows message object.
	HACCEL hAccelTable;		// Holds keyboard shortcuts.

	hInst = hInstance;		// Keep a copy of our instance ID
	m_nCmdShow = nCmdShow;	// Save display mode format from command line for
							// potential later use.

	InitMyGlobals();		// Initialse global variables to safe start points.

	VersionInfo();			// Extract the version data into global variables

	// Get the driver ID for this instance - it should be a parameter on the command line
	if (strlen(lpCmdLine) == 0)
	{
		MessageBox(NULL, "Empty command line.\nPlease provide BNCS Infodriver ID as parameter.\n\nProgram Exiting",
			"BNCS SonyVisca Driver Error", MB_ICONSTOP | MB_OK);
		return (0);
	}

	// Command line is not blank - assume we have an integer driver number present.
	myDriverId = atoi(lpCmdLine);	// Assume only one element present, the driver id. Returns 0 if no value found.

	if ((myDriverId <= 0) || (myDriverId >= 1000))
	{
		// We have an invalid driver ID so don't start the rest of the program
		if (myDriverId <= 0)
			MessageBox(NULL, "Invalid driver number (<= 0) was passed on the command line.\n\nProgram Exiting",
			"BNCS SonyVisca Driver Error", MB_ICONSTOP | MB_OK);
		else
			MessageBox(NULL, "Driver number on the command line is too large.\n\nProgram Exiting",
			"BNCS SonyVisca Driver Error", MB_ICONSTOP | MB_OK);
		return (0);		// Return a value of 0 when message loop is not started
	}

	// Initialize global strings.  LoadString(...) reads items from the stringtable
	// defintions in the resource file. The second parameter is the ID of the
	// stringtable resource.
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_SONYVISCA, szWindowClass, MAX_LOADSTRING);
	
	// Register the main window class
	MyRegisterClass(hInstance);

	// Create an instance of our main window, ready for messaging to be activated.
	// This will issue a WM_CREATE message. The service actions for this will 
	// initiate the rest of the application startup process.
	hAppWnd = CreateWindow(szWindowClass, szTitle, WS_SYSMENU | WS_MINIMIZEBOX,
		CW_USEDEFAULT, 0, APP_WIDTH, APP_HEIGHT, NULL, NULL, hInstance, NULL);
	
	if (!hAppWnd)
	{
		return FALSE;
	}
	
	// Load the keyboard accelerators
	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SONYVISCA));

	// Start the message processor loop.
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}	// End of APIENTRY WinMain(HINSTANCE...


////////////////////////////////////////////////////////////////////////////////
// FUNCTION: MyRegisterClass()
//
// PURPOSE:  Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SONYVISCA));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_SONYVISCA);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}


////////////////////////////////////////////////////////////////////////////////
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND	- process the application menu
//  WM_PAINT	- Paint the main window
//  WM_DESTROY	- post a quit message and return
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
		case WM_CREATE:
			// Initialise the state machine that steps through the init processes in turn.
			// Now start the timer that will result in a call to the state engine.
			SetTimer(hWnd, STARTUP_TIMER_ID, STARTUP_INIT_DELAY, NULL);
			return (FALSE);
			break;

		case WM_TIMER:
			switch (wParam) {
				case STARTUP_TIMER_ID:
					KillTimer(hWnd, STARTUP_TIMER_ID);	// Timer is is as a one off delay - stop this instance
					InitialiseApp();					// Call the state machine
					break;

				case COMMSTEST_TIMER_ID:
					SendCommsTestMsg();		// Send test message to the camera
					break;

				case COMMS_TIMEOUT_ID:
					// Post ourselves a message to process the timeout when we have stopped the timer.
					KillTimer(hWnd, COMMS_TIMEOUT_ID);
					SendMessage(hAppWnd, RXMSG_PROCESS, mr_Timeout, NULL);	
					break;
			}	// End switch(wParam) in WM_TIMER

		case WM_COMMAND:
			wmId    = LOWORD(wParam);
			wmEvent = HIWORD(wParam);
			// Parse the menu selections:
			switch (wmId)
			{
				case IDM_ABOUT:
					DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
					break;

				case IDM_DEBUG_ON:	// Show or hide the debug information
					fDebug = (fDebug) ? FALSE : TRUE;	// Toggle the flag
					AssertDebugOnOff();					// Update menu and debug file
					break;

				case IDM_CLEARDEBUG:	// Clear the debug listbox output
					SendMessage(hWndList, LB_RESETCONTENT, 0, 0);	// Delete all items
					break;

				case IDM_SHOWDEBUG:	// Toggle the display of the debug box
					// Toggle the state of the display flag
					fDebugShow = (fDebugShow) ? FALSE : TRUE;
					AssertDebugShow();
					break;

				case IDM_CLEARBNCSCOUNT:	// Clear the BNCS TX/RX counters to zero.
					infoTxCount = 0;
					infoRxCount = 0;
					updateInfoTxCount(0);	// Show the new values by adding 0 to total.
					updateInfoRxCount(0);
					break;

				case IDM_CLEARVISCACOUNT:	// Clear the VIOM serial counters to zero.
					serialTxCount = 0;
					serialRxCount = 0;
					updateSerialTxCount(0);	// Show new values by adding 0.
					updateSerialRxCount(0);
					break;

				case IDM_EXIT:
					DestroyWindow(hWnd);
					break;


			default:
				return DefWindowProc(hWnd, message, wParam, lParam);
			}
			break;

		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			// TODO: Add any drawing code here...
			EndPaint(hWnd, &ps);
			break;

		case WM_DESTROY:
			// Have we connected to the external infodriver. If so relinquish control
			// if (....)
			CloseApp(hWnd);			// Tidy up after ourselves.
			PostQuitMessage(0);
			break;

		case RXMSG_PROCESS:				// Message from camera waiting to be processed
			ViscaMsg((int) wParam);		// wParam carries the message process call reason
			break;

		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}	// End of CALLBACK WndProc(HWND hWnd...


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: DlgProcChild(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
//
// PURPOSE:  Message handler for child dialog that has our status displays etc.
//
// COMMENTS: 
// 
LRESULT CALLBACK DlgProcChild(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId = LOWORD(wParam);
	int wmEvent = HIWORD(wParam);
	char szData[MAX_BUFFER_STRING] = "";
	int iIndex = 0;

	HWND hButton = (HWND)lParam;
	LONG idButton = GetDlgCtrlID(hButton);

	switch (message)
	{
		case WM_INITDIALOG:
			// This will colour the background of the child dialog on startup to 
			// defined colour brushes are defined in INIT_APP function.
			HBr = hbrDlgBgd;
			return (LRESULT)HBr;
			break;

		case WM_CTLCOLORDLG:
			// Sets the colour of all the diag at startup
			SetTextColor((HDC)wParam, COL_DK_BLUE);
			SetBkColor((HDC)wParam, COL_DLG_BG);
			HBr = hbrDlgBgd;
			return (LRESULT)HBr;
			break;

		case WM_CTLCOLORSTATIC:
			// Set the colours of the static labels. This will be called on startup of the dialog by windows.
			// This section will also be called every time you do a SetDlgItemText() command for a given label.
			// Testing the value of various global variables means this can be used to colour labels for
			// Comms OK/Fail, TXRX/RXonly etc.
			switch (idButton)
			{
				case IDC_IDRX: 
				case IDC_IDTX: 
				case IDC_IDCAMERARX: 
				case IDC_IDCAMERATX:	// Comms counters
					SetTextColor((HDC)wParam, COL_DK_BLUE);
					SetBkColor((HDC)wParam, COL_WHITE);
					HBr = hbrWhite;
					break;

				case IDC_IDSTAT:		// Mode of driver TX/RX, Rx Only
					// Test the status of the driver and set the colour as appropriate
					// Show the Driver status
					switch (driverMode)
					{
						case IFMODE_NONE:
							SetTextColor((HDC)wParam, COL_DK_BLUE);
							SetBkColor((HDC)wParam, COL_LT_RED);
							HBr = hbrLightRed;
							break;
						case IFMODE_RXONLY:
							SetTextColor((HDC)wParam, COL_WHITE);
							SetBkColor((HDC)wParam, COL_DK_BLUE);
							HBr = hbrDarkBlue;
							break;
						case IFMODE_TXRX:
							SetTextColor((HDC)wParam, COL_BLACK);
							SetBkColor((HDC)wParam, COL_LT_GRN);
							HBr = hbrLightGreen;
							break;
					}
					break;

				case IDC_IDPORT:		// Comms port ID
					SetTextColor((HDC)wParam, COL_DK_BLUE);
					SetBkColor((HDC)wParam, COL_WHITE);
					HBr = hbrWhite;
					break;

				case IDC_IDCOMSTAT:		// Status of Camera comms.
					// Test the status of the comms in global variable 
					// fCameraCommStat to see comms link state to camera.
					if (fCameraCommStat)
					{
						// Comms OK. Green background
						SetTextColor((HDC)wParam, COL_BLACK);
						SetBkColor((HDC)wParam, COL_LT_GRN);
						HBr = hbrLightGreen;
					}
					else
					{
						// Comms FAIL. RED background
						SetTextColor((HDC)wParam, COL_WHITE);
						SetBkColor((HDC)wParam, COL_LT_RED);
						HBr = hbrLightRed;
					}
					break;

				case IDC_MSGS:		// The miscellaneous message box. Colour defined by variable miscMsgState
					switch (miscMsgState)
					{
						case MBM_Norm:		// White Background, Black text
							SetTextColor((HDC)wParam, COL_DK_BLUE);
							SetBkColor((HDC)wParam, COL_WHITE);
							HBr = hbrWhite;
							break;

						case MBM_Good:		// Light Green background, Black Text
							SetTextColor((HDC)wParam, COL_BLACK);
							SetBkColor((HDC)wParam, COL_LT_GRN);
							HBr = hbrLightGreen;
							break;

						case MBM_Alert:		// Yellow background, Black text
							SetTextColor((HDC)wParam, COL_BLACK);
							SetBkColor((HDC)wParam, COL_LT_YELLOW);
							HBr = hbrLightYellow;
							break;

						case MBM_Fail:		// Red background, White text
							SetTextColor((HDC)wParam, COL_WHITE);
							SetBkColor((HDC)wParam, COL_LT_RED);
							HBr = hbrLightRed;
							break;
					}
					break;		// break IDC_MSGS

				case IDD_STATUS:
				case IDC_STATIC:
					SetTextColor((HDC)wParam, COL_DK_BLUE);
					SetBkColor((HDC)wParam, COL_DLG_BG);
					HBr = hbrDlgBgd;
					break;

				default:
					SetTextColor((HDC)wParam, COL_DK_BLUE);
					SetBkColor((HDC)wParam, COL_DLG_BG);
					HBr = hbrDlgBgd;
			}	// End switch idButton

			return (LRESULT)HBr;

	}	// End of switch(message) 

	return false;

}	// End of DlgProcChild()


////////////////////////////////////////////////////////////////////////////////
// Callback message handler for the About box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}	// End of CALLBACK About(...


////////////////////////////////////////////////////////////////////////////////
// FUNCTION: InitMyGlobals()
//
// PURPOSE:	Set safe values at startup. See comments.
//
// RETURNS:  Nothing.
//
// COMMENTS: The way that globals are defined and referenced between modules 
// means that the values cannot be initialised when the variable is declared.
// The filling of values is implemented by this module
//
void InitMyGlobals(void)
{
	// Flags
	fDebug = false;
	fDebugShow = false;
	fCameraCommStat = false;	// State of comms to camera
	fExtIdConnected = false;	// External Infodriver not yet connected

	// Integers
	majorFileVersion = 0;		// File version major release
	minorFileVersion = 0;		// File version minor release
	revisionFileVersion = 0;	// File version revision
	buildFileVersion = 0;		// File version build version.
	myDriverId = 0;				// Driver ID extracted from command line
	host_BNCS_system = 0;		// BNCS system type determined by function get_BNCS_paths.
	iChildHeight = 0;			// Holds the height of the user client application window
	myWS_ID = 0;				// Workstation ID that is hosting the driver.
	csi_timeout_counter = 0;	// Used to limit time we wait for a broadcast message from CSI
	driverMode = 0;				// Contains the operational mode of the driver
	miscMsgState = 0;			// Value in this parameter controls the colour of the misc message box.

	// Unsigned Integers
	infoTxCount = 0;			// Holds the number of messages sent to CSI
	infoRxCount = 0;			// Holds the number of messages received from CSI
	serialTxCount = 0;			// Holds the number of messages sent to serial comms port
	serialRxCount = 0;			// Holds the number of messages received from serial comms port

	// Default Comms parameters
	comPort = 0;				// Port number to communicate with VIOM		
	comBaud = 0;				// Baud rate to use
	comDataBit = 0;				// Number of data bits
	comStop = 0;				// Number of stop bits
	comParity = 0;				// Parity 0=none, 1=Even, 2=Odd
	comPortOpen = false;		// TRUE if the serial comms port is available
}	// End of InitMyGlobals(void)


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: void IntialiseApp(void)
//
// PURPOSE:  Implements actions that are needed when this driver app starts.
//
// COMMENTS: Called from WM_TIMER event on timer id STARTUP_TIMER_ID.
// The timer is stopped by the WinProc function. 
// 
void InitialiseApp(void)
{
	RECT rctWindow;			// Holds the size parameters of the dialog main window
	BOOL bContinue = TRUE;
	char szMsg[MAX_LOADSTRING];
	char windowTitle[MAX_LOADSTRING];
	int	 comStat;


	// Start by creating some brushes needed for painting dialog boxes.
	CreateBrushes();

	// Initialise the status dialogs in our window.
	hWndDlg = CreateDialog(hInst, (LPCTSTR)IDD_STATUS, hAppWnd, (DLGPROC)DlgProcChild);

	GetClientRect(hWndDlg, &rctWindow);						// Get the properties for the client area of the dialog box/window.
	iChildHeight = rctWindow.bottom - rctWindow.top + 2;	// Compute the height required for the dialog box display.
	GetClientRect(hAppWnd, &rc);							// Get the client area properties for the app window

	// Now create a listbox that fills the gap between the bottom of the dialog box and the bottom of the window
	// The listbox is used to show debug messages if the mode is enabled.
	hWndList = CreateWindow("LISTBOX", "", WS_CHILD | WS_VSCROLL | LBS_NOINTEGRALHEIGHT | LBS_USETABSTOPS,
		0, iChildHeight, rc.right - rc.left, rc.bottom - rc.top - iChildHeight,
		hAppWnd, (HMENU)IDW_LIST, hInst, 0);
	ShowWindow(hWndList, SW_SHOW);

	// Start with the debug listbox hidden.
	GetWindowRect(hAppWnd, &rctWindow);		// Read the current window position data
	MoveWindow(hAppWnd, rctWindow.left, rctWindow.top, APP_WIDTH, iChildHeight + 42, TRUE);
	CheckMenuItem(GetMenu(hAppWnd), IDM_SHOWDEBUG, MF_UNCHECKED);

	// Show the main application window minimised. This allows user to 
	// hover over the icon to see initialisation status progress.
	ShowWindow(hAppWnd, SW_MINIMIZE);

	// Update the window name.
	sprintf_s(windowTitle, MAX_LOADSTRING, "%s V%d.%d.%d - Initialising", szTitle, majorFileVersion, minorFileVersion, revisionFileVersion);
	SetWindowText(hAppWnd, windowTitle);

	// Check that there is a viable host system (V3 or V4). Otherwise report the issue and exit.
	if (!((AwCcConfig::isV4Based()) || (AwCcConfig::isV3Based())))
	{
		forceDebug("SonyVisca: Unable to recognise host BNCS system type. Exiting");
		MessageBox(NULL, "Host BNCS system not recognised.\n\nProgram Exiting", "BNCS System Problem", MB_ICONSTOP | MB_OK);
		PostQuitMessage(0);
		return;					// No more work to do until the quit takes place
	}

	// Store our workstation ID into global variable.
	myWS_ID = AwCcConfig::workstation();

	// Try to create a infodriver interface. This should be in single controller mode cc::singleControllerCapable
	// Set the required state for the connection before we try to open the access to the driver
	setSingleControllerMode();

	// Connect function returns true on error.
	if (connectToExt(myDriverId))
	{
		// Failed to connect
		forceDebug("Unable to connect to infodriver. Exiting");
		MessageBox(NULL, "Unable to connect to host Infodriver.", "SonyVica - Critical Error", MB_ICONSTOP);
		PostQuitMessage(0);
		return;
	}
	else
	{
		// Connected correctly
		Debug("Sucessfully connected to Infodriver %d", myDriverId);
	}

	// Before we can initialise the comms to the camera we need to read in the operational 
	// parameters such as port number and baud rate from the dev_xxx.ini file.
	LoadIniFile();

	// Show the comm port that has been selected by entry in the INI file
	sprintf_s(szMsg, MAX_LOADSTRING, "COM%d:", comPort);
	SetDlgItemText(hWndDlg, IDC_IDPORT, szMsg);

	// Assume serial comms will work correctly
	fCameraCommStat = TRUE;			// Causes green background in status block
	SetDlgItemText(hWndDlg, IDC_IDCOMSTAT, "OK");

	// Now need to start serial comms to camera.
	comStat = openSerialPort();
	switch (comStat)
	{
		case -1:		// Unable to open the serial port. FATAL error - stop and exit.
			forceDebug("SonyVisca: Unable to open com port COM%d:", comPort);
			fCameraCommStat = FALSE;			// Causes green background in status block
			SetDlgItemText(hWndDlg, IDC_IDCOMSTAT, "Fail");
			MessageBox(NULL, "Unable to open serial port", "COM port not found", MB_ICONSTOP);
			PostQuitMessage(0);
			break;

		case -2:		// Serial port already open. FATAL error - stop and exit.
			forceDebug("SonyVisca: Unable to open com port COM%d:", comPort);
			fCameraCommStat = FALSE;			// Causes green background in status block
			SetDlgItemText(hWndDlg, IDC_IDCOMSTAT, "Fail");
			MessageBox(NULL, "Unable to open serial port", "COM port already open.", MB_ICONSTOP);
			PostQuitMessage(0);
			break;

		default:
			comPortOpen = TRUE;			// Serial port was opened without issues.
			if (!setRxProcessor())
			{
				forceDebug("SonyVisca: Unable to allocate serial message receive processor.");
				MessageBox(NULL, "Serial port problem", "Unable to allocate receive processing function for serial data.", MB_ICONSTOP);
				PostQuitMessage(0);
			}

			// Now we are ready to establish message exchange with the camera.
			// Reset the link using "Set Address" and "Clear Comms" messages. 
			// SonyVisca.cpp has required functions.
			OpenCameraComms();

			// Now have comms routes configured - We need to test the comms link regularly
			SetTimer(hAppWnd, COMMSTEST_TIMER_ID, COMMSTEST_TIMER_INTERVAL, NULL);	// Start the timer that causes the background poll.
			
			break;
	}	// End of switch(comstat)

	// Update the window name to show version and BNCS driver ID
	sprintf_s(windowTitle, MAX_LOADSTRING, "%s V%d.%d.%d - Device %03d", 
		      szTitle, majorFileVersion, minorFileVersion, revisionFileVersion, myDriverId);
	SetWindowText(hAppWnd, windowTitle);


}	// End of InitialiseApp(void)


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: CloseApp()
//
// PURPOSE:  Action things to do when main window is destroyed
//
// COMMENTS: Called from WM_DESTROY message.
// The listbox debug window is a child of the main, so it will be destroyed
// automatically. 
//
void CloseApp(HWND hWnd)
{
	// Kill active timers
	KillTimer(hAppWnd, COMMSTEST_TIMER_ID);		// Comms poll test timer
	KillTimer(hAppWnd, COMMS_TIMEOUT_ID);		// Comms Timeout timer.

	// Empty the message queues
	clearAllQueues();

	// Release external infodriver
	if (InfoDriverConnected()) disconnectFromExt();
	
	// Close comms to camera
	if (comPortOpen) closeCommPort();

	// Report Queue high water marks to debug.
	Debug("Command queue maximum used depth was %d", sendmsgHWM());
	Debug("Input queue maximum used depth was %d", inputHWM());

	// Delete classes brushes etc
	// Tidy up colours.
	ReleaseBrushes();
	DeleteObject(HBr);
}



///////////////////////////////////////////////////////////////////////////////
// FUNCTION: void VersionInfo(void)
//
// PURPOSE: Fetches the software version number from the embedded resources
//          information. The results are stored in global variables:
//			majorFileVersion, minorFileVersion, revisionFileVersion, buildFileVersion
//				
// COMMENTS:
// As part of the compile and link processes the version information held in 
// the resource file (.rc) is moved to the compiled file header. Therefore
// to recover that information for program use means that we must use several
// API calls to extract the data we wish to get.
//
void VersionInfo(void)
{
	LPBYTE	abData;
	DWORD	handle;
	DWORD	dwSize;
	LPBYTE	lpBuffer;
	LPSTR	szName, szVer, szModFileName, szBuf;
	char	acBuf[16];

	// Allocate some space which we use for temporary string stores
	szName = (LPSTR)malloc(MAX_PATH);
	szModFileName = (LPSTR)malloc(MAX_PATH);
	szVer = (LPSTR)malloc(MAX_PATH);
	szBuf = (LPSTR)malloc(MAX_PATH);

	// Get Version Info
	// Start by asking Windows to return our exe file name.
	GetModuleFileName(hInst, szModFileName, MAX_PATH);	// Parmaeter 1 is process to report (us!)
	// Parameter 2 is address of the buffer to receive the name.
	// Parameter 3 is the size of the buffer
	// Now ask if Windows can get the version information. If the data can be retrieved, dwSize is the size, in bytes, of that data.
	dwSize = GetFileVersionInfoSize(szModFileName, &handle);	// Parameter 1 is the name of the file whose data is to be read.
	// Parameter 2 is set to 0 by the function.
	// Allocate some space for the file information to be stored.
	abData = (LPBYTE)malloc(dwSize);

	// Ask Windows to fetch the data into the buffer using GetFileVersionInfo().
	// Parameter 1 is the name of the file whose data we wish to read
	// Parameter 2 is actually ignored!!
	// Parameter 3 is the size of the buffer whose address is provided in parameter 4
	// Parameter 4 is the buffer that receives the data.
	// The function returns FALSE on failure, TRUE on success.
	GetFileVersionInfo(szModFileName, handle, dwSize, abData);
	if (dwSize) // Only do the following processing of the buffer size is not zero.
	{
		// Get Country Translation
		VerQueryValue((LPVOID)abData, "\\VarFileInfo\\Translation", (LPVOID*)&lpBuffer, (UINT *)&dwSize);
		// Make the Country code
		wsprintf(acBuf, "%04X%04X", *(WORD*)lpBuffer, *(WORD*)(lpBuffer + 2));
		if (dwSize != 0) {
			// Get Version info for file version number 
			wsprintf(szName, "\\StringFileInfo\\%s\\FileVersion", (LPSTR)acBuf);
			VerQueryValue((LPVOID)abData, (LPSTR)szName, (LPVOID*)&lpBuffer, (UINT *)&dwSize);
		}
		// Copy version number from byte buffer to a string buffer
		lstrcpyn(szVer, (LPSTR)lpBuffer, (int)dwSize);
		// Copy to the dialog static text box */
		wsprintf(szBuf, "%s", szVer);
		// Replace any commas in the version string with full stops
		int verLen = (int)strlen(szBuf);
		for (int c = 0; c < verLen; c++) if (szBuf[c] == ',') szBuf[c] = '.';

		// Copy the version string to global string store.
		lstrcpyn(fileVersion, szBuf, verLen);

		// Now extract the individual elements and convert them to integers saved in global stores.
		// We can replace the full srops by null characters to make multiple strings in szBuf
		for (int c = 0; c < verLen; c++) if (szBuf[c] == '.') szBuf[c] = '\0';

		LPSTR bufPoint = szBuf;					// Major version is found as first part of string
		majorFileVersion = atoi(bufPoint);

		while (*bufPoint != '\0') bufPoint++;	// Skip over major version
		bufPoint++;								// Skip the null terminator
		minorFileVersion = atoi(bufPoint);

		while (*bufPoint != '\0') bufPoint++;	// Skip over minor version
		bufPoint++;								// Skip the null terminator
		revisionFileVersion = atoi(bufPoint);

		while (*bufPoint != '\0') bufPoint++;	// Skip over revision version
		bufPoint++;								// Skip the null terminator
		buildFileVersion = atoi(bufPoint);
	}

	// Release the temporary memory we used for string storage.
	free((PVOID)abData);
	free((PVOID)szName);
	free((PVOID)szModFileName);
	free((PVOID)szVer);
	free((PVOID)szBuf);
}	// End of VersionInfo(void)


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: LoadIniFile()
//
// PURPOSE:  Assign (global) variables with values read from .ini file
//           If the ini items do not exist, they are created with default values.
//
// COMMENTS: To ensure that the token names are consistent, the names are 
// defined in the resource file.
//			
void LoadIniFile(void)
{
	char tokenval[MAX_LOADSTRING];		// Holds the token name fetched from the stringtable
	string viscaSec = "Visca";

	// Work through the INI parameters in turn
	LoadString(hInst, INI_DBG, tokenval, MAX_LOADSTRING);	// Get the id string for debug control
	int dbgState = AwCcConfig::getInt(myDriverId, viscaSec, tokenval, 0, true);

	if (dbgState == 0)
	{
		// Debug is set to off. Note the value and set the menu item tick to off.
		fDebug = FALSE;
		CheckMenuItem(GetMenu(hAppWnd), IDM_DEBUG_ON, MF_UNCHECKED);
	}
	else
	{
		// Debug is set to on. Note the value and set the menu item tick to on.
		fDebug = TRUE;
		CheckMenuItem(GetMenu(hAppWnd), IDM_DEBUG_ON, MF_CHECKED);
	}

	// Extract the comms parameters. Some of these need translation....
	// Get COM port to use. We can use this integer directly.
	LoadString(hInst, INI_PORT, tokenval, MAX_LOADSTRING);
	comPort = AwCcConfig::getInt(myDriverId, viscaSec, tokenval, 1, true);

	// Get baud rate. We can use this value directly
	LoadString(hInst, INI_BAUD, tokenval, MAX_LOADSTRING);
	comBaud = AwCcConfig::getInt(myDriverId, viscaSec, tokenval, 9600, true);

	// Get Data bits. We can use this, if in range 5 to 8.
	LoadString(hInst, INI_BITS, tokenval, MAX_LOADSTRING);
	comDataBit = AwCcConfig::getInt(myDriverId, viscaSec, tokenval, 8, true);
	if ((comDataBit < 5) || (comDataBit > 8)) comDataBit = 8;

	// Get Stop Bits
	LoadString(hInst, INI_STOP, tokenval, MAX_LOADSTRING);
	string stopBits = AwCcConfig::getString(myDriverId, viscaSec, tokenval, "1", true);
	if (stopBits == "0") comStop = 0;			// Translate 1 stop bit to control value of 0
	else if (stopBits == "1.5") comStop = 1;	// Translate 1.5 stop bits to control value of 1
	else if (stopBits == "2") comStop = 2;		// Translate 2 stop bits to control value of 2

	// Get Parity. Valid characters are N (none) E (Even), O (Odd)
	LoadString(hInst, INI_PARITY, tokenval, MAX_LOADSTRING);
	string parity = AwCcConfig::getString(myDriverId, viscaSec, tokenval, "N", true);

	// Extract the parity and convert to integer index
	switch ((int)parity[0]) {
		case 'n': case 'N':
			comParity = 0;
			break;
		case 'e': case 'E':
			comParity = 1;
			break;
		case 'o': case 'O':
			comParity = 2;
			break;
		case 'm': case 'M':
			comParity = 3;
			break;
		case 's': case 'S':
			comParity = 4;
			break;
	}	// End switch

}	// End of function LoadIniFile


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: AssertDebugShow()
//
// PURPOSE: Sorts out window size and ini file settings for the three debug
//          controls - debug On/Off, VIOM CommsOn/Off and show/hide listbox.
//  
// COMMENTS:
// Uses the window handle stored in global variable hAppWnd
//	
void AssertDebugShow(void)
{

	RECT rctWindow;

	GetWindowRect(hAppWnd, &rctWindow);		// Read the current window position data

	if (fDebugShow)
	{
		// Show the full size including the debug listbox
		MoveWindow(hAppWnd, rctWindow.left, rctWindow.top, APP_WIDTH, APP_HEIGHT, TRUE);
		CheckMenuItem(GetMenu(hAppWnd), IDM_SHOWDEBUG, MF_CHECKED);
	}
	else
	{
		// Hide the debug listbox
		MoveWindow(hAppWnd, rctWindow.left, rctWindow.top, APP_WIDTH, iChildHeight + 42, TRUE);
		CheckMenuItem(GetMenu(hAppWnd), IDM_SHOWDEBUG, MF_UNCHECKED);
	}

}


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: AssertDebugOnOff()
//
// PURPOSE: The function uses the state of the fDebug flag to:
//          a) Update the Menu tick to the required state
//          b) Update the flag value in the dev_xxx.ini file
//  
// COMMENTS:
// The fDebug flag is toggled by the menu actions handler
//	
void AssertDebugOnOff(void)
{
	
	char szIniFileName[MAX_LOADSTRING];						// Name of dev_xxx.ini file (no path)
	char szSection[MAX_LOADSTRING] = "Visca";				// Section of INI file to update
	char szToken[MAX_LOADSTRING] = "";
	LoadString(hInst, INI_DBG, szToken, MAX_LOADSTRING);	// Get the name of the token to update
	sprintf_s(szIniFileName, "dev_%03d.ini", myDriverId);	// Form the ini file name

	if (fDebug)
	{
		// TRUE - so set the check item and update ini
		CheckMenuItem(GetMenu(hAppWnd), IDM_DEBUG_ON, MF_CHECKED);
		AwCcConfig::setString(myDriverId, szSection, szToken, "1");
	}
	else
	{
		// FALSE - so set the check item and update ini
		CheckMenuItem(GetMenu(hAppWnd), IDM_DEBUG_ON, MF_UNCHECKED);
		AwCcConfig::setString(myDriverId, szSection, szToken, "0");
	}
	
}


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: Debug(...)
//
// PURPOSE: Writes debug information to the listbox and to Windows Debug service
//
// COMMENTS: The function works the same way as printf. The message sent to the
// windows debug system is prepended with "SonyVisca: " to show ident report source.
//			
void Debug(LPCSTR szFmt, ...)
{
	if (fDebug)
	{
		LPSTR szDebug = (LPSTR)malloc(0x1000);		// Allocate a temporary buffer to assemble debug message
		va_list argptr;

		va_start(argptr, szFmt);
		vsprintf_s(szDebug, 0x1000, szFmt, argptr);
		va_end(argptr);

		if (strlen(szDebug) > 190) szDebug[190] = 0;					// Truncate long debug messages

		SendMessage(hWndList, LB_ADDSTRING, 0, (LPARAM)szDebug);		// Add message to listbox
		long lCount = SendMessage(hWndList, LB_GETCOUNT, 0, 0);			// Get List Count - number of rows
		if (lCount > MAX_LB_ITEMS)
		{
			SendMessage(hWndList, LB_DELETESTRING, 0, 0);				// Delete item
			lCount--;
		}
		SendMessage(hWndList, LB_SETTOPINDEX, (WPARAM)lCount - 1, 0);	// Set Top Index

		// Log to windows as well.
		OutputDebugString("SonyVisca: ");	// Identify the source of the messages
		OutputDebugString(szDebug);			// Output the message
		OutputDebugString("\n");			// Append new line

		// Free memory used for message buffer
		free((PVOID)szDebug);
	}
}


///////////////////////////////////////////////////////////////////////////////
// FUNCTION: forceDebug(...)
//
// PURPOSE: Writes debug information to the Windows Debug service.
//          It happens regardless of the debug flags.
//
// COMMENTS: The function works the same way as printf statement.
// Mostly intended to be used during development called by blocks of code that
// are conditionally compiled.
//
// Some calls here are to report unrecoverable errors.
//
void forceDebug(LPCSTR szFmt, ...)
{
	LPSTR szDebug = (LPSTR)malloc(0x1000);		// Allocate a temporary buffer to assemble debug message
	va_list argptr;

	va_start(argptr, szFmt);
	vsprintf_s(szDebug, 0x1000, szFmt, argptr);
	va_end(argptr);

	if (strlen(szDebug) > 190) szDebug[190] = 0;	// Truncate long debug messages

	OutputDebugString(szDebug);		// Send to windows debug.
	OutputDebugString("\n");		// Append a new line

	free((PVOID)szDebug);							// Free memory used for message buffer
}


///////////////////////////////////////////////////////////////////////////////
// BRUSHES FOR BUTTONS & LABELS
//

// Function to Create Colour brushes
void CreateBrushes(void)
{
	hbrWindowsGrey = CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
	hbrBlack = CreateSolidBrush(COL_BLACK);
	hbrWhite = CreateSolidBrush(COL_WHITE);
	hbrDarkRed = CreateSolidBrush(COL_DK_RED);
	hbrDarkGreen = CreateSolidBrush(COL_DK_GRN);
	hbrDarkOrange = CreateSolidBrush(COL_DK_ORANGE);
	hbrDarkBlue = CreateSolidBrush(COL_DK_BLUE);
	hbrYellow = CreateSolidBrush(COL_YELLOW);
	hbrDlgBgd = CreateSolidBrush(COL_DLG_BG);
	hbrLightRed = CreateSolidBrush(COL_LT_RED);
	hbrLightGreen = CreateSolidBrush(COL_LT_GRN);
	hbrLightYellow = CreateSolidBrush(COL_LT_YELLOW);
	hbrLightBlue = CreateSolidBrush(COL_LT_BLUE);

	return;
}


///////////////////////////////////////////////////////////////////////////////
// Function to Realse the colour brushes
//
void ReleaseBrushes(void)
{
	// If the brush handle is allocated, it has a non-zero value. So release the resource.
	if (hbrBlack) DeleteObject(hbrBlack);
	if (hbrWhite) DeleteObject(hbrWhite);
	if (hbrWindowsGrey) DeleteObject(hbrWindowsGrey);
	if (hbrDarkRed) DeleteObject(hbrDarkRed);
	if (hbrDarkGreen) DeleteObject(hbrDarkGreen);
	if (hbrDarkBlue) DeleteObject(hbrDarkBlue);
	if (hbrDarkOrange) DeleteObject(hbrDarkOrange);
	if (hbrYellow)	DeleteObject(hbrYellow);
	if (hbrDlgBgd)	DeleteObject(hbrDlgBgd);
	if (hbrLightRed) DeleteObject(hbrLightRed);
	if (hbrLightBlue) DeleteObject(hbrLightBlue);
	if (hbrLightGreen) DeleteObject(hbrLightGreen);
	if (hbrLightYellow) DeleteObject(hbrLightYellow);

	return;
}

