#include <QCoreApplication>
#include <QCommandLineParser>
#include <QDebug>
#include <QJsonDocument>
#include <QFile>
#include <iostream>
#include <cstdlib>
#include <QTimer>
#include <QTime>
#include <QSocketNotifier>
#include "wledconfigclient.h"
#include "wledclient.h"

// Forward declarations
void runDemoMode(const QString &address, quint16 port, int totalLeds);
void runFlashMode(const QString &address, quint16 port, int totalLeds, const QString &flashParams);
void delay(int milliseconds);
void printAsciiLayout(int ledsBottom, int ledsRight, int ledsTop, int ledsLeft, int offset, int totalLeds);


int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QCoreApplication::setApplicationName("wled_config_tool");
    QCoreApplication::setApplicationVersion("1.0");

    QCommandLineParser parser;
    parser.setApplicationDescription("WLED Configuration Tool");
    parser.addHelpOption();
    parser.addVersionOption();

    QCommandLineOption addressOption(QStringList() << "a" << "address",
                                   "WLED device IP address or hostname.",
                                   "address");
    parser.addOption(addressOption);

    QCommandLineOption portOption(QStringList() << "p" << "port",
                                  "WLED device UDP port.",
                                  "port", "21324");
    parser.addOption(portOption);

    QCommandLineOption saveOption(QStringList() << "s" << "save",
                                  "Save configuration to a file.",
                                  "filepath");
    parser.addOption(saveOption);

    QCommandLineOption configureLayoutOption(QStringList() << "l" << "configure-layout",
                                           "Start interactive LED layout configuration.");
    parser.addOption(configureLayoutOption);

    QCommandLineOption demoOption(QStringList() << "d" << "demo",
                                  "Run a demo sequence on the LEDs.");
    parser.addOption(demoOption);

    QCommandLineOption flashOption(QStringList() << "f" << "flash",
                                   "Flash a range of LEDs. Format: startIndex,count,color (e.g., 10,20,#FF0000).",
                                   "params");
    parser.addOption(flashOption);


    parser.process(app);

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

    QString wledAddress = parser.value(addressOption);
    quint16 wledPort = parser.value(portOption).toUShort();

    if (parser.isSet(configureLayoutOption)) {
        auto configClient = new WledConfigClient(wledAddress, &app);

        QObject::connect(configClient, &WledConfigClient::infoReceived, [&](const QJsonObject &info) {
            int totalLeds = info["leds"].toObject()["count"].toInt();
            qDebug() << "Your WLED device reports" << totalLeds << "LEDs.";

            QTextStream cout(stdout);
            QTextStream cin(stdin);

            cout << "Starting interactive LED layout configuration.\n";
            cout << "Please enter the number of LEDs for each section.\n";
            
            int ledsBottom, ledsRight, ledsTop, ledsLeft, offset;

            cout << "LEDs on the bottom: ";
            cout.flush();
            cin >> ledsBottom;

            cout << "LEDs on the right side: ";
            cout.flush();
            cin >> ledsRight;

            cout << "LEDs on the top: ";
            cout.flush();
            cin >> ledsTop;

            cout << "LEDs on the left side: ";
            cout.flush();
            cin >> ledsLeft;
            
            cout << "Enter the starting offset (0 if the strip starts at the first LED): ";
            cout.flush();
            cin >> offset;

            int configuredLeds = ledsBottom + ledsRight + ledsTop + ledsLeft;
            cout << "Total LEDs configured: " << configuredLeds << "\n";
            cout.flush();

            if (configuredLeds > totalLeds) {
                qWarning() << "Warning: The number of configured LEDs (" << configuredLeds
                           << ") is greater than the number of LEDs reported by the device (" << totalLeds << ").";
            }

            auto wledClient = new WledClient(wledAddress, wledPort, &app);
            auto colors = new QVector<QColor>(totalLeds, Qt::black);
            
            int currentIndex = offset;
            // Bottom
            for (int i = 0; i < ledsBottom; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::red; }
            currentIndex += ledsBottom;
            // Right
            for (int i = 0; i < ledsRight; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::green; }
            currentIndex += ledsRight;
            // Top
            for (int i = 0; i < ledsTop; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::blue; }
            currentIndex += ledsTop;
            // Left
            for (int i = 0; i < ledsLeft; ++i) { (*colors)[(currentIndex + i) % totalLeds] = Qt::yellow; }

            wledClient->setLedsColor(*colors, 60); // Set timeout to 60 seconds
            cout << "LEDs have been lit up according to your configuration.\n";
            cout.flush();

            printAsciiLayout(ledsBottom, ledsRight, ledsTop, ledsLeft, offset, totalLeds);

            cout << "Press Enter to turn off LEDs and exit.\n";
            cout.flush();

            auto notifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, &app);
            QObject::connect(notifier, &QSocketNotifier::activated, [=, &app]() {
                colors->fill(Qt::black);
                wledClient->setLedsColor(*colors);
                app.quit();
            });
        });

        QObject::connect(configClient, &WledConfigClient::error, [&](const QString &message) {
            qCritical() << "Error:" << message;
            app.quit();
        });

        configClient->getInfo();
        return app.exec();
    }


    WledConfigClient client(wledAddress);

    QObject::connect(&client, &WledConfigClient::infoReceived, [&](const QJsonObject &info) {
        if (parser.isSet(demoOption) || parser.isSet(flashOption)) {
            int totalLeds = info["leds"].toObject()["count"].toInt();
            if (parser.isSet(demoOption)) {
                runDemoMode(wledAddress, wledPort, totalLeds);
                app.quit();
            } else if (parser.isSet(flashOption)) {
                runFlashMode(wledAddress, wledPort, totalLeds, parser.value(flashOption));
                app.quit();
            }
        } else {
            QJsonDocument doc(info);
            QFile stdoutFile;
            stdoutFile.open(stdout, QFile::WriteOnly);
            QTextStream ts(&stdoutFile);
            ts << "WLED Configuration Info:\n" << doc.toJson(QJsonDocument::Indented) << Qt::endl;
            stdoutFile.close();

            QString saveFilePath = parser.value(saveOption);
            if (!saveFilePath.isEmpty()) {
                QFile file(saveFilePath);
                if (file.open(QIODevice::WriteOnly)) {
                    file.write(doc.toJson(QJsonDocument::Indented));
                    file.close();
                    qDebug() << "Configuration saved to" << saveFilePath;
                } else {
                    qWarning() << "Failed to save configuration to" << saveFilePath << ":" << file.errorString();
                }
            }
            app.quit();
        }
    });

    QObject::connect(&client, &WledConfigClient::error, [&](const QString &message) {
        qCritical() << "Error:" << message;
        app.quit();
    });

    client.getInfo();

    return app.exec();
}

// ANSI Color Codes
const QString ANSI_RESET = "\033[0m";
const QString ANSI_RED_BG = "\033[41m";
const QString ANSI_GREEN_BG = "\033[42m";
const QString ANSI_BLUE_BG = "\033[44m";
const QString ANSI_YELLOW_BG = "\033[43m";

void printAsciiLayout(int ledsBottom, int ledsRight, int ledsTop, int ledsLeft, int offset, int totalLeds) {
    QTextStream cout(stdout);

    int maxHorizontal = qMax(ledsTop, ledsBottom);
    int maxVertical = qMax(ledsLeft, ledsRight);

    // Top Row
    cout << "  "; // Indent for left side
    for (int i = 0; i < ledsTop; ++i) {
        cout << ANSI_BLUE_BG << "  " << ANSI_RESET; // Two spaces for a square
    }
    cout << "\n";

    // Middle Rows (Left and Right)
    for (int i = 0; i < maxVertical; ++i) {
        if (i < ledsLeft) {
            cout << ANSI_YELLOW_BG << "  " << ANSI_RESET;
        } else {
            cout << "  "; // Empty space if no left LED
        }
        // Calculate spaces between left and right
        int horizontalSpace = maxHorizontal * 2; // Each LED is two chars
        cout << QString(horizontalSpace, ' ');
        if (i < ledsRight) {
            cout << ANSI_GREEN_BG << "  " << ANSI_RESET;
        }
        cout << "\n";
    }

    // Bottom Row
    cout << "  "; // Indent for left side
    for (int i = 0; i < ledsBottom; ++i) {
        cout << ANSI_RED_BG << "  " << ANSI_RESET;
    }
    cout << "\n";

    cout << "\nLayout details:\n";
    cout << "  Bottom LEDs (Red): " << ledsBottom << "\n";
    cout << "  Right LEDs (Green): " << ledsRight << "\n";
    cout << "  Top LEDs (Blue): " << ledsTop << "\n";
    cout << "  Left LEDs (Yellow): " << ledsLeft << "\n";
    cout << "  Offset: " << offset << "\n";
    cout << "  Total LEDs: " << totalLeds << "\n";
    cout.flush();
}

void runDemoMode(const QString &address, quint16 port, int totalLeds)
{
    qDebug() << "Running demo mode with" << totalLeds << "LEDs on" << address << ":" << port;
    qDebug() << "Press Ctrl+C to exit.";

    WledClient client(address, port);
    QVector<QColor> colors(totalLeds, Qt::black);

    // Seed the random number generator
    QTime time = QTime::currentTime();
    srand(time.msec());

    while (true) {
        // Turn on LEDs one by one with random colors
        for (int i = 0; i < totalLeds; ++i) {
            colors[i] = QColor::fromRgb(rand() % 256, rand() % 256, rand() % 256);
            client.setLedsColor(colors);
            delay(50);
        }

        // Turn off all LEDs
        colors.fill(Qt::black);
        client.setLedsColor(colors);
        delay(1000);
    }
}

void runFlashMode(const QString &address, quint16 port, int totalLeds, const QString &flashParams)
{
    qDebug() << "Running flash mode with params:" << flashParams;

    QStringList params = flashParams.split(',');
    if (params.size() != 3) {
        qCritical() << "Error: Invalid flash parameters. Expected format: startIndex,count,color";
        return;
    }

    bool ok1, ok2;
    int startIndex = params[0].toInt(&ok1);
    int count = params[1].toInt(&ok2);
    QColor color(params[2]);

    if (!ok1 || !ok2 || !color.isValid()) {
        qCritical() << "Error: Invalid flash parameters. Could not parse values.";
        return;
    }

    if (startIndex < 0 || count <= 0 || startIndex + count > totalLeds) {
        qCritical() << "Error: Invalid range. Start index must be non-negative, count must be positive, and the range must be within the total number of LEDs (" << totalLeds << ").";
        return;
    }

    WledClient client(address, port);
    QVector<QColor> colors(totalLeds, Qt::black);

    for (int i = 0; i < count; ++i) {
        colors[startIndex + i] = color;
    }

    client.setLedsColor(colors);
    qDebug() << "Flashed" << count << "LEDs starting from index" << startIndex << "with color" << color.name();
}

void delay(int milliseconds)
{
    QEventLoop loop;
    QTimer::singleShot(milliseconds, &loop, &QEventLoop::quit);
    loop.exec();
}