#include "pch.h"

#include "AZBK_ctrl.h"
#include "AZBK_Network.h"
#include "BKMessageBox.h"
#include "Config.h"
#include "MFCStrUtil.h"
#include <ctime>
//#include <iphlpapi.h>
#include <WS2tcpip.h>

#pragma comment(lib, "IPHLPAPI.lib")
#pragma comment(lib, "ws2_32.lib")


constexpr auto WORKING_BUFFER_SIZE = 15000;
constexpr auto MAX_TRIES = 3;

// выделение памяти для списка параметров адаптеров.
// Вход: flags - флаги
//		startBufLen - начальный размер буфера
//		vBuf - вектор, который используется в качестве буфера.
// Выход: код ошибки операции
DWORD AZNetwork::MemAdapterAddrAlloc(const ULONG flags, const ULONG startBufLen, std::vector<uint8_t> &vBuf)
{
	ULONG outBufLen = startBufLen;
	int Iterations = 0;
	DWORD dwRetVal = 0;
	PIP_ADAPTER_ADDRESSES pAddresses = nullptr;

	do
	{
		vBuf.resize(outBufLen);
		pAddresses = reinterpret_cast<IP_ADAPTER_ADDRESSES *>(vBuf.data());

		if (pAddresses == nullptr)
		{
			TRACE("Не удалось выделить память для структуры IP_ADAPTER_ADDRESSES\n");
			return -1;
		}

		dwRetVal = GetAdaptersAddresses(AF_INET, flags, nullptr, pAddresses, &outBufLen);
		Iterations++;
	}
	while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));

	return dwRetVal;
}


// получаем список сетевых адаптеров, чтобы выбрать, с какого адаптера брать параметры.
std::vector<AZNetwork::sAdapterName> AZNetwork::getAdaptersList()
{
	std::vector<sAdapterName> vRet;
	// Set the flags to pass to GetAdaptersAddresses
	const ULONG flags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST
	                    | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_DNS_INFO;
	// Allocate a 15 KB buffer to start with.
	std::vector<uint8_t> vBuf;
	const DWORD dwRetVal = MemAdapterAddrAlloc(flags, WORKING_BUFFER_SIZE, vBuf);

	if (dwRetVal == NO_ERROR)
	{
		auto pAddresses = reinterpret_cast<IP_ADAPTER_ADDRESSES *>(vBuf.data());

		// If successful, output some information from the data we received
		while (pAddresses)
		{
			vRet.push_back(
			{
				MFCStrUtil::Str2WideStr(pAddresses->AdapterName),
				CString(pAddresses->Description)
			});
			pAddresses = pAddresses->Next;
		}
	}
	else
	{
		TRACE("Вызов GetAdaptersAddresses завершился ошибкой: %d\n", dwRetVal);

		if (dwRetVal == ERROR_NO_DATA)
		{
			TRACE("\tДля заданных параметров адресов не найдено\n");
		}
	}

	vBuf.clear();
	return vRet;
}

void AZNetwork::storeIPv4(uint8_t *buf, const int nOffset, char *src)
{
	memcpy(buf + nOffset, src + 2, 4);
}


// получаем параметры выбранного адаптера
// вход: buf - массив из 6 двордов,
//      strAdapterName - имя выбранного адаптера
bool AZNetwork::getNetParameters(uint8_t *buf, const CString &strAdapterName)
{
	memset(buf, 0, 24);

	if (strAdapterName.IsEmpty())
	{
		return false;
	}

	// Set the flags to pass to GetAdaptersAddresses
	const ULONG flags = GAA_FLAG_INCLUDE_GATEWAYS | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST
	                    | GAA_FLAG_SKIP_FRIENDLY_NAME;
	// Allocate a 15 KB buffer to start with.
	std::vector<uint8_t> vBuf;
	DWORD dwRetVal = MemAdapterAddrAlloc(flags, WORKING_BUFFER_SIZE, vBuf);

	if (dwRetVal == NO_ERROR)
	{
		auto pAddresses = reinterpret_cast<IP_ADAPTER_ADDRESSES *>(vBuf.data());

		// If successful, output some information from the data we received
		while (pAddresses)
		{
			const CString strName = MFCStrUtil::Str2WideStr(pAddresses->AdapterName);

			if (strName == strAdapterName) // находим нужный адаптер
			{
				// получим IP адрес
				const PIP_ADAPTER_UNICAST_ADDRESS pUnicast = pAddresses->FirstUnicastAddress;

				if (pUnicast) // берём только первый из списка
				{
					storeIPv4(buf, 0, pUnicast->Address.lpSockaddr->sa_data);
				}

				// получим шлюз
				const PIP_ADAPTER_GATEWAY_ADDRESS pGateway = pAddresses->FirstGatewayAddress;

				if (pGateway) // берём только первый из списка
				{
					storeIPv4(buf, 8, pGateway->Address.lpSockaddr->sa_data);
				}

				// в поле NTP вернём флаг - состояние DHCP
				if (pAddresses->Flags & IP_ADAPTER_DHCP_ENABLED)
				{
					buf[12] = 1;
				}

				// получим dnsы
				const IP_ADAPTER_DNS_SERVER_ADDRESS *pDnServer = pAddresses->FirstDnsServerAddress;

				if (pDnServer)
				{
					storeIPv4(buf, 16, pDnServer->Address.lpSockaddr->sa_data);
					pDnServer = pDnServer->Next;

					if (pDnServer)
					{
						storeIPv4(buf, 20, pDnServer->Address.lpSockaddr->sa_data);
					}
				}

				// а теперь, из-за того, что в этой монструозной структуре есть куча всего ненужного, но нету маски
				// получим маску другим способом
				ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
				int Iterations = 0;
				IP_ADAPTER_INFO *pAdapterInfo = nullptr;

				// Make an initial call to GetAdaptersInfo to get
				// the necessary size into the ulOutBufLen variable

				do
				{
					vBuf.resize(ulOutBufLen);
					pAdapterInfo = reinterpret_cast<IP_ADAPTER_INFO *>(vBuf.data());

					if (pAdapterInfo == nullptr)
					{
						TRACE("Error allocating memory needed to call GetAdaptersinfo\n");
						return false;
					}

					dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen);
					Iterations++;
				}
				while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES));

				if (dwRetVal == NO_ERROR)
				{
					while (pAdapterInfo)
					{
						const CString strName = MFCStrUtil::Str2WideStr(pAdapterInfo->AdapterName);

						if (strName == strAdapterName) // находим нужный адаптер
						{
							char *s = pAdapterInfo->IpAddressList.IpMask.String;
							char *e = nullptr;
							buf[4] = static_cast<uint8_t>(strtol(s, &e, 10));
							s = e + 1;
							buf[5] = static_cast<uint8_t>(strtol(s, &e, 10));
							s = e + 1;
							buf[6] = static_cast<uint8_t>(strtol(s, &e, 10));
							s = e + 1;
							buf[7] = static_cast<uint8_t>(strtol(s, &e, 10));
							vBuf.clear();
							return true;
						}

						pAdapterInfo = pAdapterInfo->Next;
					}
				}

				break;
			}

			pAddresses = pAddresses->Next;
		}
	}
	else
	{
		TRACE("Вызов GetAdaptersAddresses завершился ошибкой: %d\n", dwRetVal);

		if (dwRetVal == ERROR_NO_DATA)
		{
			TRACE("\tДля заданных параметров адресов не найдено\n");
		}
	}

	vBuf.clear();
	return false;
}

// получаем мак адрес выбранного адаптера
// вход: buf - массив куда сохраняется мак,
//      strAdapterName - имя выбранного адаптера
bool AZNetwork::getMACAddress(uint8_t *buf, const CString &strAdapterName)
{
	memset(buf, 0, 24);

	if (strAdapterName.IsEmpty())
	{
		return false;
	}

	// Set the flags to pass to GetAdaptersAddresses
	const ULONG flags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST
	                    | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_DNS_INFO;
	// Allocate a 15 KB buffer to start with.
	std::vector<uint8_t> vBuf;
	const DWORD dwRetVal = MemAdapterAddrAlloc(flags, WORKING_BUFFER_SIZE, vBuf);
	bool bRet = false;

	if (dwRetVal == NO_ERROR)
	{
		auto pAddresses = reinterpret_cast<IP_ADAPTER_ADDRESSES *>(vBuf.data());

		// If successful, output some information from the data we received
		while (pAddresses)
		{
			const CString strName = MFCStrUtil::Str2WideStr(pAddresses->AdapterName);

			if (strName == strAdapterName) // находим нужный адаптер
			{
				// получим мак
				for (ULONG i = 0; i < pAddresses->PhysicalAddressLength; i++)
				{
					buf[i] = pAddresses->PhysicalAddress[i];
				}

				bRet = true;
				break;
			}

			pAddresses = pAddresses->Next;
		}
	}
	else
	{
		TRACE("Вызов GetAdaptersAddresses завершился ошибкой: %d\n", dwRetVal);

		if (dwRetVal == ERROR_NO_DATA)
		{
			TRACE("\tДля заданных параметров адресов не найдено\n");
		}
	}

	vBuf.clear();
	return bRet;
}

uint16_t AZNetwork::crc16(uint8_t *p, uint16_t len)
{
	uint8_t crc_hi = 0xFF;
	uint8_t crc_lo = 0xFF;
	uint16_t n = len;

	do
	{
		uint16_t i = crc_hi ^ *p++;
		crc_hi = crc_lo ^ auchCRCHi[i];
		crc_lo = auchCRCLo[i];
	}
	while (--n);

	return ((uint16_t(crc_lo) << 8) | crc_hi);
}

void AZNetwork::ReportError(CString &s, int LastErr)
{
	if (LastErr == 0)
	{
		LastErr = WSAGetLastError();
	}

	LPTSTR lpvSysMsg = nullptr;
	DWORD eMsgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
	                              FORMAT_MESSAGE_FROM_SYSTEM |
	                              FORMAT_MESSAGE_IGNORE_INSERTS,        // DWORD   dwFlags,
	                              nullptr,                              // LPCVOID lpSource,
	                              LastErr,                              // DWORD   dwMessageId,
	                              LANG_USER_DEFAULT,                    // DWORD   dwLanguageId,
	                              (LPTSTR)&lpvSysMsg,                   // LPTSTR  lpBuffer, тут формируется сообщение
	                              0,                                    // DWORD   nSize,
	                              nullptr);                             // va_list *Arguments

	if (eMsgLen)
	{
		s = CString(lpvSysMsg);
		LocalFree(lpvSysMsg);
	}
	else
	{
		s = _T("Не удалось получить сообщение об ошибке.");
	}
}

void AZNetwork::ShowError(const CString &msg, const int LastErr)
{
	CString s;
	ReportError(s, LastErr);
	g_BKMsgBox.Show(msg + _T("\n") + s);
}

bool        bInited = false;
bool        bConnected = false;
u_long      ulNonBlock = 0;
WSADATA     WSAData{};
FD_SET      fsWrite{};
FD_SET      fsRead{};
TIMEVAL     tv{ 10, 0 };
SOCKET      Client = INVALID_SOCKET;
SOCKADDR_IN Server;

int AZNetwork::landisk_init()
{
	if (!bInited)
	{
		// Инициализация Winsocket
		ZeroMemory(&WSAData, sizeof(WSADATA));
		int iResult = WSAStartup(MAKEWORD(2, 2), &WSAData);
		ASSERT(SOCKET_ERROR != iResult);

		if (iResult == SOCKET_ERROR)
		{
			ShowError(_T("Ошибка WSAStartup."));
			return iResult;
		}

		// Создаём сокет
		Client = socket(AF_INET, SOCK_STREAM, 0);
		ASSERT(INVALID_SOCKET != Client);

		if (INVALID_SOCKET == Client)
		{
			ShowError(_T("Не удалось создать сокет."));
			return -1;
		}

		int iRcvTimeout = 30000; //будем ждать ответа 30 секунд
		iResult = setsockopt(Client, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<char *>(&iRcvTimeout), sizeof(iRcvTimeout));

		if (iResult == SOCKET_ERROR)
		{
			ShowError(_T("Не удалось изменить время ожидания ответа."));
			return iResult;
		}

		// назначить сокет на тот IP адрес, который в настройках выбран
		uint32_t ipaddrblock[6] {};

		if (getNetParameters(reinterpret_cast<uint8_t *>(ipaddrblock), g_Config.m_strAZNetworkAdapter))
		{
			SOCKADDR_IN addr_local{};
			addr_local.sin_addr.s_addr = ipaddrblock[0];
			addr_local.sin_family = AF_INET;
			addr_local.sin_port = htons(LANDISK_LOCAL_PORT);
			iResult = bind(Client, (sockaddr *)&addr_local, sizeof(addr_local));

			if (SOCKET_ERROR == iResult)
			{
				closesocket(Client);
				return -1;
			}
		}

		bInited = true;
		// Настраиваем, куда будем подключаться
		ADDRINFOT *result = nullptr,
		           hints{};
		hints.ai_family = AF_INET;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_protocol = IPPROTO_TCP;
		iResult = GetAddrInfo(g_Config.m_strAZLANDISK_DNS_NAME, g_Config.m_strAZLANDISK_EXT_PORT, &hints, &result);

		if (SOCKET_ERROR == iResult)
		{
			ShowError(_T("Не удалось получить ip адрес сервера."));
			return -1;
		}

		Server = *(reinterpret_cast<SOCKADDR_IN *>(result->ai_addr));
	}

	return 0;
}

bool AZNetwork::CloseConnect(SOCKET *ps)
{
	char szBuf[LANDISK_DATA_SIZE + 1] = { 0 };

	if (bConnected)
	{
		// Переключаем сокет обратно в блокирующий режим
		if (ulNonBlock)
		{
			ulNonBlock = 0;
			int iResult = ioctlsocket(Client, FIONBIO, &ulNonBlock);
			ASSERT(SOCKET_ERROR != iResult);

			if (SOCKET_ERROR == iResult)
			{
				ShowError(_T("Не удалось переключить сокет в блокирующий режим."));
				return false;
			}
		}

		// Закрываем соединение
		int iResult = shutdown(*ps, SD_BOTH);

		// для чистоты получаем всё, что там было передано.
		while (recv(*ps, szBuf, LANDISK_DATA_SIZE, 0) > 0);

		if (SOCKET_ERROR == iResult)
		{
			ShowError(_T("Не удалось закрыть соединение."));
			return false;
		}

		bConnected = false;
	}

	return true;
}

int AZNetwork::landisk_uninit()
{
	if (bInited)
	{
		// Закрываем соединение
		bool bRet = CloseConnect(&Client);
		ASSERT(bRet);

		if (!bRet)
		{
			ShowError(_T("Не удалось закрыть сокет."));
			return -1;
		}

		// Закрываем сокет
		int iResult = closesocket(Client);

		if (SOCKET_ERROR == iResult)
		{
			ShowError(_T("Не удалось закрыть сокет."));
			return iResult;
		}

		Client = INVALID_SOCKET;
		// Очищаем ресурсы Winsocket
		iResult = WSACleanup();
		ASSERT(SOCKET_ERROR != iResult);

		if (SOCKET_ERROR == iResult)
		{
			ShowError(_T("Ошибка WSACleanup."));
			return iResult;
		}

		bInited = false;
	}

	return 0;
}

std::string AZNetwork::make_build_date()
{
	static const CString monnm[13] = { _T("Nul"),
	                                   _T("Jan"), _T("Feb"), _T("Mar"), _T("Apr"),
	                                   _T("May"), _T("Jun"), _T("Jul"), _T("Aug"),
	                                   _T("Sep"), _T("Oct"), _T("Nov"), _T("Dec"),
	                                 };
	COleDateTime tCompileDate;
	tCompileDate.ParseDateTime(_T(__DATE__), LOCALE_NOUSEROVERRIDE, 1033);
	CString s = monnm[tCompileDate.GetMonth()] + _T(" ")
	            + tCompileDate.Format(_T("%d %Y")) + _T(" ")
	            + Global::getCompileTime(_T("%H:%M:%S"));
	auto chStr = MFCStrUtil::WideStr2Str(s);
	return { chStr.get() };
}

uint16_t AZNetwork::landisk_cmd(const char *path, const uint32_t numblock, uint8_t *datablock, const uint8_t cmd)
{
	// подготовим пакет для передачи.
	landisk_request_t req{0};
	req.num_block = numblock;
	req.command = cmd;
	lstrcpynA(&req.path[0], path, LANDISK_PATH_SIZE);

	switch (cmd)
	{
		case LANDISK_CMD_OPEN:
		case LANDISK_CMD_OPENDIR:
		case LANDISK_CMD_SEARCH_REQ:
		{
			// если файл открываем, то надо передать данные такие
			auto req_st = reinterpret_cast<landisk_request_status_t *>(&req.data[0]);
			time_t ltime;
			time(&ltime);
			req_st->timestamp = static_cast<unsigned long>(ltime); /* Store as unix timestamp */
			req_st->uptime = (GetTickCount() - g_Config.m_nStartTick) / 1000;
			uint32_t ipaddrblock[6] {};

			if (getNetParameters(reinterpret_cast<uint8_t *>(ipaddrblock), g_Config.m_strAZNetworkAdapter))
			{
				req_st->ip_addr = ipaddrblock[0];
				req_st->dns_srv1 = ipaddrblock[4];
				req_st->dns_srv2 = ipaddrblock[5];
			}

			long tzone = 0;
			_get_timezone(&tzone);
			req_st->tzone = LOWORD(tzone);
			req_st->platform = static_cast<uint16_t>(AZ_Req_Platform::PDP11_BK);
			req_st->controller = static_cast<uint16_t>(AZ_Req_Controller::AZBK);
			req_st->version = (STM_version << 8) | AZ_version;
			const auto Build_Date_str = make_build_date();
			lstrcpynA(&req_st->build[0], Build_Date_str.c_str(), REQ_BUILD_SIZE);
			lstrcpynA(&req_st->url[0], Product_URL, REQ_URL_SIZE);
		}
		break;

		case LANDISK_CMD_WRITE:
			memcpy(&req.data[0], datablock, LANDISK_DATA_SIZE);
#if defined(_DEBUG)
			{
				FILE *f = fopen("_send_dmp.dat", "a+b");
				fwrite(datablock, 1, LANDISK_DATA_SIZE, f);
				fclose(f);
			}
#endif
			break;

		case LANDISK_CMD_READ:
		case LANDISK_CMD_CLOSE:
		case LANDISK_CMD_READDIR:
		case LANDISK_CMD_READ_SREQ:
			// тут ничего не делаем.
			break;

		default:
			break;
	}

	req.crc = crc16(reinterpret_cast<uint8_t *>(&req), sizeof(landisk_request_t) - 2);  // подсчет CRC16
	int iResult = 0;

	if ((iResult = landisk_init()) != 0)
	{
		return iResult;
	}

	// Задаём неблокирующий режим
	if (ulNonBlock == 0)
	{
		ulNonBlock = 1;
		iResult = ioctlsocket(Client, FIONBIO, &ulNonBlock);
		ASSERT(SOCKET_ERROR != iResult);

		if (iResult == SOCKET_ERROR)
		{
			ShowError(_T("Не удалось переключить сокет в не блокирующий режим."));
			return iResult;
		}
	}

	bool bSuccessConnect = false;

	for (int i = 0; i < LANDISK_COUNT_RETRY_TCP_OPEN; ++i)
	{
		// Use a non-blocking method to connect the server, please note that the return value of the Connect operation is always SOCKET_ERROR
		iResult = connect(Client, reinterpret_cast<sockaddr *>(&Server), sizeof(sockaddr_in));
		int iErrorNo = SOCKET_ERROR;
		int iLen = sizeof(iErrorNo);

		// If the getSockopt return value is not 0, then there is an error.
		if (SOCKET_ERROR == iResult && 0 != getsockopt(Client, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&iErrorNo), &iLen))
		{
			ShowError(_T("Ошибка во время установки соединения с сервером."), iErrorNo);
			return iResult;
		}

		FD_ZERO(&fsWrite);
		FD_SET(Client, &fsWrite);
		// If the socket in the fswrite is signal, the connection is successful, and the return value of the IRET is greater than 0.
		iResult = select(1, nullptr, &fsWrite, nullptr, &tv);

		if (iResult > 0)
		{
			bSuccessConnect = true;
			bConnected = true;
			break;
		}
	}  //for

	if (!bSuccessConnect)
	{
		ShowError(_T("Не удалось соединиться с сервером."));
		return -1;
	}

	FD_ZERO(&fsWrite);
	FD_SET(Client, &fsWrite);
	int ret = 0; // возвращаемое значение, код ошибки иои ответ от сервера
	// If the socket in the fswrite is a signal, you can use the SEND operation to send data.
	iResult = select(1, nullptr, &fsWrite, nullptr, &tv);

	if (iResult > 0)
	{
		iResult = send(Client, reinterpret_cast<char *>(&req), sizeof(landisk_request_t), 0);

		if (SOCKET_ERROR == iResult)
		{
			ShowError(_T("Ошибка передачи данных."));
			return iResult;
		}

		bool bNeedReceive = true; // нужно получать ответ от сервера.
		bool bNeedGetData = false; // команда должна возвращать данные

		switch (cmd)
		{
			// по этим командам, надо возвращать прочитанные данные
			case LANDISK_CMD_OPEN:
			case LANDISK_CMD_READ:
			case LANDISK_CMD_OPENDIR:
			case LANDISK_CMD_READDIR:
			case LANDISK_CMD_SEARCH_REQ:
			case LANDISK_CMD_READ_SREQ:
				bNeedGetData = true;
				break;

			// по этим командам уже флаги в нужном значении
			case LANDISK_CMD_WRITE:
				break;

			// по этой команде не нужно получать от сервера данные
			case LANDISK_CMD_CLOSE:
				bNeedReceive = false;
				break;
		}

		if (bNeedReceive)
		{
			FD_ZERO(&fsRead);
			FD_SET(Client, &fsRead);
			// If you have a signal in the set of fsread, you can read data with RECV.
			iResult = select(1, &fsRead, nullptr, nullptr, &tv);

			if (iResult > 0)
			{
				landisk_answer_t rcvd{};
				constexpr auto recv_size = sizeof(landisk_answer_t);
				iResult = recv(Client, reinterpret_cast<char *>(&rcvd), recv_size, 0);

				if (0 == iResult)
				{
					ShowError(_T("Соединение было закрыто сервером."));
					return iResult;
				}

				if (SOCKET_ERROR == iResult)
				{
					ShowError(_T("Ошибка приёма данных."));
					return iResult;
				}

				if (bNeedGetData)
				{
					const uint16_t crc = crc16(reinterpret_cast<uint8_t *>(&rcvd), recv_size - 2);  // подсчет CRC16

					if (crc != rcvd.crc)
					{
						// ошибка контрольной суммы
						ShowError(_T("Ошибка контрольной суммы."));
						rcvd.result = LANDISK_ANS_ERROR;
					}

					memcpy(datablock, &rcvd.data[0], LANDISK_DATA_SIZE);
				}

#if defined(_DEBUG)

				if (cmd == LANDISK_CMD_WRITE)
				{
					FILE *f = fopen("_recv_dmp.dat", "a+b");
					fwrite(&rcvd.data[0], 1, LANDISK_DATA_SIZE, f);
					fclose(f);
				}

#endif
				ret = rcvd.result;
			}
			else
			{
				ShowError(_T("Ошибка сети."));
				return iResult;
			}
		}
	}
	else
	{
		ShowError(_T("Ошибка сети."));
		return iResult;
	}

	return ret;
}
