/*==========================================================================
 *
 *  Copyright (C) 1996-1997 Microsoft Corporation.  All Rights Reserved.
 *
 *  File:       serial.c
 *  Content:	Routines for serial I/O
 *  History:
 *   Date	By	Reason
 *   ====	==	======
 *  6/10/96	kipo	created it
 *  6/22/96	kipo	added support for EnumConnectionData()
 *	6/25/96	kipo	updated for DPADDRESS
 *  7/13/96	kipo	added GetSerialAddress()
 *	7/16/96	kipo	changed address types to be GUIDs instead of 4CC
 *	8/21/96	kipo	move comport address into dplobby.h
 *  1/06/97 kipo	updated for objects
 *  2/11/97 kipo	pass player flags to GetAddress()
 *  2/18/97 kipo	allow multiple instances of service provider
 *	3/17/97 kipo	deal with errors returned by DialogBoxParam()
 *  5/07/97 kipo	added support for modem choice list
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>

#include "dplaysp.h"
#include "comport.h"
#include "resource.h"

// constants

typedef enum {
	ASCII_XON = 0x11,
	ASCII_XOFF = 0x13
};

// serial object
typedef struct {
	DPCOMPORT			comPort;		// base object globals
	BOOL				bHaveSettings;	// set to TRUE after settings dialog has been displayed
	DPCOMPORTADDRESS	settings;		// settings to use
} DPSERIAL, *LPDPSERIAL;

// dialog choices for serial port settings

static DWORD	gComPorts[] =		{ 1, 2, 3, 4 };

static DWORD	gBaudRates[] =		{ CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400,
									  CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400,
									  CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000 };

static DWORD	gStopBits[] =		{ ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS };

static DWORD	gParities[] =		{ NOPARITY, EVENPARITY, ODDPARITY, MARKPARITY };

static DWORD	gFlowControls[] =	{ DPCPA_NOFLOW, DPCPA_XONXOFFFLOW, DPCPA_RTSFLOW, DPCPA_DTRFLOW, DPCPA_RTSDTRFLOW };

// globals

// this is defined in dllmain.c
extern HINSTANCE		ghInstance;

// this is defined in dpserial.c
extern GUID				DPSERIAL_GUID;

// prototypes

static HRESULT			DisposeSerial(LPDPCOMPORT baseObject);
static HRESULT			ConnectSerial(LPDPCOMPORT baseObject, BOOL bWaitForConnection, BOOL bReturnStatus);
static HRESULT			DisconnectSerial(LPDPCOMPORT baseObject);
static HRESULT			GetSerialAddress(LPDPCOMPORT baseObject, DWORD dwPlayerFlags,
										 LPVOID lpAddress, LPDWORD lpdwAddressSize);
static HRESULT			GetSerialAddressChoices(LPDPCOMPORT baseObject,
									     LPVOID lpAddress, LPDWORD lpdwAddressSize);

static BOOL				SetupConnection(HANDLE hCom, LPDPCOMPORTADDRESS portSettings);
static BOOL FAR PASCAL	EnumAddressData(REFGUID lpguidDataType, DWORD dwDataSize,
										LPCVOID lpData, LPVOID lpContext);
static BOOL				GetSerialSettings(HINSTANCE hInstance, HWND hWndParent, LPDPSERIAL globals);
static UINT_PTR CALLBACK SettingsDialog(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
static void				InitDialog(HWND hDlg, LPDPCOMPORTADDRESS settings);
static void				GetSettingsFromDialog(HWND hDlg, LPDPCOMPORTADDRESS settings);
static int				ValueToIndex(LPDWORD buf, int bufLen, DWORD value);
static void				FillComboBox(HWND hDlg, int dlgItem, int startStr, int stopStr);

/*
 * NewSerial
 *
 * Create new serial port object.
 */
HRESULT NewSerial(LPVOID lpConnectionData, DWORD dwConnectionDataSize,
				  LPDIRECTPLAYSP lpDPlay, LPREADROUTINE lpReadRoutine,
				  LPDPCOMPORT *storage)
{
	LPDPCOMPORT baseObject;
	LPDPSERIAL	globals;
	HRESULT		hr;

	// create base object with enough space for our globals
	hr = NewComPort(sizeof(DPSERIAL), lpDPlay, lpReadRoutine, &baseObject);
	if FAILED(hr)
		return (hr);

	// fill in methods we implement
	baseObject->Dispose = DisposeSerial;
	baseObject->Connect = ConnectSerial;
	baseObject->Disconnect = DisconnectSerial;
	baseObject->GetAddress = GetSerialAddress;
	baseObject->GetAddressChoices = GetSerialAddressChoices;

	// setup default settings
	globals = (LPDPSERIAL) baseObject;
	globals->settings.dwComPort = 1;					// COM port to use (1-4)
	globals->settings.dwBaudRate = CBR_57600;			// baud rate (100-256k)
	globals->settings.dwStopBits = ONESTOPBIT;			// no. stop bits (1-2)
	globals->settings.dwParity = NOPARITY;				// parity (none, odd, even, mark)
	globals->settings.dwFlowControl = DPCPA_RTSDTRFLOW;	// flow control (none, xon/xoff, rts, dtr)

	// check for valid connection data
	if (lpConnectionData)
	{
		baseObject->lpDPlay->lpVtbl->EnumAddress(baseObject->lpDPlay, EnumAddressData, 
									lpConnectionData, dwConnectionDataSize,
									globals);
	}

	// return object pointer
	*storage = baseObject;

	return (DP_OK);
}

/*
 * EnumConnectionData
 *
 * Search for valid connection data
 */

static BOOL FAR PASCAL EnumAddressData(REFGUID lpguidDataType, DWORD dwDataSize,
							LPCVOID lpData, LPVOID lpContext)
{
	LPDPSERIAL			globals = (LPDPSERIAL) lpContext;
	LPDPCOMPORTADDRESS	settings = (LPDPCOMPORTADDRESS) lpData;

	// this is a com port chunk
	if ( IsEqualGUID(lpguidDataType, &DPAID_ComPort) &&
		 (dwDataSize == sizeof(DPCOMPORTADDRESS)) )
	{
		// make sure it's valid!
		if ((ValueToIndex(gComPorts, sizeof(gComPorts), settings->dwComPort) >= 0) &&
			(ValueToIndex(gBaudRates, sizeof(gBaudRates), settings->dwBaudRate) >= 0) &&
			(ValueToIndex(gStopBits, sizeof(gStopBits), settings->dwStopBits) >= 0) &&
			(ValueToIndex(gParities, sizeof(gParities), settings->dwParity) >= 0) &&
			(ValueToIndex(gFlowControls, sizeof(gFlowControls), settings->dwFlowControl) >= 0))
		{
			globals->settings = *settings;		// copy the data
			globals->bHaveSettings = TRUE;		// we have valid settings
		}
	}

	return (TRUE);
}

/*
 * DisposeSerial
 *
 * Dispose serial port object.
 */

static HRESULT DisposeSerial(LPDPCOMPORT baseObject)
{
	LPDPSERIAL	globals = (LPDPSERIAL) baseObject;

	// make sure we are disconnected
	DisconnectSerial(baseObject);

	// free object
	GlobalFreePtr((HGLOBAL) baseObject);

	return (DP_OK);
}

/*
 * ConnectSerial
 *
 * Open serial port and configure based on user settings.
 */

static HRESULT ConnectSerial(LPDPCOMPORT baseObject,
							 BOOL bWaitForConnection, BOOL bReturnStatus)
{
	LPDPSERIAL	globals = (LPDPSERIAL) baseObject;
	HANDLE		hCom;
	TCHAR		portName[10];
	HRESULT		hr;

	// see if com port is already connected
	hCom = baseObject->GetHandle(baseObject);
	if (hCom)
		return (DP_OK);

	// ask user for settings if we have not already
	if (!globals->bHaveSettings)
	{
		if (!GetSerialSettings(ghInstance, GetForegroundWindow(), globals))
		{
			hr = DPERR_USERCANCEL;
			goto Failure;
		}

		globals->bHaveSettings = TRUE;
	}

	// open specified com port
	CopyMemory(portName, "COM0", 5);
	portName[3] += (BYTE) globals->settings.dwComPort;

	hCom = CreateFile(	portName,
						GENERIC_READ | GENERIC_WRITE,
						0,    /* comm devices must be opened w/exclusive-access */
						NULL, /* no security attrs */
						OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */
						FILE_ATTRIBUTE_NORMAL | 
						FILE_FLAG_OVERLAPPED, // overlapped I/O
						NULL  /* hTemplate must be NULL for comm devices */
						);

	if (hCom == INVALID_HANDLE_VALUE)
	{
		hCom = NULL;
		hr = HRESULT_FROM_WIN32(GetLastError());
		goto Failure;
	}

	// configure com port to proper settings
	if (!SetupConnection(hCom, &globals->settings))
	{
		hr = HRESULT_FROM_WIN32(GetLastError());
		goto Failure;
	}

	// setup com port
	hr = baseObject->Setup(baseObject, hCom);
	if FAILED(hr)
		goto Failure;

	return (DP_OK);

Failure:
	if (hCom)
		CloseHandle(hCom);

	return (hr);
}

/*
 * DisconnectSerial
 *
 * Close serial port.
 */

static HRESULT DisconnectSerial(LPDPCOMPORT baseObject)
{
	HANDLE		hCom;
	HRESULT		hr;

	hCom = baseObject->GetHandle(baseObject);

	// com port is already disconnected
	if (hCom == NULL)
		return (DP_OK);

	// shut down com port
	hr = baseObject->Shutdown(baseObject);

	// close com port
	CloseHandle(hCom);

	return (hr);
}

/*
 * SetupConnection
 *
 * Configure serial port with specified settings.
 */

static BOOL SetupConnection(HANDLE hCom, LPDPCOMPORTADDRESS portSettings)
{
	DCB		dcb;

	dcb.DCBlength = sizeof(DCB);
	if (!GetCommState(hCom, &dcb))
		return (FALSE);

	// setup various port settings

	dcb.fBinary = TRUE;
	dcb.BaudRate = portSettings->dwBaudRate;
	dcb.ByteSize = 8;
	dcb.StopBits = (BYTE) portSettings->dwStopBits;

	dcb.Parity = (BYTE) portSettings->dwParity;
	if (portSettings->dwParity == NOPARITY)
		dcb.fParity = FALSE;
	else
		dcb.fParity = TRUE;

	// setup hardware flow control

	if ((portSettings->dwFlowControl == DPCPA_DTRFLOW) ||
		(portSettings->dwFlowControl == DPCPA_RTSDTRFLOW))
	{
		dcb.fOutxDsrFlow = TRUE;
		dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
	}
	else
	{
		dcb.fOutxDsrFlow = FALSE;
		dcb.fDtrControl = DTR_CONTROL_ENABLE;
	}

	if ((portSettings->dwFlowControl == DPCPA_RTSFLOW) ||
		(portSettings->dwFlowControl == DPCPA_RTSDTRFLOW))
	{
		dcb.fOutxCtsFlow = TRUE;
		dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
	}
	else
	{
		dcb.fOutxCtsFlow = FALSE;
		dcb.fRtsControl = RTS_CONTROL_ENABLE;
	}

	// setup software flow control

	if (portSettings->dwFlowControl == DPCPA_XONXOFFFLOW)
	{
		dcb.fInX = TRUE;
		dcb.fOutX = TRUE;
	}
	else
	{
		dcb.fInX = FALSE;
		dcb.fOutX = FALSE;
	}

	dcb.XonChar = ASCII_XON;
	dcb.XoffChar = ASCII_XOFF;
	dcb.XonLim = 100;
	dcb.XoffLim = 100;

	if (!SetCommState( hCom, &dcb ))
	   return (FALSE);

	return (TRUE);
}

/*
 * GetSerialAddress
 *
 * Return current serial port info if available.
 */

static HRESULT GetSerialAddress(LPDPCOMPORT baseObject, DWORD dwPlayerFlags,
								LPVOID lpAddress, LPDWORD lpdwAddressSize)
{
	LPDPSERIAL	globals = (LPDPSERIAL) baseObject;
	HRESULT		hResult;

	// no settings yet
	if (!globals->bHaveSettings)
		return (DPERR_UNAVAILABLE);

	hResult = baseObject->lpDPlay->lpVtbl->CreateAddress(baseObject->lpDPlay,
						&DPSERIAL_GUID, &DPAID_ComPort,
						&globals->settings, sizeof(DPCOMPORTADDRESS),
						lpAddress, lpdwAddressSize);

	return (hResult);
}

/*
 * GetSerialAddressChoices
 *
 * Return current serial address choices
 */

static HRESULT GetSerialAddressChoices(LPDPCOMPORT baseObject,
									   LPVOID lpAddress, LPDWORD lpdwAddressSize)
{
	LPDPSERIAL	globals = (LPDPSERIAL) baseObject;

	// currently the serial provider does not support any choices
	return (E_NOTIMPL);
}

/*
 * GetComPortSettings
 *
 * Displays a dialog to gather and return the COM port settings.
 */

static BOOL GetSerialSettings(HINSTANCE hInstance, HWND hWndParent, LPDPSERIAL globals)
{
	INT_PTR	iResult;

    iResult = (INT_PTR)DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SETTINGSDIALOG), hWndParent, SettingsDialog, (LPARAM) globals);
	return (iResult > 0);
}

/*
 * SettingsDialog
 *
 * The dialog callback routine to display and edit the COM port settings.
 */

static UINT_PTR CALLBACK SettingsDialog(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	LPDPSERIAL			globals = (LPDPSERIAL) GetWindowLongPtr(hDlg, DWLP_USER);
	HWND				hWndCtl;
	BOOL				msgHandled = FALSE;
    
	switch (msg)
	{
	case WM_INITDIALOG:
		// serial info pointer passed in lParam
		globals = (LPDPSERIAL) lParam;

         // save the globals with the window
		SetWindowLongPtr(hDlg, DWLP_USER, (LONG_PTR) globals);

		hWndCtl = GetDlgItem(hDlg, IDC_COMPORT);

		// make sure our dialog item is there
		if (hWndCtl == NULL)
		{
			EndDialog(hDlg, FALSE);
			msgHandled = TRUE;
		}
		else
		{
			InitDialog(hDlg, &globals->settings);	// setup our dialog
			SetFocus(hWndCtl);				// focus on com port combo box
			msgHandled = FALSE;				// keep windows from setting input focus for us
		}
		break;

    case WM_COMMAND:

		if (HIWORD(wParam) == 0)
		{
			switch (LOWORD(wParam))
			{
			case IDOK:						// return settings
				GetSettingsFromDialog(hDlg, &globals->settings);
				EndDialog(hDlg, TRUE);
				msgHandled = TRUE;
 				break;

			case IDCANCEL:					// cancel
				EndDialog(hDlg, FALSE);
				msgHandled = TRUE;
 				break;
			}
		}
		break;
    }

    return (msgHandled);
}

/*
 * InitDialog
 *
 * Initialize the dialog controls to display the given COM port settings.
 */

static void InitDialog(HWND hDlg, LPDPCOMPORTADDRESS settings)
{
	// fill dialog combo boxes with items from string table
	FillComboBox(hDlg, IDC_COMPORT, IDS_COM1, IDS_COM4);
	FillComboBox(hDlg, IDC_BAUDRATE, IDS_BAUD1, IDS_BAUD15);
	FillComboBox(hDlg, IDC_STOPBITS, IDS_STOPBIT1, IDS_STOPBIT3);
	FillComboBox(hDlg, IDC_PARITY, IDS_PARITY1, IDS_PARITY4);
	FillComboBox(hDlg, IDC_FLOW, IDS_FLOW1, IDS_FLOW5);

	// select default values in combo boxes
	SendDlgItemMessage(hDlg, IDC_COMPORT, CB_SETCURSEL,
					   ValueToIndex(gComPorts, sizeof(gComPorts), settings->dwComPort), 0);
	SendDlgItemMessage(hDlg, IDC_BAUDRATE, CB_SETCURSEL,
					   ValueToIndex(gBaudRates, sizeof(gBaudRates), settings->dwBaudRate), 0);
	SendDlgItemMessage(hDlg, IDC_STOPBITS, CB_SETCURSEL,
					   ValueToIndex(gStopBits, sizeof(gStopBits), settings->dwStopBits), 0);
	SendDlgItemMessage(hDlg, IDC_PARITY, CB_SETCURSEL,
					   ValueToIndex(gParities, sizeof(gParities), settings->dwParity), 0);
	SendDlgItemMessage(hDlg, IDC_FLOW, CB_SETCURSEL,
					   ValueToIndex(gFlowControls, sizeof(gFlowControls), settings->dwFlowControl), 0);
}

/*
 * GetSettingsFromDialog
 *
 * Get the COM port settings from the dialog controls.
 */

static void GetSettingsFromDialog(HWND hDlg, LPDPCOMPORTADDRESS settings)
{
	INT_PTR		index;

	index = SendDlgItemMessage(hDlg, IDC_COMPORT, CB_GETCURSEL, 0, 0);
	if (index == CB_ERR)
		return;

	settings->dwComPort = gComPorts[index];

	index = SendDlgItemMessage(hDlg, IDC_BAUDRATE, CB_GETCURSEL, 0, 0);
	if (index == CB_ERR)
		return;

	settings->dwBaudRate = gBaudRates[index];

	index = SendDlgItemMessage(hDlg, IDC_STOPBITS, CB_GETCURSEL, 0, 0);
	if (index == CB_ERR)
		return;

	settings->dwStopBits = gStopBits[index];

	index = SendDlgItemMessage(hDlg, IDC_PARITY, CB_GETCURSEL, 0, 0);
	if (index == CB_ERR)
		return;

	settings->dwParity = gParities[index];

	index = SendDlgItemMessage(hDlg, IDC_FLOW, CB_GETCURSEL, 0, 0);
	if (index == CB_ERR)
		return;

	settings->dwFlowControl = gFlowControls[index];
}

/*
 * FillComboBox
 *
 * Add the specified strings to the combo box.
 */

#define MAXSTRINGSIZE	200

static void FillComboBox(HWND hDlg, int dlgItem, int startStr, int stopStr)
{
	int		i;
	TCHAR	str[MAXSTRINGSIZE];

	for (i = startStr; i <= stopStr; i++)
	{
		if (LoadString(ghInstance, i, str, MAXSTRINGSIZE))
			SendDlgItemMessage(hDlg, dlgItem, CB_ADDSTRING, (WPARAM) 0, (LPARAM) str);
	}
}

/*
 * ValueToIndex
 *
 * Convert a settings value to a combo box selection index.
 */

static int ValueToIndex(LPDWORD buf, int bufLen, DWORD value)
{
	int		i;

	bufLen /= sizeof(DWORD);
	for (i = 0; i < bufLen; i++)
		if (buf[i] == value)
			return (i);

	return (-1);
}

