#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <SPI.h>
#include <WiFiClientSecureBearSSL.h>

#include "bitmaps.h"
#include "wifi_credentials.h"

#define TFT_CS D8
#define TFT_DC D4
#define TFT_RESET D3

// Button stuff
#define BUTTON_PIN D6

// Display stuff
#define FRAME_DELAY 80
#define COLOR_BG (rgb(0, 0, 0))
#define COLOR_DIVIDER (rgb(0xff, 0x60, 0x0d))
#define COLOR_TEXT (rgb(0xff, 0x60, 0x0d))

// Layout

#define WIDTH 160
#define HEIGHT 128

#define CX 6
#define CY 8

#define MARGIN_LEFT 5
#define MARGIN_RIGHT 5

#define MAIN_X MARGIN_LEFT
#define MAIN_Y 9
#define MAIN_WIDTH (WIDTH - 2 * MARGIN_LEFT)
#define MAIN_HEIGHT (8 * CY)

#define CLOCK_X MARGIN_LEFT
#define CLOCK_Y 113

#define STATUS_X 95
#define STATUS_Y CLOCK_Y
#define STATUS_WIDTH (10 * CX)
#define STATUS_HEIGHT CY

// Peripherals

Adafruit_ST7735 display(TFT_CS, TFT_DC, TFT_RESET);

// App state

typedef enum {
  CONNECTING,
  SHOWING_DEPARTURES
} State;

State state = CONNECTING;
State previous_state = state;
int frame = 0;
// Use this variable to trigger state enter effects
int state_changed = 1;

int buttonPushed = false;
JsonDocument departures;

void ICACHE_RAM_ATTR onButtonFalling() {
  buttonPushed = true;
}

uint16_t rgb(uint16_t r, uint16_t g, uint16_t b) {
  // Color layout: RRRRRGGGGGGBBBBB (5, 6, 5)
  return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111);
}

void drawLayout() {
  display.fillScreen(COLOR_BG);
  display.drawFastHLine(5, 4, 150, COLOR_DIVIDER);
  display.drawFastHLine(5, 109, 150, COLOR_DIVIDER);
  display.drawFastHLine(5, 123, 150, COLOR_DIVIDER);
  display.setCursor(CLOCK_X, CLOCK_Y);
  display.print("13:12");
}

void setup() {
  Serial.begin(9600);

  Serial.println("serial init");
  display.initR();
  display.setSPISpeed(40000000);
  display.setRotation(3);
  Serial.println("display initialized");
  Serial.printf("display dimensions: %" PRId16 "x%" PRId16 "\n", display.width(), display.height());

  display.setTextSize(1);
  display.setTextColor(COLOR_TEXT);
  drawLayout();

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(BUTTON_PIN, onButtonFalling, FALLING);
}

#define QUERY_STRING "stop_id=de:08221:1225&platform=A"

// TODO: Error handling
String fetchDepartures() {
  std::unique_ptr<BearSSL::WiFiClientSecure> https(new BearSSL::WiFiClientSecure);
  https->setInsecure();
  
  HTTPClient client;
  if (!client.begin(*https, "vrnp.beany.club", 443, "/departures?" QUERY_STRING)) {
    display.fillScreen(COLOR_BG);
    display.setCursor(0, 0);
    display.print("begin failed");
    for (;;);
  }

  client.addHeader("Authorization", AUTH_TOKEN);
  client.addHeader("Accept", "application/json");
  int statusCode = client.GET();
  if (statusCode != 200) {
    display.fillScreen(COLOR_BG);
    display.setCursor(0, 0);
    display.printf("http non-ok: %d\n", statusCode);
    for (;;);
  }

  return client.getString();
}

void loop() {
  if (state != previous_state) {
    state_changed = 1;
    previous_state = state;
  }
  logic_loop();
  frame++;
  state_changed = 0;
}

void logic_loop() {
  switch (state) {
    case CONNECTING: {
      if (WiFi.status() != WL_CONNECTED) {
        display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG);
        display.setCursor(STATUS_X, STATUS_Y);
        display.printf(" connect %c", "|/-\\"[frame % 4]);
        delay(FRAME_DELAY);
        return;
      }

      display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG);
      display.setCursor(STATUS_X, STATUS_Y);
      display.print("  fetching");

      String departuresRaw = fetchDepartures();
      deserializeJson(departures, departuresRaw);
      
      state = SHOWING_DEPARTURES;
      frame = 0;
      return;
    }
      
    case SHOWING_DEPARTURES:
      if (WiFi.status() != WL_CONNECTED) {
        state = CONNECTING;
        return;
      }

      display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG);
      display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG);

      int line = 0;
      for (JsonVariant departure : departures["departures"].as<JsonArray>()) {
        display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line);
        display.printf("%2s %-15.15s ",
          departure["symbol"].as<const char*>(),
          departure["direction"].as<const char *>()
        );
        if (departure["leaving"].as<String>().equals("sofort")) {
          int16_t x = display.getCursorX();
          int16_t y = display.getCursorY();
          display.drawBitmap(x + 6 * CX - tiny_train_dims[0], y, tiny_train[0], tiny_train_dims[0], tiny_train_dims[1], COLOR_TEXT);
        } else {
          display.printf("%6s", departure["leaving"].as<const char*>());
        }

        line++;
      }
      
      delay(25000);
      state = CONNECTING;
      return;
  }
}