#ifndef FTPSERVER_H
#define FTPSERVER_H

#include <SD.h>
#include "TCPServer.h"
#include "utilities.h"
#include "Dictionary.h"
//#define DEBUG_FTPS
#define SEND_RECV_BUFFER_SIZE 5000

template <typename T>
class FTPServer : public TCPServer<T>
{
  public:
    enum FTPClientState {INIT, WAITING_FOR_COMMANDS};
    enum FTPClientDataTransfer {NONE = 0, LIST_DF, NLST_DF, RETR_DF, STOR_DF, APPE_DF};
    enum FileTransferStatus {OK, NOT_FOUND, NO_FILE_NAME, MALLOC_ERROR};
    enum BinaryFlag {OFF = 0, ON};
    enum FTPMsgCode {_150, _200, _215, _220, _221, _230, _226, _227, _250, _257, _331, _350, _451, _502, _504, _530, _550 };

    FTPServer(SDClass *sdClass = NULL, uint16_t port = 21, const char *username = NULL, const char *password = NULL, uint8_t maxClient = MAX_CLIENT, uint16_t clientCommandDataBufferSize = 255) : TCPServer<T>(port, maxClient, clientCommandDataBufferSize),
      _FTPPassiveDataSocket(_FTPPassiveDataPort),
      _sdClass(sdClass)
    {
      if (username != NULL)
      {
        if (strlen(username))
        {
          _username = (char *)malloc((sizeof(char) * strlen(username)) + 1);
          strcpy(_username, username);
        }
      }

      if (password != NULL)
      {
        if (strlen(password))
        {
          _password = (char *)malloc((sizeof(char) * strlen(password)) + 1);
          strcpy(_password, password);
        }
      }
    }

    virtual ~FTPServer()
    {
      _FTPPassiveDataSocket.stop();
      free(_username); free(_password); free(_FTPDir);
    }

    virtual void start(void)
    {
      if(!TCPServer<T>::isStarted())
      {
        _FTPPassiveDataSocket.begin(_FTPPassiveDataPort);
        TCPServer<T>::start();
      }
    }

    virtual void stop(void)
    {
      if(TCPServer<T>::isStarted())
      {
        _FTPPassiveDataSocket.stop();
        TCPServer<T>::stop();
      }
    }

    virtual void setFTPPassiveDataPort(uint16_t port)
    {
      _FTPPassiveDataPort = port;
      if(TCPServer<T>::isStarted())
      {
        _FTPPassiveDataSocket.stop();
        _FTPPassiveDataSocket.begin(_FTPPassiveDataPort);
      }
    }

    virtual uint16_t getFTPPassiveDataPort(void) const
    {
      return _FTPPassiveDataPort;
    }

    virtual void setFTPDir(const char *FTPDir)
    {
      if(FTPDir)
      {
        free(_FTPDir);
        _FTPDir = (char *)malloc((strlen(FTPDir) * sizeof(char)) + 1);
        if(_FTPDir)
        {
          strcpy(_FTPDir, FTPDir);
        }
      }
      else
      {
        free(_FTPDir);
        _FTPDir = nullptr;
      }
    }

    virtual const char * getFTPDir(void)
    {
      return _FTPDir;
    }

    virtual void setUsername(const char *username)
    {
      if(username)
      {
        free(_username);
        _username = (char *)malloc((strlen(username) * sizeof(char)) + 1);
        if(_username)
        {
          strcpy(_username, username);
        }
      }
      else
      {
        free(_username);
        _username = nullptr;
      }
    }

    virtual const char * getUsername(void)
    {
      return _username;
    }

    virtual void setPassword(const char *password)
    {
      if(password)
      {
        free(_password);
        _password = (char *)malloc((strlen(password) * sizeof(char)) + 1);
        if(_password)
        {
          strcpy(_password, password);
        }
      }
      else
      {
        free(_password);
        _password = nullptr;
      }
    }

    virtual const char * getPassword(void)
    {
      return _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;
    }

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

        if(dataClient)
        {
          #ifdef DEBUG_FTPS
          Serial.println("Data client returns true");
          #endif
          if (dataClient.connected())
          {
            client->_waitingForDataConnection = false;
            client->setDataClient(dataClient);
            #ifdef DEBUG_FTPS
            Serial.println("Data client accepted successfully");
            #endif
          }
          else
            dataClient.stop();
        }
      }*/
      
      switch(client->_dataTransferPending)
      {
        case LIST_DF:
          if (client->_dataClient.connected())
          {
            #ifdef DEBUG_FTPS
            Serial.println("Listing started");
            #endif
            
            client->_client.println("150 File status okay.");
            if(sendFSTree(client))
            {
              client->_client.println("226 Closing data connection.");
            }
            else
            {
              client->_client.println("451 Requested action aborted.");
            }
            
            client->closeDataConnection();
            client->_dataTransferPending = NONE;
          }
          break;
        case RETR_DF:
          if (client->_dataClient.connected())
          {
            if(client->_fileSentBytes == 0)
              client->_client.println("150 File status okay.");

            FileTransferStatus fts;
            if(!sendFile(client,&fts))//Whole file was sent or error an occured
            {
              //we check the return code
              if(fts == OK)
              {
                client->_client.println("226 Closing data connection.");
                client->closeDataConnection();
                client->_dataTransferPending = NONE;
              }
              else if(fts == NOT_FOUND)
              {
                client->_client.println("451 File not found.");
                client->closeDataConnection();
                client->_dataTransferPending = NONE;
              }
              else if(fts == MALLOC_ERROR)
              {
                client->_client.println("451 Insufficient RAM space.");
                client->closeDataConnection();
                client->_dataTransferPending = NONE;
              }
            }
          }
        else if(client->_fileSentBytes != 0)
        {
          client->_client.println("426 Connection closed; transfer aborted.");
          client->closeDataConnection();
          client->_dataTransferPending = NONE;
        }
        break;
      case STOR_DF:
        if (client->_dataClient.connected() || client->_dataClient.available())//Here we need to check if client has some data available for reading. IMPORTANT 
          {
            if(client->_dataClient.available())
            {
              client->_fileIsBeeingReceived = true;
              #ifdef DEBUG_FTPS
              Serial.printf("receiving file %s\n", client->_currentFile);
              #endif
              FileTransferStatus fts = OK;
              
              if(!writeToSdCard(client, &fts)) //An error occured
              {
                if(fts == NO_FILE_NAME)
                {
                  client->_client.println("501 No file name given.");
                }
                else if(fts == NOT_FOUND)
                {
                  client->_client.println("451 File not found.");
                }
                else if(fts == MALLOC_ERROR)
                {
                  client->_client.println("451 Insufficient RAM space.");
                }
                else
                {
                  client->_client.println("451 Requested action aborted: local error in processing.");
                }
                client->closeDataConnection();
                client->_fileIsBeeingReceived = false;
                client->_dataTransferPending = NONE;
              }
            }
          }
          //If no data connection exists and no error was raised during writting, then it could be just a file creation with no data connection opened
          else if(client->_fileIsBeeingReceived || (client->_dataClientConnected || millis() - client->_actionTimeout > 5000)) 
          {
            if(client->_fileRecvBytes == 0) //File was just created empty
            {
              if(_sdClass->exists(client->_currentFile))
                _sdClass->remove(client->_currentFile);
        
              File file2create = _sdClass->open(client->_currentFile, FILE_WRITE);

              file2create.close();
              #ifdef DEBUG_FTPS
              Serial.println("File just created");
              #endif
            }
            else
            {
              #ifdef DEBUG_FTPS
              Serial.println("Whole file received");
              #endif
            }
            
            client->_client.println("226 Closing data connection.");
            
            client->_fileIsBeeingReceived = false;
            client->closeDataConnection();
            client->_dataTransferPending = NONE;
            client->_fileRecvBytes = 0;
          }
        break;
      case APPE_DF:
        if (client->_dataClient.connected() || client->_dataClient.available())//Here we need to check if client has some data for reading. IMPORTANT 
        {    
          if(client->_dataClient.available())
          {
            client->_fileIsBeeingReceived = true;
            #ifdef DEBUG_FTPS
            Serial.printf("appending to file %s\n", client->_currentFile);
            #endif
            FileTransferStatus fts = OK;
            
            if(!writeToSdCard(client, &fts, true)) //An error occured
            {
              if(fts == NO_FILE_NAME)
              {
                client->_client.println("501 No file name given.");
              }
              else if(fts == NOT_FOUND)
              {
                client->_client.println("451 File not found.");
              }
              else if(fts == MALLOC_ERROR)
              {
                client->_client.println("451 Insufficient RAM space.");
              }
              else
              {
                client->_client.println("451 Requested action aborted: local error in processing.");
              }
              client->closeDataConnection();
              client->_fileIsBeeingReceived = false;
              client->_dataTransferPending = NONE;
            }
          }
        }
        //If the connection is closed and data has been received, then we got the whole file
        else if(client->_fileIsBeeingReceived) 
        {
          #ifdef DEBUG_FTPS
          Serial.println("Whole file received");
          #endif
 
          client->_client.println("226 Closing data connection.");
            
          client->_fileIsBeeingReceived = false;
          client->closeDataConnection();
          client->_dataTransferPending = NONE;
          client->_fileRecvBytes = 0;
        }
        break;
      default:
        break;
      }
      
      #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;
          case INIT:
            _FTPDir ? client->setCurrentDirectory(_FTPDir) : client->setCurrentDirectory("");
            client->_ftpClientState = WAITING_FOR_COMMANDS;
            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 (_username != 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
            sendInfoResponse(_530, client);//client->_client.println("530 Username required.");
        }
        else //The ftp access is open
        {
          client->_loggedIn = true;
          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(_username, client->_username) == 0 && strcmp(_password, client->_cmdParameters->getAt(0)->getString()) == 0)
          {
            client->_loggedIn = true;
            client->_client.println("230 User logged in, proceed.");
          }
          else
          {
            sendInfoResponse(_530, client, "Wrong username or password !");//client->_client.println("530 Wrong username or password !.");
          }
        }
        else
        {
          sendInfoResponse(_530, client,"");//client->_client.println("530 Password required.");
        }
      }
      else if (strcmp(client->_ftpCommand, "PWD") == 0) //We set the default directory
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        client->_client.printf("257 \"%s\"\r\n", client->_currentDirectory);
      }
      else if (strcmp(client->_ftpCommand, "TYPE") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          switch (client->_cmdParameters->getAt(0)->getString()[0])
          {
            case 'I':
              client->_binaryFlag = ON;
              sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay.");
              break;
            case 'L':
              client->_binaryFlag = ON;
              sendInfoResponse(_200, client,"");//client->_client.println("200 Command okay.");
              break;
            case 'A':
              client->_binaryFlag = OFF;
              sendInfoResponse(_200, client,"");//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)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        client->_client.printf("227 Entering Passive Mode (%u,%u,%u,%u,%d,%d).\r\n", client->_client.localIP()[0], client->_client.localIP()[1], client->_client.localIP()[2], client->_client.localIP()[3], _FTPPassiveDataPort / 256, _FTPPassiveDataPort % 256);
        #ifdef DEBUG_FTPS
        Serial.println("Opening data server for new data client");
        #endif
        //We listen for 100 ms directly to accept the client
        uint64_t timeOut(millis());
        while(true)
        {
          WiFiClient dataClient = _FTPPassiveDataSocket.available();
          //Serial.printf("Client state : %d\n", dataClient.status());
          if (dataClient)
          {
            //Serial.printf("Data client is true , available : %d\n", dataClient.available());
            if (dataClient.connected())//Connected returns true if the status() is ESTABLISHED or available() returns true
            {
              client->setDataClient(dataClient);
              #ifdef DEBUG_FTPS
              Serial.println("Data client accepted from loop");
              #endif
              break;
            }
            else
              dataClient.stop();
          }

          if(millis() - timeOut > 100)
          {
            client->_waitingForDataConnection = true;
            break;
          }
          yield();
        }
      }
      else if (strcmp(client->_ftpCommand, "LIST") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        //We inform that a data transfer is pending
        client->_dataTransferPending = LIST_DF;
      }
      else if (strcmp(client->_ftpCommand, "CWD") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          //Go back one level
          if (strcmp(client->_cmdParameters->getAt(0)->getString(), "..") == 0)
          {
            char *dirCopy = (char *)malloc((sizeof(char) * strlen(client->_currentDirectory)) + 1);
            strcpy(dirCopy, client->_currentDirectory);
            
            char *p = strrchr(dirCopy, '/');
            if (dirCopy == p)
            {
              *(p + 1) = '\0';
            }
            else
              *(p) = '\0';

            #ifdef DEBUG_FTPS
            Serial.printf("Final dir : %s\n", dirCopy);
            #endif

            client->setCurrentDirectory(dirCopy);
            free(dirCopy);
          }
          else if (strcmp(client->_cmdParameters->getAt(0)->getString(), "/") == 0)
          {
            client->setCurrentDirectory("/");
          }
          else //If the client already nows the path, he will send it with a /
          {
            if(client->_cmdParameters->getAt(0)->getString()[0] == '/')//Then this is a name prefix
            {
              char *fullDirPath = constructFileNameWithPath("",client->_cmdParameters);
              client->setCurrentDirectory(fullDirPath);
              free(fullDirPath);
            }
            else
            {
              //Directories with spaces are now working:
              char *directoryFullpath = constructFileNameWithPath(client->_currentDirectory,client->_cmdParameters);
              
              #ifdef DEBUG_FTPS
              Serial.printf("Final dir : %s\n", directoryFullpath);
              #endif
              client->setCurrentDirectory(directoryFullpath);
              free(directoryFullpath);
            }
          }
          
          client->_client.println("250 Requested file action okay, completed.");
          #ifdef DEBUG_FTPS
          Serial.printf("CWD new dir : %s\n", client->_currentDirectory);
          #endif
        }
      }
      else if(strcmp(client->_ftpCommand, "RETR") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          //We save the file path to be sent
          char *file2store(NULL);
          if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
            file2store = constructFileNameWithPath("", client->_cmdParameters);
          else
            file2store = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
          
          #ifdef DEBUG_FTPS
          Serial.printf("Final file path : %s\n",file2store);
          #endif
          client->setCurrentFile(file2store);
          free(file2store);
          client->_dataTransferPending = RETR_DF;
        }
      }
      else if(strcmp(client->_ftpCommand, "MKD") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          char *dirNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
          
          if(dirNameWithPath != NULL)
          {
            #ifdef DEBUG_FTPS
            Serial.printf("Final dirName : #%s#\n",dirNameWithPath);
            #endif
            
            if(_sdClass->mkdir(dirNameWithPath))
            {
              client->_client.printf("257 \"%s\"\r\n", dirNameWithPath);
            }
            else
              client->_client.println("550 Failed to mkdir (no spaces allowed in dir name).");
              
            free(dirNameWithPath);
          }
          else
          {
            sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
          }
        }
        else
        {
          sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
        }
      }
      else if(strcmp(client->_ftpCommand, "RMD") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          //We have the dir name, we need to append the current directory...
          char *dirNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
          
          #ifdef DEBUG_FTPS
          Serial.printf("pathDirName to delete : #%s#\n",dirNameWithPath);
          #endif

          if(_sdClass->rmdir(dirNameWithPath))
          {
            client->_client.println("250 Requested file action okay.");
          }
          else
          {
            sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
          }
          
          free(dirNameWithPath);
        }
        else
        {
          sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
        }
      }
      else if(strcmp(client->_ftpCommand, "STOR") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          client->_client.printf_P(PSTR("150 File status okay; about to open data connection.\r\n"));
          //We save the file path to be sent
          char *fileNameWithPath(NULL);

          if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
            fileNameWithPath = constructFileNameWithPath("", client->_cmdParameters);
          else
            fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
          
          client->setCurrentFile(fileNameWithPath);
          #ifdef DEBUG_FTPS
          Serial.printf("File to store : #%s#\n", client->_currentFile);
          #endif
          free(fileNameWithPath);
          client->_dataTransferPending = STOR_DF;
          client->startTimeout();
        }
      }
      else if(strcmp(client->_ftpCommand, "APPE") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          client->_client.printf_P(PSTR("150 File status okay; about to open data connection.\r\n"));
          //We save the file path to be sent
          char *fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
          client->setCurrentFile(fileNameWithPath);
          #ifdef DEBUG_FTPS
          Serial.printf("File to append : #%s#\n", client->_currentFile);
          #endif
          free(fileNameWithPath);
          client->_dataTransferPending = APPE_DF;
        }
      }
      else if(strcmp(client->_ftpCommand, "SYST") == 0)
      {
        client->_client.println("215 UNIX Type: L8");
      }
      else if(strcmp(client->_ftpCommand, "DELE") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0)
        {
          //We have the file name, we need to append the current directory...
          char *file2deleteNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
          
          #ifdef DEBUG_FTPS
          Serial.printf("file to delete : #%s#\n",file2deleteNameWithPath);
          #endif

          if(_sdClass->remove(file2deleteNameWithPath))
          {
            client->_client.println("250 Requested file action okay.");
          }
          else
          {
            sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
          }
          
          free(file2deleteNameWithPath);
        }
        else
        {
          sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
        }
      }
      else if(strcmp(client->_ftpCommand, "RNFR") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
          
        if (client->_cmdParameters->count() > 0)
        {
          //We have the file name, we need to append the current directory...
          char *fileNameWithPath(NULL);
          
          if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
            fileNameWithPath = constructFileNameWithPath("", client->_cmdParameters);
          else
            fileNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
          
          #ifdef DEBUG_FTPS
          Serial.printf("file to rename : #%s#\n",fileNameWithPath);
          #endif

          client->setCurrentFile(fileNameWithPath);
          free(fileNameWithPath);

          client->_client.println("350 Requested file action pending further information.");
        }
        else
        {
          sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
        }
      }
      else if(strcmp(client->_ftpCommand, "RNTO") == 0)
      {
        if(!client->_loggedIn)
        {
          sendInfoResponse(_530, client);
          return;
        }
        
        if (client->_cmdParameters->count() > 0) //If the name starts with a /, we do not need to prepend the directory
        {
          //Here we rename the file
          char *file2RenameNameWithPath(NULL);
          if(client->_cmdParameters->getAt(0)->getString()[0] == '/')
            file2RenameNameWithPath = constructFileNameWithPath("", client->_cmdParameters);
          else
            file2RenameNameWithPath = constructFileNameWithPath(client->_currentDirectory, client->_cmdParameters);
            
          #ifdef DEBUG_FTPS
          Serial.printf("file to rename to : #%s#\n",file2RenameNameWithPath);
          Serial.printf("Old name : %s --> %s\n",client->_currentFile,file2RenameNameWithPath);
          #endif

          if(_sdClass->rename(client->_currentFile,file2RenameNameWithPath))
          {
            client->_client.println("250 Requested file action okay.");
          }else
            sendInfoResponse(_550, client,"");//client->_client.println("550 Requested action not taken.");
          
          free(file2RenameNameWithPath);
        }
      }
      else if(strcmp(client->_ftpCommand, "QUIT") == 0)
      {
        if(client->_dataTransferPending == NONE)//If no transfers are in progress
        {
          sendInfoResponse(_221, client);

          client->_client.stop();
          client->_clientState = TCPClient::ClientState::DISCARDED;
        }
        else
          sendInfoResponse(_221, client);
      }
      /*else if(strcmp(client->_ftpCommand, "SIZE") == 0)
      {
        if (client->_cmdParameters->count() > 0)
        {
          client->_client.println("213 15224");
        }
      }*/
      else
      {
        sendInfoResponse(_502, client);
        #ifdef DEBUG_FTPS
        Serial.println("Command not implemented");
        #endif
      }
    }

    //Here we write the received file to the sd card
    IRAM_ATTR inline boolean writeToSdCard(T *client, FileTransferStatus *fts, boolean append = false)
    {
      if (client->_currentFile != NULL)
      {
        if(_sdClass->exists(client->_currentFile) && client->_fileRecvBytes == 0 && !append)
          _sdClass->remove(client->_currentFile);
        
        File fileBeeingReceived = _sdClass->open(client->_currentFile, FILE_WRITE);

        if(fileBeeingReceived)
        {
          size_t mallocAcceptedSize(0);
          uint8_t *recvBuffer = (uint8_t*)mallocWithFallback(SEND_RECV_BUFFER_SIZE * sizeof(uint8_t), &mallocAcceptedSize);

          if(!recvBuffer)
          {
            *fts = MALLOC_ERROR;
            fileBeeingReceived.close();
            return false;
          }

          /*fileBeeingReceived.seek(client->_fileRecvBytes);*/
          size_t size = client->_dataClient.read(recvBuffer, mallocAcceptedSize);
          fileBeeingReceived.write(recvBuffer, size);

          free(recvBuffer);

          client->_fileRecvBytes += size;
          fileBeeingReceived.close();
          #ifdef DEBUG_FTPS
          Serial.printf("Writting : %d bytes to file\n", size);
          #endif
        }
        else
        {
          *fts = NOT_FOUND;
          return false;
        }
      }
      else
      {
        *fts = NO_FILE_NAME;
        return false;
      }
      return true;
    }
    
    //Here we send the fs tree to the ftp client
    IRAM_ATTR inline boolean sendFSTree(T *client)
    {
      if (client->_currentDirectory != NULL)
      {
        #ifdef DEBUG_FTPS
        Serial.printf("Directory : %s\n",client->_currentDirectory);
        #endif
        File currentDirectory = _sdClass->open(client->_currentDirectory);
        if (currentDirectory)
        {
          //currentDirectory.rewindDirectory();
          while (true) //May be removed in the future to improve responsiveness
          {
            File fileOrDir = currentDirectory.openNextFile();

            if (!fileOrDir) //No more files in the directory
              break;

            //We try to retrieve the last modification date and we remove one day and one hour
            const time_t fileModifDate = fileOrDir.getLastWrite() - 60*60;
            struct tm *timeP = localtime(&fileModifDate);
            
            //We get the month's three letter abbreviation
            uint32_t monthAbbreviation = monthNumTo3LetterAbbreviation(timeP->tm_mon + 1); //+1 because in the tm struct, month goes from 0 to 11 ...
            
            #ifdef DEBUG_FTPS
            Serial.printf("Filename : %s, %s %d %d %d:%d\n", fileOrDir.name(), (char *)&monthAbbreviation, timeP->tm_mon, timeP->tm_mday, timeP->tm_hour, timeP->tm_min);
            #endif

            char zero_prepended[3][3] = {"","",""};
            
            client->_dataClient.printf("%crwxrwxrwx 1 owner esp8266           %d %s %s %s:%s %s\r\n", 
            fileOrDir.isDirectory() ? 'd' : '-', 
            fileOrDir.isDirectory() ? 0 : fileOrDir.size(),
            (char *)&monthAbbreviation,
            dateTimeFormater(zero_prepended[0],timeP->tm_mday,'0'),
            dateTimeFormater(zero_prepended[1],timeP->tm_hour ,'0'),
            dateTimeFormater(zero_prepended[2],timeP->tm_min,'0'),
            fileOrDir.name());

            fileOrDir.close();
          }

          currentDirectory.close();
        }
        else //Failed to open directory
        {
          #ifdef DEBUG_FTPS
          Serial.println("Failed to open directory");
          #endif
          return false;
        }
        return true;
      }
      return false;
    }

    //The binary flag needs to be taken into consideration
    IRAM_ATTR inline boolean sendFile(T *client, FileTransferStatus *fts)
    {
      if (client->_currentFile != nullptr)
      {
        File fileToSend = _sdClass->open(client->_currentFile);
        
        if (fileToSend)
        {
          *fts = OK;
          size_t readBytes(0), sentBytes(0);
          fileToSend.seek(client->_fileSentBytes);
          
          if(fileToSend.available())
          {
            size_t mallocAcceptedSize(0);
            uint8_t *sendBuffer = (uint8_t*)mallocWithFallback(SEND_RECV_BUFFER_SIZE * sizeof(uint8_t), &mallocAcceptedSize);

            if(!sendBuffer)
            {
              *fts = MALLOC_ERROR;
              fileToSend.close();
              return false;
            }

            readBytes = fileToSend.read(sendBuffer, mallocAcceptedSize);
            sentBytes = client->_dataClient.write(sendBuffer, readBytes);

            free(sendBuffer);

            client->_fileSentBytes += sentBytes;

            #ifdef DEBUG_FTPS
            Serial.printf("File : bytes read : %u, bytes sent : %u - free stack : %u\n", readBytes, sentBytes, ESP.getFreeContStack());
            #endif
          }
          else //The whole file has been sent
          {
            fileToSend.close();
            client->_fileSentBytes = 0;
            return false;
          }
          
          fileToSend.close();
        }
        else //Failed to open file maybe not found
        {
          #ifdef DEBUG_FTPS
          Serial.println("Failed to open file");
          #endif
          *fts = NOT_FOUND;
          return false;
        }
        
        return true;//Still things to send
      }
      
      *fts = NOT_FOUND;
      return false;
    }
    
    //This functions construct the full file path.
    //ie : if the current directory is : "/somedir/subdir" and the received file name is : "my file .txt"
    //then it will return : "/somedir/subdir/my file .txt"
    //Note that the file name is contained in a parameter list : "my" in the first element, then "file" in the second and finally ".txt" in the third.
	  //Return NULL if malloc fails
	  // DO NOT FORGET TO FREE THE ALLOCATED STRING AFTER USING IT....
    static char *constructFileNameWithPath(const char *dir, Dictionary<DictionaryHelper::StringEntity> *parameters)
    {
      uint16_t fileWithPathSize(strlen(dir) + 1) /*dir path plus filename*/, paramCount(parameters->count());
      char *fileNameWithPath(NULL);
	  
      for(int i(0); i < paramCount; i++)
      {
        fileWithPathSize += strlen(parameters->getAt(i)->getString()) + 1;
      }
      
      fileNameWithPath = (char *)malloc( sizeof(char) * fileWithPathSize );

      #ifdef DEBUG_FTPS
      Serial.printf("Allocated string size : %d\n",fileWithPathSize);
      #endif
	  
	    if(fileNameWithPath == NULL)//Malloc fails
		    return NULL;
      
      strcpy(fileNameWithPath, dir);
      if(strcmp(fileNameWithPath, "/") != 0 && strlen(dir) != 0)
        strcat(fileNameWithPath,"/");
      
      for(int i(0); i < paramCount; i++)
      {
        strcat(fileNameWithPath, parameters->getAt(i)->getString());

        if(i != paramCount-1)
          strcat(fileNameWithPath," ");
      }
	  
      return fileNameWithPath;
    }

    //Error code functions
    void sendInfoResponse(FTPMsgCode code, T *client, const char *msg = "")
    {
      switch(code)
      {
        case _200:
          client->_client.printf_P(PSTR("200 Command okay. %s\r\n"), msg);
          break;
        case _221:
          client->_client.printf_P(PSTR("221 Service closing control connection. %s\r\n"), msg);
          break;
        case _502:
          client->_client.printf_P(PSTR("502 Command not implemented. %s\r\n"), msg);
          break;
        case _530:
          client->_client.printf_P(PSTR("530 Password required. %s\r\n"), msg);
          break;
        case _550:
          client->_client.printf_P(PSTR("550 Requested action not taken. %s\r\n"), msg);
          break;
        default:
          client->_client.printf_P(PSTR("XXX Unhandled error code. %s\r\n"), msg);
      }
    }
    
    char *_username = nullptr;
    char *_password = nullptr;
    char *_FTPDir =  nullptr;
    uint16_t _FTPPassiveDataPort = 1024;
    
    WiFiServer _FTPPassiveDataSocket; //In passive mode, the FTP server opens two different ports (one for the commands and the other for the data stream)
    SDClass *_sdClass;
};

#endif //FTPSERVER_H
