#include "WEBServerManager.h"

//#define DEBUG
#define DEBUG_BODY
//#define DEBUG_PARAMETER
//#define DEBUG_CONTENT_LENGTH
//#define DEBUG_RAW
//#define DEBUG_FILEPATH

WEBServerManager::WEBServerManager(unsigned int port, SDCardManager *sdCardManager) : _wifiServer(port), _sdCardManager(sdCardManager), _httpRequestData({UNDEFINED, UNKNOWN, UNKNOWN_MIME, Dictionary<DictionaryHelper::StringEntity>(), Dictionary<DictionaryHelper::StringEntity>(), NULL,NULL}), _httpParserState(INIT), _clientState(WAITING_FOR_CLIENT), _port(port), _clientTimeout(0)
{
  _wifiServer.begin();
}

boolean WEBServerManager::addApiRoutine(const char *uri, boolean (*apiRoutine)(HttpRequestData&, WiFiClient* , void*), void *pData, HttpRequestMethod HRM)
{
  return _apiDictionary.add(uri,new ApiRoutine({apiRoutine,pData, HRM}));
}

void WEBServerManager::clearApiRoutine()
{
  _apiDictionary.clear();
}

boolean WEBServerManager::removeApiRoutine(const char *uri)
{
  _apiDictionary.remove(uri);
}

boolean WEBServerManager::runServer()
{
  switch(_clientState)
  {
    case WAITING_FOR_CLIENT:
      _wifiClient.stopAll();
      
      _wifiClient = _wifiServer.available();
      if(_wifiClient)
      {
        _clientState = NEW;
        #ifdef DEBUG
        Serial.println("Client connected !!!");
        #endif
      }
      break;
    case NEW:
      _clientState = NOT_HANDLED;
      break;
    case NOT_HANDLED:
      clearHttpRequestData();
      parseQuery(&_wifiClient);
      #ifdef DEBUG
      Serial.println("Nothing more from client !!!");
      #endif
      _clientState = QUERY_PARSED;
      break;
    case QUERY_PARSED:
      #ifdef DEBUG
      Serial.println("Sending response !!!");
      #endif
      //We first check if it's an api call
      if(!sendPageToClientFromApiDictio(&_wifiClient))
      {
        sendPageToClientFromSdCard(&_wifiClient);
      }
      _clientState = RESPONSE_SENT;
      break;
    case RESPONSE_SENT:
      #ifdef DEBUG
      Serial.println("Client handled !!!");
      #endif
      _clientState = HANDLED;
      break;
    case HANDLED:
      _wifiClient.stopAll();
      _clientState = WAITING_FOR_CLIENT;
      #ifdef DEBUG
      Serial.println("Client discarded !!!");
      #endif
      break;
    default:
      break;
  }
  return true;
}

boolean WEBServerManager::parseQuery(WiFiClient *wifiClient)
{
  char readChar(0), *parseBuffer(NULL), *parseKey(NULL), *parseValue(NULL), *parseParameter(NULL), *contentLength(NULL);
  boolean isKey(true), receivingDone(false);
  unsigned int activeTimeout = 10000;
  unsigned long dataBytesCounter = 0, dataBytes = 0;
  
  /* Better way to read data
  char temp[2048];
  temp[wifiClient->read((uint8_t*)temp,2040)] = '\0';
  Serial.print(temp);
  */
  _httpParserState = INIT;
  _clientTimeout = millis();
  boolean slashesOrAntiSlashesOnly(true);
  while((wifiClient->available() || ( millis() - _clientTimeout < activeTimeout)) && wifiClient->connected())
  {
    if(wifiClient->available())
    {
      readChar = (char)wifiClient->read();
	  
      #ifdef DEBUG_RAW
      Serial.print(readChar);
      #endif
      //INIT, LINE_BREAK, HTTP_VERB_SECTION, HTTP_RESOURCE_SECTION, HTTP_VER_SECTION, BODY_SECTION, IGNORED, ERROR
	    switch(_httpParserState)
	    {
	      case INIT:
	  	    if(readChar >= 65 && readChar <= 90)
	  	    {
	  	      parseBuffer = addChar(parseBuffer, readChar);
	  	      _httpParserState = HTTP_VERB_SECTION;
	  	    }
	  	    else
	  	      _httpParserState = ERROR;
	  	    break;
	      case LINE_BREAK:
			    if(readChar == '\n')
          {
            if(_httpRequestData.HRM == GET)
            {
              #ifdef DEBUG
              Serial.println("GET DONE");
              #endif
              receivingDone = true;
            }
			      _httpParserState = BODY_SECTION;
          }
			    else if(readChar != '\r')
          {
            if(parseParameter != NULL)
            {
              contentLength = strstr(parseParameter, "ent-Len");//Matches Content-Length short to save some RAM
              if(contentLength != NULL)
              {
                dataBytes = strtol(contentLength+11,NULL,10);
                #ifdef DEBUG_CONTENT_LENGTH
                Serial.print("Data length : ");Serial.println(dataBytes);
                #endif
              }

              #ifdef DEBUG_PARAMETER
              Serial.println(parseParameter);
              #endif
              free(parseParameter);parseParameter = NULL;
            }
            parseParameter = addChar(parseParameter, readChar);
			      _httpParserState = PARAMETER_SECTION;
          }
			    break;
	      case HTTP_VERB_SECTION:
	  	    if(readChar >= 65 && readChar <= 90)
	  	    {
	  	      parseBuffer = addChar(parseBuffer, readChar);
	  	      _httpParserState = HTTP_VERB_SECTION;
	  	    }
	  	    else if (readChar == ' ')
	  	    {
	  	      //This is the end of the section
	  	      _httpRequestData.HRM = getHttpVerbEnumValue(parseBuffer);
	  	      free(parseBuffer);parseBuffer = NULL;
	  	      _httpParserState = HTTP_RESOURCE_SECTION;
	  	    }
	  	    else
	  	      _httpParserState = ERROR;
	  	    break;
	      case HTTP_RESOURCE_SECTION: 
          if(readChar == '?' )
          {
            free(_httpRequestData.httpResource);_httpRequestData.httpResource = NULL;
            _httpRequestData.httpResource = parseBuffer;parseBuffer = NULL;

            _httpParserState = HTTP_RESOURCE_PARAM_SECTION;
          }
	  	    else if(readChar == ' ')
	  	    {
            free(_httpRequestData.httpResource);_httpRequestData.httpResource = NULL;
            if(slashesOrAntiSlashesOnly)
            {
               free(parseBuffer);parseBuffer = NULL;
                _httpRequestData.httpResource = (char *) malloc(sizeof(char)*2);
                strcpy(_httpRequestData.httpResource,"/");
            }
            else
               _httpRequestData.httpResource = parseBuffer;parseBuffer = NULL;
       
             _httpParserState = HTTP_VER_SECTION;
	  	    }
	  	    else
	  	    {
	  	      if(readChar != '/' && readChar != '\\') slashesOrAntiSlashesOnly = false;
            
            parseBuffer = addChar(parseBuffer, readChar);
            _httpParserState = HTTP_RESOURCE_SECTION;
	  	    }
	  	    break;
          case HTTP_RESOURCE_PARAM_SECTION: //index.web?var1=1&var2=2...
            if(readChar == ' ')
            {
              _httpRequestData.getParams.add(parseKey,new DictionaryHelper::StringEntity(parseValue));
              free(parseKey);free(parseValue);
              parseKey = NULL;parseValue = NULL;
              _httpParserState = HTTP_VER_SECTION;
            }
            else if( readChar == '=')
              isKey = false;
            else if(readChar == '&')
            {
              isKey = true;
               _httpRequestData.getParams.add(parseKey, new DictionaryHelper::StringEntity(parseValue));
              free(parseKey);free(parseValue);
              parseKey = NULL;parseValue = NULL;
            }
            else
            {
              if(isKey)
                parseKey = addChar(parseKey, readChar);
              else
                parseValue = addChar(parseValue, readChar);
            }
            break;
	      case HTTP_VER_SECTION:
	  	    if((readChar >= 48 && readChar <= 57) || readChar == '.')
	  	    {
	  	      parseBuffer = addChar(parseBuffer, readChar);
	  	      _httpParserState = HTTP_VER_SECTION;
	  	    }
	  	    else if(readChar == '\n')
	  	    {
			      _httpRequestData.HV = getHttpVersionEnumValue(parseBuffer);
			      free(parseBuffer);parseBuffer = NULL;
			      _httpParserState = LINE_BREAK;
	  	    }
	  	    break;
	      case BODY_SECTION:
			    //parseBuffer = addChar(parseBuffer, readChar);
          if(_httpRequestData.HRM != GET)
          {
            dataBytesCounter++;//Should be always true
          }
			    #ifdef DEBUG_BODY
			    Serial.print(readChar);
			    #endif
	  	    break;
		    case PARAMETER_SECTION: //Here are all the http header params
            if(readChar == '\n')
            {
              _httpParserState = LINE_BREAK;
            }else
              parseParameter = addChar(parseParameter, readChar);
            break;
	      case IGNORED:
	  	    break;
	      case ERROR:
	  	    return false;
	        break; //Not necessary
	      default :
	  	    break;
	    }
      //Exit condition
      if(receivingDone) break;
      if(_httpRequestData.HRM == POST && dataBytes != 0 && dataBytes == dataBytesCounter) break;
     
      _clientTimeout = millis();
    }
  //yield(); //Likely causing a crash
  ESP.wdtFeed();
  }

  if(parseBuffer != NULL)
  {
    if(strlen(parseBuffer) > 0)
    {
      _httpRequestData.httpBody = parseBuffer;
      parseBuffer = NULL;
    }
  }
  
  free(parseParameter);parseParameter = NULL;
  
  #ifdef DEBUG
  Serial.print("HTTP VERB : ");
  Serial.println(_httpRequestData.HRM);
  Serial.print("HTTP RESOURCE : ");
  Serial.println(_httpRequestData.httpResource);
  Serial.print("HTTP VERSION : ");
  Serial.println(_httpRequestData.HV);
  Serial.print("BODY CONTENT : ");
  Serial.println(_httpRequestData.httpBody);
  Serial.println("GET PARAMS :");
  for(int i = 0; i < _httpRequestData.getParams.count(); i++)
  {
    Serial.print(_httpRequestData.getParams.getParameter(i));Serial.print(" : ");Serial.println(_httpRequestData.getParams.getAt(i)->getString());
  }
  #endif
  
  return true;
}

unsigned int WEBServerManager::getPort() const
{
  return _port;
}

boolean WEBServerManager::sendPageToClientFromSdCard(WiFiClient *wifiClient)
{
  if(_sdCardManager != NULL)
  {
    File pageToSend;
    char readChar(0), *filePath(NULL), *header(NULL), sendBuffer[2048];
    
    //We check what kind of http verb it is
    switch(_httpRequestData.HRM)
    {
      case GET:
        filePath = getFilePathByHttpResource(_httpRequestData.httpResource);
        if(filePath == NULL)
        {
          wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<p>Failed to malloc filePath</p>\r\n</html>"));
          return false;
        }
        
        #ifdef DEBUG
        Serial.print("FILE PATH : ");
        Serial.println(filePath);
        #endif
        
        pageToSend = _sdCardManager->open(filePath);
        free(filePath);filePath = NULL;
        
        if(!pageToSend)
        {
          char *response(NULL);
          response = (char *) malloc(sizeof(char) * (strlen("HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Page not found for : </h1>\r\n<h4></h4>\r\n</html>") + strlen(_httpRequestData.httpResource) + 1));
          if(response == NULL)
          {
            wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Failed to malloc response</h1>\r\n</html>"));
            return false;
          }
          sprintf(response, "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Page not found for : </h1>\r\n<h4>%s</h4>\r\n</html>", _httpRequestData.httpResource);
          wifiClient->print(response);
          free(response);response = NULL;
          pageToSend.close();
          return false;
        }

        #ifdef DEBUG
        Serial.print("FILE SIZE : ");
        Serial.println(pageToSend.size());
        Serial.print("FILE NAME : ");
        Serial.println(pageToSend.name());
        Serial.print("FILE EXTENSION : ");
        Serial.println(getFileExtension(pageToSend.name()));
        #endif

        header = getHTTPHeader(getMIMETypeByExtension(getFileExtension(pageToSend.name())), pageToSend.size());
        if(header == NULL)
        {
          wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Failed to malloc header</h1>\r\n</html>"));
          pageToSend.close();
          return false;
        }
          
        wifiClient->print(header);
        free(header);header = NULL;
        
        while(pageToSend.available())
        {
          if(wifiClient->write(sendBuffer, pageToSend.read(sendBuffer,2048)) == 0)
            break;
        }

        pageToSend.close();
        break;
      default: //If not supported
        wifiClient->print(F("HTTP/1.1 405 Method Not Allowed\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>Method Not Allowed</h1>\r\n</html>"));
        break;
    }
  }else
  {
    wifiClient->print(F("HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n<h1>SDCardManager is NULL<br \\>Check code</h1>\r\n</html>"));
  }

  return true;
}

boolean WEBServerManager::sendPageToClientFromApiDictio(WiFiClient *wifiClient)
{
  if(_apiDictionary.count() == 0 || _httpRequestData.httpResource == NULL)
    return false;
    
  ApiRoutine *ref = _apiDictionary(_httpRequestData.httpResource);
  
  if(ref == NULL)
    return false;

  if(ref->HRM == UNDEFINED)
  {
    return (*(ref->apiRoutine))(_httpRequestData, wifiClient, ref->pData);
  }else if(ref->HRM == _httpRequestData.HRM)
  {
    return (*(ref->apiRoutine))(_httpRequestData, wifiClient, ref->pData);
  }
  else
    return false;
}

WEBServerManager::HttpMIMEType WEBServerManager::getMIMETypeByExtension(const char *extension)
{
  //TEXT_PLAIN, TEXT_CSS, TEXT_HTML, TEXT_JAVASCRIPT
  if(strcmp(extension,"web") == 0) return TEXT_HTML;
  else if(strcmp(extension,"htm") == 0) return TEXT_HTML;
  else if(strcmp(extension,"css") == 0) return TEXT_CSS;
  else if(strcmp(extension,"js") == 0) return TEXT_JAVASCRIPT;
  else if(strcmp(extension,"png") == 0) return IMAGE_PNG;
  else if(strcmp(extension,"jpg") == 0) return IMAGE_JPEG;
  else if(strcmp(extension, "mp3") == 0) return AUDIO_MPEG;
  else return UNKNOWN_MIME;
}

char *WEBServerManager::getHTTPHeader(HttpMIMEType httpMIMEType, unsigned long size)
{
  char *header = (char *) malloc(sizeof(char) + strlen("HTTP/1.1 200 OK\r\nContent-Type: \r\nContent-Length: \r\n\r\n") + 74/*Longest MIME-TYPE*/ + 10 /*Max unsigned long footprint*/ + 1);

  switch(httpMIMEType)
  {
    case TEXT_HTML:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/html",size);
      break;
    case TEXT_CSS:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/css",size);
      break;
    case TEXT_JAVASCRIPT:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/javascript",size);
      break;
    case IMAGE_PNG:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","image/png",size);
      break;
    case IMAGE_JPEG:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","image/jpeg",size);
      break;
    case TEXT_PLAIN:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","text/plain",size);
      break;
    case AUDIO_MPEG:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","audio/mpeg",size);
      break;
    case APPLICATION_OCTET_STREAM:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","application/octet-stream",size);
      break;
    default:
      sprintf(header,"HTTP/1.1 200 OK\r\nContent-Type: %s\r\nContent-Length: %lu\r\n\r\n","application/octet-stream",size);
      break;
    break;
  }

  return header;
}

char *WEBServerManager::getFileExtension(char *name)
{
  if(name == NULL)return "";
  
  char *ptr(name);
  int index(0);
  while(ptr[index] != '\0')
  {
    if(ptr[++index] == '.')
    {
      return (name + index +1);
    }
  }
  return "";
}

char *WEBServerManager::getFilePathByHttpResource(char *res)
{
  uint16_t buffSize = strlen(WWW_DIR)  + (strcmp(res, "/") == 0 ? 10:strlen(res)) + 1;//10 for /index.htm +1 for \0
  char *filePath = (char*) malloc( sizeof(char) * buffSize); 
  
  if(filePath == NULL)
    return NULL;

  strcpy(filePath, WWW_DIR);
  strcat(filePath, strcmp(res, "/") == 0 ? "/index.htm":res);
  //sprintf(filePath,"%s%s",WWW_DIR, strcmp(res, "/") == 0 ? "/index.htm":res);

  #ifdef DEBUG_FILEPATH
  Serial.println(res);
  Serial.print("Reserved space : ");Serial.println(buffSize);
  Serial.print("Actual size : ");Serial.println(strlen(filePath));
  Serial.println(filePath);
  #endif
  
  return filePath;
}

WEBServerManager::HttpRequestMethod WEBServerManager::getHttpVerbEnumValue(const char *parseBuffer)
{
  //UNDEFINED, GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
  if(strcmp(parseBuffer,"GET") == 0){return GET;}
  else if(strcmp(parseBuffer,"POST") == 0){return POST;}
  else if(strcmp(parseBuffer,"HEAD") == 0){return HEAD;}
  else if(strcmp(parseBuffer,"PUT") == 0){return PUT;}
  else if(strcmp(parseBuffer,"DELETE") == 0){return DELETE;}
  else if(strcmp(parseBuffer,"CONNECT") == 0){return CONNECT;}
  else if(strcmp(parseBuffer,"TRACE") == 0){return TRACE;}
  else if(strcmp(parseBuffer,"PATCH") == 0){return PATCH;}
  else if(strcmp(parseBuffer,"OPTIONS") == 0){return OPTIONS;}
  else
    return UNDEFINED;
}

WEBServerManager::HttpVersion WEBServerManager::getHttpVersionEnumValue(const char *parseBuffer)
{
  //HTTP_0_9, HTTP_1_1, HTTP_1_0, HTTP_2_0
  if(strcmp(parseBuffer,"1.1") == 0){return HTTP_1_1;}
  else if(strcmp(parseBuffer,"2.0") == 0){return HTTP_2_0;}
  else if(strcmp(parseBuffer,"1.0") == 0){return HTTP_1_0;}
  else if(strcmp(parseBuffer,"0.9") == 0){return HTTP_0_9;}
  else
    return UNKNOWN;
}

void WEBServerManager::clearHttpRequestData()
{
  _httpRequestData.HRM = UNDEFINED;
  _httpRequestData.HV = UNKNOWN;
  _httpRequestData.HMT = UNKNOWN_MIME;
  free(_httpRequestData.httpResource);free(_httpRequestData.httpBody);
  _httpRequestData.httpResource = NULL;_httpRequestData.httpBody = NULL;
  _httpRequestData.getParams.dispose();
  _httpRequestData.postParams.dispose();
}
