#include "OTAUpdater.h"
#include "HttpClient.h"

//#define DEBUG_OTA_UPDATER 

OTAUpdater::OTAUpdater(const char *serverAddress, const char *path, const char *ota_auth_key, uint16_t port) : _port(port)
{
  if(serverAddress)
    _serverAddress = strdup(serverAddress);
  
  if(path)
    _path = strdup(path);

  if(ota_auth_key)
    _ota_auth_key = strdup(ota_auth_key);
}

OTAUpdater::OTAUpdater(int httpClientTimeout,const char *serverAddress, const char *path, const char *ota_auth_key, uint16_t port) : ESP8266HTTPUpdate(httpClientTimeout), _port(port)
{
  if(serverAddress)
    _serverAddress = strdup(serverAddress);
  
  if(path)
    _path = strdup(path);

  if(ota_auth_key)
    _ota_auth_key = strdup(ota_auth_key);
}
    
OTAUpdater::~OTAUpdater()
{
  if(_publicKey != nullptr)
    delete _publicKey;
    
  if(_hash != nullptr)
    delete _hash;
    
  if(_signingVerifier != nullptr)
    delete _signingVerifier;

  free(_serverAddress);
  free(_path);
  free(_ota_auth_key);
}

//Enables the binary signing verification
void OTAUpdater::enableSHA256UpdateVerification(const char *publicKey)
{
  if(_publicKey != nullptr)
    delete _publicKey;
    
  if(_hash != nullptr)
    delete _hash;
    
  if(_signingVerifier != nullptr)
    delete _signingVerifier;

  _publicKey = new BearSSL::PublicKey(publicKey);
  _hash = new BearSSL::HashSHA256();
  _signingVerifier = new BearSSL::SigningVerifier(_publicKey);

  Update.installSignature(_hash, _signingVerifier);
}

void OTAUpdater::setServerAddress(const char *serverAddress)
{
  if(!serverAddress)
    return;

  free(_serverAddress);
  _serverAddress = strdup(serverAddress);
}

void OTAUpdater::setPath(const char *path)
{
  //The path can be empty
  if(!path)
  {
    free(_path);
    _path = nullptr;
  }
  else
  {
    free(_path);
    _path = strdup(path);
  }

}

void OTAUpdater::setOtaAuthKey(const char *ota_auth_key)
{
  //The authKey can be empty
  if(!ota_auth_key)
  {
    free(_ota_auth_key);
    _ota_auth_key = nullptr;
  }
  else
  {
    free(_ota_auth_key);
    _ota_auth_key = strdup(ota_auth_key);
  }
}

void OTAUpdater::setPort(uint16_t port)
{
  _port = port;
}

OTAUpdater::UpdateInfo OTAUpdater::fetchUpdateInfo(const char *softwareVersion, uint32_t timeout)
{
  HttpClient httpClient(_serverAddress, _path, _port, timeout);
  UpdateInfo ui;

  if(!softwareVersion)
  {
    ui.info = OTAUpdater::UpdateInfo::HTTP_NO_UPDATE;
    return ui;
  }

  Dictionary<DictionaryHelper::StringEntity> getData;
  getData.add("authKey", DictionaryHelper::StringEntity(_ota_auth_key));
  getData.add("checkonly", NULL);

  Dictionary<DictionaryHelper::StringEntity> headerData;
  headerData.add("x-ESP8266-version",DictionaryHelper::StringEntity(softwareVersion));
  headerData.add("x-ESP8266-STA-MAC",DictionaryHelper::StringEntity(WiFi.macAddress().c_str()));
  headerData.add("User-Agent",DictionaryHelper::StringEntity("ESP8266-http-Update"));

  if(httpClient.sendHttpQuery(HttpClient::HttpRequestMethod::GET, &getData, NULL, &headerData) == HttpClient::HttpQueryStatus::SUCCESS)
  {
    HttpConstants::HTTP_CODE result = httpClient.isReplyAvailable(1000);
    switch(result)
    {
      case HttpConstants::HTTP_CODE::HTTP_CODE_OK:
      {
        char buffer[100];
        httpClient.readHttpBody((uint8_t *)buffer, 100);
        #ifdef DEBUG_OTA_UPDATER
        Serial.printf("Response from update service : %s\n", buffer);
        #endif
        
        char *p = strstr_P(buffer, PSTR("bin_version"));
        if(p)
        {
          p+=14;
          char *end = strchr(p, '"');
          if(end)
          {
            *end = '\0';
            
            ui.version = (char *)malloc(strlen(p) * sizeof(char) + 1);
            if(ui.version)
              strcpy(ui.version, p);
          }
         }
        ui.info = OTAUpdater::UpdateInfo::HTTP_UPDATE_AVAILABLE;
      }
        break;
      case HttpConstants::HTTP_CODE::HTTP_CODE_NOT_MODIFIED:
        ui.info = OTAUpdater::UpdateInfo::HTTP_NO_UPDATE;
        break;
      case HttpConstants::HTTP_CODE::HTTP_CODE_FORBIDDEN:
        ui.info = OTAUpdater::UpdateInfo::HTTP_UPDATE_AUTH_ERROR;
        break;
      case HttpConstants::HTTP_CODE::HTTP_CODE_NOT_FOUND:
        ui.info = OTAUpdater::UpdateInfo::HTTP_UPDATE_REACH_ERROR;
        break; 
      default:
        #ifdef DEBUG_OTA_UPDATER
        Serial.printf("HTTP ERROR CODE IS : %u\n", result);
        #endif
        ui.info = OTAUpdater::UpdateInfo::HTTP_UPDATE_UNDEFINED_ERROR;
    }

    httpClient.stop();
  }
  else
    ui.info = OTAUpdater::UpdateInfo::HTTP_UPDATE_REACH_ERROR;

  return ui;
}

#define DEBUG_OTA_UPDATER

HTTPUpdateResult OTAUpdater::update(const char *softwareVersion)
{
  if(!softwareVersion)
  {
    #ifdef DEBUG_OTA_UPDATER
    Serial.printf("Missing softwareVersion !\n");
    #endif
    return HTTPUpdateResult::HTTP_UPDATE_NO_UPDATES;
  }

  if(!_serverAddress)
  {
    #ifdef DEBUG_OTA_UPDATER
    Serial.printf("Missing server address !\n");
    #endif
    return HTTPUpdateResult::HTTP_UPDATE_FAILED;
  }

  //Depending on the presence or not of the path and the authKey, we compute the size we must allocate for the full path
  uint16_t length = 1 /*for the \0*/;
  length += _path ? strlen(_path) : 0;
  length += _ota_auth_key ? strlen(_ota_auth_key) + 9/*for : ?authKey=*/ : 0;

  char *fullpath = (char *)malloc(length * sizeof(char));
  if(!fullpath)return HTTP_UPDATE_FAILED;

  fullpath[0] = '\0';

  WiFiClient client;

  if(_path)strcpy(fullpath, _path);
  if(_ota_auth_key)
  {
    strcat(fullpath, "?authKey=");
    strcat(fullpath, _ota_auth_key);
  }

  #ifdef DEBUG_OTA_UPDATER
  Serial.printf("The fullpath is : %s\n", fullpath);
  #endif

  HTTPUpdateResult result = ESP8266HTTPUpdate::update(client, _serverAddress, _port, fullpath, softwareVersion);

  free(fullpath);
  return result;
}
