#include <SFML/Graphics.hpp>
#include <SFML/Network.hpp>
#include <cjson/cJSON.h>

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <atomic>

// ============================================================
// KONFIGURACJA
// ============================================================

const std::string URL_HOST = "192.168.1.2";
const std::string URL_PATH = "/api";

// Jak często sprawdzamy serwer.
// 100 ms daje maksymalnie ~100 ms oczekiwania na wykrycie zmiany.
constexpr int POLL_MS = 100;

// Jak często traktujemy dane jako pełny cykl odświeżenia.
constexpr int DATA_REFRESH_MS = 1000;

// Timeout HTTP.
// Nie może być kilka sekund, bo wtedy GUI może długo czekać
// na kolejną próbę.
constexpr int HTTP_TIMEOUT_MS = 150;

// GUI
constexpr unsigned int FPS = 120;

// ============================================================
// DANE
// ============================================================

struct RadarData
{
    bool presence = false;
    int distance = 0;
    bool valid = false;

    // Numer wersji danych.
    // Zwiększany po każdym poprawnym odebraniu JSON.
    unsigned long long version = 0;
};

// ============================================================
// WSPÓŁDZIELONE DANE
// ============================================================

// Każde pole jest atomowe.
// Dzięki temu GUI nie musi blokować się mutexem.

std::atomic<bool> radarPresence{false};
std::atomic<int> radarDistance{0};
std::atomic<bool> radarValid{false};

// Zwiększane po otrzymaniu nowych danych.
std::atomic<unsigned long long> radarVersion{0};

// Sterowanie programem.
std::atomic<bool> running{true};

// ============================================================
// THREAD HTTP
// ============================================================

void fetchLoop()
{
    sf::Http http(URL_HOST);

    sf::Http::Request request(
        URL_PATH,
        sf::Http::Request::Get
    );

    request.setField(
        "User-Agent",
        "radar-monitor/2.0"
    );

    request.setField(
        "Accept",
        "application/json"
    );

    // --------------------------------------------------------
    // Następny request
    // --------------------------------------------------------

    auto nextPoll =
        std::chrono::steady_clock::now();

    while (running)
    {
        // ====================================================
        // Czekamy tylko do następnego requestu.
        // ====================================================

        auto now =
            std::chrono::steady_clock::now();

        if (now < nextPoll)
        {
            std::this_thread::sleep_until(nextPoll);
        }

        if (!running)
            break;

        // Następne odpytywanie za 100 ms.
        nextPoll +=
            std::chrono::milliseconds(POLL_MS);

        // ====================================================
        // REQUEST
        // ====================================================

        sf::Http::Response response =
            http.sendRequest(
                request,
                sf::milliseconds(HTTP_TIMEOUT_MS)
            );

        // ====================================================
        // JEŚLI SERWER ODPOWIEDZIAŁ
        // ====================================================

        if (response.getStatus() ==
            sf::Http::Response::Ok)
        {
            const std::string& body =
                response.getBody();

            cJSON* json =
                cJSON_Parse(body.c_str());

            if (json != nullptr)
            {
                cJSON* presence =
                    cJSON_GetObjectItemCaseSensitive(
                        json,
                        "presence"
                    );

                cJSON* distance =
                    cJSON_GetObjectItemCaseSensitive(
                        json,
                        "detectionDistance"
                    );

                bool validPresence =
                    cJSON_IsBool(presence);

                bool validDistance =
                    cJSON_IsNumber(distance);

                // --------------------------------------------
                // POPRAWNY JSON
                // --------------------------------------------

                if (validPresence &&
                    validDistance)
                {
                    bool newPresence =
                        cJSON_IsTrue(presence);

                    int newDistance =
                        distance->valueint;

                    // ----------------------------------------
                    // NAJPIERW zapisujemy dane
                    // ----------------------------------------

                    radarPresence.store(
                        newPresence,
                        std::memory_order_relaxed
                    );

                    radarDistance.store(
                        newDistance,
                        std::memory_order_relaxed
                    );

                    radarValid.store(
                        true,
                        std::memory_order_relaxed
                    );

                    // ----------------------------------------
                    // NA KOŃCU zwiększamy VERSION.
                    //
                    // GUI widząc nową wersję wie, że komplet
                    // danych jest już zapisany.
                    // ----------------------------------------

                    radarVersion.fetch_add(
                        1,
                        std::memory_order_release
                    );
                }

                cJSON_Delete(json);
            }
        }

        // ====================================================
        // JEŚLI REQUEST SIĘ NIE UDAŁ
        // ====================================================

        // Nie zerujemy od razu danych.
        //
        // Dzięki temu chwilowy błąd sieci nie powoduje
        // migania GUI.
        //
        // Dopiero kolejne poprawne dane je zastąpią.
    }
}

// ============================================================
// MAIN
// ============================================================

int main()
{
    // ========================================================
    // OKNO
    // ========================================================

    sf::RenderWindow window(
        sf::VideoMode(800, 480),
        "Radar Monitor",
        sf::Style::Titlebar |
        sf::Style::Close
    );

    // 120 FPS.
    //
    // Nowe dane mogą zostać pokazane maksymalnie
    // po około 8 ms od następnej klatki GUI.

    window.setFramerateLimit(FPS);

    // ========================================================
    // CZCIONKA
    // ========================================================

    sf::Font font;

    if (!font.loadFromFile(
            "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"))
    {
        if (!font.loadFromFile(
                "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"))
        {
            std::cerr
                << "Nie można załadować czcionki!\n";

            return 1;
        }
    }

    // ========================================================
    // TEKSTY
    // ========================================================

    sf::Text title(
        "RADAR MONITOR",
        font,
        48
    );

    title.setFillColor(
        sf::Color::White
    );

    title.setPosition(
        200,
        20
    );

    // --------------------------------------------------------

    sf::Text presenceLabel(
        "PRESENCE",
        font,
        36
    );

    presenceLabel.setFillColor(
        sf::Color(180, 180, 180)
    );

    presenceLabel.setPosition(
        80,
        140
    );

    // --------------------------------------------------------

    sf::Text distanceLabel(
        "DISTANCE",
        font,
        36
    );

    distanceLabel.setFillColor(
        sf::Color(180, 180, 180)
    );

    distanceLabel.setPosition(
        80,
        280
    );

    // --------------------------------------------------------

    sf::Text distanceValue(
        "---",
        font,
        72
    );

    distanceValue.setFillColor(
        sf::Color::White
    );

    distanceValue.setPosition(
        400,
        260
    );

    // --------------------------------------------------------

    sf::Text statusText(
        "Laczenie...",
        font,
        24
    );

    statusText.setFillColor(
        sf::Color(150, 150, 150)
    );

    statusText.setPosition(
        20,
        430
    );

    // ========================================================
    // LED
    // ========================================================

    sf::CircleShape led(
        50.f
    );

    led.setPosition(
        420,
        120
    );

    led.setOutlineThickness(
        4.f
    );

    led.setOutlineColor(
        sf::Color(40, 40, 40)
    );

    led.setFillColor(
        sf::Color(80, 80, 80)
    );

    // ========================================================
    // START HTTP
    // ========================================================

    std::thread fetcher(
        fetchLoop
    );

    // ========================================================
    // LOKALNY CACHE GUI
    // ========================================================

    unsigned long long lastVersion = 0;

    RadarData displayedData;

    // ========================================================
    // PĘTLA GUI
    // ========================================================

    while (window.isOpen())
    {
        // ====================================================
        // EVENTY
        // ====================================================

        sf::Event event;

        while (window.pollEvent(event))
        {
            if (event.type ==
                sf::Event::Closed)
            {
                window.close();
            }
        }

        // ====================================================
        // SPRAWDZAMY, CZY PRZYSZŁY NOWE DANE
        // ====================================================

        unsigned long long version =
            radarVersion.load(
                std::memory_order_acquire
            );

        if (version != lastVersion)
        {
            lastVersion = version;

            // -----------------------------------------------
            // Odczytujemy komplet danych
            // -----------------------------------------------

            displayedData.presence =
                radarPresence.load(
                    std::memory_order_relaxed
                );

            displayedData.distance =
                radarDistance.load(
                    std::memory_order_relaxed
                );

            displayedData.valid =
                radarValid.load(
                    std::memory_order_relaxed
                );

            // =================================================
            // LED
            // =================================================

            if (displayedData.valid)
            {
                if (displayedData.presence)
                {
                    led.setFillColor(
                        sf::Color(0, 220, 0)
                    );
                }
                else
                {
                    led.setFillColor(
                        sf::Color(220, 0, 0)
                    );
                }

                // =================================================
                // DISTANCE
                // =================================================

                distanceValue.setString(
                    std::to_string(
                        displayedData.distance
                    ) + " cm"
                );

                // =================================================
                // STATUS
                // =================================================

                statusText.setString(
                    "OK"
                );

                statusText.setFillColor(
                    sf::Color(0, 200, 0)
                );
            }
            else
            {
                led.setFillColor(
                    sf::Color(80, 80, 80)
                );

                distanceValue.setString(
                    "---"
                );

                statusText.setString(
                    "Brak danych"
                );

                statusText.setFillColor(
                    sf::Color(200, 80, 80)
                );
            }
        }

        // ====================================================
        // RENDER
        // ====================================================

        window.clear(
            sf::Color(25, 25, 30)
        );

        window.draw(title);
        window.draw(presenceLabel);
        window.draw(led);
        window.draw(distanceLabel);
        window.draw(distanceValue);
        window.draw(statusText);

        window.display();
    }

    // ========================================================
    // ZATRZYMANIE
    // ========================================================

    running.store(
        false
    );

    if (fetcher.joinable())
    {
        fetcher.join();
    }

    return 0;
}
