#include "swordMain.hpp"

#include "interface.hpp"

using namespace pal;
using namespace std::literals::string_view_literals;
using namespace std::literals::chrono_literals;
using namespace std;
// using namespace std::literals;

const string BrockerUrl = "mqtts://nerdyssey.de:8883"; 
const string StationPath = "outdoor/king-arthur/sword/";

constexpr uint8_t VersionArr[4] = {'T', 0, 5, 0};
const string Version = "T0.5.0";

constexpr int SwordEntryPin = 5;
constexpr int SwordEndPin = 0;
constexpr int SwordPulledPin = 25;

constexpr int LinMotPin1 = 13;
constexpr int LinMotPin2 = 12;

constexpr int GPIO_RST_PIN = 4;
constexpr int GPIO_DIO0_PIN = 27;
constexpr ConMode_e ConMode{ConMode_e::ModeMqtt};

const string soundPromo = "/home/gustice/workspace/kingArth/swordStation/assets/promo.wav";
const string soundDeny = "/home/gustice/workspace/kingArth/swordStation/assets/deny.wav";
const string soundAllow = "/home/gustice/workspace/kingArth/swordStation/assets/allow.wav";

void playWavFile(const string& filePath) {
    if (filePath.empty()) {
        fmt::println(stderr, "ERROR: Empty file path");
        return;
    }
    thread audioThread([filePath]() {
        string cmd = "aplay \"" + filePath + "\" 2>/dev/null";
        int result = system(cmd.c_str());
        if (result != 0) {
            fmt::println(stderr, "ERROR: Failed to play audio file: {}", filePath);
        }
    });
    audioThread.detach();
}

enum class OpMode {
    Undefined,        // Enable in Game mode
    Maintanance = 1,  // Set Maintanance mode
    PromoMode,        // Enable PromoMode
    GamingMode,       // Enable in Game mode
};
OpMode Mode = OpMode::Maintanance;

unique_ptr<DeviceIoState> IoStat;

/// @brief MQTT-Service provider
void MQttServiceTask(Thread::Context& ctx, Queue<RxMsg>& rxQueue, Queue<TxMsg>& txQueue) {
    string runTopic = StationPath + "run";
    string intTopic = StationPath + "int";
    string statTopic = StationPath + "stat";

    fmt::println("MQTT: Starting task:");
    fmt::println("    Topic: {}", runTopic);

    auto mqttCallback = [&](string topic, string payload) {
        fmt::print("MQTT: on sendTrigger {}:{}'\n", topic, payload);

        static std::map<string, IncommingCommands_e> commands = {
            {"Qurey", IncommingCommands_e::Qurey},
            {"SetMaintanance", IncommingCommands_e::SetMaintanance},
            {"SetPromoMode", IncommingCommands_e::SetPromoMode},
            {"SetGamingMode", IncommingCommands_e::SetGamingMode},
        };
        static std::map<string, ServiceCommands_e> serCommands = {
            {"ServiceOpen", ServiceCommands_e::Open},
            {"ServiceClose", ServiceCommands_e::Close},
            {"ServicePlay1", ServiceCommands_e::Play1},
            {"ServicePlay2", ServiceCommands_e::Play2},
            {"ServicePlay3", ServiceCommands_e::Play3},
        };
        static std::map<string, GameModeCommands_e> gameCommands = {
            {"GameStayLocked", GameModeCommands_e::GameNextTryLocked},
            {"GameReleaseLock", GameModeCommands_e::GameNextTryRelease},
        };

        if (topic != runTopic) {
            fmt::print("MQTT: ignore invalid topic {}'\n", topic);
            return;
        }

        if (commands.contains(payload)) {
            rxQueue.emplace<RxMsg>(commands.at(payload));
            return;
        }
        if (serCommands.contains(payload)) {
            auto code = serCommands.at(payload);
            rxQueue.emplace<RxMsg>(code);
            return;
        }
        if (gameCommands.contains(payload)) {
            rxQueue.emplace<RxMsg>(serCommands.at(payload));
            return;
        }

        fmt::print("MQTT: unknown command {}:{}'\n", topic, payload);
    };

    try {
        std::vector<string> topicsToRegister{runTopic};
        MqttClient client("Sword", BrockerUrl, topicsToRegister);
        fmt::println("MQTT: service: successfully connected ...");
        client.registerCallback(mqttCallback);

        client.send(statTopic, fmt::format("{} Mode={}, sE:0,sS:0,sP:0,lE:0",
                                           Version, (int)Mode));

        int cnt = 0;
        fmt::println("MQTT: Running ...");
        while (!ctx.isCancelled()) {
            auto tx = txQueue.dequeue();

            switch (tx->cmd) {
                case OutgoingCommands_e::Status: {
                    auto& s = *IoStat;
                    client.send(statTopic, fmt::format("{} Mode={}, sE:{},sS:{},sP:{},lE:{}",
                                                       Version, (int)Mode,
                                                       s.SwordEnd.isActive(),
                                                       s.SwordEntry.isActive(),
                                                       s.SwordPulled.isActive(),
                                                       s.SwordLatch.isLocked()));
                } break;
                case OutgoingCommands_e::Response:
                    client.send(intTopic, "some response");
                    break;
                case OutgoingCommands_e::Error:
                    client.send(intTopic, "some error");
                    break;
                case OutgoingCommands_e::PullHappend:
                    client.send(intTopic, "pulled");
                    break;

                default:
                    break;
            }
            client.send(intTopic, "sent");
        }
    } catch (const exception& exc) {
        fmt::println(stderr, "\nERROR: Unable to connect to Setup bridge: {}", exc.what());
        ctx.markFailed();
    }
}

/// @brief LoRa-Service provider
void LoRaServiceTask(Thread::Context& ctx, Queue<RxMsg>& rxQueue, Queue<TxMsg>& txQueue) {
    fmt::println("LoRa: Starting task:");

    auto loraCallback = [&](std::vector<uint8_t> data) {
        auto hex = bytesToHex(data);
        auto str = bytesToPrintable(data);
        fmt::print("LoRa: Received Byts: {}\n    0x{}\n    {}\n", data.size(), hex, str);

        auto e = std::make_unique<RxMsg>(0);
        rxQueue.enqueue(std::move(e));
        return true;
    };
    auto spi = SpiPort({.mode = SpiPort::mode0,
                        .cs = SpiPort::Cs0,
                        .frequ = 1'000'000});

    LoRa loRa({
                  .frequency = 868'000'000,
                  .port = spi,
                  .DioPin = GPIO_DIO0_PIN,
                  .resetPin = GPIO_RST_PIN,
              },
              loraCallback);

    fmt::println("LoRa: Running ...");
    while (!ctx.isCancelled()) {
        auto tx = txQueue.dequeue();
        loRa.send({0});
        // printf ("Message: %s %lu\n", msg->message.c_str(), msg->cnt);
    }

    try {
    } catch (const exception& exc) {
        fmt::println(stderr, "\nERROR: Unable to connect to Setup bridge: {}", exc.what());
        ctx.markFailed();
    }
}

int main(int argc, char* argv[]) {
    fmt::println("Sword Firmware");

    auto itrHandler = [&]() {
        TriggeredInput itr(26, "PItr");
        while (true) {
            std::cout << "Await next interrupt\n";
            if (itr.await()) {
                std::cout << "Interrupt Event\n";
            }
        }
    };
    std::thread interruptThread(itrHandler);

    InputPort spPort(SwordPulledPin, "PInSPull");
    InputPort sbPort(SwordEntryPin, "PinSEntry");
    InputPort sePort(SwordEndPin, "PInSEnd");
    OutputPort mOut1(LinMotPin1, "M1:1");
    OutputPort mOut2(LinMotPin2, "M1:2");
    InputTransAware SwordEntry(sbPort);
    InputTransAware SwordEnd(sePort);
    InputTransAware SwordPulled(spPort);
    LinMot SwordLatch(mOut1, mOut2);

    IoStat = make_unique<DeviceIoState>(SwordEntry, SwordEnd, SwordPulled, SwordLatch);

    auto tick = [&]() {
        SwordEntry.sample();
        SwordEnd.sample();
        SwordPulled.sample();
    };

    static Queue<RxMsg> rxQueue;
    static Queue<TxMsg> txQueue;
    static auto conTask = (ConMode == ModeMqtt) ? Thread::createExplicit("MqttTask", MQttServiceTask, std::ref(rxQueue), std::ref(txQueue))
                                                : Thread::createExplicit("LoRaTask", LoRaServiceTask, std::ref(rxQueue), std::ref(txQueue));

    SwordLatch.moveOut();

    const static map<IncommingCommands_e, OpMode> opCodes = {
        {SetMaintanance, OpMode::Maintanance},
        {SetPromoMode, OpMode::PromoMode},
        {SetGamingMode, OpMode::GamingMode},
    };

    fmt::println("Running App");
    while (true) {
        auto msg = rxQueue.dequeue();
        fmt::println("MAIN: Got message {}", msg->code);

        auto code = static_cast<IncommingCommands_e>(msg->code);
        if (opCodes.contains(code)) {
            Mode == opCodes.at(code);
        } else {
            Mode = OpMode::Undefined;
        }

        switch (Mode) {
            case OpMode::Maintanance: {
                fmt::println("Got Maintanance");
                auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                txQueue.enqueue(std::move(e));
                // txQueue.emplace<ResponseTxMsg>(msg->code, 0);

                while (true) {
                    auto serMsg = rxQueue.tryDequeueFor(1000ms);
                    if (serMsg) {
                        auto code = static_cast<ServiceCommands_e>(serMsg->code);
                        switch (code) {
                            case ServiceCommands_e::Open: {
                                IoStat->SwordLatch.moveIn();
                            } break;
                            case ServiceCommands_e::Close: {
                                IoStat->SwordLatch.moveOut();
                            } break;
                            case ServiceCommands_e::Play1: {
                                playWavFile(soundPromo);
                            } break;
                            case ServiceCommands_e::Play2: {
                                playWavFile(soundDeny);
                            } break;
                            case ServiceCommands_e::Play3: {
                                playWavFile(soundAllow);
                            } break;
                        }
                    }

                    auto e = std::make_unique<StatusTxMsg>(
                        (uint8_t)Mode,
                        IoStat->SwordEnd.get(),
                        IoStat->SwordEntry.get(),
                        IoStat->SwordPulled.get(),
                        IoStat->SwordLatch.isLocked());
                    txQueue.enqueue(std::move(e));
                }
                e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                txQueue.enqueue(std::move(e));
            } break;
            case OpMode::PromoMode: {
                fmt::println("Got SetPromoMode");
                auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                txQueue.enqueue(std::move(e));

                while (true) {
                    SwordPulled.sample();
                    auto rx = txQueue.tryDequeueFor(10ms);
                    if (SwordPulled.isPosEdge()) {
                        // say: Play our tours
                        Thread::sleep(5s);
                    }
                }
            } break;
            case OpMode::GamingMode: {
                fmt::println("Got SetGamingMode");
                auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                txQueue.enqueue(std::move(e));

                while (true) {
                    auto gameMsg = txQueue.dequeue();
                    auto code = static_cast<GameModeCommands_e>(msg->code);
                    switch (code) {
                        case GameModeCommands_e::GameNextTryLocked: {
                            auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                            txQueue.enqueue(std::move(e));

                            fmt::println("Got NextTryLock");
                            while (true) {
                                SwordPulled.sample();
                                auto rx = txQueue.tryDequeueFor(20ms);
                                bool pulled = SwordPulled.read();
                                if (SwordPulled.isPosEdge()) {
                                    // say: You are not the one
                                    Thread::sleep(5s);
                                }
                            }
                        } break;
                        case GameModeCommands_e::GameNextTryRelease: {
                            auto e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                            txQueue.enqueue(std::move(e));
                            SwordLatch.moveIn();

                            fmt::println("Got NextTryRelease");
                            while (true) {
                                SwordEnd.sample();
                                auto rx = txQueue.tryDequeueFor(20ms);
                                if (SwordEnd.isNegEdge()) {
                                    break;
                                }
                            }
                            while (true) {
                                SwordEntry.sample();
                                auto rx = txQueue.tryDequeueFor(20ms);
                                if (SwordEntry.isNegEdge()) {
                                    break;
                                }
                            }

                            // say: The prophecy is fulfilled
                            e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                            txQueue.enqueue(std::move(e));

                            while (true) {
                                SwordEnd.sample();
                                auto rx = txQueue.tryDequeueFor(20ms);
                                if (SwordEnd.isNegEdge()) {
                                    break;
                                }
                            }
                            while (true) {
                                SwordEntry.sample();
                                auto rx = txQueue.tryDequeueFor(20ms);
                                if (SwordEntry.isNegEdge()) {
                                    break;
                                }
                            }

                            e = std::make_unique<ResponseTxMsg>(msg->code, 0);
                            txQueue.enqueue(std::move(e));

                        } break;
                    }
                }

            } break;

            default:
                break;
        }
    }
    interruptThread.join();
    return EXIT_SUCCESS;
}
