#pragma once
#include <string>
#include <tchar.h>
#include <iostream>
#include <atomic>
#include <Ws2tcpip.h>
#include <winsock2.h>
#include <Windows.h>
#include <thread>
#include <sstream>
#include <semaphore> 
#include <queue>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Iphlpapi.lib")
#include "BaseCommunication.h"
#include "EthernetSettings.h"
#include "BaseDataExtractor.h"
#include "Utility.h"
#include "DevicePinger.h"
#include "CommunicationStructures.h"
#include "CommunicationEnumeration.h"
#include "ClockUtilities.h"
#include <IPTypes.h>
#include <iphlpapi.h>

using namespace std;

#ifdef STARFLEETTOOLBOX_EXPORTS
#define ETHCOMM __declspec(dllexport)
#else
#define ETHCOMM __declspec(dllimport)
#endif

#define DATA_READY 1;
#define DATA_NOT_READY 0;

#define INTERFACE_IS_IN_SAME_NET 0;
#define INTERFACE_IS_NOT_IN_SAME_NET 1;
#define INTERFACE_NOT_USED 2;
#define INTERFACE_INVALID_MASKING 3;


/// <summary>
/// An ETHERNET oriented communication class. It inherits from BaseCommunication.
/// It ensures the ETHERNET communication on both UDP and TCP
/// This class holds the multi process mechanism to hold the command exchange between a HOST and a CLIENT
/// </summary>
class ETHCOMM EthernetCommunication :
    public BaseCommunication
{
public:
    const int TCP_NEWORKQUEUE_OVERFLOWLIMIT = 20;
    const int TCP_RAWDATA_QUEUE_OVERFLOWLIMIT = 20;
    const int MAX_SIZE_TO_READ = 40000;
    const int COMMAND_ACK_TIMEOUT = 5000;

    /// <summary>
    /// Base constructor
    /// </summary>
    EthernetCommunication();


    /// <summary>
    /// Copy constructor
    /// </summary>
    EthernetCommunication(MONITORING_MODE monitoring);

    /// <summary>
    /// Copy constructor (sets the ethernet settings)
    /// </summary>
    /// <param name="settings">The ethernet settings to be set</param>
    EthernetCommunication(EthernetSettings* settings);

    /// <summary>
    /// Copy constructor (sets the ethernet settings)
    /// </summary>
    /// <param name="settings">The ethernet settings to be set</param>
    /// <param name="monitoring">The MONITORING_MASK to be set</param>
    EthernetCommunication(EthernetSettings* settings, MONITORING_MODE monitoring);

    /// <summary>
    /// Destructor, stops the running threads by calling the StopAndClean internal method
    /// </summary>
    ~EthernetCommunication();

    /// <summary>
    /// Proceed to TCP and UDP connection semaphores and mutex creation and Threads launching
    /// </summary>
    CommunicationStatus Connect();


    /// <summary>
    /// Disconnects all communication channels, stops the running threads and wait them to join
    /// Closes the semaphores and mutex handles
    /// </summary>
    void Disconnect();

    /// <summary>
    /// Send a string command on the appropriate communication channel media
    /// </summary>
    /// <param name="commandToSend">The command to send as a string</param>
    /// <returns>A string representing the command replied on the communication channel media</returns>
    string SendCommand(string commandToSend);

    /// <summary>
    /// Wait the DATAWORKER to produce a RAW DATA. This method will wait on the _selRawDataAvailable semaphore to
    /// release the data dequeued from the _rawDataQueue.
    /// </summary>
    /// <param name="waitTimeout">The amount of miliseconds the process has to wait befor being released</param>
    /// <param name="semStatus">The status of the semaphore (updated by reference)</param> 
    /// <returns>A pointer to the RAW Data</returns>
    RawData WaitForRawData(int waitTimeout, unsigned long* semStatus);

private:
    in_addr stringToInAddr(const string& ipStr);
    int isInSameSubnet(const string& ipAddr, const string& subnetAddr, const string& subnetMask);
    ConnectionState IsSensorInSameNet(const string& sensorIp);
    PIP_ADAPTER_INFO FindAdapterForSecoket(SOCKET s);

    PIP_ADAPTER_INFO _tcpAdapterInfo;

    atomic<chrono::time_point<std::chrono::steady_clock>> _lastTcpDataTime;
    atomic<chrono::time_point<std::chrono::steady_clock>> _lastUdpDataTime;

    /// <summary>
    /// Returns the last communication error code
    /// </summary>
    CommErrorCode GetLastErrorCode();
    /// <summary>
    /// Returns the communication status
    /// </summary>
    /// <returns>Success = 0, -1 for failure</returns>
    CommunicationStatus GetConnectionStatus();

    /// <summary>
    /// Reconnect the socket
    /// </summary>
    CommunicationStatus Reconnect();

    /// <summary>
    /// Sets a flag to force the thread to exit the execution
    /// </summary>
    void StopAndClean();

    /// <summary>
    /// Connects the TCP media. Returns the connected socket
    /// </summary>
    /// <returns>The TCP socket</returns>
    CommunicationStatus ConnectTcp();

    /// <summary>
    /// Connects the UDP media. Returns the connected socket
    /// </summary>
    /// <returns>The UDP socket</returns>
    CommunicationStatus ConnectUdp();

    /// <summary>
    /// Disconnects the TCP media
    /// </summary>
    void DisconnectTcp();

    /// <summary>
    /// Disconnects the UDP media
    /// </summary>
    void DisconnectUdp();

    // TCP WORKER VARIABLES AND METHODS
    thread _tcpWorker;

    /// <summary>
    /// Starts the TCPWORKER thread
    /// </summary>
    void StartTcpWorker();

    /// <summary>
    /// TCP Worker business.
    /// It listens on the TCP socked for a data and discriminates either if it is a command ACK reply or a RAW Data.
    /// In case of a RAW data, the readed string is placed in the _dataTreaterQueue and the _semNetworkDataAvailable is released,
    /// advising the DATAWORKER of a RAW data availability
    /// </summary>
    /// <param name="name">The Thread name as a string</param>
    void TcpWorkerBusiness(string name);
    std::atomic<bool>  _stopTcpThread;
    string _commandAckAnswer;
    HANDLE _mutexcommandAckAnswer;      // semaphore to protect the _commandAckAnswer concurent access
    HANDLE _semCommandAckAvailable;		// semaphore to warn that Command ACK is available
    HANDLE _mutexSendAvailable;		    // prevent the socket to be used from another process to send data
    /// <summary>
    /// Sets the command ack answer (thread safe)
    /// </summary>
    /// <param name="pCommandAckAnswer">The string to assign</param>
    void setCommandAckAnswer(string pCommandAckAnswer);

    /// <summary>
    /// Gets the command ack answer (thread safe)
    /// </summary>
    /// <returns></returns>
    string getCommandAckAnswer();
    // END OF TCP WORKER VARIABLES AND METHODS

    // UDP WORKER VARIABLES AND METHODS
    thread _udpWorker;

    /// <summary>
    /// Starts the UDPWORKER thread
    /// </summary>
    void StartUdpWorker();

    /// <summary>
    /// UDP Worker business.
    /// It listens on the UDP socked for a RAW the readed string is placed in the _dataTreaterQueue and the _semNetworkDataAvailable is released,
    /// advising the DATAWORKER of a RAW data availability<
    /// </summary>
    /// <param name="name">The Thread name as a string</param>
    void UdpWorkerBusiness(string name);
    std::atomic<bool>  _stopUdpThread;
    // END UDP WORKER VARIABLES AND METHODS

    
    /// <summary>
    /// Process the data discrimination from the dataPacket variable and puts it into the dataVector reference
    /// </summary>
    /// <param name="dataPacket">The string containing the data packet to be treated</param>
    /// <param name="dataVector">A working vector reference</param>    
    /// <returns>DATA_READY when all dataPackets have been processed</returns>
    int ProcessRawData(string dataPacket, vector<string>* dataVector);

    queue<RawData> _rawDataQueue;	    // the MDI Raw data queue that contains the encapsulated raw data    
    HANDLE _mutexRawDataQueue;			// protects the MDI Raw data queue from multi process access
    HANDLE _semRawDataAvailable;		// semaphore to warn that RAW Data are available
    // END OF WORKER VARIABLES AND METHODS
    
    //MODE _mode = UDP;
    SOCKET _tcpSocket;
    SOCKET _udpSocket;   
    DevicePinger _pinger;
    std::atomic<bool> _wasWired;

    bool _dataRequested = false;
};

