#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <SPIFFS.h>
#include <WiFiSettings.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include <ESPDateTime.h>
#include <DateTimeTZ.h>

#include "wifiFix.h"
#include "pitches.h"
#include "utils.h"
#include "oled.h"

#define HSPI_MISO 12
#define HSPI_MOSI 13
#define HSPI_SCLK 14
#define HSPI_SS   15
#define HSPI_RST  16

#define BUZZER_PIN 26

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
MFRC522 mfrc522(HSPI_SS, HSPI_RST);
WiFiClient *wifi = new WiFiClientFixed();
HTTPClient *http = new HTTPClient();

e_state state = STATE_IDLE;
String apiUrl = "";
const int gTimeout = 5000;
const int scanTimeout = 15000;

String scannedCardId = "";
String lastStatusText = "";

unsigned long timer = 0;

void IRAM_ATTR TransactInterruptHandler() {
    if (state != STATE_IDLE) {
        return;
    }
    timer = millis();
    state = STATE_TRANSACT_CARDSCAN;
}

void IRAM_ATTR LinkInterruptHandler() {
    if (state != STATE_IDLE) {
        return;
    }
    timer = millis();
    state = STATE_LINK_CARDSCAN;
}

void IRAM_ATTR BalanceInterruptHandler() {
    if (state != STATE_IDLE) {
        return;
    }
    timer = millis();
    state = STATE_BALANCE_CARDSCAN;
}

bool cooldownCheck(long timeout) {
    if (millis() - timer > timeout) {
        state = STATE_IDLE;
        updateOLED(u8g2, state);
        return true;
    }

    return false;
}

void setup() {
    Serial.begin(115200);
    u8g2.begin();
    drawLogo(u8g2, LOGO_MATECARD);
    u8g2.sendBuffer();
    SPI.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI);
    SPIFFS.begin(true);
    WiFiSettings.hostname = "afrapay-";
    apiUrl = WiFiSettings.string("AfRApay.Web API", "http://192.168.50.170:5296");
    WiFiSettings.connect();
    mfrc522.PCD_Init();
    DateTime.setTimeZone(TZ_Europe_Berlin);
    DateTime.begin();
    updateOLED(u8g2, state);

    ledcSetup(0, 5000, 12);
    pinMode(25, INPUT_PULLUP);
    pinMode(32, INPUT_PULLUP);
    pinMode(33, INPUT_PULLUP);
    attachInterrupt(25, BalanceInterruptHandler, FALLING);
    attachInterrupt(32, LinkInterruptHandler, FALLING);
    attachInterrupt(33, TransactInterruptHandler, FALLING);
}

void loop() {
    if (WiFi.status() != WL_CONNECTED) {
        updateOLED(u8g2, state, "WiFi disconnected :(");
        state = STATE_IDLE;
        WiFi.reconnect();
        delay(50);
        return;
    }

    switch (state) {
        case STATE_IDLE:
            updateOLED(u8g2, state);
            break;
        case STATE_TRANSACT_CARDSCAN:
            if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
                scannedCardId = byteArrayAsHexString(mfrc522.uid.uidByte, mfrc522.uid.size);
                mfrc522.PICC_HaltA();
                lastStatusText = "Card #" + scannedCardId;
                state = STATE_TRANSACT_VERIFY;
                return;
            } else {
                if (!cooldownCheck(scanTimeout)) {
                    updateOLED(u8g2, state, String("1.50€"), String(cooldownSecondsRemaining(scanTimeout, timer)));
                }
            }
            break;
        case STATE_TRANSACT_VERIFY:
            updateOLED(u8g2, state, lastStatusText);
            tone(BUZZER_PIN, NOTE_A5, 25);
            tone(BUZZER_PIN, NOTE_NONE, 150);
            lastStatusText = cardTransaction(wifi, http, apiUrl, scannedCardId, "1.50");
            if (lastStatusText.startsWith("S:")) {
                tone(BUZZER_PIN, NOTE_C7, 650);
                state = STATE_RESULT_SUCCESS;
            } else {
                tone(BUZZER_PIN, NOTE_CS5, 100);
                tone(BUZZER_PIN, NOTE_NONE, 25);
                tone(BUZZER_PIN, NOTE_CS5, 400);
                state = STATE_RESULT_FAILURE;
            }
            lastStatusText = lastStatusText.substring(2);
            timer = millis();
            return;
        case STATE_LINK_CARDSCAN:
            if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
                scannedCardId = byteArrayAsHexString(mfrc522.uid.uidByte, mfrc522.uid.size);
                mfrc522.PICC_HaltA();
                lastStatusText = "Card #" + scannedCardId;
                state = STATE_LINK_VERIFY;
                return;
            } else {
                if (!cooldownCheck(scanTimeout)) {
                    updateOLED(u8g2, state, "Link", String(cooldownSecondsRemaining(scanTimeout, timer)));
                }
            }
            break;
        case STATE_LINK_VERIFY:
            updateOLED(u8g2, state, lastStatusText);
            tone(BUZZER_PIN, NOTE_A5, 25);
            tone(BUZZER_PIN, NOTE_NONE, 150);
            lastStatusText = cardLink(wifi, http, apiUrl, scannedCardId);
            if (lastStatusText.startsWith("S:")) {
                tone(BUZZER_PIN, NOTE_C6, 100);
                tone(BUZZER_PIN, NOTE_NONE, 10);
                tone(BUZZER_PIN, NOTE_C6, 100);
                state = STATE_RESULT_SUCCESS;
            } else {
                tone(BUZZER_PIN, NOTE_CS5, 100);
                tone(BUZZER_PIN, NOTE_NONE, 25);
                tone(BUZZER_PIN, NOTE_CS5, 400);
                state = STATE_RESULT_FAILURE;
            }
            lastStatusText = lastStatusText.substring(2);
            timer = millis();
            return;
        case STATE_BALANCE_CARDSCAN:
            if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
                scannedCardId = byteArrayAsHexString(mfrc522.uid.uidByte, mfrc522.uid.size);
                mfrc522.PICC_HaltA();
                lastStatusText = "Card #" + scannedCardId;
                state = STATE_BALANCE_VERIFY;
                return;
            } else {
                if (!cooldownCheck(scanTimeout)) {
                    updateOLED(u8g2, state, "Balance", String(cooldownSecondsRemaining(scanTimeout, timer)));
                }
            }
            break;
        case STATE_BALANCE_VERIFY:
            updateOLED(u8g2, state, lastStatusText);
            tone(BUZZER_PIN, NOTE_A5, 25);
            tone(BUZZER_PIN, NOTE_NONE, 150);
            lastStatusText = cardBalance(wifi, http, apiUrl, scannedCardId);
            if (lastStatusText.startsWith("S:")) {
                lastStatusText = lastStatusText + ":Card #" + scannedCardId;
                state = STATE_RESULT_DISPLAY;
            } else {
                tone(BUZZER_PIN, NOTE_CS5, 100);
                tone(BUZZER_PIN, NOTE_NONE, 25);
                tone(BUZZER_PIN, NOTE_CS5, 400);
                state = STATE_RESULT_FAILURE;
            }
            lastStatusText = lastStatusText.substring(2);
            timer = millis();
        case STATE_RESULT_SUCCESS:
        case STATE_RESULT_FAILURE:
        case STATE_RESULT_DISPLAY:
            if (!cooldownCheck(gTimeout)) {
                updateOLED(u8g2, state, lastStatusText, String(cooldownSecondsRemaining(gTimeout, timer)));
            }
            break;
    }

    delay(50);
}