#ifndef WEBSERVER_H
#define WEBSERVER_H

#include "TCPServer.h"
#include "Dictionary.h"
#define DEBUG_WEBS

template <typename T>
class WEBServer : public TCPServer<T>
{
	public:
		enum HttpRequestMethod {UNDEFINED, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH};
		enum HttpVersion {UNKNOWN, HTTP_0_9, HTTP_1_1, HTTP_1_0, HTTP_2_0};
		enum HttpMIMEType{UNKNOWN_MIME, TEXT_PLAIN, TEXT_CSS, TEXT_HTML, TEXT_JAVASCRIPT, APPLICATION_JSON, APPLICATION_X_WWW_FORM_URLENCODED, IMAGE_PNG, IMAGE_JPEG, AUDIO_MPEG, APPLICATION_OCTET_STREAM};
		enum HttpParserStatus {HTTP_VERB, HTTP_RESSOURCE, HTTP_VERSION, HTTP_PARAMS, POST_DATA};
    enum WEBClientState {ACCEPTED, QUERY_PARSED, RESPONSE_SENT, DONE};
    enum HTTP_CODE {_400, _401, _403, _404, _500, _501};

    WEBServer(unsigned int port = 80, uint8_t maxClient = MAX_CLIENT, uint16_t clientDataBufferSize = 512) : TCPServer<T>(port, maxClient, clientDataBufferSize) {}
	protected:
	private:
    virtual T* createNewClient(WiFiClient wc)
    {
      return new T(wc, TCPServer<T>::freeClientId());
    }
    
    virtual void greetClient(T *client)
    {
  
    }
    
		virtual void processClientData(T *client)
    {
      switch(client->_WEBClientState)
      {
        case ACCEPTED:
          queryParser(client);
          break;
        case QUERY_PARSED:
          sendDataToClient(client);
          break;
        case RESPONSE_SENT:
          break;
        case DONE:
          break;
      }
      
        
    }

    void queryParser(T *client)
    {
        switch(client->_httpParserState)
        {
          case HttpParserStatus::HTTP_VERB:
          {
            #ifdef DEBUG_WEBS
            Serial.println((char *)client->_data);
            #endif
            
            char *pVerb = strstr((char *)client->_data, " ");
            
            if(pVerb != NULL)
            {
              *pVerb = '\0';
              client->_httpRequestData.HRM = getHttpVerbEnumValue((char *)client->_data);
              client->freeDataBuffer((pVerb - (char *)client->_data) +1);

              if(client->_httpRequestData.HRM == HttpRequestMethod::UNDEFINED) //Error 400
              {
                sendInfoResponse(HTTP_CODE::_400, client, "The server could not understand the request due to invalid syntax");
                client->_clientState = TCPClient::ClientState::DISCARDED;
                break;
              }
              
              #ifdef DEBUG_WEBS
              Serial.print("Verb : ");Serial.println(client->_httpRequestData.HRM);
              Serial.println((char *)client->_data);
              #endif
              
              client->_httpParserState = HttpParserStatus::HTTP_RESSOURCE;
            }
            else
            {
              sendInfoResponse(HTTP_CODE::_400, client, "The server could not understand the request due to invalid syntax");
              client->_clientState = TCPClient::ClientState::DISCARDED;
              break;
            }
          } 
            break;
          case HttpParserStatus::HTTP_RESSOURCE:
          {
            char *pRsrc = strstr((char *)client->_data, " ");
          
            if(pRsrc != NULL)
            {
              *pRsrc = '\0';
              uint16_t safeLength = pRsrc - (char *)client->_data <= client->_httpRequestData.maxResourceBuffer ? pRsrc - (char *)client->_data : client->_httpRequestData.maxResourceBuffer;

              #ifdef DEBUG_WEBS
              Serial.print("Resrc length : ");Serial.println(safeLength);
              #endif

              client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (safeLength+1) ); //for \0
              if(client->_httpRequestData.httpResource != NULL)
              {
                strncpy(client->_httpRequestData.httpResource, (char *)client->_data, safeLength);
                client->_httpRequestData.httpResource[safeLength] = '\0';
              }
              else //Error 500
              {
                sendInfoResponse(HTTP_CODE::_500, client, "Failed to allocate memory for resources");
                client->_clientState = TCPClient::ClientState::DISCARDED;
                break;
              }
              
              client->freeDataBuffer(safeLength + 1);
                
              #ifdef DEBUG_WEBS
              Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource);
              Serial.println((char *)client->_data);
              #endif
            }
            else //Resource is probably too long, so we truncate it
            {
              client->_httpRequestData.httpResource = (char *) malloc(sizeof(char) * (client->_httpRequestData.maxResourceBuffer+1) ); //for \0
              if(client->_httpRequestData.httpResource != NULL)
              {
                strncpy(client->_httpRequestData.httpResource, (char *)client->_data, client->_httpRequestData.maxResourceBuffer);
                client->_httpRequestData.httpResource[client->_httpRequestData.maxResourceBuffer] = '\0';
              }
              else //Error 500
              {
                sendInfoResponse(HTTP_CODE::_500, client, "Failed to allocate memory for resources");
                client->_clientState = TCPClient::ClientState::DISCARDED;
                break;
              }
              
              client->freeDataBuffer(client->_httpRequestData.maxResourceBuffer + 1);
            }
            client->_httpParserState = HttpParserStatus::HTTP_PARAMS;
          }
            break;
          case HttpParserStatus::HTTP_VERSION:
          {
            char *pEndline = strstr((char *)client->_data, "\r\n");
            char *pVers = strstr((char *)client->_data, "HTTP/");
            
            if(pEndline != NULL && pVers!= NULL)
            {
              *pEndline = '\0';
              client->_httpRequestData.HV = getHttpVersionEnumValue(pVers+5);
              
              #ifdef DEBUG_WEBS
              Serial.print("Vers : ");Serial.println(pVers+5);
              Serial.print("Vers : ");Serial.println(client->_httpRequestData.HV);
              #endif
              
              client->freeDataBuffer((pEndline - (char *)client->_data)+2);

              #ifdef DEBUG_WEBS
              Serial.println((char *)client->_data);
              #endif
              
              client->_httpParserState = HttpParserStatus::POST_DATA;
            }
          }
            break;
          case  HttpParserStatus::HTTP_PARAMS: //index.htm?var1=1&var2=2...
            if(!httpParamParser(client))
            {
              #ifdef DEBUG_WEBS
              Serial.print("Resrc : ");Serial.println(client->_httpRequestData.httpResource);
              Serial.println("Get params :");
              for(int i = 0; i < client->_httpRequestData.getParams.count(); i++)
              {
                Serial.print(client->_httpRequestData.getParams.getParameter(i));Serial.print(" : ");Serial.println(client->_httpRequestData.getParams.getAt(i)->getString());
              }
              #endif
              client->_httpParserState = HttpParserStatus::HTTP_VERSION;
            }
            break;
          case  HttpParserStatus::POST_DATA:
              
              client->_WEBClientState = WEBClientState::QUERY_PARSED;
            break;
          default :
            sendInfoResponse(HTTP_CODE::_500, client, "WEB server error");
            client->_clientState = TCPClient::ClientState::DISCARDED;
            break;
        }
    }

    boolean httpParamParser(T *client)
    {
      char *pGetParam = strchr((char *)client->_httpRequestData.httpResource, '?');
      
      
      if(pGetParam != NULL) //There are some params to be parsed
      {
        if(client->_httpRequestData.getParamsDataPointer == NULL)
        {
          client->_httpRequestData.getParamsDataPointer = pGetParam +1;//We save the starting position of the string to parse
        }
          
        char *key = strchr(client->_httpRequestData.getParamsDataPointer, '=');
        char *value = strchr(client->_httpRequestData.getParamsDataPointer, '&');

        if(key == NULL && value == NULL) //Only the key is present
        {
          client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL));
          *pGetParam = '\0';
          return false;
        }
        else if(key != NULL && value != NULL)
        {
          if(key < value)*key = '\0';
          *value = '\0';
          
          client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(key > value ? NULL : key + 1));
          strcpy(client->_httpRequestData.getParamsDataPointer,value+1);
          
          #ifdef DEBUG_WEBS
          Serial.print("Params pointer : ");Serial.println(client->_httpRequestData.getParamsDataPointer);
          #endif
        }
        else if(key != NULL && value == NULL) //Only one key/value pair present
        {
          *key = '\0';
          
          client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(key+1));
          *pGetParam = '\0';
          return false;
        }
        else if(key == NULL && value != NULL)
        {
          *value = '\0';

          client->_httpRequestData.getParams.add(client->_httpRequestData.getParamsDataPointer, new DictionaryHelper::StringEntity(NULL));
          strcpy(client->_httpRequestData.getParamsDataPointer,value+1);
        }
      }
      else //nothing to parse or done
      {
        return false;
      }
    }

    void sendDataToClient(T *client)
    {
      
    }
    
    void sendInfoResponse(HTTP_CODE http_code, T *client, const char *message)
    {
      switch(http_code)
      {
        case _500:
          client->_client.print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\nContent-Length: "));
          client->_client.print(strlen(message) + 59);
          client->_client.print(F("\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Error 500</h1><p>"));
          client->_client.print(message);
          client->_client.print(F("</p>\r\n</html>"));
          break;
        case _400:
          client->_client.print(F("HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\nContent-Length: "));
          client->_client.print(strlen(message) + 59);
          client->_client.print(F("\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Error 400</h1><p>"));
          client->_client.print(message);
          client->_client.print(F("</p>\r\n</html>"));
          break;
      }
    }
    
    /*Static helper methods*/
    
		static HttpRequestMethod getHttpVerbEnumValue(const char *parseBuffer)
    {
      //UNDEFINED, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
      if(strcmp(parseBuffer,"GET") == 0){return HttpRequestMethod::GET;}
      else if(strcmp(parseBuffer,"POST") == 0){return HttpRequestMethod::POST;}
      else if(strcmp(parseBuffer,"HEAD") == 0){return HttpRequestMethod::HEAD;}
      else if(strcmp(parseBuffer,"PUT") == 0){return HttpRequestMethod::PUT;}
      else if(strcmp(parseBuffer,"DELETE") == 0){return HttpRequestMethod::DELETE;}
      else if(strcmp(parseBuffer,"CONNECT") == 0){return HttpRequestMethod::CONNECT;}
      else if(strcmp(parseBuffer,"TRACE") == 0){return HttpRequestMethod::TRACE;}
      else if(strcmp(parseBuffer,"PATCH") == 0){return HttpRequestMethod::PATCH;}
      else if(strcmp(parseBuffer,"OPTIONS") == 0){return HttpRequestMethod::OPTIONS;}
      else
        return HttpRequestMethod::UNDEFINED;
    }
    
	 static HttpVersion getHttpVersionEnumValue(const char *parseBuffer)
   {
      //HTTP_0_9, HTTP_1_1, HTTP_1_0, HTTP_2_0
      if(strcmp(parseBuffer,"1.1") == 0){return HttpVersion::HTTP_1_1;}
      else if(strcmp(parseBuffer,"2.0") == 0){return HttpVersion::HTTP_2_0;}
      else if(strcmp(parseBuffer,"1.0") == 0){return HttpVersion::HTTP_1_0;}
      else if(strcmp(parseBuffer,"0.9") == 0){return HttpVersion::HTTP_0_9;}
      else
        return HttpVersion::UNKNOWN;
    }
};

#endif //WEBSERVER_H
