// *********************************************************************
//    Copyright (c) 2023  Christoph Giesselink
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *********************************************************************

// *********************************************************************
// Udp.cpp : implementation file
// *********************************************************************

#include "StdAfx.h"
#include "Udp.h"

#define ARRAYSIZEOF(a) (sizeof(a) / sizeof(a[0]))

Udp *Udp::pInstance_ = NULL;

Udp::Udp()
	: m_strAddr("localhost")
	, m_fpSendByte(&Udp::SendByteUdp)
{
	WSADATA wsd;
	VERIFY(WSAStartup(MAKEWORD(1,1),&wsd) == 0);

	m_sServer.sin_family = AF_INET;			// IPv4 address family
	m_sServer.sin_port = htons(5025);		// UDP port
	m_sServer.sin_addr.s_addr = INADDR_NONE;

	ZeroMemory(&m_dcb,sizeof(m_dcb));
	m_dcb.DCBlength = sizeof(m_dcb);
	m_dcb.BaudRate = 9600;
	m_dcb.fBinary = TRUE;
	m_dcb.fParity = TRUE;
	m_dcb.fOutxCtsFlow = FALSE;
	m_dcb.fOutxDsrFlow = FALSE;
	m_dcb.fDtrControl = DTR_CONTROL_DISABLE;
	m_dcb.fDsrSensitivity = FALSE;
	m_dcb.fOutX = FALSE;
	m_dcb.fErrorChar = FALSE;
	m_dcb.fNull = FALSE;
	m_dcb.fRtsControl = RTS_CONTROL_DISABLE;
	m_dcb.fAbortOnError = FALSE;
	m_dcb.ByteSize = 8;
	m_dcb.Parity = NOPARITY;
	m_dcb.StopBits = ONESTOPBIT;
}

Udp::~Udp(void)
{
	WSACleanup();							// cleanup network stack
}

Udp &Udp::operator=(const Udp&)
{
	return *getInstance();
}

void Udp::deleteInstance()
{
	delete pInstance_;
	pInstance_ = NULL;
}

//
// is COM port device name
//
bool Udp::IsComPort(LPCTSTR lpszComPort)
{
	HKEY hKey;
	bool bValid = false;

	if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
					 _T("Hardware\\DeviceMap\\SerialComm"),
					 0,
					 KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS,
					 &hKey) == ERROR_SUCCESS)
	{
		TCHAR cKey[256],cData[256];
		DWORD dwKeySize,dwDataSize;
		DWORD dwType,dwEnumVal;
		LONG  lRet;

		dwEnumVal = 0;
		do
		{
			dwKeySize = ARRAYSIZEOF(cKey);	// init return buffer sizes
			dwDataSize = sizeof(cData);

			lRet = RegEnumValue(hKey,dwEnumVal++,
								cKey,&dwKeySize,
								NULL,&dwType,
								(LPBYTE) cData,&dwDataSize);

			bValid =   lRet == ERROR_SUCCESS && dwType == REG_SZ
					&& lstrcmpi(lpszComPort,cData) == 0;
		}
		while (lRet == ERROR_SUCCESS && !bValid);
		RegCloseKey(hKey);
	}
	return bValid;
}

//
// set interface parameter
//
VOID Udp::SetParam(LPCSTR lpszAddr, DWORD dwPort)
{
	m_strAddr = lpszAddr;				// new port settings
	m_dcb.BaudRate = dwPort;			// COM port baud rate

	// UDP port
	m_sServer.sin_port = htons(static_cast<word>(dwPort));

	m_fpSendByte = IsComPort(lpszAddr)	// choose send function
				 ? &Udp::SendByteRS232
				 : &Udp::SendByteUdp;
	Reset();
}

//
// reset UDP IPv4 server address
//
VOID Udp::Reset()
{
	// invalidate saved UDP address
	m_sServer.sin_addr.s_addr = INADDR_NONE;
}

//
// send byte over UDP
//
bool Udp::SendByteUdp(BYTE byData)
{
	SOCKET sClient;

	bool bErr = true;

	// IP address not specified
	if (m_sServer.sin_addr.s_addr == INADDR_NONE)
	{
		m_sServer.sin_addr.s_addr = inet_addr(T2CA(m_strAddr));

		// not a valid ip address -> try to get ip address from name server
		if (m_sServer.sin_addr.s_addr == INADDR_NONE)
		{
			PHOSTENT host = gethostbyname(T2CA(m_strAddr));
			if (host == NULL)
			{
				return true;				// server not found
			}

			m_sServer.sin_addr.s_addr = ((PIN_ADDR) host->h_addr_list[0])->s_addr;
		}
	}

	// create UDP socket
	if ((sClient = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET)
	{
		// transmit data byte
		bErr = sendto(sClient, (LPCCH) &byData, sizeof(byData), 0, (LPSOCKADDR) &m_sServer, sizeof(m_sServer)) == SOCKET_ERROR;
		closesocket(sClient);
	}

	return bErr;
}

//
// send byte over RS-232C
//
bool Udp::SendByteRS232(BYTE byData)
{
	bool bErr = true;

	const CString strComPort(_T("\\\\.\\") + m_strAddr);

	// open COM port
	HANDLE hComm = CreateFile(
		strComPort,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);

	if (hComm != INVALID_HANDLE_VALUE)
	{
		COMMTIMEOUTS CommTimeouts = { 0L, 0L, 50L, 0L, 0L };

		// com port settings
		SetCommState(hComm,&m_dcb);

		// timeouts
		SetCommTimeouts(hComm,&CommTimeouts);

		DWORD dwWritten;
		bErr = WriteFile(hComm,&byData,sizeof(byData),&dwWritten,NULL) == 0 || dwWritten != sizeof(byData);
		CloseHandle(hComm);
	}

	return bErr;
}

bool Udp::SendByte(BYTE byData)
{
	return (this->*m_fpSendByte)(byData);
}
