//---------------------------------------------------------------------------

#include <vcl.h>

#pragma hdrstop

#include "TPinger.h"
#pragma comment(lib, "Ws2_32.lib")
#pragma package(smart_init)

using namespace std;
using namespace std::chrono_literals;

bool TPinger::TestPing(AnsiString meta)
{
	bool result = false;
	auto icmpClient = new TIdIcmpClient(nullptr);
	auto logger = new TLogger();
	icmpClient->ReceiveTimeout = 1000;
	try
	{
		icmpClient->Host = meta;
		icmpClient->Ping();
		bool success = icmpClient->ReplyStatus->ReplyStatusType == rsEcho;
		if (success)
		{
			logger->Log("Ping ok ka: " + meta, llDebug);
			result = true;
		}else
		{
			logger->Log("Ping ne prolazi ka: " + meta, llCritical);
		}
	}catch (Exception &ex)
	{
		logger->Log("Ping ne prolazi ka: " + meta, llCritical);
	}catch (std::exception &stdex)
	{
		logger->Log("Ping ne prolazi ka: " + meta, llCritical);
	}
	delete icmpClient;
	delete logger;
	return result;

}

bool TPinger::TestPort(AnsiString meta, int portInt)
{
	try
	{
		AnsiString port = IntToStr(portInt);
		WSADATA wsaData;
		SOCKET ConnectSocket = INVALID_SOCKET;
		struct sockaddr_in clientService;
		int iResult;

		iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
		if (iResult != 0)
		{
			return false;
		}

		struct addrinfo* result = nullptr;
		struct addrinfo hints;
		struct sockaddr_in sa;
		int ipAdresa = inet_pton(AF_INET, meta.c_str(), &(sa.sin_addr));

		if (ipAdresa == 1)
		{
			sa.sin_family = AF_INET;
			sa.sin_port = htons(atoi(port.c_str()));

			ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (ConnectSocket == INVALID_SOCKET)
			{
				WSACleanup();
				return false;
			}
			iResult = connect(ConnectSocket, (struct sockaddr*)&sa, sizeof(sa));
		} else
		{
			ZeroMemory(&hints, sizeof(hints));
			hints.ai_family = AF_INET;
			hints.ai_socktype = SOCK_STREAM;
			hints.ai_protocol = IPPROTO_TCP;
			iResult = getaddrinfo(meta.c_str(), port.c_str(), &hints, &result);
			if (iResult != 0)
			{
				WSACleanup();
				return false;
			}

			ConnectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
			if (ConnectSocket == INVALID_SOCKET)
			{
				freeaddrinfo(result);
				WSACleanup();
				return false;
			}
			iResult = connect(ConnectSocket, result->ai_addr, (int)result->ai_addrlen);
		}

		bool ok = false;
		if (iResult == SOCKET_ERROR)
		{
			int error = WSAGetLastError();
			if (error == WSAEWOULDBLOCK || error == WSAEINPROGRESS)
			{
				ok = true;
			}
		}
		else
		{
			ok = true;
		}

		if (result != nullptr)
		{
			freeaddrinfo(result);
		}
		closesocket(ConnectSocket);
		WSACleanup();
		return ok;
	}catch(...)
	{
		return false;
	}
}

void TPinger::StartTrajniTest(AnsiString meta, int razmakSekunde, bool logujSve)
{
	auto logger = new TLogger();
	if (_pingRunning)
	{
		logger->Log("Ping vec pokrenut!", llCritical);
		delete logger;
		return;
	}
	_pingRunning = true;
	_stopPing = false;
	_pingLog = new TPingLog();
	_backPing = TTask::Create ([&, razmakSekunde, logujSve, meta]()
		{
			std::chrono::duration<int, std::ratio<1, 1>> razmak = razmakSekunde * 1s;
			TIdIcmpClient *client = new TIdIcmpClient(nullptr);
			client->Host = meta;
			client->ReceiveTimeout = 1000;
			do
			{
				if (_stopPing)
				{
					break;
				}
				try
				{
					client->Ping("BUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERS"
								 "BUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERSBUSCOPMUTERS");
					if (client->ReplyStatus->ReplyStatusType == rsEcho)
					{
						unsigned int rtt = client->ReplyStatus->MsRoundTripTime;
						_pingLog->DodajPing(rtt);
						if (logujSve)
						{
							logger->Log("Ping: " + meta + " -- " + UIntToStr(rtt) + "ms", llDebug);
						}
					}else
					{
						_pingLog->DodajNeuspeli();
						logger->Log("Ping: " + meta + " -- nije uspeo", llCritical);
					}
				} catch(Exception &ex)
				{
					logger->Log("Ping: " + meta + " -- nije uspeo", llCritical);
					logger->Log("Greska: " + ex.Message, llCritical);
					_pingLog->DodajNeuspeli();
				}
				unique_lock<mutex> lock(_awaiterMutex);
				_awaiter.wait_for(lock, razmak);
			}while(true);
			delete logger;
			delete client;
		});
	_backPing->Start();
}

void TPinger::StartTrajniPortTest(AnsiString meta, unsigned int port,
	int razmakSekunde, bool logujSve)
{
	auto logger = new TLogger();
	if (_pingRunning)
	{
		logger->Log("Ping vec pokrenut!", llCritical);
		return;
	}
	_pingRunning = true;
	_stopPing = false;
	_pingLog = new TPingLog();
	_backPing = TTask::Create ([&, razmakSekunde, logujSve, meta, port]()
		{
			std::chrono::duration<int, std::ratio<1, 1>> razmak = razmakSekunde * 1s;
			TIdTCPClient *tcpClient = new TIdTCPClient(nullptr);
			tcpClient->Host = meta;
			tcpClient->Port = port;
			tcpClient->ConnectTimeout = 1000;
			do
			{
				if (_stopPing)
				{
					break;
				}
				try
				{
					auto start = std::chrono::high_resolution_clock::now();
					tcpClient->Connect();
					tcpClient->Disconnect();
					auto end = std::chrono::high_resolution_clock::now();
					std::chrono::duration<double> duration = end - start;
					int durationMs = int(duration.count()*1000);
					_pingLog->DodajPing(durationMs);
					if (logujSve)
					{
						logger->Log("FB konekt/diskonekt: " + meta + " -- " + IntToStr(durationMs) + "ms", llInfo);
					}
				}catch (Exception &e)
				{
					logger->Log( "Nema konekcije ka serveru: " + meta + ":" + UIntToStr(port), llCritical);
					_pingLog->DodajNeuspeli();
				}catch (std::exception &stdex)
				{
					logger->Log( "Nema konekcije ka serveru: " + meta + ":" + UIntToStr(port), llCritical);
					_pingLog->DodajNeuspeli();
				}
				unique_lock<mutex> lock(_awaiterMutex);
				_awaiter.wait_for(lock, razmak);
			}
			while(true);
			delete logger;
			delete tcpClient;
		});
	_backPing->Start();
}

TPingLog *TPinger::ZaustaviTrajniTest(bool logujRezultate)
{
	auto logger = new TLogger();
	_stopPing = true;
	_awaiter.notify_all();
	_backPing->Wait();
	_pingRunning = false;
	if (logujRezultate)
	{
		logger->Log("Broj izvrsenih: " + IntToStr(_pingLog->BrojPingova()), llCritical);
		logger->Log("Procenat izgubljenih: " + IntToStr(_pingLog->ProcenatIzgubljenih()) + "%", llCritical);
		if (!_pingLog->KatastrofalnaGreska())
		{
			logger->Log("Prosecno: " + IntToStr(_pingLog->SrednjiRoundTrip()) + "ms", llCritical);
			logger->Log("Minimum: " + IntToStr(_pingLog->MinRoundTrip()) + "ms", llCritical);
			logger->Log("Maksimum: " + IntToStr(_pingLog->MaxRoundTrip()) + "ms", llCritical);
		}
	}
    delete logger;
	return _pingLog;
}
