// riovjoy2.cpp : This file contains the 'main' function. Program execution begins and ends there.
//


#include <windows.h>
#include <iostream>
#include <thread>
#include <iomanip>
#include "SimpleIni.h"
#include "vjoyinterface.h"
#include "public.h"

#define  _CRT_SECURE_NO_WARNINGS 
#pragma warning(disable : 4996) 
#define ESC		27

class C232Comm
{
private:
	HANDLE m_hCom;
	int m_nPort;
	DCB m_dcb;
	COMMTIMEOUTS m_commTimeOuts;
	COMMCONFIG m_cc;
	COMMPROP m_cp;
public:
	C232Comm();
	virtual ~C232Comm();
	BOOL IsValid() const { return m_hCom != INVALID_HANDLE_VALUE; }
	BOOL Open(int nPort, DWORD dwBaudRate = CBR_9600, BYTE dwParity = NOPARITY, BYTE bByteSize = 8, BYTE dwStopBits = ONESTOPBIT);
	void Close();
	int rxCom(void* pData, int nLen);
	BOOL rxComLoop(void* pData, int nLen);
	int txCom(const void* pData, int nLen);
	BOOL txComLoop(const void* pData, int nLen);
	void locProcessCommError();
	int CommunicationConfig(HWND hWndParent);
};
const char g_szPortFormat[] = "\\\\.\\com%d"; // fh trying to resolve C2664 compile error by explicitly converting the char[32] sz to LPCWSTR lpwsz

class CPlasma : public C232Comm
{
public:
	DWORD m_dwStart;
	int m_nOldRank;
	int m_nOldScore;
	enum {
		ATTR_NORMAL = 0,
		ATTR_HALF = 1,
		ATTR_LINE = 2,
		ATTR_REVERSE = 4,
		ATTR_FLASH = 8,
		ATTR_PROTECTED = 128
	};
	enum { PLASMA_OFF, PLASMA_ON, PLASMA_FLASH };

	int m_nGlobalState;
	//    Time m_timeLastPlasma2;
public:
	CPlasma() {
		Open(2);
		m_dwStart = GetTickCount();
		PlasmaClear();
		PlasmaCursor(CPlasma::PLASMA_OFF);
		m_nOldRank = INT_MIN;
		m_nOldScore = INT_MIN;

		m_nGlobalState = -1;
	}
	virtual ~CPlasma() {
		PlasmaCursor(CPlasma::PLASMA_OFF);
		PlasmaClear();
		Close();
	}
public:
	void SendPacket(const BYTE* pbPacket, char nLen);
	void PlasmaClear();

	void PlasmaCursor(char n = PLASMA_OFF);
	void PlasmaCursorHome();
	void PlasmaCursorX(char n);
	void PlasmaCursorY(char n);

	void PlasmaFontAttr(char n);
	void PlasmaFont(char n);

	void PlasmaText(const char* szText);
	POINT GetFontSize(int Font);

	void PlasmaBoxDraw(RECT tag);
	void PlasmaBoxFill(RECT tag);
	void PlasmaPosText(const char* szText, char x = 0, char y = 0, char Attr = 0, char Font = 5);
	void PlasmaScoreDraw(const char* Rank, const char* Score, BOOL bFlag);

	void DoPlasma(char nMode);
private:
	void PlasmaDisplay(int Gamestate, const char* szName, const char* Rank, const char* Score);
};

const char* sINI{ new char[32] {"c:\\games\\RIO\\RIO.ini"} };
const char* sGreeting{ new char[16] {"RIOvJoy v.03"} };
const char* sPlayer{ new char[16] {"Player"} };
const char* sDisplay{ new char[24] {"Pod PowerUp Detected"} };
const char* newDesktop{ new char[32] {"C:\\games\\RIO\\VWE2.bmp"} };
bool* invertX{ new bool{0} };
bool* invertY{ new bool{0} };
bool* invertZ{ new bool{0} };
bool* invertXR{ new bool{0} };
bool* invertYR{ new bool{0} };
bool* enableZR{ new bool{0} };
bool* invertZR{ new bool{0} };
int* iRIO{ new int[112] {} };

LONG g_Throttle;
LONG g_LeftPedal;
LONG g_RightPedal;
LONG g_JoystickY;
LONG g_JoystickX;
LONG g_Rudder;
LONG g_ThrottleResult = -1L;
LONG g_HatResult = -1L;

LONG g_ThrottleStart = INT_MIN;
LONG g_LeftPedalStart = INT_MAX;
LONG g_RightPedalStart = INT_MAX;
LONG g_JoystickXLast = 16383L;
LONG g_JoystickYLast = 16383L;
LONG g_ThrottleLast = 16383L;
LONG g_LeftPedalLast = 16383L;
LONG g_RightPedalLast = 16383L;
LONG g_RudderLast = 16838L;
LONG maxt = 0;
LONG mint = 0;
LONG maxl = 0;
LONG minl = 0;
LONG maxr = 0;
LONG minr = 0;
LONG maxx = 0;
LONG minx = 0;
LONG maxy = 0;
LONG miny = 0;
BOOL g_rawaxes = false;
bool enablecount = false;
int count = 0;
int now = GetTickCount();
int last = GetTickCount();
int lastAnalogEvent = GetTickCount();


static int g_nOpenComState = 0;

#define ASCII_XON       0x11
#define ASCII_XOFF      0x13
#define ACK_CHAR		0xFC
#define NAK_CHAR		0xFD
#define RESTART_CHAR	0xFE
#define IDLE_CHAR		0xFF

static HANDLE g_hCom = INVALID_HANDLE_VALUE;
static OVERLAPPED wos;
static OVERLAPPED ros;
static OVERLAPPED wcos;//WaitCommEvent
static HANDLE g_hCommWatchThread = NULL;
static HANDLE g_hWatchEvent = NULL;
static HANDLE g_hLampEvent = NULL;
static DWORD g_dwThreadID;
static BYTE packetbytes[16];
static int packetbyteremain = 0;
static int chinpacket = 0;
static int g_naButtonThrottles[8];
static BYTE g_baRIOLengthsA[] = {
	0,		// 0.CheckRequest
	0,		// 1.VersionRequest
	0,		// 2.AnalogRequest
	1,		// 3.ResetRequest
	2,		// 4.LampRequest
	2,		// 5.CheckReply
	2,		// 6.VersionReply
	10,		// 7.AnalogReply
	1,		// 8.ButtonPressed
	1,		// 9.ButtonReleased
	2,		// 10.KeyPressed
	2,		// 11.KeyReleased
	1,		// 12.TestModeChange
};
int g_nRIOPacketCountA = (sizeof(g_baRIOLengthsA) / sizeof(g_baRIOLengthsA[0]));
BYTE* g_pbRIOLengths = NULL;
int g_nRIOPacketCount = 0;
enum RIOCommand {
	rio_CheckRequest = 0x80, // 128
	rio_VersionRequest,		 // 129
	rio_AnalogRequest,		 // 130
	rio_ResetRequest,		 // 131
	rio_LampRequest,		 // 132
	rio_CheckReply,          // 133
	rio_VersionReply,        // 134
	rio_AnalogReply,         // 135
	rio_ButtonPressed,       // 136
	rio_ButtonReleased,      // 137
	rio_KeyPressed,          // 138
	rio_KeyReleased,         // 139
	rio_TestModeChange,      // 140
};
enum RIOStatusType {
	BoardOk,
	BoardMissing,
	BoardBad,
	LampBad,
	RestartCount,
	AbandonCount,
	FullBufferCount
};
enum LampState {
	solid = 0, flashSlow = 1, flashMed = 2, flashFast = 3,
	state1Off = 0x00, state1Dim = 0x04, state1Bright = 0x0C,
	state2Off = 0x00, state2Dim = 0x10, state2Bright = 0x30,
};
enum {
	LampSolidOff = solid + state1Off + state2Off,
	LampSolidDim = solid + state1Dim + state2Dim,
	LampSolidBright = solid + state1Bright + state2Bright
};
DWORD g_dwLastBytesToWrite = 0;
DWORD g_dCancelCount = 0;
BYTE g_baLastPacket[20];
static bool _continue;

CSimpleIniA ini = new CSimpleIni;

///////////////////////////
// forward declarations  //
///////////////////////////
void PrintMenu();
bool ReadyvJoy();
char* getCmdOption(char** begin, char** end, const std::string& option);
bool cmdOptionExists(char** begin, char** end, const std::string& option);

BOOL SetupConnection(HANDLE hCom);
HANDLE OpenConnection(int port);
BOOL CloseConnection();
void TransmitCommChar2(HANDLE g_hCom, int ch);
int  ReadCommBlock(LPSTR lpszBlock, int nMaxLength);
BOOL WriteCommBlock(LPSTR lpByte, DWORD dwBytesToWrite);
DWORD FAR PASCAL CommWatchProc(LPSTR lpData);
short CombinePair(BYTE low_value, BYTE high_value);
void AnalogEvent(BYTE* pData);
void ButtonEvent(BYTE* pData);
void KeypadEvent(BYTE* pData);
BOOL SendPacket(const BYTE* ba, int length);
BOOL SendCommand(const BYTE* pbPacket);
BOOL SetLamp(int lampNumber, int state);
void AnalogAllReset();
void GeneralReset();
void ResetThrottle();
void ResetLeftPedal();
void ResetRightPedal();
void ResetVerticalJoystick();
void ResetHorizontalJoystick();
void RequestCheck();
void CheckReply(BYTE* pData);
const char* GetLampName(int lamp_number);
const char* GetBoardName(int board_number);
void RequestVersion();
void VersionReply(BYTE* pData);
void RequestAnalogUpdate();
void UpdateJoystick();
void UpdateThrottle();
void UpdatePadal();
void TestModeChange(BYTE* pData);
void RIOcmd(unsigned char ucValue);
static void Press_V2(unsigned char iAddress);
static void Release_V2(unsigned char iAddress);
static void ModKeySHIFT(bool bIsPressed);
static void ModKeyCTRL(bool bIsPressed);
static void ModKeyALT(bool bIsPressed);
static void Key(unsigned char ucwVk, bool bIsPressed, bool bIsExtended);
static void Mouse(unsigned char ucValue, bool bIsPressed);

///////////////////
// Main function //
///////////////////
int main(int argc, char** argv)
{

	_continue = true;	// the show stopper set to false and watch the world burn
	std::cout << "Useage: riovjoy.exe -f [ini file path] -p [\"Pilot\"] [console]\n";

	// check the command line for any arguments
	if (cmdOptionExists(argv, argv + argc, "console"))
	{
		std::cout << "console enabled" << std::endl;
	}
	else {
		HWND hWnd = GetConsoleWindow();
		ShowWindow(hWnd, SW_MINIMIZE);
	}

	char* inifilename = getCmdOption(argv, argv + argc, "-f");
	if (inifilename)
	{
		std::cout << "Will try " << inifilename << std::endl;
		sINI = inifilename;
	}
	else {
		std::cout << "Will try default C:\\games\\RIO\\RIO.ini\n";
	}

	char* oPlayer{ new char[16] {} };
	oPlayer = getCmdOption(argv, argv + argc, "-p");
	if (oPlayer)
	{
		std::cout << "player name supplied: " << oPlayer << std::endl;
		sPlayer = oPlayer;
	}
	else {
		std::cout << "no player name supplied\n";
	}

	//Read in INI config data
	switch (ini.LoadFile(sINI))
	{
	case SI_OK:
		std::cout << "Found: " << sINI << std::endl;
		break;
	case SI_UPDATED:
		std::cout << "updated INI file\n";
		break;
	case SI_INSERTED:
		std::cout << "A new value was inserted\n";
		break;
	case SI_FAIL:
		std::cout << "INI Generic failure\n";
		break;
	case SI_NOMEM:
		std::cout << "Ran Out of memory error loading INI\n";
		break;
	case SI_FILE:
		std::cout << "INI File not found\n";
		break;
	};

	// set all of the varables 
	newDesktop = ini.GetValue("Desktop", "File", "C:\\games\\RIO\\VWE2.bmp", 0);
	sGreeting = ini.GetValue("Plasma", "Greeting", "RIOvJoy2 v.03", 0);
	*invertX = ini.GetBoolValue("JoyStick", "invertX", false);
	*invertY = ini.GetBoolValue("JoyStick", "invertY", false);
	*invertZ = ini.GetBoolValue("JoyStick", "invertZ", false);
	*invertXR = ini.GetBoolValue("JoyStick", "invertXR", false);
	*invertYR = ini.GetBoolValue("JoyStick", "invertYR", false);
	*enableZR = ini.GetBoolValue("JoyStick", "enableZR", true);
	*invertZR = ini.GetBoolValue("JoyStick", "invertZR", false);
	iRIO[0] = ini.GetLongValue("Buttons", "RIO00", 0x8049);
	iRIO[1] = ini.GetLongValue("Buttons", "RIO01", 0x805A);
	iRIO[2] = ini.GetLongValue("Buttons", "RIO02", 0x8055);
	iRIO[3] = ini.GetLongValue("Buttons", "RIO03", 0x804C);
	iRIO[4] = ini.GetLongValue("Buttons", "RIO04", 0x8926);
	iRIO[5] = ini.GetLongValue("Buttons", "RIO05", 0x8928);
	iRIO[6] = ini.GetLongValue("Buttons", "RIO06", 0x8927);
	iRIO[7] = ini.GetLongValue("Buttons", "RIO07", 0x8925);
	iRIO[8] = ini.GetLongValue("Buttons", "RIO08", 0x8051);
	iRIO[9] = ini.GetLongValue("Buttons", "RIO09", 0x8059);
	iRIO[10] = ini.GetLongValue("Buttons", "RIO0A", 0x8045);
	iRIO[11] = ini.GetLongValue("Buttons", "RIO0B", 0x8052);
	iRIO[12] = ini.GetLongValue("Buttons", "RIO0C", 0x8823);
	iRIO[13] = ini.GetLongValue("Buttons", "RIO0D", 0x80BE);
	iRIO[14] = ini.GetLongValue("Buttons", "RIO0E", 0x80BC);
	iRIO[15] = ini.GetLongValue("Buttons", "RIO0F", 0x8824);
	iRIO[16] = ini.GetLongValue("Buttons", "RIO10", 0xC000);
	iRIO[17] = ini.GetLongValue("Buttons", "RIO11", 0xC001);
	iRIO[18] = ini.GetLongValue("Buttons", "RIO12", 0xC002);
	iRIO[19] = ini.GetLongValue("Buttons", "RIO13", 0xC003);
	iRIO[20] = ini.GetLongValue("Buttons", "RIO14", 0xC004);
	iRIO[21] = ini.GetLongValue("Buttons", "RIO15", 0x8009);
	iRIO[22] = ini.GetLongValue("Buttons", "RIO16", 0x0);
	iRIO[23] = ini.GetLongValue("Buttons", "RIO17", 0x0);
	iRIO[24] = ini.GetLongValue("Buttons", "RIO18", 0x800D);
	iRIO[25] = ini.GetLongValue("Buttons", "RIO19", 0x8012);
	iRIO[26] = ini.GetLongValue("Buttons", "RIO1A", 0x8011);
	iRIO[27] = ini.GetLongValue("Buttons", "RIO1B", 0x8010);
	iRIO[28] = ini.GetLongValue("Buttons", "RIO1C", 0x88AF);
	iRIO[29] = ini.GetLongValue("Buttons", "RIO1D", 0x88AE);
	iRIO[30] = ini.GetLongValue("Buttons", "RIO1E", 0x0);
	iRIO[31] = ini.GetLongValue("Buttons", "RIO1F", 0x0);
	iRIO[32] = ini.GetLongValue("Buttons", "RIO20", 0x8827);
	iRIO[33] = ini.GetLongValue("Buttons", "RIO21", 0x8828);
	iRIO[34] = ini.GetLongValue("Buttons", "RIO22", 0x8826);
	iRIO[35] = ini.GetLongValue("Buttons", "RIO23", 0x8825);
	iRIO[36] = ini.GetLongValue("Buttons", "RIO24", 0x804E);
	iRIO[37] = ini.GetLongValue("Buttons", "RIO25", 0x804D);
	iRIO[38] = ini.GetLongValue("Buttons", "RIO26", 0x804C);
	iRIO[39] = ini.GetLongValue("Buttons", "RIO27", 0x804B);
	iRIO[40] = ini.GetLongValue("Buttons", "RIO28", 0x8056);
	iRIO[41] = ini.GetLongValue("Buttons", "RIO29", 0x8055);
	iRIO[42] = ini.GetLongValue("Buttons", "RIO2A", 0x8054);
	iRIO[43] = ini.GetLongValue("Buttons", "RIO2B", 0x8053);
	iRIO[44] = ini.GetLongValue("Buttons", "RIO2C", 0x804A);
	iRIO[45] = ini.GetLongValue("Buttons", "RIO2D", 0x8049);
	iRIO[46] = ini.GetLongValue("Buttons", "RIO2E", 0x8048);
	iRIO[47] = ini.GetLongValue("Buttons", "RIO2F", 0x8047);
	iRIO[48] = ini.GetLongValue("Buttons", "RIO30", 0x805A);
	iRIO[49] = ini.GetLongValue("Buttons", "RIO31", 0x8059);
	iRIO[50] = ini.GetLongValue("Buttons", "RIO32", 0x8058);
	iRIO[51] = ini.GetLongValue("Buttons", "RIO33", 0x8057);
	iRIO[52] = ini.GetLongValue("Buttons", "RIO34", 0x8052);
	iRIO[53] = ini.GetLongValue("Buttons", "RIO35", 0x8051);
	iRIO[54] = ini.GetLongValue("Buttons", "RIO36", 0x8050);
	iRIO[55] = ini.GetLongValue("Buttons", "RIO37", 0x804F);
	iRIO[56] = ini.GetLongValue("Buttons", "RIO38", 0x4000);
	iRIO[57] = ini.GetLongValue("Buttons", "RIO39", 0x4001);
	iRIO[58] = ini.GetLongValue("Buttons", "RIO3A", 0x4002);
	iRIO[59] = ini.GetLongValue("Buttons", "RIO3B", 0x4003);
	iRIO[60] = ini.GetLongValue("Buttons", "RIO3C", 0x4004);
	iRIO[61] = ini.GetLongValue("Buttons", "RIO3D", 0x801B);
	iRIO[62] = ini.GetLongValue("Buttons", "RIO3E", 0x4005);
	iRIO[63] = ini.GetLongValue("Buttons", "RIO3F", 0x1008);
	iRIO[64] = ini.GetLongValue("Buttons", "RIO40", 0x1001);
	iRIO[65] = ini.GetLongValue("Buttons", "RIO41", 0x2002);
	iRIO[66] = ini.GetLongValue("Buttons", "RIO42", 0x2000);
	iRIO[67] = ini.GetLongValue("Buttons", "RIO43", 0x2001);
	iRIO[68] = ini.GetLongValue("Buttons", "RIO44", 0x2003);
	iRIO[69] = ini.GetLongValue("Buttons", "RIO45", 0x1004);
	iRIO[70] = ini.GetLongValue("Buttons", "RIO46", 0x1003);
	iRIO[71] = ini.GetLongValue("Buttons", "RIO47", 0x1002);
	iRIO[72] = ini.GetLongValue("Buttons", "RIO48", 0x0);
	iRIO[73] = ini.GetLongValue("Buttons", "RIO49", 0x0);
	iRIO[74] = ini.GetLongValue("Buttons", "RIO4A", 0x0);
	iRIO[75] = ini.GetLongValue("Buttons", "RIO4B", 0x0);
	iRIO[76] = ini.GetLongValue("Buttons", "RIO4C", 0x0);
	iRIO[77] = ini.GetLongValue("Buttons", "RIO4D", 0x0);
	iRIO[78] = ini.GetLongValue("Buttons", "RIO4E", 0x0);
	iRIO[79] = ini.GetLongValue("Buttons", "RIO4F", 0x0);
	iRIO[80] = ini.GetLongValue("Buttons", "RIO50", 0x30);
	iRIO[81] = ini.GetLongValue("Buttons", "RIO51", 0x31);
	iRIO[82] = ini.GetLongValue("Buttons", "RIO52", 0x32);
	iRIO[83] = ini.GetLongValue("Buttons", "RIO53", 0x33);
	iRIO[84] = ini.GetLongValue("Buttons", "RIO54", 0x34);
	iRIO[85] = ini.GetLongValue("Buttons", "RIO55", 0x35);
	iRIO[86] = ini.GetLongValue("Buttons", "RIO56", 0x36);
	iRIO[87] = ini.GetLongValue("Buttons", "RIO57", 0x37);
	iRIO[88] = ini.GetLongValue("Buttons", "RIO58", 0x38);
	iRIO[89] = ini.GetLongValue("Buttons", "RIO59", 0x39);
	iRIO[90] = ini.GetLongValue("Buttons", "RIO5A", 0x41);
	iRIO[91] = ini.GetLongValue("Buttons", "RIO5B", 0x42);
	iRIO[92] = ini.GetLongValue("Buttons", "RIO5C", 0x43);
	iRIO[93] = ini.GetLongValue("Buttons", "RIO5D", 0x44);
	iRIO[94] = ini.GetLongValue("Buttons", "RIO5E", 0x45);
	iRIO[95] = ini.GetLongValue("Buttons", "RIO5F", 0x46);
	iRIO[96] = ini.GetLongValue("Buttons", "RIO60", 0x1051);
	iRIO[97] = ini.GetLongValue("Buttons", "RIO61", 0x1052);
	iRIO[98] = ini.GetLongValue("Buttons", "RIO62", 0x1053);
	iRIO[99] = ini.GetLongValue("Buttons", "RIO63", 0x1054);
	iRIO[100] = ini.GetLongValue("Buttons", "RIO64", 0x1055);
	iRIO[101] = ini.GetLongValue("Buttons", "RIO65", 0x1056);
	iRIO[102] = ini.GetLongValue("Buttons", "RIO66", 0x1057);
	iRIO[103] = ini.GetLongValue("Buttons", "RIO67", 0x1058);
	iRIO[104] = ini.GetLongValue("Buttons", "RIO68", 0x1059);
	iRIO[105] = ini.GetLongValue("Buttons", "RIO69", 0x105A);
	iRIO[106] = ini.GetLongValue("Buttons", "RIO6A", 0x105B);
	iRIO[107] = ini.GetLongValue("Buttons", "RIO6B", 0x105C);
	iRIO[108] = ini.GetLongValue("Buttons", "RIO6C", 0x105D);
	iRIO[109] = ini.GetLongValue("Buttons", "RIO6D", 0x105E);
	iRIO[110] = ini.GetLongValue("Buttons", "RIO6E", 0x105F);
	iRIO[111] = ini.GetLongValue("Buttons", "RIO6F", 0x1060);

	// if the Vjoy setup fails set _continue to false and let the program die.
	if (!ReadyvJoy()) {
//		std::cout << "vJoy setup failed, exiting\n";
//		_continue = false;
//		return 1;
	}

	//start the RIO shinanigins

	OpenConnection(1);
	RequestVersion();
	RequestCheck();


	Sleep(1000);						// to allow the comread thread to get set up and rolling the analog updates
	for (int b = 0; b < 72; b++) {
		if (iRIO[b] & 0x8000)
			SetLamp(b, LampSolidDim);
	}


	// try to open com2 to plasma display, else no display for you
	CPlasma* _Plasma = new CPlasma;
	if (_Plasma->IsValid()) {
		std::cout << "Com2 open\n";
		_Plasma->PlasmaPosText(sDisplay);
		std::string playergreeting = std::string(sGreeting) + "," + std::string(sPlayer);
		Sleep(3000);
		_Plasma->PlasmaClear();
		_Plasma->PlasmaPosText(playergreeting.c_str());
	}
	else {
		std::cout << "No Display for you" << std::endl;
	}

	/////////////////////////////////////////////
	// code block to check the desktop image   //
	/////////////////////////////////////////////
	//get current desktop image, store for replacement
	wchar_t * worgDesktop = new wchar_t[MAX_PATH];
	SystemParametersInfoW(SPI_GETDESKWALLPAPER, MAX_PATH, worgDesktop, 0);
	//set desktop from ini
	int wchar_numnew = MultiByteToWideChar( CP_UTF8 , 0 , newDesktop , -1 , NULL , 0 );
	wchar_t* wnewDesktop = new wchar_t[wchar_numnew];
	MultiByteToWideChar(CP_UTF8, 0, newDesktop, -1, wnewDesktop, wchar_numnew);
	bool setnewdesktop = SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, wnewDesktop, SPIF_UPDATEINIFILE);
	wnewDesktop = nullptr;
	/////////////////////////////////////////////

	//Main while wait loop
	while (_continue) // wait loop
	{

		PrintMenu();

	}

	// quit signal clean up 
	// close com2 to plasma
	if (_Plasma->IsValid()) {
		std::cout << "Closing Com2 to Plasma\n";
		_Plasma->~CPlasma();
	}

	// end RIO 
	GeneralReset();
	CloseConnection();


	// relinquish vjoy device
	RelinquishVJD(1);
	std::cout << "Relinquished vJoy Device\n";

	// set desktop back to original
	SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, worgDesktop, SPIF_UPDATEINIFILE);
	worgDesktop = nullptr;

	return 0;
}

/////////////////
//  Functions  //
/////////////////

void PrintMenu()
{
	char myChoice = 'Z';

	std::cout << "**********  Menu **********" << std::endl;
	std::cout << "(0): Reset all analog axes" << std::endl;
	std::cout << "(1): Reset Throttle analog axes" << std::endl;
	std::cout << "(2): Reset LeftPedal analog axes" << std::endl;
	std::cout << "(3): Reset RightPedal analog axes" << std::endl;
	std::cout << "(4): Reset VerticalJoystick analog axes" << std::endl;
	std::cout << "(5): Reset HorizontalJoystick analog axes" << std::endl;
	std::cout << "(6): Reset Digital" << std::endl;
	std::cout << "(7): RequestVersion and status" << std::endl;
	std::cout << "(8): enable/disable axes readout" << std::endl;
	std::cout << "(9): enable/disable analog Frequency readout" << std::endl;
	std::cout << "(Q): quit" << std::endl;
	std::cin >> myChoice;

	switch (myChoice)
	{
	case '0':
		RIOcmd(0);
		break;
	case '1':
		RIOcmd(1);
		break;
	case '2':
		RIOcmd(2);
		break;
	case '3':
		RIOcmd(3);
		break;
	case '4':
		RIOcmd(4);
		break;
	case '5':
		RIOcmd(5);
		break;
	case '6':
		RIOcmd(6);
		break;
	case '7':
		RIOcmd(7);
		break;
	case '8':
		RIOcmd(8);
		break;
	case '9':
		RIOcmd(9);
		break;
	case 'Q':
	case 'q':
		_continue = false;
		std::cout << "Closing Application" << std::endl;
		break;
	default:
		std::cout << "ERROR! You have selected an invalid choice." << std::endl;
		break;
	}
}
bool ReadyvJoy()		// make ready the vJoy interface
{
	BYTE id = 1;										// ID of the target vjoy device (Default is 1)
	UINT iInterface = 1;								// Default target vJoy device
	//	WORD VerDll, VerDrv;

	if (!vJoyEnabled())
	{
		std::cout << "vJoy driver not enabled: Failed Getting vJoy attributes.\n";
	}
	else
	{
		//			std::cout << "vJoyEnabled on this system\n";
	};

	//if (!DriverMatch(&VerDll, &VerDrv)) {
	//	std::cout << "vJoy Driver version " << VerDrv << " does not match vJoyInterface DLL version " << VerDll << std::endl;
	//}
	//else {
	//	std::cout << "Driver and DLL match version" << VerDrv;
	//};

	// Get the state of the requested device
	VjdStat status = GetVJDStatus(iInterface);
	switch (status)
	{
	case VJD_STAT_OWN:
		//			std::cout << "vJoy Device " << iInterface << " is already owned by this feeder";
		break;
	case VJD_STAT_FREE:
		//			std::cout << "vJoy Device " << iInterface << " is free";
		break;
	case VJD_STAT_BUSY:
		std::cout << "vJoy Device " << iInterface << " is already owned by another feeder Cannot continue";
		return false;
		break;
	case VJD_STAT_MISS:
		std::cout << "vJoy Device " << iInterface << " is not installed or disabled\nCannot continue";
		return false;
		break;
	default:
		std::cout << "vJoy Device " << iInterface << " general error\nCannot continue";
		return false;
		break;
	};
	// Check which axes are supported
	BOOL AxisX = GetVJDAxisExist(iInterface, HID_USAGE_X);
	BOOL AxisY = GetVJDAxisExist(iInterface, HID_USAGE_Y);
	BOOL AxisZ = GetVJDAxisExist(iInterface, HID_USAGE_Z);
	BOOL AxisRX = GetVJDAxisExist(iInterface, HID_USAGE_RX);
	BOOL AxisRY = GetVJDAxisExist(iInterface, HID_USAGE_RY);
	BOOL AxisRZ = GetVJDAxisExist(iInterface, HID_USAGE_RZ);
	// Get the number of buttons supported and hats by this vJoy device
	int nButtons = GetVJDButtonNumber(iInterface);
	int DiscPovNumber = GetVJDDiscPovNumber(iInterface);

	if (!AxisX || !AxisY || !AxisZ || !AxisRX || !AxisRY || !AxisRZ || DiscPovNumber != 1 || nButtons != 96) {
		std::cout << "vJoy Interface not corectly setup, Please enable X,Y,Z,RX,RY,RZ, one descrete Pov Hat, and 96 buttons";
	//	return false;
	}

	// Acquire the target
	if ((status == VJD_STAT_OWN) || ((status == VJD_STAT_FREE) && (!AcquireVJD(iInterface))))
	{
		std::cout << "Failed to acquire vJoy device, exiting";
		return false;
	}
	else
	{
		std::cout << "Acquired: vJoy device." << std::endl;
		ResetVJD(1);
		return true;
	}
}
char* getCmdOption(char** begin, char** end, const std::string& option)
{
	char** itr = std::find(begin, end, option);
	if (itr != end && ++itr != end)
	{
		return *itr;
	}
	return 0;
}
bool cmdOptionExists(char** begin, char** end, const std::string& option)
{
	return std::find(begin, end, option) != end;
}
BOOL SetupConnection(HANDLE hCom)
{
	DCB dcb;
	BOOL fRetVal;
	dcb.DCBlength = sizeof(DCB);

	GetCommState(hCom, &dcb);
	dcb.BaudRate = CBR_9600;
	dcb.ByteSize = 8;
	dcb.Parity = 0;
	dcb.StopBits = 0;
	dcb.fOutxDsrFlow = 0;
	dcb.fDtrControl = DTR_CONTROL_ENABLE;
	dcb.fOutxCtsFlow = 0;
	dcb.fRtsControl = RTS_CONTROL_ENABLE;
	dcb.fInX = 0;
	dcb.fOutX = 0;
	dcb.XonChar = ASCII_XON;
	dcb.XoffChar = ASCII_XOFF;
	dcb.XonLim = 100;
	dcb.XoffLim = 100;
	dcb.fBinary = TRUE;
	dcb.fParity = TRUE;

	fRetVal = SetCommState(hCom, &dcb);
	if (fRetVal == 0)return FALSE;


	// get any early notifications
	fRetVal = SetCommMask(hCom, EV_RXCHAR);
	if (fRetVal == 0)return FALSE;

	// setup device buffers
	fRetVal = SetupComm(hCom, 2, 2);
	if (fRetVal == 0)return FALSE;

	// purge any information in the buffer
	fRetVal = PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
	if (fRetVal == 0)return FALSE;

	// set up for overlapped I/O
	COMMTIMEOUTS CommTimeOuts = { 0, };
	CommTimeOuts.ReadIntervalTimeout = 1;
	CommTimeOuts.ReadTotalTimeoutMultiplier = 1;
	CommTimeOuts.ReadTotalTimeoutConstant = 0;
	CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
	CommTimeOuts.WriteTotalTimeoutConstant = 0;
	fRetVal = SetCommTimeouts(hCom, &CommTimeOuts);
	if (fRetVal == 0)return FALSE;
	return TRUE;
}
HANDLE OpenConnection(int port)
{
	g_pbRIOLengths = &g_baRIOLengthsA[0];
	g_nRIOPacketCount = g_nRIOPacketCountA;


	//port: 1~n
	//char portname[8];
	//wsprintf(portname, "COM%d", port);

	//const char* portprefix = "COM";
	//LPWSTR portname = std::strcat(portprefix, port);


	DWORD dwFAs = FILE_ATTRIBUTE_NORMAL;

	dwFAs |= FILE_FLAG_OVERLAPPED;

	//g_hCom = CreateFile( portname, GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING, dwFAs, NULL );
	g_hCom = CreateFile(L"COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, dwFAs, NULL);
	if (g_hCom != INVALID_HANDLE_VALUE) {
		if (SetupConnection(g_hCom)) {
			ros.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
			wos.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
			g_hWatchEvent = CreateEvent(0, 0, 0, 0);
			g_hLampEvent = CreateEvent(0, FALSE, 0, 0);
			g_hCommWatchThread = CreateThread((LPSECURITY_ATTRIBUTES)NULL, 0, (LPTHREAD_START_ROUTINE)CommWatchProc, NULL, CREATE_SUSPENDED, &g_dwThreadID);
			if (g_hCommWatchThread) {
				//"Normally, were produced by Thread."
				//////////////////////////////////////////////
				//All OK
				//Exit Point	<======
				EscapeCommFunction(g_hCom, SETDTR);
				Sleep(50);
				EscapeCommFunction(g_hCom, CLRDTR);
				Sleep(1000);
				SetThreadPriority(g_hCommWatchThread, THREAD_PRIORITY_TIME_CRITICAL);
				ResumeThread(g_hCommWatchThread);
				g_nOpenComState = 1;

				return g_hCom;
			}
		}
		CloseConnection();
	}

	return INVALID_HANDLE_VALUE;
}
BOOL CloseConnection()
{
	if (g_hCom != INVALID_HANDLE_VALUE) {
		SetCommMask(g_hCom, 0);

		//Terminate Watch Thread
		if (g_hCommWatchThread) {
			SetEvent(g_hWatchEvent);
			WaitForSingleObject(g_hCommWatchThread, 1000 /*INFINITE*/); //comwatch thread not responding to temination signal
			g_hCommWatchThread = NULL;
		}

		if (g_hWatchEvent) {
			CloseHandle(g_hWatchEvent);
			g_hWatchEvent = NULL;
		}

		if (g_hLampEvent) {
			CloseHandle(g_hLampEvent);
			g_hLampEvent = NULL;
		}

		EscapeCommFunction(g_hCom, CLRDTR);

		// purge any outstanding reads/writes and close device handle
		PurgeComm(g_hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
		CloseHandle(g_hCom);
		g_hCom = INVALID_HANDLE_VALUE;

		// clean up event objects

		if (ros.hEvent) {
			CloseHandle(ros.hEvent);
			ros.hEvent = NULL;
		}
		if (wos.hEvent) {
			CloseHandle(wos.hEvent);
			wos.hEvent = NULL;
		}
		g_nOpenComState = 0;
	}

	return TRUE;

}
void TransmitCommChar2(HANDLE g_hCom, int ch)
{
	TransmitCommChar(g_hCom, (char)ch);
}
int  ReadCommBlock(LPSTR lpszBlock, int nMaxLength)
{
	BOOL       fReadStat;
	COMSTAT    ComStat;
	DWORD      dwErrorFlags;
	DWORD      dwLength;
	DWORD      dwLength2;
	DWORD      dwError;

	// only try to read number of bytes in queue
	ClearCommError(g_hCom, &dwErrorFlags, &ComStat);
	dwLength = __min((DWORD)nMaxLength, ComStat.cbInQue);
	dwLength2 = __min((DWORD)nMaxLength, ComStat.cbInQue);

	if (dwLength > 0) {
		fReadStat = ReadFile(g_hCom, lpszBlock, dwLength, &dwLength2, &ros);

		for (int i = 0; i < (int)dwLength; i++) {

			int ch = (BYTE)lpszBlock[i];


			//"Loops as received letters".
			//"Currently there is no packet receiving .. .. The state can come any control characters ."

			if (packetbyteremain != 0) {
				//"The letters arrived for the packet .. get the rest of the characters ."
				if (ch & 0x80) {
					packetbyteremain = 0;
					chinpacket = 0;
					continue;
				}
				packetbyteremain--;
				packetbytes[chinpacket] = ch;
				chinpacket++;
				if (packetbyteremain == 0) {
					//"A packet was completed . ... Respond immediately arrived ."
					chinpacket--; // exclude check byte
					int packettype = packetbytes[0];
					BYTE bCheckByte = 0;
					for (int kkk = 0; kkk < chinpacket; kkk++) {
						bCheckByte += packetbytes[kkk] & 0x7f;
					}
					static bool s_bool = true;
					if (s_bool || (bCheckByte & 0x7f) == packetbytes[chinpacket]) {
						switch (packettype) {
							//PC to RIO
						case rio_CheckRequest:
						case rio_VersionRequest:
						case rio_AnalogRequest:
						case rio_ResetRequest:
						case rio_LampRequest:
							//RIO to PC
						case rio_CheckReply:
							if (ComStat.cbOutQue == 0)
								TransmitCommChar2(g_hCom, ACK_CHAR);
							CheckReply(packetbytes);
							break;
						case rio_VersionReply:
							if (ComStat.cbOutQue == 0)
								TransmitCommChar2(g_hCom, ACK_CHAR);
							VersionReply(packetbytes);
							break;
						case rio_TestModeChange:
							if (ComStat.cbOutQue == 0)
								TransmitCommChar2(g_hCom, ACK_CHAR);
							TestModeChange(packetbytes);
							break;
						case rio_AnalogReply:
							if (ComStat.cbOutQue == 0)
								TransmitCommChar2(g_hCom, ACK_CHAR);
							AnalogEvent(packetbytes);
							break;
						case rio_ButtonPressed:
						case rio_ButtonReleased:
							if (ComStat.cbOutQue == 0)
								TransmitCommChar2(g_hCom, ACK_CHAR);
							ButtonEvent(packetbytes);
							break;
						case rio_KeyPressed:
						case rio_KeyReleased:
							if (ComStat.cbOutQue == 0)
								TransmitCommChar2(g_hCom, ACK_CHAR);
							KeypadEvent(packetbytes);


						}
					}
					else {
						if (ComStat.cbOutQue == 0) {
							if ((packettype == rio_ButtonPressed) || (packettype == rio_ButtonReleased))
								TransmitCommChar2(g_hCom, NAK_CHAR);
							else
								TransmitCommChar2(g_hCom, ACK_CHAR);
						}
					}
					//					 TransmitCommChar2(g_hCom,0xFC);
					//					 WriteTTYBlock(hWnd,"|",1);
					chinpacket = 0;

					//packetbytes"Clear the buffer."
				}
				else {
					//"Packet has not yet arrived . The characters do nothing."
					;
				}
			}
			else {
				chinpacket = 0;
				if ((rio_CheckRequest <= ch) && (ch < (rio_CheckRequest + g_nRIOPacketCount))) {
					packetbyteremain = g_pbRIOLengths[ch - rio_CheckRequest] + 1;
					chinpacket = 0;
					packetbytes[chinpacket] = ch;
					chinpacket++;
				}
				else {
					WriteCommBlock(NULL, ch);
				}
			}
		}//for(int i=0;i<(int)dwLength;i++)


		//"After awards"
		if (!fReadStat) {
			if (GetLastError() == ERROR_IO_PENDING) {
				while (!GetOverlappedResult(g_hCom, &ros, &dwLength, TRUE)) {
					dwError = GetLastError();
					if (dwError == ERROR_IO_INCOMPLETE)
						// normal result if not finished
						continue;
					else {
						// an error occurred, try to recover
						ClearCommError(g_hCom, &dwErrorFlags, &ComStat);
						break;
					}
				}
			}
			else {
				// some other error occurred
				dwLength = 0;
				ClearCommError(g_hCom, &dwErrorFlags, &ComStat);
			}
		}
	}
	return dwLength;
}
BOOL WriteCommBlock(LPSTR lpByte, DWORD dwBytesToWrite)
{
	if (lpByte == NULL) {
		switch (dwBytesToWrite) {
		case ACK_CHAR:
			g_dwLastBytesToWrite = 0;
			break;
		case NAK_CHAR:
			if (g_dwLastBytesToWrite != 0) {
				if (g_baLastPacket[0] == rio_LampRequest) {
				}
				g_dwLastBytesToWrite = 0;
			}
			break;
		case RESTART_CHAR:
			break;
		case IDLE_CHAR:
			break;
		default:
			break;
		}
		return FALSE;
	}
	//Normal Variables
	BOOL        fWriteStat;
	DWORD       dwBytesWritten;
	DWORD       dwBytesSent = 0;
	//Error Report
	DWORD       dwErrorFlags;
	COMSTAT     ComStat;

	if (g_dwLastBytesToWrite == 0) {
		g_dCancelCount = 0;
		//		g_dwLastBytesToWrite = dwBytesToWrite;
		memcpy(g_baLastPacket, lpByte, dwBytesToWrite);
	}
	else {
		if (lpByte[0] == rio_LampRequest) {
		}
		if (++g_dCancelCount >= 5) {
			g_dwLastBytesToWrite = 0;
		}
		return FALSE;
	}
	fWriteStat = WriteFile(g_hCom, lpByte, dwBytesToWrite, &dwBytesWritten, &wos);

	if (fWriteStat == FALSE) {
		if (GetLastError() == ERROR_IO_PENDING) {
			while (!GetOverlappedResult(g_hCom, &wos, &dwBytesWritten, TRUE)) {
				if (GetLastError() == ERROR_IO_INCOMPLETE) {
					dwBytesSent += dwBytesWritten;
					Sleep(0);//Yield the process to the others
				}
				else {
					ClearCommError(g_hCom, &dwErrorFlags, &ComStat);
					g_dwLastBytesToWrite = 0;
					return FALSE;
				}
			}
		}
		else {
			ClearCommError(g_hCom, &dwErrorFlags, &ComStat);
			g_dwLastBytesToWrite = 0;
			return FALSE;
		}
	}

	return TRUE;
}
DWORD FAR PASCAL CommWatchProc(LPSTR lpData)
{
	wcos.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	HANDLE events[3] = { wcos.hEvent,g_hWatchEvent,g_hLampEvent };

	while (1) {
		DWORD dwEvtMask = 0;

		WaitCommEvent(g_hCom, &dwEvtMask, &wcos);

		DWORD ret = WaitForMultipleObjects(3, events, FALSE, 55 ); // this timeout value is the defaultwait for requesting an analog update
		if (ret == WAIT_OBJECT_0) {
			//An Comm Event has arrived.
			DWORD bytesread;

			GetOverlappedResult(g_hCom, &wcos, &bytesread, FALSE);

			BYTE       abIn[16];
			ReadCommBlock((LPSTR)abIn, 16);
			//((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)

		}
		else if (ret == WAIT_OBJECT_0 + 1) {
			//Terminate This Thread.
			goto terminate;
		}
		else if (ret == WAIT_OBJECT_0 + 2) {
			//g_hLampEvent is set.
//			g_pRIOMain->RunMain();
		}
		else if (ret == WAIT_FAILED) {
			//can't be reached here.!!!
			//break;
		}
		else if (ret == WAIT_TIMEOUT) {
			if (packetbyteremain == 0) {
				int currenttick = GetTickCount();
				int timeouttick = lastAnalogEvent + 5000;
				if (timeouttick < currenttick) { // more than 5000ms since last AnalogEvent was seen send digital reset to try to recover
					std::cout << "more than 1000ms since last AnalogEvent was seen send digital reset to try to recover" << std::endl;
					std::cout << "timeouttick:currenttick " << timeouttick << ":" << currenttick << std::endl;
					RIOcmd(6);
				}

				RequestAnalogUpdate();

			}
			else {
			}
		}
	}
terminate:
	// get rid of event handle

	CloseHandle(wcos.hEvent);
	return(TRUE);
}
short CombinePair(BYTE low_value, BYTE high_value)
{
	short result;
	result = (short)(((short)(low_value & 0x7F)) | (((short)high_value) << 7));
	if (result & 0x2000)
	{
		result |= ~0x3FFF;
	}

	return result;
}
void AnalogEvent(BYTE* pData)
{

	for (int i = 1; i <= 10; i++) {
		if (pData[i] == 0xFE) {
			//AnalogAllReset ();
			return;
		}
	}

	g_Throttle = CombinePair(pData[1], pData[2]);
	g_LeftPedal = CombinePair(pData[3], pData[4]);
	g_RightPedal = CombinePair(pData[5], pData[6]);
	g_JoystickY = CombinePair(pData[7], pData[8]);
	g_JoystickX = CombinePair(pData[9], pData[10]);

	maxt = (g_Throttle > maxt) ? g_Throttle : maxt;
	mint = (g_Throttle < mint) ? g_Throttle : mint;
	maxl = (g_LeftPedal > maxl) ? g_LeftPedal : maxl;
	minl = (g_LeftPedal < minl) ? g_LeftPedal : minl;
	maxr = (g_RightPedal > maxr) ? g_RightPedal : maxr;
	minr = (g_RightPedal < minr) ? g_RightPedal : minr;
	maxx = (g_JoystickX > maxx) ? g_JoystickX : maxx;
	minx = (g_JoystickX < minx) ? g_JoystickX : minx;
	maxy = (g_JoystickY > maxy) ? g_JoystickY : maxy;
	miny = (g_JoystickY < miny) ? g_JoystickY : miny;
	lastAnalogEvent = GetTickCount();
	if (g_rawaxes) {
			std::cout << "min:cur:max" <<
			" T:" << std::setw(4) << mint << ":" << std::setw(4) << g_Throttle << ":" << std::setw(4) << maxt <<
			" L:" << std::setw(4) << minl << ":" << std::setw(4) << g_LeftPedal << ":" << std::setw(4) << maxl <<
			" R:" << std::setw(4) << minr << ":" << std::setw(4) << g_RightPedal << ":" << std::setw(4) << maxr <<
			" Y:" << std::setw(4) << miny << ":" << std::setw(4) << g_JoystickY << ":" << std::setw(4) << maxy <<
			" X:" << std::setw(4) << minx << ":" << std::setw(4) << g_JoystickX << ":" << std::setw(4) << maxx <<
			":" << std::endl;
	}
	UpdateJoystick();
	UpdateThrottle();
	UpdatePadal();
}
void ButtonEvent(BYTE* pData)
{

	BYTE state = pData[0];
	BYTE index = pData[1];

	switch (state)
	{
	case rio_ButtonPressed:
		Press_V2(index);
		break;
	case rio_ButtonReleased:
		Release_V2(index);
		break;
	default:
		break;
	}
}
void KeypadEvent(BYTE* pData)
{

	BYTE state = pData[0];
	BYTE pad = pData[1];
	BYTE index = pData[2];

	switch (state)
	{
	case rio_KeyPressed:
		if (pad == 0)
			index = index + 80;
		if (pad == 1)
			index = index + 96;
		Press_V2(index);
		break;
	case rio_KeyReleased:
		if (pad == 0)
			index = index + 80;
		if (pad == 1)
			index = index + 96;
		Release_V2(index);
		break;
	default:
		break;
	}
}
BOOL SendPacket(const BYTE* ba, int length)
{

	return WriteCommBlock((LPSTR)ba, length);
}
BOOL SendCommand(const BYTE* pbPacket)
{
	BYTE ba[256];
	BYTE bCmd = *pbPacket;
	int cmdindex = bCmd & 0x7F;

	if (bCmd & 0x80) { /*reduce the command byte down to the index number listed in the g_baRIOLengths array */
		if (cmdindex < g_nRIOPacketCount) {
			int nLeft = 1/* 1byte command */ + g_pbRIOLengths[cmdindex]; /* plus any additional bytes for arguments to the commands*/

			BYTE bCheck = 0;
			BYTE* p = ba;

			while (nLeft > 0) {
				BYTE b = *pbPacket++;
				*p++ = b;
				bCheck += b & 0x7F;
				nLeft--;
			}

			*p++ = bCheck & 0x7F;

			return SendPacket(ba, p - ba);
		}
		else {
			//unknown command
		}
	}
	else {
		;//error
	}

	return TRUE;
}
BOOL SetLamp(int lampNumber, int state)
{
	static BYTE	request_lamp_string[] = { rio_LampRequest, 0, 0, 0 };

	request_lamp_string[1] = (BYTE)(lampNumber & 0x7F);
	request_lamp_string[2] = (BYTE)(state & 0x7F);

	return SendCommand(request_lamp_string);
}
void AnalogAllReset()
{
	ResetThrottle();
	ResetLeftPedal();
	ResetRightPedal();
	ResetVerticalJoystick();
	ResetHorizontalJoystick();
}
void GeneralReset()
{
	static BYTE	request_reset_string[] = { rio_ResetRequest, 0 };
	SendCommand(request_reset_string);
}
void ResetThrottle()
{
	static BYTE	request_throttle_string[] = { rio_ResetRequest, 1 };
	SendCommand(request_throttle_string);
}
void ResetLeftPedal()
{
	static BYTE	request_lpedal_string[] = { rio_ResetRequest, 2 };
	SendCommand(request_lpedal_string);
}
void ResetRightPedal()
{
	static BYTE	request_rpedal_string[] = { rio_ResetRequest, 3 };
	SendCommand(request_rpedal_string);
}
void ResetVerticalJoystick()
{
	static BYTE	request_vstick_string[] = { rio_ResetRequest, 4 };
	SendCommand(request_vstick_string);
}
void ResetHorizontalJoystick()
{
	static BYTE	request_hstick_string[] = { rio_ResetRequest, 5 };
	SendCommand(request_hstick_string);
}
void RequestCheck()
{
	static BYTE	request_check_string[] = { rio_CheckRequest };
	SendCommand(request_check_string);
}
void CheckReply(BYTE* pData) {

	switch (pData[1])
	{
	case BoardMissing:
		std::cout << "missing_board : " << int(pData[2]) << " : " << GetBoardName(int(pData[2])) << std::endl;
		break;

	case BoardBad:
		std::cout << "dead_board    : " << int(pData[2]) << std::endl;
		break;

	case LampBad:
		std::cout << "RIODeadLamp   : " << GetLampName(int(pData[2])) << std::endl;
		break;

	case RestartCount:
				std::cout << "remoteRetryCount:" << int(pData[2]) << std::endl;
		break;

	case AbandonCount:
				std::cout << "remoteAbandonCount:" << int(pData[2]) << std::endl;
		break;

	case FullBufferCount:
				std::cout << "remoteFullBufferCount:" << int(pData[2]) << std::endl;
		break;
	}
}
const char* GetLampName(int lamp_number)
{
	const char* lamp_name[] =
	{
		"AuxLowerRight8",
		"AuxLowerRight7",
		"AuxLowerRight6",
		"AuxLowerRight5",
		"AuxLowerRight4",
		"AuxLowerRight3",
		"AuxLowerRight2",
		"AuxLowerRight1",
		"AuxLowerLeft8",
		"AuxLowerLeft7",
		"AuxLowerLeft6",
		"AuxLowerLeft5",
		"AuxLowerLeft4",
		"AuxLowerLeft3",
		"AuxLowerLeft2",
		"AuxLowerLeft1",
		"Secondary1",			//=0x10
		"Secondary2",
		"Secondary3",
		"Secondary4",
		"Secondary5",
		"Secondary6",
		"TeslaRelay3",
		"undefined_0x17",
		"Secondary7",			//=0x18
		"Secondary8",
		"Secondary9",
		"Secondary10",
		"Secondary11",
		"Secondary12",
		"TeslaRelay1",
		"TeslaRelay2",
		"AuxUpperCenter8",	//=0x20
		"AuxUpperCenter7",
		"AuxUpperCenter6",
		"AuxUpperCenter5",
		"AuxUpperCenter4",
		"AuxUpperCenter3",
		"AuxUpperCenter2",
		"AuxUpperCenter1",
		"AuxUpperLeft8",		//=0x28
		"AuxUpperLeft7",
		"AuxUpperLeft6",
		"AuxUpperLeft5",
		"AuxUpperLeft4",
		"AuxUpperLeft3",
		"AuxUpperLeft2",
		"AuxUpperLeft1",
		"AuxUpperRight8",		//=0x30
		"AuxUpperRight7",
		"AuxUpperRight6",
		"AuxUpperRight5",
		"AuxUpperRight4",
		"AuxUpperRight3",
		"AuxUpperRight2",
		"AuxUpperRight1",
		"PanicButton",		//=0x38
		"IcomAmpEnableRelay",//=0x39
		"IcomIncRelay",		//=0x3A
		"IcomPTTRelay",		//=0x03B
		"IcomDecRelay",		//=0x3C
		"Eject",			//=0x3D unimplemented convenience lamp
		"FloorEntry",		//=0x3E
		"Reverse",			//=0x3F
		"Joystickboard1",	//=0x41
		"Joystickboard2",	//=0x42
		"Joystickboard3",	//=0x43
		"Joystickboard4",	//=0x44
		"Joystickboard5",	//=0x45
		"Joystickboard6",	//=0x46
		"Joystickboard7",	//=0x47
		"Joystickboard8",	//=0x48
	};

	return lamp_name[lamp_number];

}
const char* GetBoardName(int board_number)
{
	const char* board_name[] =
	{
		"AuxLowerRight",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"AuxLowerLeft",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Secondary1",			//=0x10
		"Secondary2",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"AuxUpperCenter",			//=0x18
		"AuxUpperLeft",
		"AuxUpperRight",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"IntKeyPad",	//=0x20
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"ExtKeyPad",		//=0x28
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Throttel",		//=0x30
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"Undefined",
		"JoyStick",			//=0x38
	};

	return board_name[board_number];

}
void RequestVersion()
{
	static BYTE	request_version_string[] = { rio_VersionRequest };
	SendCommand(request_version_string);
}
void VersionReply(BYTE* pData) {
	std::cout << "RIO Version : " << int(pData[1]) << " . " << int(pData[2]) << std::endl;
}
void RequestAnalogUpdate()
{
	static BYTE	request_analog_string[] = { rio_AnalogRequest };

	SendCommand(request_analog_string);
	if (enablecount) {
		count++;
		now = GetTickCount();
		if (count == 10) {

			std::cout << "10 analog requests took ," << (now - last) << ",MS about ," << (10000 / (now - last)) << ", per second" << std::endl;

			last = now;
			count = 0;
		}
	}
}
void UpdatePadal()
{
#define RANGE_PADAL		500L
#define DEADZONE_PADAL	10L	
	/*
		== PADAL ==

		Up		: (-)
		Down	: (+)

		------------------
		Set Start Position
		------------------

				 Down			 Up
				 ======================
				   (+)			(-)
				 ======================

		g_LeftPedalStart: INT_MAX (+)

		lP = (LONG)g_LeftPedalStart - g_LeftPedal;
		"lP negative result is unconditional"
	*/

	LONG lP = 0;
	LONG rudder = 16383L;

	//
	// LEFT PADAL
	//

	if ((g_LeftPedal < -RANGE_PADAL) || (g_LeftPedal > RANGE_PADAL))
	{

		if (g_LeftPedal > RANGE_PADAL) {
			g_LeftPedalLast = -1000L;
		}
		else {
			g_LeftPedalLast = 0L;
		}
	}
	else
	{
		if (g_LeftPedalStart > g_LeftPedal) {
			g_LeftPedalStart = g_LeftPedal;
		}

		lP = (LONG)g_LeftPedalStart - g_LeftPedal;
		if (lP != 0) {
			lP = lP * 1000 / RANGE_PADAL;
		}

		// Dead Zone
		if (abs(lP) < DEADZONE_PADAL) {
			lP = 0;
		}

		g_LeftPedalLast = lP;
	}
	g_LeftPedalLast = std::abs(g_LeftPedalLast * 32L);

	if (*invertXR) {
		g_LeftPedalLast = (32766L - g_LeftPedalLast);
	}

	if (!*enableZR) {
		SetAxis(g_LeftPedalLast, 1, HID_USAGE_RX);
	}
	else {
		SetAxis(16383L, 1, HID_USAGE_RX);
	}

	//
	// RIGHT PADAL
	//

	lP = 0;

	if ((g_RightPedal < -RANGE_PADAL) || (g_RightPedal > RANGE_PADAL))
	{

		if (g_RightPedal > RANGE_PADAL) {
			g_RightPedalLast = 1000L;
		}
		else {
			g_RightPedalLast = 0L;
		}
	}
	else
	{
		if (g_RightPedalStart > g_RightPedal) {
			g_RightPedalStart = g_RightPedal;
		}

		lP = (LONG)g_RightPedalStart - g_RightPedal;
		if (lP != 0) {
			lP = lP * 1000 / RANGE_PADAL;
		}

		// Dead Zone
		if (abs(lP) < DEADZONE_PADAL) {
			lP = 0;
		}

		g_RightPedalLast = -lP;
	}
	g_RightPedalLast = std::abs(g_RightPedalLast * 32L);

	if (*invertYR) {
		g_RightPedalLast = (32766L - g_RightPedalLast);
	}

	if (!*enableZR) {
		SetAxis(g_RightPedalLast, 1, HID_USAGE_RY);
	}
	else {
		SetAxis(16383L, 1, HID_USAGE_RY);
	}

	rudder = 16383L - (g_LeftPedalLast / 2L) + (g_RightPedalLast / 2L);

	if (*invertZR) {
		rudder = (32766L - rudder);
	}

	if (*enableZR) {
		SetAxis(rudder, 1, HID_USAGE_RZ);
	}
	else {
		SetAxis(16383L, 1, HID_USAGE_RZ);
	}
}
void UpdateThrottle()
{
	// [[RIO button value range]]
	// RIO board spec: (up) -800 ~ +800 (down)
	//   actually maximum throttle-value-range is 800, not 1600(-800 ~ 800)
	//   possible throttle value (g_ThrottleMax ~ (g_ThrottleMax - 800))
	//   g_ThrottleMax will be changedfrom input value...
	//
	//   g_Throttle/g_ThrottleMax
#define RANGE_THROTTLE			800L

#define ZERO_THROTTLE			0L  // see,,, control_mapping.cpp, 0.3

#define DEADZONE_THROTTLE  50L

/*
	== THROTTLE ==

	Up		: (-)
	Down	: (+)

	------------------
	Set Start Position
	------------------

			 Up			 Down
			 ======================
			  (-)			(+)
			 ======================

	g_ThrottleStart: INT_MIN (-)
*/


	LONG lT;

	if ((g_Throttle < -RANGE_THROTTLE) || (g_Throttle > RANGE_THROTTLE))
	{
		// error
		if (g_Throttle < RANGE_THROTTLE) {
			if (g_ThrottleResult > 0L) { // back
				g_ThrottleLast = 1000L;
			}
			else {
				g_ThrottleLast = -1000L;
			}
		}
		else {
			g_ThrottleLast = 0L;
		}
	}
	else
	{
		// Set StartPosition
		if (g_ThrottleStart < g_Throttle)
			g_ThrottleStart = g_Throttle;

		lT = (LONG)g_ThrottleStart - g_Throttle;

		if (lT > 800L)
			lT = 800L;

		if (lT != 0) {
			lT = lT * 1000L / 800L;

			if ((lT > -DEADZONE_THROTTLE) && (lT < DEADZONE_THROTTLE)) {
				g_ThrottleLast = ZERO_THROTTLE;
			}
			else {
				if (g_ThrottleResult > 0L) { // back
					g_ThrottleLast = 900L + (lT * g_ThrottleResult / 10L);
				}
				else { // front
					g_ThrottleLast = lT * g_ThrottleResult;
				}
			}
		}
	}
	g_ThrottleLast = std::abs(g_ThrottleLast * 32L);

	if (*invertZ) {
		g_ThrottleLast = 32766L - g_ThrottleLast;
	}
	SetAxis(g_ThrottleLast, 1, HID_USAGE_Z);
}
void UpdateJoystick()
{
	//////////////////////////////////////////////////////////////////////////////////
		/*
			"The meaning of 80"

			"RIO (LEFT: 120 ~ RIGHT: -80) home and promised to return the number of
			(LEFT uigyeong right) cut away the rest of the maximum value of 80 or greater RIGHT
			The LEFT and RIGHT case you can move at the same speed"

			FH changed the method to make the rate adjust to the max/min value seen from the axis
			starting with that default value of 80 like the original code but then not sniping off the 
			rest of the range
		*/
		//////////////////////////////////////////////////////////////////////////////////
	LONG	lJx;
	LONG	lJy;
#define DEADZONE_JOYSTICKX_LEFT		5L
#define DEADZONE_JOYSTICKX_RIGHT	5L
	LONG startingLeftrate = -80;
	LONG startingrightrate = 80;

	startingLeftrate = (minx < startingLeftrate) ? minx : startingLeftrate;
	startingrightrate = (maxx > startingrightrate) ? maxx : startingrightrate;


	const LONG sLeftRate = (LONG)((16383L) / (LONG)(abs(startingLeftrate) - DEADZONE_JOYSTICKX_LEFT));
	const LONG sRightRate = (LONG)((16383L) / (LONG)(startingrightrate - DEADZONE_JOYSTICKX_RIGHT));

	lJx = g_JoystickX;

	if (lJx < 0L) // LEFT
	{
		lJx += DEADZONE_JOYSTICKX_LEFT;

		if (lJx < 0L) {
//			if (lJx < (-80L + DEADZONE_JOYSTICKX_LEFT)) 
//				lJx = (-80L + DEADZONE_JOYSTICKX_LEFT);

			g_JoystickXLast = (16383L - (lJx * sLeftRate)); 
		}
		else {
			g_JoystickXLast = 16383L;
		}
	}
	else if (lJx > 0L) // RIGHT
	{
		lJx -= DEADZONE_JOYSTICKX_RIGHT;

		if (lJx > 0L) {
//			if (lJx > (80L - DEADZONE_JOYSTICKX_RIGHT)) 
//				lJx = (80L - DEADZONE_JOYSTICKX_RIGHT);

			g_JoystickXLast = (16838L - (( lJx + 2 ) * sRightRate)); // add in a -2 adjustment to prevent snaping alittle to the oposit direction.
	}
		else {
			g_JoystickXLast = 16383L;
		}
	}

	if (*invertX) {
		g_JoystickXLast = 32766L - g_JoystickXLast;
	}

	SetAxis(g_JoystickXLast, 1, HID_USAGE_X);
//	std::cout << "  X:" << std::setw(4) << minx << ":" << std::setw(4) << g_JoystickX << ":" << std::setw(4) << maxx << ":" << sLeftRate << ":" << g_JoystickXLast << ":" << sRightRate << std::endl;

	/////////////////////////

#define DEADZONE_JOYSTICKY_UP		5L
#define DEADZONE_JOYSTICKY_DOWN		5L
	LONG startingUprate = -80;
	LONG startingDownrate = 80;

	startingUprate = (miny < startingUprate) ? miny : startingUprate;
	startingDownrate = (maxy > startingDownrate) ? maxy : startingDownrate;
	const LONG sUpRate = (LONG)((16383L) / (LONG)(abs(startingUprate) - DEADZONE_JOYSTICKY_UP));
	const LONG sDownRate = (LONG)((16383L) / (LONG)(startingDownrate - DEADZONE_JOYSTICKY_DOWN));

	lJy = g_JoystickY;

	if (lJy < 0L) // UP
	{
		lJy += DEADZONE_JOYSTICKY_UP;

		if (lJy < 0L) {
//			if (lJy < (-80L + DEADZONE_JOYSTICKY_UP))
//				lJy = (-80L + DEADZONE_JOYSTICKY_UP);

			g_JoystickYLast = (16383L - (lJy * sUpRate)); 
		}
		else {
			g_JoystickYLast = 16383L;
		}
	}
	else if (lJy > 0L) // Down
	{
		lJy -= DEADZONE_JOYSTICKY_DOWN;

		if (lJy > 0L) {
//			if (lJy > (80L - DEADZONE_JOYSTICKY_DOWN))
//				lJy = (80L - DEADZONE_JOYSTICKY_DOWN);

			g_JoystickYLast = (16383L - (lJy * sDownRate)); 

		}
		else {
			g_JoystickYLast = 16383L;
		}
	}

	if (*invertY) {
		g_JoystickYLast = 32766L - g_JoystickYLast;
	}

	SetAxis(g_JoystickYLast, 1, HID_USAGE_Y);
//	std::cout << "Y:" << std::setw(4) << miny << ":" << std::setw(4) << g_JoystickY << ":" << std::setw(4) << maxy << ":" << sUpRate << ":" << g_JoystickYLast << ":" << sDownRate << std::endl;
}
void TestModeChange(BYTE* pData) {
	if (pData[1] != 0)
	{
		std::cout << "RIO entered test mode : " << int(pData[1]) << std::endl;
		//		TestModeActive = 1;
	}
	else
	{
		std::cout << "RIO exited test mode : " << int(pData[1]) << std::endl;
		//		TestModeActive = 0;
	}
}
void RIOcmd(unsigned char ucValue) { // all the things we may want the rio to do that are not outputs to the computer
	switch (int(ucValue)) {
	case 0:
		std::cout << "Reset Analog axes and clabration data" << std::endl;
		AnalogAllReset();
		g_ThrottleResult = -1L;
		g_ThrottleStart = INT_MIN;
		g_LeftPedalStart = INT_MAX;
		g_RightPedalStart = INT_MAX;
		g_JoystickXLast = 16383L;
		g_JoystickYLast = 16383L;
		g_ThrottleLast = 0L;
		g_LeftPedalLast = 0L;
		g_RightPedalLast = 0L;
		g_RudderLast = 16838L;
		maxt = 0;
		mint = 0;
		maxl = 0;
		minl = 0;
		maxr = 0;
		minr = 0;
		maxx = 0;
		minx = 0;
		maxy = 0;
		miny = 0;
		break;
	case 1:
		std::cout << "ResetThrottle" << std::endl;
		ResetThrottle();
		g_ThrottleResult = -1L;
		g_ThrottleStart = INT_MIN;
		g_ThrottleLast = 0L;
		maxt = 0;
		mint = 0;
		break;
	case 2:
		std::cout << "ResetLeftPedal" << std::endl;
		ResetLeftPedal();
		g_LeftPedalStart = INT_MAX;
		g_LeftPedalLast = 0L;
		maxl = 0;
		minl = 0;
		break;
	case 3:
		std::cout << "ResetRightPedal" << std::endl;
		ResetRightPedal();
		g_RightPedalStart = INT_MAX;
		g_RightPedalLast = 0L;
		maxr = 0;
		minr = 0;
		break;
	case 4:
		std::cout << "ResetVerticalJoystick" << std::endl;
		ResetVerticalJoystick();
		g_JoystickYLast = 16383L;
		maxy = 0;
		miny = 0;
		break;
	case 5:
		std::cout << "ResetHorizontalJoystick" << std::endl;
		ResetHorizontalJoystick();
		g_JoystickXLast = 16383L;
		maxx = 0;
		minx = 0;
		break;
	case 6:
		std::cout << "GeneralReset" << std::endl;
		GeneralReset();
		for (int b = 0; b < 72; b++) {
			if (iRIO[b] & 0x8000)
				SetLamp(b, LampSolidDim);
		}
		break;
	case 7:
		std::cout << "RequestVersion and RequestCheck" << std::endl;
		RequestVersion();
		RequestCheck();
		break;
	case 8:
		std::cout << "enable/disable axes readout" << std::endl;
		g_rawaxes = !g_rawaxes;
		break;
	case 9:
		std::cout << "enable/disable polling Frequency output" << std::endl;
		last = GetTickCount();// set the "last" time to be now so that the first output does not count all of the time since program start
		enablecount = !enablecount;
		break;
	default:
		std::cout << "unimplimented option : " << int(ucValue) << std::endl;
		break;
	}
}
static void Press_V2(unsigned char iAddress)
{
	int iValue = iRIO[iAddress];		//
	bool bIsDim = iValue & 0x8000;	// would it be faster/better to just calculate the values as needed 
	bool bIsMouse = iValue & 0x4000;	// or express them out like this and pass values?
	bool bIsHat = iValue & 0x2000;
	bool bIsJoy = iValue & 0x1000;
	bool bIsExt = iValue & 0x0800;
	bool bIsALT = iValue & 0x0400;
	bool bIsCTRL = iValue & 0x0200;
	bool bIsSHIFT = iValue & 0x0100;
	unsigned char ucValue = iValue & 0xFF;

	if (!bIsJoy && !bIsHat && !bIsMouse)	//this must then be a Keyboard press
	{
		if (bIsSHIFT) { ModKeySHIFT(1); }	//take care of any mod keys
		if (bIsCTRL) { ModKeyCTRL(1); }
		if (bIsALT) { ModKeyALT(1); }

		Key(ucValue, 1, bIsExt);			// Then press the Key
	}
	if (bIsJoy && bIsHat && bIsMouse) { 	//this must then be a RIO command
		RIOcmd(ucValue);
		return;
	}
	if (bIsJoy) {							// call the vJoy button function with press and Value
		SetBtn(TRUE, 1, ucValue);
	}
	else if (bIsHat) {					// call the vJoy hat function with direction information
		SetDiscPov(ucValue, 1, 1);
	}
	else if (bIsMouse) {					// call the mouse function as a press (1)
		Mouse(ucValue, 1);
	}
	// Work on button lighting
	if (iRIO[iAddress] & 0x8000)
		SetLamp(iAddress, LampSolidBright);
}
static void Release_V2(unsigned char iAddress)
{
	int iValue = iRIO[iAddress];
	bool bIsDim = iValue & 0x8000;	// would it be faster/better to just calculate the values as needed 
	bool bIsMouse = iValue & 0x4000;	// or express them out like this and pass values?
	bool bIsHat = iValue & 0x2000;
	bool bIsJoy = iValue & 0x1000;
	bool bIsExt = iValue & 0x0800;
	bool bIsALT = iValue & 0x0400;
	bool bIsCTRL = iValue & 0x0200;
	bool bIsSHIFT = iValue & 0x0100;
	unsigned char ucValue = iValue & 0xFF;

	if (!bIsJoy && !bIsHat && !bIsMouse)	//this must then be a Keyboard press
	{
		Key(ucValue, 0, bIsExt);			// release the Key

		if (bIsALT) { ModKeyALT(0); }		//take care of any mod keys
		if (bIsCTRL) { ModKeyCTRL(0); }
		if (bIsSHIFT) { ModKeySHIFT(0); }

	}
	if (bIsJoy && bIsHat && bIsMouse) { 	//this must then be a RIO command

		return;
	}
	if (bIsJoy) {							// call the vJoy button function with release and Value
		SetBtn(FALSE, 1, ucValue);

	}
	else if (bIsHat) {					// call the vJoy hat function with centering information
		SetDiscPov(-1, 1, 1);

	}
	else if (bIsMouse) {					// call the mouse function as a release (0)
		Mouse(ucValue, 0);
	}
	//work on button lighting
	if (iRIO[iAddress] & 0x8000)
		SetLamp(iAddress, LampSolidDim);

}
static void ModKeySHIFT(bool bIsPressed)
{
	INPUT sInput = { 0 };

	sInput.type = INPUT_KEYBOARD;
	if (!bIsPressed) { sInput.ki.dwFlags = KEYEVENTF_KEYUP; }
	sInput.ki.wVk = VK_SHIFT;
	sInput.ki.wScan = 0x2A;
	SendInput(1, &sInput, sizeof(sInput));

}
static void ModKeyCTRL(bool bIsPressed)
{
	INPUT cInput = { 0 };
	cInput.type = INPUT_KEYBOARD;
	if (!bIsPressed) { cInput.ki.dwFlags = KEYEVENTF_KEYUP; }
	cInput.ki.wVk = VK_CONTROL;
	cInput.ki.wScan = 0x1D;
	SendInput(1, &cInput, sizeof(cInput));

}
static void ModKeyALT(bool bIsPressed)
{
	INPUT aInput = { 0 };
	aInput.type = INPUT_KEYBOARD;
	if (!bIsPressed) { aInput.ki.dwFlags = KEYEVENTF_KEYUP; }
	aInput.ki.wVk = VK_MENU;
	aInput.ki.wScan = 0x38;
	SendInput(1, &aInput, sizeof(aInput));

}
static void Key(unsigned char ucwVk, bool bIsPressed, bool bIsExtended)
{
	//INPUT kInput = { 0 };
	//kInput.type = INPUT_KEYBOARD;
	//if (!bIsPressed) { kInput.ki.dwFlags = KEYEVENTF_KEYUP; }
	//if (bIsExtended) { kInput.ki.dwFlags = kInput.ki.dwFlags | KEYEVENTF_EXTENDEDKEY; }
	//kInput.ki.wVk = ucwVk;
	//kInput.ki.wScan = MapVirtualKey(kInput.ki.wVk, MAPVK_VK_TO_VSC);
	//SendInput(1, &kInput, sizeof(kInput));
	INPUT kInput = { 0 };
	kInput.type = INPUT_KEYBOARD;
	kInput.ki.dwFlags = KEYEVENTF_SCANCODE;
	if (!bIsPressed) { kInput.ki.dwFlags = KEYEVENTF_KEYUP | KEYEVENTF_SCANCODE; }
	if (bIsExtended) { kInput.ki.dwFlags = kInput.ki.dwFlags | KEYEVENTF_EXTENDEDKEY; }
	kInput.ki.wVk = 0;
	kInput.ki.wScan = MapVirtualKey(ucwVk, MAPVK_VK_TO_VSC);
	kInput.ki.time = 0;
	SendInput(1, &kInput, sizeof(kInput));

}
static void Mouse(unsigned char ucValue, bool bIsPressed)
{
	INPUT mInput = { 0 };
	mInput.type = INPUT_MOUSE;
	if (ucValue == 0) { //do the UP thing
		if (bIsPressed) {
			mInput.mi.dwFlags = MOUSEEVENTF_MOVE;
			mInput.mi.dx = -50;
			SendInput(1, &mInput, sizeof(mInput));
		}
		else {
			// do nothing?
		}

	}
	else if (ucValue == 1) { //do the RIGHT thing
		if (bIsPressed) {
			mInput.mi.dwFlags = MOUSEEVENTF_MOVE;
			mInput.mi.dy = 50;
			SendInput(1, &mInput, sizeof(mInput));
		}
		else {
			//do nothing?
		}

	}
	else if (ucValue == 2) { //do the DOWN thing
		if (bIsPressed) {
			mInput.mi.dwFlags = MOUSEEVENTF_MOVE;
			mInput.mi.dx = 50;
			SendInput(1, &mInput, sizeof(mInput));
		}
		else {
			// do mothing?
		}

	}
	else if (ucValue == 3) { //do the LEFT thing
		if (bIsPressed) {
			mInput.mi.dwFlags = MOUSEEVENTF_MOVE;
			mInput.mi.dy = -50;
			SendInput(1, &mInput, sizeof(mInput));
		}
		else {
			// do nothing?
		}

	}
	else if (ucValue == 4) { //do the Lclick thing
		if (bIsPressed) {
			mInput.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | KEYEVENTF_EXTENDEDKEY;
		}
		else {
			mInput.mi.dwFlags = MOUSEEVENTF_LEFTUP | KEYEVENTF_EXTENDEDKEY;
		}
		SendInput(1, &mInput, sizeof(mInput));

	}
	else if (ucValue == 5) { //do the Rclick thing
		if (bIsPressed) {
			mInput.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN | KEYEVENTF_EXTENDEDKEY;
		}
		else {
			mInput.mi.dwFlags = MOUSEEVENTF_RIGHTUP | KEYEVENTF_EXTENDEDKEY;
		}
		SendInput(1, &mInput, sizeof(mInput));

	}

}

void CPlasma::SendPacket(const BYTE* pbPacket, char nLen)
{
	if (IsValid())
		txComLoop(pbPacket, nLen);
}
void CPlasma::PlasmaClear()
{
	char string[] = { ESC, '@' };
	SendPacket((BYTE*)string, 2);
}
void CPlasma::PlasmaCursor(char n)
{
	char string[] = { ESC, 'G', char(n) };
	SendPacket((BYTE*)string, 3);
}
void CPlasma::PlasmaCursorHome()
{
	char string[] = { ESC, 'L' };
	SendPacket((BYTE*)string, 2);
}
void CPlasma::PlasmaCursorX(char n)
{
	char string[] = { ESC, 'R', n };
	SendPacket((BYTE*)string, 3);
}
void CPlasma::PlasmaCursorY(char n)
{
	char string[] = { ESC, 'Q', n };
	SendPacket((BYTE*)string, 3);
}
void CPlasma::PlasmaFontAttr(char n)
{
	char string[] = { ESC, 'H', (BYTE)n };
	SendPacket((BYTE*)string, 3);
}
void CPlasma::PlasmaFont(char n)
{
	char string[] = { 27 /*ESC*/, 'K', n };
	SendPacket((BYTE*)string, 3);
}
void CPlasma::PlasmaText(const char* szText)
{
	if (!szText)
		return;

	int len = strlen(szText);

	if (len >= 256)
		return;

	SendPacket((BYTE*)szText, (char)len);
}
POINT CPlasma::GetFontSize(int Font)
{
	POINT size = { 0, 0 };

	switch (Font)
	{
	case 0:
	case 1:
	case 2:
	case 3:
		size.x = 5;
		size.y = 7;
		break;
	case 4:
	case 5:
		size.x = 10;
		size.y = 14;
		break;
	case 6:
	case 7:
		size.x = 5;
		size.y = 7;
		break;
	}

	return size;
}
void CPlasma::PlasmaBoxDraw(RECT tag)
{
	char string[] = { ESC, 'X', (char)tag.left, (char)tag.top, (char)tag.right, (char)tag.bottom };
	SendPacket((BYTE*)string, 6);
}
void CPlasma::PlasmaBoxFill(RECT tag)
{
	char string[] = { ESC, 'x', 0, (char)tag.left, (char)tag.top, (char)tag.right, (char)tag.bottom };
	SendPacket((BYTE*)string, 7);
}
void CPlasma::PlasmaPosText(const char* szText, char x, char y, char Attr, char Font)
{
	if (!szText)
		return;

	int len = strlen(szText);

	if (len <= 0)
		return;
	if (Font != 2) // !Score
	{
		if (len <= 9) {
			Font = 5;
		}
		else if (len <= 20) {
			Font = 2;
		}
		else {
			Font = 2;
			len = 20;
		}
	}
	else
	{
		if (len > 20)
			len = 20;
	}

	if (x == 0 && y == 0) {
		x = 56 - ((len * GetFontSize(Font).x) / 2);
		y = 15 - ((GetFontSize(Font).y) / 2);
	}

	PlasmaCursorX(x);
	PlasmaCursorY(y);
	PlasmaFontAttr(Attr);
	PlasmaFont(Font);

	if (len >= 256)
		return;

	SendPacket((const BYTE*)szText, len);
}
void CPlasma::PlasmaScoreDraw(const char* Rank, const char* Score, BOOL bFlag)
{
	int CurRank = atoi(Rank);
	int CurScore = atoi(Score);

	RECT tag1 = { 27,19,42,30 };
	RECT tag2 = { 42,19,93,30 };

	RECT tag3 = { 27,19,93,30 }; //Fill

	if (bFlag == TRUE)
	{
		PlasmaBoxFill(tag3);

		PlasmaBoxDraw(tag1);
		PlasmaBoxDraw(tag2);
	}

	int len_Rank = strlen(Rank);
	int len_Score = strlen(Score);

	//{
	if (len_Rank <= 2 && len_Rank > 0)
		if (len_Score <= 8 && len_Score > 0)
		{
			if (CurRank != m_nOldRank)
			{
				PlasmaPosText("  ", 29, 20, 0, 2);
				if (len_Rank == 1)
					PlasmaPosText(Rank, 33, 20, 0, 2);
				else
					PlasmaPosText(Rank, 29, 20, 0, 2);
			}

			if (m_nOldScore != CurScore)
			{
				PlasmaPosText("        ", 44, 20, 0, 2);
				PlasmaPosText(Score, (char)(85 - (len_Score * 5)), 20, 0, 2);
			}
		}

	m_nOldRank = CurRank;
	m_nOldScore = CurScore;
	//}	
}
void CPlasma::DoPlasma(char nMode)
{

	switch (nMode)
	{
	case 0:
		m_nGlobalState = -1;
		break;
	case 1:
		if ((m_nGlobalState != 0) && (m_nGlobalState != 2))
			return;
		break;
	case 2:
		if ((m_nGlobalState != 1) && (m_nGlobalState != 2))
			return;
		break;
	case 9:
		if ((m_nGlobalState != 1) && (m_nGlobalState != 2))
			return;
		PlasmaDisplay(0, NULL, NULL, NULL);
		m_nGlobalState = -1;
		return;
	default:
		return;
	}

}
void CPlasma::PlasmaDisplay(int Gamestate, const char* szName/*9*/, const char* Rank/*2*/, const char* Score/*8*/)
{
	static BOOL BoxFlag = FALSE;
	static BOOL NameFlag = FALSE;

	if (Gamestate == 0) // end
	{
		NameFlag = TRUE;
		PlasmaClear();
	}
	else if (Gamestate == 1) // ready
	{
		if (NameFlag) {
			PlasmaPosText("         ");
			PlasmaPosText(szName);
		}

		NameFlag = FALSE;
		BoxFlag = TRUE;
		m_nOldRank = INT_MIN;
		m_nOldScore = INT_MIN;
	}
	else if (Gamestate == 2) // start
	{
		PlasmaScoreDraw(Rank, Score, BoxFlag);
		BoxFlag = FALSE;
	}
}

C232Comm::C232Comm()
	: m_hCom(INVALID_HANDLE_VALUE), m_nPort(0)
{
}
C232Comm::~C232Comm()
{
	Close();
}
BOOL C232Comm::Open(int nPort, DWORD dwBaudRate/* = CBR_9600*/, BYTE byParity/* = NOPARITY*/, BYTE bByteSize/* = 8*/, BYTE byStopBits/* = ONESTOPBIT*/)
{
	m_nPort = nPort;

	char sz[32];

	sprintf_s(sz, g_szPortFormat, m_nPort);
	// fh trying to resolve C2664 compile error by explicitly converting the char[32] sz to LPCWSTR lpwsz,
	wchar_t wsz[32];
	std::mbstowcs(wsz, sz, std::strlen(sz) + 1);
	LPWSTR lpwsz = wsz;
	m_hCom = CreateFile(lpwsz, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
	if (IsValid()) {
		m_cp.wPacketLength = sizeof(COMMPROP); /* get comm properties */
		if (GetCommProperties(m_hCom, &m_cp)) {
			if (GetCommState(m_hCom, &m_dcb)) {
				m_dcb.BaudRate = (DWORD)dwBaudRate;
				m_dcb.ByteSize = bByteSize; // ONESTOPBIT TWOSTOPBITS
				m_dcb.Parity = byParity; // NOPARITY EVENPARITY ODDPARITY
				m_dcb.StopBits = byStopBits;

				m_dcb.fOutxCtsFlow = 0;
				m_dcb.fOutxDsrFlow = 0;
				m_dcb.fRtsControl = 0;
				m_dcb.fDtrControl = DTR_CONTROL_ENABLE; // HANDSHAKE; // 9;

				m_dcb.fOutX = 0;
				m_dcb.fInX = 0;

				if (SetCommState(m_hCom, &m_dcb)) {
					memset(&m_commTimeOuts, 0, sizeof(m_commTimeOuts));

					m_commTimeOuts.ReadIntervalTimeout = MAXDWORD;
					m_commTimeOuts.ReadTotalTimeoutMultiplier = 0;
					m_commTimeOuts.ReadTotalTimeoutConstant = 0;
					m_commTimeOuts.WriteTotalTimeoutMultiplier = 5;
					m_commTimeOuts.WriteTotalTimeoutConstant = 5;

					if (SetupComm(m_hCom, 2048 * 5, 1024)) {
						if (SetCommTimeouts(m_hCom, &m_commTimeOuts)) {
							if (GetCommTimeouts(m_hCom, &m_commTimeOuts)) {
								if (GetCommState(m_hCom, &m_dcb)) {
									m_dcb.fDtrControl = DTR_CONTROL_DISABLE;
									Sleep(10);
									SetCommState(m_hCom, &m_dcb);

									return TRUE;
								}
							}
						}
					}
					else {
					}
				}
				else {
				}
			}
			else {
			}
		}
		else {
		}
		locProcessCommError();
		Close();
	}
	else {

	}

	return FALSE;
}
void C232Comm::Close()
{
	if (IsValid()) {
		CloseHandle(m_hCom);
		m_hCom = INVALID_HANDLE_VALUE;
	}
}
int C232Comm::rxCom(void* pData, int nLen)
{
	if (!ReadFile(m_hCom, pData, (DWORD)nLen, (LPDWORD)&nLen, (LPOVERLAPPED)NULL)) {
		locProcessCommError();
		return -1;
	}
	/* clean out any pending bytes in the receive buffer */
	PurgeComm(m_hCom, PURGE_RXCLEAR);

	return nLen;
}
BOOL C232Comm::rxComLoop(void* pData, int nLen)
{
	while (nLen > 0) {
		int nRead = rxCom(pData, nLen);
		if (nRead == -1)
			return FALSE;
		nLen -= nRead;
		pData = &((BYTE*)pData)[nRead];
	}

	return TRUE;
}
int C232Comm::txCom(const void* pData, int nLen)
{

	if (!WriteFile(m_hCom, pData, (DWORD)nLen, (LPDWORD)&nLen, NULL)) {
		locProcessCommError();
		return -1;
	}

	return nLen;
}
int C232Comm::txComLoop(const void* pData, int nLen)
{
	while (nLen > 0) {
		int nSent = txCom(pData, nLen);
		if (nSent == -1)
			return FALSE;
		nLen -= nSent;
		pData = &((BYTE*)pData)[nSent];
	}

	return TRUE;
}
void C232Comm::locProcessCommError()
{

	DWORD dwError;
	COMSTAT cs;
	/* clear error */
	ClearCommError(m_hCom, &dwError, &cs);

}
int C232Comm::CommunicationConfig(HWND hWndParent)
{
	if (IsValid()) {
		DWORD dwCC = sizeof(m_cc);
		m_cc.dwSize = dwCC;

		if (GetCommConfig(m_hCom, &m_cc, &dwCC)) {
			char sz[32];

			sprintf_s(sz, g_szPortFormat, m_nPort);
			// fh trying to resolve C2664 compile error by explicitly converting the char[32] sz to LPCWSTR lpwsz,
			wchar_t wsz[32];
			std::mbstowcs(wsz, sz, std::strlen(sz) + 1);
			LPWSTR lpwsz = wsz;
			if (CommConfigDialog(lpwsz, hWndParent, &m_cc)) {
				if (SetCommState(m_hCom, &m_cc.dcb)) {
					// write new settings 
					return +1;
				}
				locProcessCommError();
				return 0;
			}
			locProcessCommError();
			return -1;
		}
		else {
			locProcessCommError();
			return -2;
		}
	}
	else {
		return -3;
	}
}
