#include <Wire.h>
#include <Adafruit_BME280.h>
#include <TFT_eSPI.h>
#include <SPI.h>
#include <SPIMemory.h>
#include <math.h>

// **SPI dla TFT**
#define TFT_CS   15
#define TFT_DC   2
#define TFT_RST  4

// **SPI dla W25Q128 (pamięć)**
#define FLASH_CS  5
#define FLASH_SCK 14
#define FLASH_MISO 12
#define FLASH_MOSI 13

#define BUTTON_PIN 25                   // **Przycisk (GPIO 25, zwiera do masy)**
#define GRID_LINES 10                   // Ilość linii siatki poziomej
#define TIME_TO_SAVE 6000               // Czas co ile jest zapis do pamięci Flash
#define TIME_TO_REFRESH_DISPLAY 1000    // czas co ile odswieżane są dane na wyświetlaczu
#define NUM_SAMPLES 100                 // Ilość wyświetlanych próbek na wykresie

SPIClass hspi(HSPI);
SPIFlash flash(FLASH_CS, &hspi);
Adafruit_BME280 bme;
TFT_eSPI tft = TFT_eSPI();

// **Struktura logowanych danych**
struct DataLog 
{
  uint32_t timestamp;
  float temperature;
  float humidity;
  float pressure;
};

enum ScreenState 
{
    SCREEN_DATA,       // 0 - Aktualne dane
    SCREEN_TEMP,       // 1 - Wykres temperatury
    SCREEN_HUMIDITY,   // 2 - Wykres wilgotności
    SCREEN_PRESSURE    // 3 - Wykres ciśnienia
};

ScreenState currentState = SCREEN_DATA;

uint32_t flashAddress = 0;
bool showGraph = false;
bool graphDrawn = false;  // Blokowanie ponownego rysowania wykresu
static float lastTemp = -1000, lastHumidity = -1000, lastPressure = -1000;
static uint32_t lastSaveTimeFlash = 0;
static uint32_t lastSaveTimeDisplay = 0;

void findLastFlashAddress() 
{
    Serial.println("Sprawdzanie pamięci...");
    for (uint32_t addr = 0; addr < flash.getCapacity(); addr += sizeof(DataLog)) {
        DataLog log;
        flash.readByteArray(addr, (uint8_t*)&log, sizeof(DataLog));

        if (log.timestamp == 0xFFFFFFFF) {  
            flashAddress = addr;
            Serial.print("Nowe dane zapiszemy pod adresem: ");
            Serial.println(flashAddress);
            return;
        }
    }
    Serial.println("Pamięć pełna! Nadpisywanie od początku.");
    flashAddress = 0;
}

void WriteToFlash()
{
  lastSaveTimeFlash = millis();
  
  DataLog log;
  log.timestamp = millis() / 1000;
  log.temperature = bme.readTemperature();
  log.humidity = bme.readHumidity();
  log.pressure = bme.readPressure() / 100.0F;

  flash.writeByteArray(flashAddress, (uint8_t*)&log, sizeof(DataLog));
  flashAddress += sizeof(DataLog);

  //Serial.println("Zapisano pomiar do pamięci."); DEBUG
}

void displaySensorData() 
{
    float temperature = bme.readTemperature();
    float humidity = bme.readHumidity();
    float pressure = bme.readPressure() / 100.0F;

    temperature = floorf(temperature * 10) / 10;  
    humidity = floorf(humidity * 10) / 10;
    pressure = floorf(pressure * 10) / 10;

    tft.setTextSize(2);

    // **Temperatura + dynamiczny kolor tekstu**
    if (temperature != lastTemp) 
    {
        tft.fillRect(70, 0, 160, 20, TFT_BLACK);
        tft.setCursor(0, 0);
        
        if (temperature < 10)      tft.setTextColor(TFT_CYAN, TFT_BLACK);  // Zimno - niebieski
        else if (temperature > 30) tft.setTextColor(TFT_RED, TFT_BLACK);   // Gorąco - czerwony
        else                       tft.setTextColor(TFT_WHITE, TFT_BLACK); // W normie - biały
        
        tft.print("Temp: "); tft.print(temperature, 1); tft.println(" C");
        lastTemp = temperature;
    }

    // **Wilgotność + dynamiczny kolor tekstu**
    if (humidity != lastHumidity) 
    {
        tft.fillRect(70, 30, 160, 20, TFT_BLACK);
        tft.setCursor(0, 30);

        if (humidity < 30)      tft.setTextColor(TFT_ORANGE, TFT_BLACK); // Sucho - pomarańczowy
        else if (humidity > 70) tft.setTextColor(TFT_BLUE, TFT_BLACK);   // Wilgotno - niebieski
        else                    tft.setTextColor(TFT_WHITE, TFT_BLACK);  // W normie - biały

        tft.print("Wilg: "); tft.print(humidity, 1); tft.println(" %");
        lastHumidity = humidity;
    }

    // **Ciśnienie + dynamiczny kolor tekstu**
    if (pressure != lastPressure) 
    {
        tft.fillRect(70, 60, 160, 20, TFT_BLACK);
        tft.setCursor(0, 60);

        if (pressure < 980)      tft.setTextColor(TFT_YELLOW, TFT_BLACK); // Niskie ciśnienie - żółty
        else if (pressure > 1030) tft.setTextColor(TFT_GREEN, TFT_BLACK); // Wysokie ciśnienie - zielony
        else                      tft.setTextColor(TFT_WHITE, TFT_BLACK); // W normie - biały

        tft.print("Cisn: "); tft.print(pressure, 1); tft.println(" hPa");
        lastPressure = pressure;
    }
}

void ChangeScreen() 
{
    tft.fillScreen(TFT_BLACK);
    lastTemp = -1000; 
    lastHumidity = -1000; 
    lastPressure = -1000;
    graphDrawn = false;  // **Reset flagi, aby wykres narysował się raz**
}

void drawGraph(const char* title, uint16_t color, float (*getValue)(DataLog)) 
{
    tft.fillRect(10, 25, 300, 180, TFT_BLACK); 
    tft.setTextColor(TFT_WHITE);
    tft.setCursor(100, 5);
    tft.setTextSize(2);
    tft.print(title);

    int sampleCount = flashAddress / sizeof(DataLog);
    if (sampleCount > NUM_SAMPLES) sampleCount = NUM_SAMPLES;
    if (sampleCount < 2) 
    {
        tft.setCursor(50, 100);
        tft.setTextSize(2);
        tft.print("Brak danych!");
        return;
    }

    int startAddress = flashAddress - sampleCount * sizeof(DataLog);
    if (startAddress < 0) startAddress = 0;

    float minVal = 100000, maxVal = -100000;
    float values[NUM_SAMPLES];

    for (int i = 0; i < sampleCount; i++) 
    {
        DataLog log;
        flash.readByteArray(startAddress + i * sizeof(DataLog), (uint8_t*)&log, sizeof(DataLog));
        values[i] = getValue(log);

        if (values[i] < minVal) minVal = values[i];
        if (values[i] > maxVal) maxVal = values[i];
    }

    if (maxVal - minVal < 0.5) 
    {
        maxVal += 0.25;
        minVal -= 0.25;
    }

    float valueRange = maxVal - minVal;

    // **Przesunięcie wykresu w prawo i w dół**
    int graphStartX = 45;  // Start X (przesunięcie w prawo)
    int graphEndX = 310;   // Koniec wykresu
    int graphStartY = 25;  // **Przesunięcie w dół o 5px**
    int graphEndY = 205;   // Koniec osi Y

    // **Rysowanie osi X i Y (przesuniętych w dół)**
    tft.drawLine(graphStartX, graphStartY, graphStartX, graphEndY, TFT_WHITE); 
    tft.drawLine(graphStartX, graphEndY, graphEndX, graphEndY, TFT_WHITE);

    // **Rysowanie siatki Y + wartości (przesunięte o 5px w dół)**
    for (int i = 0; i <= GRID_LINES; i++) {
        int y = map(i, 0, GRID_LINES, graphEndY, graphStartY);
        tft.drawLine(graphStartX, y, graphEndX, y, TFT_DARKGREY);

        float value = minVal + ((maxVal - minVal) / GRID_LINES) * i;
        tft.setCursor(5, y - 5);
        tft.setTextSize(1);
        tft.print(value, 2);  
    }

    // **Etykiety osi X (przesunięte w dół)**
    tft.setCursor(graphStartX, 215);
    tft.print("0");
    tft.setCursor((graphStartX + graphEndX) / 2, 215);
    tft.print(sampleCount / 2);
    tft.setCursor(graphEndX - 30, 215);
    tft.print(sampleCount);

    // **Rysowanie wykresu przesuniętego w prawo i w dół**
    for (int i = 0; i < sampleCount - 1; i++) {
        int x1 = map(i, 0, sampleCount - 1, graphStartX, graphEndX);
        int y1 = map(values[i] * 100, minVal * 100, maxVal * 100, graphEndY, graphStartY);
        int x2 = map(i + 1, 0, sampleCount - 1, graphStartX, graphEndX);
        int y2 = map(values[i + 1] * 100, minVal * 100, maxVal * 100, graphEndY, graphStartY);

        tft.drawLine(x1, y1, x2, y2, color);
    }

    // **Opis min/max wartości (przesunięte w dół)**
    tft.setCursor(50, 230);
    tft.print("Min: "); tft.print(minVal, 2);

    tft.setCursor(200, 230);
    tft.print("Max: "); tft.print(maxVal, 2);
}

void drawGraphTemperature() 
{
    drawGraph("Temperatura C", TFT_YELLOW, [](DataLog log) { return log.temperature; });
}

void drawGraphHumidity() 
{
    drawGraph("Wilgotnosc %", TFT_CYAN, [](DataLog log) { return log.humidity; });
}

void drawGraphPressure() 
{
  drawGraph("Cisnienie hPa", TFT_RED, [](DataLog log) { return log.pressure; });
}

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

    // **Inicjalizacja SPI dla pamięci**
    hspi.begin(FLASH_SCK, FLASH_MISO, FLASH_MOSI, FLASH_CS);
    pinMode(FLASH_CS, OUTPUT);
    digitalWrite(FLASH_CS, HIGH);

    // **Inicjalizacja TFT**
    tft.init();
    tft.setRotation(3);
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.setTextSize(2);

    // **Inicjalizacja BME280**
    if (!bme.begin(0x76)) {
        tft.println("BME280 ERROR!");
        while (1);
    }
    tft.println("BME280 OK");

    // **Inicjalizacja pamięci W25Q128**
    if (!flash.begin()) {
        tft.println("FLASH ERROR!");
        while (1);
    }
    tft.println("FLASH OK");

    findLastFlashAddress();  
    pinMode(BUTTON_PIN, INPUT_PULLUP);

    delay(5000);
    tft.fillScreen(TFT_BLACK);
}

void loop() 
{
  static bool lastButtonState = HIGH;
  bool buttonState = digitalRead(BUTTON_PIN);

  // Zmiana stanu po naciśnięciu przycisku
  if (buttonState == LOW && lastButtonState == HIGH) 
  {
      currentState = static_cast<ScreenState>((currentState + 1) % 4);
      ChangeScreen();  // Reset ekranu i flagi wykresu
      delay(200);
  }
  lastButtonState = buttonState;

  // **Jeśli ekran to `SCREEN_DATA`, odświeżaj dane co TIME_TO_REFRESH_DISPLAY**
  if (currentState == SCREEN_DATA) 
  {
    if (millis() - lastSaveTimeDisplay > TIME_TO_REFRESH_DISPLAY) 
    {
      displaySensorData();
      lastSaveTimeDisplay = millis();
    }
  }
  // **Dla innych ekranów (wykresów), rysuj tylko raz po przełączeniu**
  else if (!graphDrawn) 
  {
    switch (currentState) 
    {
      case SCREEN_TEMP:
        drawGraphTemperature();
        break;
      case SCREEN_HUMIDITY:
        drawGraphHumidity();
        break;
      case SCREEN_PRESSURE:
        drawGraphPressure();
        break;
      default:
        break;
    }
    graphDrawn = true;  // Zablokowanie ponownego rysowania wykresu
  }

  // **Zapis do pamięci co `TIME_TO_SAVE`**
  if (millis() - lastSaveTimeFlash > TIME_TO_SAVE) 
  {
    WriteToFlash();
  }
}
