#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <ESP8266WiFi.h>
#include "List.h"
#include "TCPClient.h"

#define MAX_CLIENT 0
//#define DEBUG_TCPS


template <typename T>
class TCPServer
{
  public: 
    TCPServer(uint16_t port, uint8_t maxClient = MAX_CLIENT, uint16_t clientDataBufferSize = 256)  : _maxClient(maxClient), _clientDataBufferSize(clientDataBufferSize), _port(port), _wifiServer(_port)
    {

    }
    
    virtual ~TCPServer()
    {
      _clientList.dispose();
    }

    void setMaxClient(uint8_t maxClient)
    {
      _maxClient = maxClient;
    }
    
    uint8_t getMaxClient() const
    {
      return _maxClient;
    }

    void setPort(uint16_t port)
    {
      _port = port;
      if(isStarted())
      {
        stop();
        start();
      }
    }

    uint16_t getPort() const
    {
      return _port;
    }
    
    uint8_t getConnectedClientsCount()
    {
      return _clientList.count();
    }
    
    virtual void run()
    {
      handleNewClients();
      getClientData();
    }
    
  	virtual void start(uint16_t port = 0, uint8_t backlog = 5)
    {
      if(!_serverStarted)
      {
        if(port)
        {
          _port = port;
          _wifiServer.begin(_port, backlog);
        }
        else
        {
          _wifiServer.begin(_port);
        }

        _serverStarted = true;
      }
    }
  
  	virtual void stop()
    {
      if(_serverStarted)
      {
        _wifiServer.stop();
        _clientList.dispose();
        _currentClient = nullptr;
        _serverStarted = false;
      }
    }

    virtual boolean isStarted(void) const
    {
      return _serverStarted;
    }

    virtual void enableTCPKeepAlive(uint16_t timeBetween2KATransmitions = TCP_DEFAULT_KEEPALIVE_IDLE_SEC,
                                    uint16_t timeBetweenFailedKARetransmissions = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC,
                                    uint8_t retriesBeforeDrop = TCP_DEFAULT_KEEPALIVE_COUNT)
    {
      _TKAIdleSec = timeBetween2KATransmitions;
      _TKAIntvSec = timeBetweenFailedKARetransmissions;
      _TKACount = retriesBeforeDrop;
    }

    virtual void disableTCPKeepAlive(){ _TKAIdleSec = 0;_TKAIntvSec = 0;_TKACount = 0; }
  
  protected:
    virtual T* createNewClient(WiFiClient wc)
    {
      return new T(wc, freeClientId(), _clientDataBufferSize);
    }
    
    virtual void handleNewClients()
    {
      WiFiClient wc;
      
      if(!_maxClient || _maxClient > _clientList.count())
      {
        wc = _wifiServer.available();
      }
      
      if(wc && wc.connected())
      {
        T *clientPointer = createNewClient(wc);
        //Serial.printf("Addr : %lu\n", clientPointer);
		    //We activate the TKA : (The check is internally done in the
		    //ClientContext.h class : ie if one of the provided parameters is 0, then TKA is disabled)
		    (clientPointer->_client).keepAlive(_TKAIdleSec, _TKAIntvSec, _TKACount);
       
        _clientList.addFirst(clientPointer);
        #ifdef DEBUG_TCPS
        Serial.printf("TCPServer : New client accepted. Id : %u , Number of clients : %u, local port : %u, remote port : %u\n",clientPointer->_id, _clientList.count(),clientPointer->_client.localPort(),clientPointer->_client.remotePort());
        if(_TKAIdleSec && _TKAIntvSec && _TKACount)
          Serial.printf("TCPKeepAlive enabled for client id : %u\n", clientPointer->_id);
        #endif
        greetClient(clientPointer);
      }
    
      _currentClient = _clientList.removeLastRef();//We pick a client in the list to process it's request
    
      if(_currentClient != nullptr)
      {
        if(_currentClient->_error != TCPClient::OK)
        {
          _currentClient->closeConnection();
          delete _currentClient; _currentClient = nullptr;
        }
      }
    }
    
    virtual void greetClient(T *client)
    {
	    client->_client.printf_P(PSTR("Successfully connected at %lu !\r\nYour are client with id : %u\r\n"), millis(), client->_id);
    }
    
    virtual void getClientData()
    {
      if(_currentClient == nullptr)
      {
        return;
      }
      
      if((_currentClient->_client).available())
      {
        fillDataBuffer(_currentClient);
      }
      else if(!(_currentClient->_client).connected())
      {
        #ifdef DEBUG_TCPS
        Serial.printf("TCPServer : Client disconnected and can be discarded : %u\n", _currentClient->_id);
        #endif
        _currentClient->_clientState = TCPClient::DISCARDED;
      }
	    else //Strange
	    {
		    #ifdef DEBUG_TCPS
		    Serial.printf("Client(%u/%u) id : %u, with TCP status : %u and available : %u, Is keepAlive enabled : %d\n", _clientList.count()+1, _maxClient, _currentClient->_id, (_currentClient->_client).status(), (_currentClient->_client).available(), (_currentClient->_client).isKeepAliveEnabled());
		    #endif
	    }

      processClientData(_currentClient);//We process the actual data
      _currentClient->_newDataAvailable = false;
      
      if(_currentClient->_clientState == TCPClient::ClientState::DISCARDED)
      {
        _currentClient->closeConnection();
        #ifdef DEBUG_TCPS
        Serial.printf("TCPServer : Client was discarded : %u\n", _currentClient->_id);
        #endif
        delete _currentClient;
        _currentClient = nullptr;
        
        return;
      }
      _clientList.addFirst(_currentClient);
    }
    
    virtual void processClientData(T *client)
    {
      if(client->_dataSize > 0 && ((char) client->_data[0] != '\r' && (char) client->_data[0] != '\n'))
      {
        Serial.printf("Client --> %u : #%s#\r\n", client->_id, (char *)client->_data);
      }
      client->freeDataBuffer(client->_dataSize);
    }

    virtual void fillDataBuffer(T *client)
    {
      uint16_t freeSpace = (client->_dataBufferSize-1/*for \0*/ - client->_dataSize);
          
      if(freeSpace > 0)
      {
        uint32_t bytesAvailable(client->_client.available());
        int amountToBeRead = bytesAvailable < freeSpace ? bytesAvailable : freeSpace;
          
        (client->_client).read(client->_data + client->_dataSize, amountToBeRead);
        client->_dataSize += amountToBeRead;
        client->_data[_currentClient->_dataSize] = '\0';
        client->_newDataAvailable = true;
      }
    }
    
    uint8_t freeClientId()
    {
      int listCounter(_clientList.count());
      int freeId(0);
      
      for(int i(0); i < listCounter; i++)
      {
        if(_clientList.getRef(i)->_id == freeId)
        {
          freeId++;
          i = -1; //We start from 0 again
        }
      }
      return freeId;
    }
    
	  boolean _serverStarted = false;
    uint8_t _maxClient, _TKACount = 0;
    uint16_t _clientDataBufferSize, _TKAIdleSec = 0, _TKAIntvSec = 0, _port;
    WiFiServer _wifiServer;
    T *_currentClient = nullptr; //current client to be processed
    List<T> _clientList;
  private:
};

#endif //TCPSERVER_H
