#include <QApplication>
#include <QCommandLineParser>
#include <QDebug>
#include <csignal>
#include <QJsonObject>
#include <QTextStream>
#include <QTimer>
#include "hyperiongrabber.h"
#include "HyperionProcessor.h"
#include "LedColorMapping.h"
#include "wledclient.h"

// ANSI escape code to clear the screen
const QString ANSI_RESET = "\033[0m";
const QString ANSI_CLEAR_SCREEN = "\033[2J\033[1;1H";

// Function to get a printable block with a specific background color
QString getAnsiColorBlock(const QColor& color) {
    if (!color.isValid()) {
        return "  ";
    }
    // Use 24-bit color ANSI escape codes
    return QString("\033[48;2;%1;%2;%3m  ").arg(color.red()).arg(color.green()).arg(color.blue());
}

void printAsciiLayout(const QVector<QColor>& ledColors, const LedLayout& layout) {
    QTextStream cout(stdout);
    cout << ANSI_CLEAR_SCREEN;

    // Calculate grid dimensions based on the maximum extent of LEDs on each side
    int max_h_extent = qMax(layout.bottom, layout.top);
    int max_v_extent = qMax(layout.left, layout.right);

    // Add 2 for the corner LEDs (one for left side, one for right side)
    int grid_width = max_h_extent + 2;
    // Add 2 for the corner LEDs (one for top side, one for bottom side)
    int grid_height = max_v_extent + 2;

    // Create a 2D grid to store the color blocks
    QVector<QVector<QString>> grid(grid_height, QVector<QString>(grid_width, "  "));

    // Populate the grid with LED colors
    int currentLedIndex = 0;

    // Bottom LEDs (left to right)
    for (int i = 0; i < layout.bottom; ++i) {
        if (currentLedIndex < ledColors.size()) {
            grid[grid_height - 1][1 + i] = getAnsiColorBlock(ledColors.value(currentLedIndex));
        }
        currentLedIndex++;
    }

    // Right LEDs (bottom to top)
    for (int i = 0; i < layout.right; ++i) {
        if (currentLedIndex < ledColors.size()) {
            grid[grid_height - 2 - i][grid_width - 1] = getAnsiColorBlock(ledColors.value(currentLedIndex));
        }
        currentLedIndex++;
    }

    // Top LEDs (right to left)
    for (int i = 0; i < layout.top; ++i) {
        if (currentLedIndex < ledColors.size()) {
            grid[0][grid_width - 2 - i] = getAnsiColorBlock(ledColors.value(currentLedIndex));
        }
        currentLedIndex++;
    }

    // Left LEDs (top to bottom)
    for (int i = 0; i < layout.left; ++i) {
        if (currentLedIndex < ledColors.size()) {
            grid[1 + i][0] = getAnsiColorBlock(ledColors.value(currentLedIndex));
        }
        currentLedIndex++;
    }

    // Print the grid
    for (int r = 0; r < grid_height; ++r) {
        for (int c = 0; c < grid_width; ++c) {
            cout << grid[r][c];
        }
        cout << ANSI_RESET << "\n";
    }
    cout.flush();
}


static HyperionGrabber *grabber = nullptr;
static QApplication *app = nullptr;
static HyperionProcessor *processor = nullptr;
static WledClient *wledClient = nullptr;

void quit(int)
{
    if (grabber != nullptr) {
        delete grabber;
    }
    if (processor != nullptr) {
        delete processor;
    }
    if (wledClient != nullptr) {
        delete wledClient;
    }
    if (app != nullptr) {
        app->quit();
    }
}

class GrabberConfigurator : public QObject
{
    Q_OBJECT

public:
    GrabberConfigurator(const QHash<QString, QString> &opts, const LedLayout& layout, const QJsonObject& config, const QString& wledAddress, quint16 wledPort, QObject *parent = nullptr)
        : QObject(parent), _layout(layout)
    {
        grabber = new HyperionGrabber(opts);
        processor = new HyperionProcessor(layout, config);
        // Connect grabber to a new slot that will trigger the timer
        connect(grabber, &HyperionGrabber::imageReady, this, &GrabberConfigurator::onImageReady);

        _timer = new QTimer(this);
        _timer->setInterval(1000); // 1 second
        connect(_timer, &QTimer::timeout, this, &GrabberConfigurator::processCurrentFrame);
        _timer->start();

        wledClient = new WledClient(wledAddress, wledPort, this);
    }

public slots:
    void onImageReady(const QImage &image) {
        _currentImage = image; // Store the latest image
    }

    void processCurrentFrame() {
        QTextStream cout(stdout);
        cout << "\n\n\n"; // Print newlines for separation
        qDebug() << "GrabberConfigurator::processCurrentFrame called.";

        if (_currentImage.isNull()) {
            qDebug() << "No image available yet.";
            return;
        }

        // Process the image with HyperionProcessor
        QVector<QColor> ledColors = processor->process(_currentImage);

        // Send colors to WLED
        wledClient->setLedsColor(ledColors);

        // Debugging: Print some sample LED colors
        if (!ledColors.isEmpty()) {
            qDebug() << "Sample LED Colors:";
            qDebug() << "  LED 0:" << ledColors.at(0).name();
            if (ledColors.size() > 1) qDebug() << "  LED 1:" << ledColors.at(1).name();
            if (ledColors.size() > _layout.bottom) qDebug() << "  LED (bottom+1):" << ledColors.at(_layout.bottom).name();
            if (ledColors.size() > _layout.bottom + _layout.right) qDebug() << "  LED (bottom+right+1):" << ledColors.at(_layout.bottom + _layout.right).name();
        }

        // Print LED colors as a rectangular layout in the terminal
        printAsciiLayout(ledColors, _layout);
    }

private:
    LedLayout _layout; // Still needed for HyperionProcessor constructor
    QTimer *_timer;
    QImage _currentImage;
};

int main(int argc, char *argv[])
{
    app = new QApplication(argc, argv);
    signal(SIGINT, quit);
    signal(SIGTERM, quit);

    QApplication::setApplicationName("GrabberConfigurator");
    QApplication::setApplicationVersion("0.2");

    QCommandLineParser parser;
    parser.setApplicationDescription("A tool to test the Hyperion processing pipeline.");
    parser.addHelpOption();
    parser.addVersionOption();

    // Add options for HyperionGrabber (scale, frameskip, etc.)
    parser.addOption(QCommandLineOption(QStringList() << "s" << "scale", "Divisor used to scale your screen resolution (e.g., 8).", "scale", "8"));
    parser.addOption(QCommandLineOption(QStringList() << "f" << "frameskip", "Number of frames to skip between captures (e.g., 1).", "frameskip", "0"));

    // Add options for the LED layout
    parser.addOption(QCommandLineOption(QStringList() << "leds-bottom", "Number of LEDs on the bottom edge.", "count", "70"));
    parser.addOption(QCommandLineOption(QStringList() << "leds-right", "Number of LEDs on the right edge.", "count", "20"));
    parser.addOption(QCommandLineOption(QStringList() << "leds-top", "Number of LEDs on the top edge.", "count", "70"));
    parser.addOption(QCommandLineOption(QStringList() << "leds-left", "Number of LEDs on the left edge.", "count", "20"));

    // Add options for the processor
    parser.addOption(QCommandLineOption(QStringList() << "bbt", "Black border threshold (0.0 to 1.0).", "threshold", "0.2")); // Increased threshold
    parser.addOption(QCommandLineOption(QStringList() << "sf", "Smoothing factor (0.0 to 1.0).", "factor", "0.1")); // Smoothing factor

    // Add WLED options
    parser.addOption(QCommandLineOption(QStringList() << "a" << "address", "IP address of the WLED device.", "address"));
    parser.addOption(QCommandLineOption(QStringList() << "p" << "port", "UDP port of the WLED device.", "port", "21324"));

    parser.process(*app);

    // Validate WLED address
    if (!parser.isSet("address")) {
        qCritical() << "Error: WLED IP address not provided. Use -a or --address.";
        parser.showHelp(1);
    }

    QString wledAddress = parser.value("address");
    quint16 wledPort = parser.value("port").toUShort();

    QHash<QString, QString> grabberOpts;
    grabberOpts.insert("scale", parser.value("scale"));
    grabberOpts.insert("frameskip", parser.value("frameskip"));

    LedLayout layout;
    layout.bottom = parser.value("leds-bottom").toInt();
    layout.right = parser.value("leds-right").toInt();
    layout.top = parser.value("leds-top").toInt();
    layout.left = parser.value("leds-left").toInt();

    QJsonObject processorConfig;
    processorConfig["blackBorderThreshold"] = parser.value("bbt").toDouble();
    processorConfig["smoothingFactor"] = parser.value("sf").toDouble();

    GrabberConfigurator configurator(grabberOpts, layout, processorConfig, wledAddress, wledPort);

    qInfo() << "Starting GrabberConfigurator. Sending LED colors to WLED device." << wledAddress << ":" << wledPort << ". Press Ctrl+C to exit.";

    return app->exec();
}

#include "grabberconfigurator.moc"