/*
  __  __    _  _____ ____  _____  __
 |  \/  |  / \|_   _|  _ \|_ _\ \/ /
 | |\/| | / _ \ | | | |_) || | \  / 
 | |  | |/ ___ \| | |  _ < | | /  \ 
 |_|  |_/_/   \_\_| |_| \_\___/_/\_\


Script to launch screens to display compounds
Get values from SQLite database:
- NextPM data from data_NPM table
- CO2 data from data_MHZ19 or data_CO2 table
- Additional sensors as needed

Pour compiler:
g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v1.cc -o /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v1 -lrgbmatrix -lsqlite3

Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v1
*/

#include "led-matrix.h"
#include "graphics.h"
#include <unistd.h>
#include <string.h>
#include <fstream>
#include <iostream>
#include <signal.h>
#include <atomic>
#include <sqlite3.h>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <ctime>

using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;

std::atomic<bool> running(true);

// Path to the SQLite database
const std::string DB_PATH = "/var/www/moduleair_pro_4g/sqlite/sensors.db";

void signal_handler(int signum) {
    running = false;
}

// Helper function to get current time as string for logging
std::string get_current_time() {
    auto now = std::chrono::system_clock::now();
    std::time_t now_time = std::chrono::system_clock::to_time_t(now);
    std::stringstream ss;
    ss << std::put_time(std::localtime(&now_time), "%Y-%m-%d %H:%M:%S");
    return ss.str();
}

// Function to retrieve latest NPM data from the database
bool get_npm_data(sqlite3* db, float& pm10, float& pm25, float& pm1) {
    sqlite3_stmt* stmt;
    bool success = false;
    
    std::cout << get_current_time() << " - Querying NPM data from database..." << std::endl;
    
    // First, check if the table exists
    const char* table_check = "SELECT name FROM sqlite_master WHERE type='table' AND name='data_NPM';";
    if (sqlite3_prepare_v2(db, table_check, -1, &stmt, NULL) != SQLITE_OK) {
        std::cerr << "SQL error checking table: " << sqlite3_errmsg(db) << std::endl;
        return false;
    }
    
    bool table_exists = false;
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        table_exists = true;
    }
    sqlite3_finalize(stmt);
    
    if (!table_exists) {
        std::cerr << "Error: data_NPM table does not exist" << std::endl;
        return false;
    }
    
    // Query to get the average of the last 6 NPM measurements
    const char* query = "SELECT AVG(PM10), AVG(PM25), AVG(PM1) FROM data_NPM ORDER BY rowid DESC LIMIT 6";
    
    if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) != SQLITE_OK) {
        std::cerr << "SQL error preparing query: " << sqlite3_errmsg(db) << std::endl;
        return false;
    }
    
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        pm10 = sqlite3_column_double(stmt, 0);
        pm25 = sqlite3_column_double(stmt, 1);
        pm1 = sqlite3_column_double(stmt, 2);
        
        std::cout << "  Retrieved NPM values - PM10: " << pm10 
                  << ", PM2.5: " << pm25 
                  << ", PM1: " << pm1 << std::endl;
        
        success = true;
    } else {
        std::cerr << "  No NPM data found in database" << std::endl;
    }
    
    sqlite3_finalize(stmt);
    return success;
}

// Function to retrieve latest CO2 data from the database
bool get_co2_data(sqlite3* db, float& co2) {
    sqlite3_stmt* stmt;
    bool success = false;
    
    std::cout << get_current_time() << " - Querying CO2 data from database..." << std::endl;
    
    // Try data_CO2 table directly
    const char* query_co2 = "SELECT CO2 FROM data_CO2 ORDER BY rowid DESC LIMIT 1";
    
    if (sqlite3_prepare_v2(db, query_co2, -1, &stmt, NULL) == SQLITE_OK) {
        if (sqlite3_step(stmt) == SQLITE_ROW) {
            co2 = sqlite3_column_double(stmt, 0);
            std::cout << "  Retrieved CO2 value from data_CO2: " << co2 << " ppm" << std::endl;
            success = true;
        } else {
            std::cout << "  No data found in data_CO2 table" << std::endl;
        }
        sqlite3_finalize(stmt);
    } else {
        std::cerr << "  Error preparing CO2 query: " << sqlite3_errmsg(db) << std::endl;
        
        // Check if table exists
        const char* table_check = "SELECT name FROM sqlite_master WHERE type='table' AND name='data_CO2';";
        if (sqlite3_prepare_v2(db, table_check, -1, &stmt, NULL) == SQLITE_OK) {
            if (sqlite3_step(stmt) == SQLITE_ROW) {
                std::cout << "  data_CO2 table exists but query failed" << std::endl;
            } else {
                std::cout << "  data_CO2 table does not exist in database" << std::endl;
            }
            sqlite3_finalize(stmt);
        }
    }
    
    return success;
}

// Store previous values to check for changes
struct SensorData {
    float pm10 = 0, pm25 = 0, pm1 = 0, co2 = 0;
    std::string pm10_str, pm25_str, pm1_str, co2_str;
    std::string pm10_status, pm25_status, pm1_status, co2_status;
    rgb_matrix::Color pm10_color, pm25_color, pm1_color, co2_color;
};

// Initial screen setup - draw all static elements
void setup_screen(Canvas *canvas) {
    std::cout << get_current_time() << " - Setting up initial screen layout..." << std::endl;
    canvas->Clear();

    rgb_matrix::Color myCYAN(0, 255, 255);
    rgb_matrix::Color bg_color(0, 0, 0);
    rgb_matrix::Font font1;

    if (!font1.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) {
        std::cerr << "Error loading font!" << std::endl;
        return;
    }

    int letter_spacing = 0;
    int x, y1;
    
    // Draw static labels for PM10
    x = 0;
    y1 = font1.baseline() - 1;
    rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "PM10 µg/m³", letter_spacing);
    std::cout << "  Drew PM10 label" << std::endl;

    // Draw static labels for PM2.5
    x = 64;
    rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "PM2.5 µg/m³", letter_spacing);
    std::cout << "  Drew PM2.5 label" << std::endl;

    // Draw static labels for PM1
    x = 0;
    y1 += 33;
    rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "PM1 µg/m³", letter_spacing);
    std::cout << "  Drew PM1 label" << std::endl;

    // Draw static labels for CO2
    x = 64;
    rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "CO₂ ppm", letter_spacing);
    std::cout << "  Drew CO2 label" << std::endl;
}

// Function to update only the values and status messages on the screen
void update_screen_values(Canvas *canvas, SensorData& prevData, float pm10, float pm25, float pm1, float co2) {
    std::cout << get_current_time() << " - Updating screen values..." << std::endl;
    
    rgb_matrix::Color myWHITE(255, 255, 255);
    rgb_matrix::Color myGREEN(0, 255, 0);       // Good
    rgb_matrix::Color myYELLOW(255, 255, 0);    // Moderate
    rgb_matrix::Color myORANGE(255, 165, 0);    // Degraded
    rgb_matrix::Color myRED(255, 0, 0);         // Bad
    rgb_matrix::Color bg_color(0, 0, 0);

    rgb_matrix::Font font1;
    rgb_matrix::Font font2;

    if (!font1.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) {
        std::cerr << "Error loading font1!" << std::endl;
        return;
    }
    if (!font2.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/9x18B.bdf")) {
        std::cerr << "Error loading font2!" << std::endl;
        return;
    }

    int letter_spacing = 0;
    int x, y1, y2, y3;
    
    // Convert float values to strings with 1 decimal place
    std::stringstream ss_pm10, ss_pm25, ss_pm1, ss_co2;
    ss_pm10 << std::fixed << std::setprecision(1) << pm10;
    ss_pm25 << std::fixed << std::setprecision(1) << pm25;
    ss_pm1 << std::fixed << std::setprecision(1) << pm1;
    ss_co2 << std::fixed << std::setprecision(0) << co2;
    
    std::string str_pm10 = ss_pm10.str();
    std::string str_pm25 = ss_pm25.str();
    std::string str_pm1 = ss_pm1.str();
    std::string str_co2 = ss_co2.str();
    
    // Define coordinates for values and status messages
    x = 0;
    y1 = font1.baseline() - 1;
    y2 = y1 + font2.baseline() + 1;
    y3 = y1 + y2 + 4;
    
    // Update PM10 value and status if changed
    if (str_pm10 != prevData.pm10_str) {
        std::cout << "  Updating PM10 value from " << prevData.pm10_str << " to " << str_pm10 << std::endl;
        // Clear previous value area
        for (int i = 0; i < 60; i++) {
            for (int j = y1 + 1; j < y2 + 5; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new value
        rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm10.c_str(), letter_spacing);
        prevData.pm10_str = str_pm10;
    }
    
    // Determine PM10 status
    std::string pm10_status;
    rgb_matrix::Color pm10_color;
    if (pm10 < 15) {
        pm10_status = "BON";
        pm10_color = myGREEN;
    } else if (pm10 >= 15 && pm10 < 30) {
        pm10_status = "MOYEN";
        pm10_color = myYELLOW;
    } else if (pm10 >= 30 && pm10 < 75) {
        pm10_status = "DEGRADE";
        pm10_color = myORANGE;
    } else {
        pm10_status = "MAUVAIS";
        pm10_color = myRED;
    }
    
    // Update PM10 status if changed
    if (pm10_status != prevData.pm10_status || pm10_color.r != prevData.pm10_color.r || 
        pm10_color.g != prevData.pm10_color.g || pm10_color.b != prevData.pm10_color.b) {
        std::cout << "  Updating PM10 status to " << pm10_status << std::endl;
        // Clear previous status area
        for (int i = 0; i < 60; i++) {
            for (int j = y2 + 6; j < y3 + 10; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new status
        rgb_matrix::DrawText(canvas, font1, x, y3, pm10_color, &bg_color, pm10_status.c_str(), letter_spacing);
        prevData.pm10_status = pm10_status;
        prevData.pm10_color = pm10_color;
    }

    // Update PM2.5 value and status
    x = 64;
    if (str_pm25 != prevData.pm25_str) {
        std::cout << "  Updating PM2.5 value from " << prevData.pm25_str << " to " << str_pm25 << std::endl;
        // Clear previous value area
        for (int i = 0; i < 60; i++) {
            for (int j = y1 + 1; j < y2 + 5; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new value
        rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm25.c_str(), letter_spacing);
        prevData.pm25_str = str_pm25;
    }
    
    // Determine PM2.5 status
    std::string pm25_status;
    rgb_matrix::Color pm25_color;
    if (pm25 < 10) {
        pm25_status = "BON";
        pm25_color = myGREEN;
    } else if (pm25 >= 10 && pm25 < 20) {
        pm25_status = "MOYEN";
        pm25_color = myYELLOW;
    } else if (pm25 >= 20 && pm25 < 50) {
        pm25_status = "DEGRADE";
        pm25_color = myORANGE;
    } else {
        pm25_status = "MAUVAIS";
        pm25_color = myRED;
    }
    
    // Update PM2.5 status if changed
    if (pm25_status != prevData.pm25_status || pm25_color.r != prevData.pm25_color.r || 
        pm25_color.g != prevData.pm25_color.g || pm25_color.b != prevData.pm25_color.b) {
        std::cout << "  Updating PM2.5 status to " << pm25_status << std::endl;
        // Clear previous status area
        for (int i = 0; i < 60; i++) {
            for (int j = y2 + 6; j < y3 + 10; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new status
        rgb_matrix::DrawText(canvas, font1, x, y3, pm25_color, &bg_color, pm25_status.c_str(), letter_spacing);
        prevData.pm25_status = pm25_status;
        prevData.pm25_color = pm25_color;
    }

    // Update PM1 value and status
    x = 0;
    y1 += 33;
    y2 += 33;
    y3 += 33;
    
    if (str_pm1 != prevData.pm1_str) {
        std::cout << "  Updating PM1 value from " << prevData.pm1_str << " to " << str_pm1 << std::endl;
        // Clear previous value area
        for (int i = 0; i < 60; i++) {
            for (int j = y1 + 1; j < y2 + 5; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new value
        rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm1.c_str(), letter_spacing);
        prevData.pm1_str = str_pm1;
    }
    
    // Determine PM1 status
    std::string pm1_status;
    rgb_matrix::Color pm1_color;
    if (pm1 < 10) {
        pm1_status = "BON";
        pm1_color = myGREEN;
    } else if (pm1 >= 10 && pm1 < 20) {
        pm1_status = "MOYEN";
        pm1_color = myYELLOW;
    } else if (pm1 >= 20 && pm1 < 50) {
        pm1_status = "DEGRADE";
        pm1_color = myORANGE;
    } else {
        pm1_status = "MAUVAIS";
        pm1_color = myRED;
    }
    
    // Update PM1 status if changed
    if (pm1_status != prevData.pm1_status || pm1_color.r != prevData.pm1_color.r || 
        pm1_color.g != prevData.pm1_color.g || pm1_color.b != prevData.pm1_color.b) {
        std::cout << "  Updating PM1 status to " << pm1_status << std::endl;
        // Clear previous status area
        for (int i = 0; i < 60; i++) {
            for (int j = y2 + 6; j < y3 + 10; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new status
        rgb_matrix::DrawText(canvas, font1, x, y3, pm1_color, &bg_color, pm1_status.c_str(), letter_spacing);
        prevData.pm1_status = pm1_status;
        prevData.pm1_color = pm1_color;
    }

    // Update CO2 value and status
    x = 64;
    if (str_co2 != prevData.co2_str) {
        std::cout << "  Updating CO2 value from " << prevData.co2_str << " to " << str_co2 << std::endl;
        // Clear previous value area
        for (int i = 0; i < 60; i++) {
            for (int j = y1 + 1; j < y2 + 5; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new value
        rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_co2.c_str(), letter_spacing);
        prevData.co2_str = str_co2;
    }
    
    // Determine CO2 status
    std::string co2_status;
    rgb_matrix::Color co2_color;
    if (co2 < 800) {
        co2_status = "BON";
        co2_color = myGREEN;
    } else if (co2 >= 800 && co2 < 1500) {
        co2_status = "AERER SVP";
        co2_color = myORANGE;
    } else {
        co2_status = "AERER VITE";
        co2_color = myRED;
    }
    
    // Update CO2 status if changed
    if (co2_status != prevData.co2_status || co2_color.r != prevData.co2_color.r || 
        co2_color.g != prevData.co2_color.g || co2_color.b != prevData.co2_color.b) {
        std::cout << "  Updating CO2 status to " << co2_status << std::endl;
        // Clear previous status area
        for (int i = 0; i < 60; i++) {
            for (int j = y2 + 6; j < y3 + 10; j++) {
                canvas->SetPixel(x + i, j, 0, 0, 0);
            }
        }
        // Draw new status
        rgb_matrix::DrawText(canvas, font1, x, y3, co2_color, &bg_color, co2_status.c_str(), letter_spacing);
        prevData.co2_status = co2_status;
        prevData.co2_color = co2_color;
    }
    
    // Update stored values
    prevData.pm10 = pm10;
    prevData.pm25 = pm25;
    prevData.pm1 = pm1;
    prevData.co2 = co2;
}

// Function to dump database tables for debugging
void list_database_tables(sqlite3* db) {
    sqlite3_stmt* stmt;
    const char* query = "SELECT name FROM sqlite_master WHERE type='table';";
    
    std::cout << "====== DATABASE TABLES ======" << std::endl;
    
    if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) == SQLITE_OK) {
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            const unsigned char* table_name = sqlite3_column_text(stmt, 0);
            std::cout << "Table: " << table_name << std::endl;
        }
    } else {
        std::cerr << "Error listing tables: " << sqlite3_errmsg(db) << std::endl;
    }
    
    sqlite3_finalize(stmt);
    std::cout << "============================" << std::endl;
}

int main(int argc, char *argv[]) {
    std::cout << "===== Starting ModuleAir Display Program =====" << std::endl;
    std::cout << get_current_time() << " - Program started" << std::endl;
    
    // Handle signals for graceful exit
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);

    // Initialize database connection
    sqlite3* db;
    std::cout << get_current_time() << " - Opening SQLite database at " << DB_PATH << std::endl;
    int rc = sqlite3_open(DB_PATH.c_str(), &db);
    if (rc) {
        std::cerr << "Error opening SQLite database: " << sqlite3_errmsg(db) << std::endl;
        sqlite3_close(db);
        return 1;
    }
    
    // List available tables for debugging
    list_database_tables(db);

    // Initialize LED matrix
    std::cout << get_current_time() << " - Initializing LED matrix..." << std::endl;
    RGBMatrix::Options defaults;
    defaults.hardware_mapping = "moduleair_pinout";
    defaults.rows = 64;
    defaults.cols = 128;
    defaults.chain_length = 1;
    defaults.parallel = 1;
    defaults.row_address_type = 3;
    defaults.show_refresh_rate = false;
    defaults.brightness = 100;
    defaults.pwm_bits = 1;
    defaults.panel_type = "FM6126A";
    defaults.disable_hardware_pulsing = false;

    rgb_matrix::RuntimeOptions runtime_opt;
    runtime_opt.gpio_slowdown = 4;  // Adjust based on your Raspberry Pi model
    runtime_opt.daemon = 0;
    runtime_opt.drop_privileges = 0;

    Canvas *canvas = RGBMatrix::CreateFromOptions(defaults, runtime_opt);
    if (canvas == NULL) {
        std::cerr << "Error creating LED matrix canvas" << std::endl;
        sqlite3_close(db);
        return 1;
    }
    std::cout << get_current_time() << " - LED matrix initialized successfully" << std::endl;

    // Set up the initial screen layout (static elements)
    setup_screen(canvas);
    
    // Structure to store previous data values
    SensorData prevData;
    
    // Initial sensor values
    float pm10 = 0, pm25 = 0, pm1 = 0, co2 = 0;
    
    int update_count = 0;
    
    // Main loop
    std::cout << get_current_time() << " - Starting main loop..." << std::endl;
    while (running) {
        update_count++;
        std::cout << "\n" << get_current_time() << " - Update cycle #" << update_count << " started" << std::endl;
        
        // Get data from database
        bool npm_success = get_npm_data(db, pm10, pm25, pm1);
        bool co2_success = get_co2_data(db, co2);
        
        if (!npm_success) {
            std::cerr << "Error retrieving NPM data from database" << std::endl;
        }
        
        if (!co2_success) {
            std::cerr << "Error retrieving CO2 data from database" << std::endl;
        }
        
        // Update just the values and status messages
        update_screen_values(canvas, prevData, pm10, pm25, pm1, co2);
        
        // Sleep before next update
        std::cout << get_current_time() << " - Update complete, sleeping for 10 seconds..." << std::endl;
        for (int i = 0; i < 10 && running; i++) {
            sleep(1); // Sleep in 1-second increments to be more responsive to stop signals
        }
    }

    // Clean up
    std::cout << get_current_time() << " - Program terminating, cleaning up..." << std::endl;
    canvas->Clear();
    delete canvas;
    sqlite3_close(db);
    std::cout << get_current_time() << " - Program terminated" << std::endl;
    return 0;
}