#ifndef TCPSERVER_H
#define TCPSERVER_H

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

#define MAX_CLIENT -1
//#define DEBUG_TCPS


template <typename T>
class TCPServer
{
  public: 
    TCPServer(unsigned int port = 80, uint8_t maxClient = MAX_CLIENT, uint16_t clientDataBufferSize = 255)  : _wifiServer(port), _port(port), _serverStarted(true), _maxClient(maxClient), _clientDataBufferSize(clientDataBufferSize), _currentClient(NULL)
    {
      _wifiServer.begin();
    }
    
    virtual ~TCPServer()
    {
      _clientList.dispose();
    }
    
    uint8_t getMaxClient()
    {
      return _maxClient;
    }

    unsigned int getPort() const
    {
      return _port;
    }
    
    uint8_t getConnectedClientsCount()
    {
      return _clientList.count();
    }
    
    virtual void runServer()
    {
      handleNewClients();
      getClientData();
    }
    
  	virtual void startServer()
    {
      if(!_serverStarted)
      {
        _wifiServer.begin();
        _serverStarted = true;
      }
    }
  
  	virtual void stopServer()
    {
      if(_serverStarted)
      {
        _wifiServer.stop();
        _clientList.dispose();
        _serverStarted = false;
      }
    }
  
  protected:
    virtual T* createNewClient(WiFiClient wc)
    {
      return new T(wc, freeClientId(), _clientDataBufferSize);
    }
    
    virtual void handleNewClients()
    {
      WiFiClient wc;
      
      if(_maxClient == -1 || _maxClient > _clientList.count())
      {
        wc = _wifiServer.available();
      }
      
      if(wc && wc.connected())
      {
        T *clientPointer = createNewClient(wc);
        _clientList.addFirst(clientPointer);
        #ifdef DEBUG_TCPS
        Serial.print("TCPServer : New client accepted : ");Serial.println(clientPointer->_id);
        #endif
        greetClient(clientPointer);
      }
    
      _currentClient = _clientList.removeLastRef();//We pick a client in the list to process it's request
    
      if(_currentClient != NULL)
      {
        if(_currentClient->_error != TCPClient::OK)
        {
          _currentClient->closeConnection();
          delete _currentClient; _currentClient = NULL;
        }
      }
    }
    
    virtual void greetClient(T *client)
    {
      client->_client.println(F("Successfully connected !"));
      client->_client.print(F("Your are client with id : "));
      client->_client.println(client->_id);
    }
    
    virtual void getClientData()
    {
      if(_currentClient == NULL)
      {
        return;
      }
      
      uint32_t bytesAvailable((_currentClient->_client).available());
      
      if(bytesAvailable)
      {
          uint16_t freeSpace = (_currentClient->_dataBufferSize-1/*for \0*/ - _currentClient->_dataSize);
          
          if(freeSpace != 0)
          {
            int amountToBeRead = bytesAvailable < freeSpace ? bytesAvailable : freeSpace;
          
            (_currentClient->_client).read(_currentClient->_data + _currentClient->_dataSize, amountToBeRead);
            _currentClient->_dataSize += amountToBeRead;
            _currentClient->_data[_currentClient->_dataSize] = '\0';
            _currentClient->_newDataAvailable = true;
          }
      }
      else if(!(_currentClient->_client).connected())
      {
        #ifdef DEBUG_TCPS
        Serial.print("TCPServer : Client disconnected and can be discarded : ");Serial.println(_currentClient->_id);
        #endif
        _currentClient->_clientState = TCPClient::DISCARDED;
      }

      processClientData(_currentClient);//We process the actual data
      _currentClient->_newDataAvailable = false;
      
      if(_currentClient->_clientState == TCPClient::ClientState::DISCARDED)
      {
        _currentClient->closeConnection();
        #ifdef DEBUG_TCPS
        Serial.print("TCPServer : Client was discarded : ");Serial.println(_currentClient->_id);
        #endif
        delete _currentClient;
        _currentClient = NULL;
        
        return;
      }
      
      _clientList.addFirst(_currentClient);
      
    }
    
    virtual void processClientData(T *client)
    {
      if(client->_dataSize > 0 && ((char) client->_data[0] != '\r' && (char) client->_data[0] != '\n'))
      {
        Serial.print("Client --> ");Serial.print(client->_id);Serial.print(" : ");Serial.print((char *)client->_data);Serial.println("#");
      }
    
      client->freeDataBuffer(client->_dataSize);
    }
    
    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;
        }
      }
      return freeId;
    }
    
	  boolean _serverStarted;
    uint8_t _maxClient;
    unsigned int _port;
    uint16_t _clientDataBufferSize;
    WiFiServer _wifiServer;
    T *_currentClient; //current client to be processed
    List<T> _clientList;
  private:
};

#endif //TCPSERVER_H
