#ifndef FTPSERVER_H
#define FTPSERVER_H

#include "TCPServer.h"
#define DEBUG_FTPS

template <typename T>
class FTPServer : public TCPServer<T>
{
  public:
    enum FTPClientState {WAITING_FOR_COMMANDS};
    enum FTPClientDataTransfer {NONE, LIST_DF, NLST_DF};
    enum BinaryFlag {OFF = 0, ON};
  
    FTPServer(unsigned int port = 21, const char *login = NULL, const char *password = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientCommandDataBufferSize = 255) : TCPServer<T>(port, maxClient, clientCommandDataBufferSize),
    _login(NULL),
    _password(NULL),
    _dataPort(1024),
    _dataServer(_dataPort)
    {
      if(login != NULL)
      {
        if(strlen(login) > 0)
        {
          _login = (char *)malloc((sizeof(char) * strlen(login)) + 1);
          strcpy(_login, login);
        }  
      }
      
      if(password != NULL)
      {
        if(strlen(password) > 0)
        {
          _password = (char *)malloc((sizeof(char) * strlen(password)) + 1);
          strcpy(_password, password);
        }
      } 
    }

    void setCustomDataPort(unsigned int port)
    {
      _dataPort = port;
    }

    virtual ~FTPServer()
    {
      free(_login);free(_password);
    }
    
  protected:
    virtual T* createNewClient(WiFiClient wc)
    {
      return new T(wc, TCPServer<T>::freeClientId(), TCPServer<T>::_clientDataBufferSize);
    }

    virtual void greetClient(T *client)
    {
      //The first time the client connects, we send the server's information
      client->_client.println("220 Welcome to the ESP8266SwissArmyBoard embedded FTP server.");
      client->_clientState = TCPClient::HANDLED;
    }

    virtual void processClientData(T *client)
    {
      if(client->_waitingForDataConnection)
      {
        #ifdef DEBUG_FTPS
        Serial.println("Listening for new data client");
        #endif
        WiFiClient dataClient = _dataServer.available();

        if(dataClient)
        {
          if(dataClient.connected())
          {
            client->_waitingForDataConnection = false;
            client->setDataClient(dataClient);
            #ifdef DEBUG_FTPS
            Serial.println("Data client accepted successfully");
            #endif
          }
          else
            dataClient.stop();
        }
      }

      if(client->_dataTransferPending == LIST_DF)//We list the files of the current directory
      {
        //We check if the dataConnection is established:
        if(client->_dataClient.connected())
        {
          //client->_client.println("125 Data connection already open.");
          
          client->_dataClient.print("-rw-r--r-- 1 owner group           213 Aug 26 16:31 README.txt\r\n\
          -rw-r--r-- 1 owner group           213 Aug 26 16:31 music.mp3\r\n\
          drw-r--r-- 1 owner group           213 Aug 26 16:31 directory\r\n\
          drw-r--r-- 1 owner group           213 Aug 26 16:31 subdir\r\n\
          ");

          client->_client.println("226 Closing data connection.");
          client->_dataClient.stop();
          client->_dataTransferPending = NONE;
        }
        //With timeout
        /*else
        {
          client->_client.println("425 Can't open data connection.");
        }*/
      }

      #ifdef DEBUG_FTPS
      if(client->_newDataAvailable)
      {
        Serial.print("Client --> ");Serial.print(client->_id);Serial.print(" : ");Serial.print((char *)client->_data);Serial.println("#");
      }
      #endif

      if(client->_dataSize > 0)
      {
        switch(client->_ftpClientState)
        {
          case WAITING_FOR_COMMANDS:
            processCommands(client);
            break;
        }
      }
    }
    
  private:
    void processCommands(T *client)
    {
      if(!client->parseCommandAndParameters()) //Failed to retrieve command and parameters
      {
        //We can close the connection or do other things
        return;
      }
      
      #ifdef DEBUG_FTPS
      Serial.printf("Issued command : '%s'\n",client->_ftpCommand);
      Serial.println("Get params :");
      for(int i = 0; i < client->_cmdParameters->count(); i++)
      {
        Serial.print(client->_cmdParameters->getParameter(i));Serial.print(" : ");Serial.printf("'%s'\n",client->_cmdParameters->getAt(i)->getString());
      }
      #endif
      
      if(strcmp(client->_ftpCommand,"USER") == 0)
      {
        //We check if we set a login and a password :
        if(_login != NULL && _password != NULL)
        {
            if(client->_cmdParameters->count() > 0)
            {
              client->_client.println("331 User name okay, need password.");
              client->setUsername(client->_cmdParameters->getAt(0)->getString());
            }
            else
              client->_client.println("530 Username required.");
        }
        else //The ftp access is open
        {
          client->_client.println("230 User logged in, proceed.");
        }
      }
      else if(strcmp(client->_ftpCommand,"PASS") == 0)
      {
        //We now check if the username and password correspond
        if(client->_cmdParameters->count() > 0)
        {
          if(strcmp(_login,client->_username) == 0 && strcmp(_password, client->_cmdParameters->getAt(0)->getString()) == 0)
          {
            client->_loggedIn = true;
            client->_client.println("230 User logged in, proceed.");
          }
          else
          {
            client->_client.println("530 Wrong username or password !.");
          }
        }
        else
        {
          client->_client.println("530 Password required.");
        }
      }
      else if(strcmp(client->_ftpCommand,"PWD") == 0)
      {
        client->_client.println("257 \"/FTP\"");
      }
      else if(strcmp(client->_ftpCommand,"TYPE") == 0)
      {
        if(client->_cmdParameters->count() > 0)
        {
          switch(client->_cmdParameters->getAt(0)->getString()[0])
          {
            case 'I':
              client->_binaryFlag = ON;
              client->_client.println("200 Command okay.");
              break;
            case 'L':
              client->_binaryFlag = ON;
              client->_client.println("200 Command okay.");
              break;
            case 'A':
              client->_binaryFlag = OFF;
              client->_client.println("200 Command okay.");
              break;
            default:
              client->_client.println("504 Command not implemented for TYPE.");
          }
        }
        else
        {
          client->_client.println("504 Command not implemented for TYPE.");
        }
      }
      else if(strcmp(client->_ftpCommand,"PASV") == 0)
      {
        _dataServer.begin(_dataPort);
        client->_client.printf("227 Entering Passive Mode (%u,%u,%u,%u,%d,%d).\r\n", WiFi.localIP()[0],  WiFi.localIP()[1],  WiFi.localIP()[2],  WiFi.localIP()[3], _dataPort/256, _dataPort%256);
        client->_waitingForDataConnection = true;
      }
      else if(strcmp(client->_ftpCommand,"LIST") == 0)
      {
        //We inform that a data transfer is pending
        client->_dataTransferPending = LIST_DF;
      }
      else
        client->_client.println("502 Command not implemented.");
    }
  
    char *_login;
    char *_password;
    unsigned int _dataPort;
    
    WiFiServer _dataServer; //In passive mode, the FTP server opens two different ports (one for the commands and the other for the data stream)
};

#endif //FTPSERVER_H
