#ifndef __MODBUSLIBRARY__
#define __MODBUSLIBRARY__
//TODO: manager connections - dát obslužnou rutinu klienta do threadu (mutex = data), ve FreeLibrary kill all
// Server adresace : diskrétní vstupy(16)..0x0000  cívky(16)..0x0100  vstupní registry(8)..0x0200
//                    uchovávací registry(8)..0x0300   <- adresace všech segmentů je od 0x0000(řešeno fcemi)

// V GUI : fronta požadavků : vybere z fronty->pošle->čeká na odpověď->výsledek     -> dašlý z fronty
// v GUI : Handler - provede dannou funkci až se provede funkce v knihovně (posílání a čtení) - testovat
//			integritu zpráv - MsgNo
// TODO: když bude zpráva delší než MSGSIZE rozdělit   - teoreticky by nemělo překročit limit
//		server je uživatelsky nepřístupný, a v klientu je ošetřeno pomocí uživatelských funkcí
// TODO: klient - read, když dojde Modbus hlavička čekat na zbytek zprávy

//#define Develop

#include "stdio.h"
#include "winsock.h"
#include "string.h"
#include "time.h"
#include "process.h"
#include <iostream>
//#include "windows.h"
#include "math.h"
#include <sys/stat.h>
#pragma comment(lib, "ws2_32.lib")

#ifndef Develop
#define EXPORTCALL extern "C" __declspec(dllexport)
#else
#define EXPORTCALL extern
#endif

#define ES_ERROR_FUNCCODE 1
#define ES_ERROR_ADDRESS 2
#define ES_ERROR_DATA 3
#define ES_ERROR_EXECUTION 4
//#define ES_ERROR_EXECUTION 5
//#define ES_ERROR_EXECUTION 6
// chybové kódy serveru dle modbus protokolu

#define EC_NEW_PARAMETERS 1
#define ES_NEW_PARAMETERS 2
#define EL_INITIALIZE 3
#define EC_CONNECT_HOST 4
#define EC_CONNECT_SOCKET 5
#define EC_CONNECT_CONNECTED 6
#define EC_CONNECT_PROBLEM 7
#define EC_DISCONNECT_DISCONNECTED 8
#define EC_SENDING_DISCONNECTED 9
#define EC_SENDING_PROBLEM 10
#define EC_READING_DISCONNECTED 11
#define EC_READING_PROBLEM 12
#define EC_REQUEST_EXCEPTION 13
#define ES_ESTABLISH_SOCKET 14
#define ES_ESTABLISH_RUNNING 16
#define ES_ESTABLISH_NAMING 17
#define ES_ESTABLISH_LISTENING 18
#define ES_ESTABLISH_TREAD 19
#define ES_HANDLING_ACCEPT 20
#define ES_SEND_CONNECTION 21
#define ES_SEND_PROBLEM 22
#define ES_READ_CONNECTION 23
#define ES_READ_PROBLEM 24
#define ES_SHUTDOWN_STOPPED 25
#define ES_RESPONSE_EXCEPTION 26
#define ES_REQUEST_EXCEPTION 27
#define ES_REQUEST_DISCONNECT 28
#define EC_ANSWER_EXCEPTION 29
#define EC_ANSWER_DISCONNECT 30
#define EC_SERVER_ERROR 31
#define EC_SERVER_ERROR_FUNCCODE EC_SERVER_ERROR+ES_ERROR_FUNCCODE
#define EC_SERVER_ERROR_ADDRESS EC_SERVER_ERROR+ES_ERROR_ADDRESS
#define EC_SERVER_ERROR_DATA EC_SERVER_ERROR+ES_ERROR_DATA
#define EC_SERVER_ERROR_EXECUTION EC_SERVER_ERROR+ES_ERROR_EXECUTION
//#define EC_SERVER_ERROR_EXECUTION EC_SERVER_ERROR+ES_ERROR_EXECUTION
//#define EC_SERVER_ERROR_EXECUTION EC_SERVER_ERROR+ES_ERROR_EXECUTION
#define EC_PARSE_ERROR 40
#define ES_CHANGEDATA_ERROR 41
// definice chybových kódů

#define FC_READ_COILS 1
#define FC_READ_DISCRETE_INPUTS 2
#define FC_READ_HOLDING_REGISTERS 3
#define FC_READ_INPUT_REGISTERS 4
#define FC_WRITE_COIL 5
#define FC_WRITE_COILS 15
#define FC_WRITE_HOLDING_REGISTER 6
#define FC_WRITE_HOLDING_REGISTERS 16
// definice podporovaných funkčních kódů

#define MSGSIZE 260
// maximální délka Modbus zprávy (historicky omezeno)

static bool FLibraryInitialized;
// je-li knihovna nainicializována

typedef struct SClient
{
	bool FConnected;
	char FIP[15];
	int FPort;
	unsigned char FLastError;
	WORD FMsgCounter;
	SOCKET FSocket;
	char FSendMessage[MSGSIZE];
	char FReadMessage[MSGSIZE];
	int FSendMessageSize;
	int FReadMessageSize;
}TClient, *PClient;
// struktura pracovního spojení klienta, která si uchová všechny potřebné parametry
// pro spojení a jeho obsluhu

typedef struct SServerData
{
	bool *FDiscreteInputs;
	bool *FCoils;
	WORD *FInputRegisters;
	WORD *FHoldingRegisters;
	WORD nDiscreteInputs;
	WORD nCoils;
	WORD nInputRegisters;
	WORD nHoldingRegisters;
}TServerData, *PServerData;
// struktura dat serveru

typedef struct SServer
{
	bool FRunning;
	int FPort;
	WORD FLastError;
	SOCKET FMainSocket;
	SOCKET FClient;
	HANDLE FServerThread;
	WORD FServerMaxClients;
	//HANDLE *FClientThreads;
	PServerData FData;
}TServer, *PServer;
// struktura pracovního spojení serveru, která si uchová všechny potřebné parametry
// pro spojení a jeho obsluhu

//PConnection FConnectionList[20];

// ***GENERAL***
int InitializeLibrary();
// inicializace knihovny
int ReleaseLibrary();
// korektní ukončení práce se socket
EXPORTCALL char* GetErrorMessage(WORD errorCode);
EXPORTCALL int LastClientError(PClient client);
EXPORTCALL int LastServerError(PServer server);
// vypíše poslední chybu v pracovním spojení
void sleep(clock_t x_ms);
// pozastaví danné vlákno na x milisekund

// ***CLIENT***
EXPORTCALL PClient NewClient(char *ip, int port);
// vytvoří nového clienta a nainicializuje ho, vrátí v návratovém typu
EXPORTCALL int DestroyClient(PClient client);
// korektní uvolnění clienta z paměti
EXPORTCALL int ConnectClient(PClient client);
// nachystá socket a připojí klienta k serveru
EXPORTCALL int DisconnectClient(PClient client);
// odpojí klienta od serveru
int SendMsg(PClient client, char *buffer, DWORD length);
// pošle obecnou zprávu serveru
int ReadMsg(PClient client, char *buffer, DWORD *read);
// čte obecnou zprávu ze serveru
int SendRequest(PClient client, unsigned char functionType, WORD baseAddress, WORD count, unsigned char *data, unsigned char dataLength);
// pošle Modbus zprávu serveru
int GetAnswer(PClient client, WORD *msgNo, unsigned char *deviceId, unsigned char *functionCode, WORD *baseAddress, WORD *count, unsigned char **data, unsigned char *dataLength);
// čeká na odpověď ze serveru a zparsuje ji
EXPORTCALL int WriteCoil(PClient client, WORD number, bool stateon);
// pošle zprávu o zapsání cívky
EXPORTCALL int ReadCoil(PClient client, WORD number, bool *stateon);
// pošle zprávu o vrácení stavu cívky
EXPORTCALL int WriteHoldingRegister(PClient client, WORD number, WORD value);
// pošle zprávu o zapsání hodnoty registru
EXPORTCALL int ReadHoldingRegister(PClient client, WORD number, WORD *value);
// pošle zprávu o vrácení hodnoty registru
EXPORTCALL int ReadCoils(PClient client, WORD from, WORD count, bool **stateons);
// pošle zprávu o vrácení stavů cívek v novém poli
EXPORTCALL int ReadHoldingRegisters(PClient client, WORD from, WORD count, WORD **values);
// pošle zprávu o vrácení hodnot registrů v novém poli
EXPORTCALL int ReadDiscreteInputs(PClient client, WORD from, WORD count, bool **stateons);
// pošle zprávu o vrácení stavů diskrétních vstupů v novém poli
EXPORTCALL int ReadInputRegisters(PClient client, WORD from, WORD count, WORD **values);
// pošle zprávu o vrácení hodnot analogových vstupů v novém poli
EXPORTCALL int WriteCoils(PClient client, WORD from, WORD count, bool stateon[]);
// pošle zprávu o zapsání cívek
EXPORTCALL int WriteHoldingRegisters(PClient client, WORD from, WORD count, WORD values[]);
// pošle zprávu o zapsání hodnot registrů
EXPORTCALL int GetRWBuffer(PClient client, char **RBuffer, char **WBuffer, int *RBufferSize, int *WBufferSize);
// vrátí surová data poslané a přečtené zprávy

// ***SERVER***
EXPORTCALL PServer NewServer(int port, WORD serverMaxClients, WORD nDiscreteInputs, WORD nCoils, WORD nInputRegisters, WORD nHoldingRegisters);
// vytvoří nový server a nainicializuje ho, vrátí v návratovém typu
EXPORTCALL int DestroyServer(PServer server);
// korektní uvolnění serveru z paměti
EXPORTCALL int EstablishServer(PServer server);
// nainicializuje socket serveru a uvede ho do stavu naslouchání, pak spustí nekonečnou
// obslužnou rutinu klientů
EXPORTCALL int ShutdownServer(PServer server);
// ukončí běh serveru, případně odpojí aktuálního clienta
void HandlingClients(void *param);
// nekonečná obslužná rutina klientů
int ClientService(PServer server);
// rutina pro obsluhu připojeného klienta - příjem žádosti a vyslání odpovědi, nastavování
// datového prostoru a jeho čtení
int SendMsg(PServer server, char *buffer, DWORD length);
// pošle obecnou zprávu clientovi
int ReadMsg(PServer server, char *buffer, DWORD *read);
// čte obecnou zprávu od clienta
int SendResponse(PServer server, WORD msgNo, unsigned char deviceId, unsigned char functionType, unsigned char *data, unsigned char dataLength);
// pošle klientovi modbus zprávu - korektní odpověď
int SendErrorResponse(PServer server, WORD msgNo, unsigned char deviceId, unsigned char functionType, unsigned char errorCode);
// pošle klientovi modbus zprávu - chybovou odpověď
int GetRequest(PServer server, WORD *msgNo, unsigned char *deviceId, unsigned char *functionType, WORD *baseAddress, WORD *count, unsigned char **data, unsigned char *dataLength);
// čte modbus zprávu a zparsuje ji
bool IsValidFunctionCode(unsigned char functionCode);
// testuje, zda je v pořádku funkční kód
bool IsValidAddress(PServer server, unsigned char functionCode, WORD baseAddress, WORD count);
// testuje, zda-li je v pořádku adresace
bool IsValidDataValue(PServer server, unsigned char functionCode, WORD baseAddress, WORD count, unsigned char *data, unsigned char dataLength);
// testuje, zda jsou hodnoty dat korektní
int DoOperation(PServer server, WORD msgNo, unsigned char deviceId, unsigned char functionCode, WORD baseAddress, WORD count, unsigned char *data);
// provede operaci, pošle korektní odpověď klientovi a vrátí 0
EXPORTCALL int ChangeData(PServer server, bool discreteInputs[], WORD inputRegisters[], bool **coils, WORD **holdingRegisters);
// funkce čte hodnoty cívek a uchovávacích registrů, zapisuje hodnoty diskrétních vstupů a vstupních registrů
// použití funkce je pro ovládání datové části virtuálního serveru

#endif
