#include "main.h"
#include <Arduino.h>
#include <Wire.h>
#include "setup_server.h"
#include <ESP8266WiFi.h>
#include <string>
#include "settings.h"

#include <ChaChaPoly.h>
#include <BLAKE2s.h>

Environment::Settings settings;
utils::byte_buffer passcode(DERIVATION_HASH_SIZE);

void setup()
{
  settings.apiID = 11085093266951290551U;
  settings.endpoint = std::string("http://192.168.64.244:8080/data/authed/");
  settings.passcode = std::string("password");

  Serial.begin(9600);
  Serial.println("\nSTART");
  Serial.print("Last Shutdown: ");
  Serial.println(ESP.getResetReason());

  Serial.print("Derive Key: ");
  DeriveKey();
  // put your setup code here, to run once:
  screen.begin();
  Wire.begin();
  bme.begin();
  environmentUpdate.attach_scheduled(1, UpdateEnvironment);
  environmentPost.attach_scheduled(60, PostEnvironment);
  screen.firstPage();
  do
  {
    screen.setFont(FONT);
    screen.drawStr(2, LINE_1, "Connecting...");
  } while (screen.nextPage());
  InitializeWifi();
}

void loop()
{
  // put your main code here, to run repeatedly:
  screen.firstPage();
  do
  {
    screen.setFont(FONT);
    screen.drawStr(2, LINE_1, "Hello World!");
    screen.drawStr(2, LINE_2, "Hi Butlersaurus!");
  } while (screen.nextPage());

  delay(1000);

  screen.firstPage();
  do
  {
    screen.setFont(FONT);
    screen.drawStr(2, LINE_1, TEMP);
    screen.setCursor(screen.getStrWidth(TEMP), LINE_1);
    screen.print(temp);
    screen.drawStr(2, LINE_2, HUMID);
    screen.setCursor(screen.getStrWidth(HUMID), LINE_2);
    screen.print(humidity);
  } while (screen.nextPage());
  delay(1000);
}

void UpdateEnvironment()
{
  bme.read(pressure, temp, humidity, tempUnit, presUnit);
}

void PostEnvironment()
{
  utils::debug_print("Post");
  //requests.begin(client, "http://192.168.64.239/api/environment");
  auto endpoint = std::string(settings.endpoint);
  endpoint.append(std::to_string(settings.apiID));
  requests.begin(client, endpoint.c_str());
  requests.addHeader("Content-Type", "application/json");
  char dataBuffer[128] = {0};
  char formatString[] = "{\"h\":%f,\"t\":%f}";
  snprintf(&dataBuffer[0], 128, &formatString[0], humidity, temp);
  auto data = Encrypt(std::string(dataBuffer));
  auto code = requests.PUT(data.get_ptr(), data.get_length());
  if (code > 0)
  {
    utils::debug_print(code);
  }
  else
  {
    utils::debug_print(requests.errorToString(code));
  }
  requests.end();
  utils::debug_print("Posted");
}

utils::byte_buffer Encrypt(std::string data)
{
  // struct used by go, with basically no docs :|
  // iv (12) | cipher (x) | tag(16)
  const uint_fast16_t ivSize = 12;
  auto size = ivSize + data.length() + 16;
  auto dataOut = utils::byte_buffer(size);
  // for the iv
  dataOut.fill_random();
  // first 12 bytes
  auto cipher = ChaChaPoly();

  cipher.setKey(passcode.get_ptr(), passcode.get_length());
  cipher.setIV(dataOut.get_ptr(), ivSize);

  uint_fast16_t buffLen = 0;
  auto encryptBuffer = dataOut.get_ptr(ivSize, buffLen);
  cipher.encrypt(encryptBuffer, (const uint8_t *)data.c_str(), data.length());

  auto tagBuffer = dataOut.get_ptr(ivSize + data.length(), buffLen);
  cipher.computeTag(tagBuffer, buffLen);

  return dataOut;
}

void InitializeWifi()
{
  delay(1);
  WiFi.mode(WIFI_STA);
  WiFi.begin();
  auto ret = WiFi.waitForConnectResult();

  if (ret != WL_CONNECTED)
  {
    utils::debug_print("Could not connect to Wifi");
    auto setup = setup_server();
    std::string ssid = std::string();
    std::string password = std::string();
    setup.get_connection(ssid, password);
  }

  utils::debug_print("Wifi Connected: " + WiFi.SSID());
}

void DeriveKey()
{
  BLAKE2s hash = BLAKE2s();
  hash.reset();
  hash.update(DERIVATION_SALT, strlen(DERIVATION_SALT));
  hash.update(settings.passcode.c_str(), settings.passcode.length());
  hash.finalize(passcode.get_ptr(), passcode.get_length());
  utils::debug_print(passcode.get_base64());
}