#include <QDebug>
#include <QMutexLocker>

#include <dracal/common/log.hpp>

#include "DaemonThreadManager.h"
#include "OriginTimestamp.h"
#include "TUSBProvider.h"
#include "USBThread.h"
#include "timestamp.h"
#include "usbtenki_provider.h"

USBThread::USBThread() { DaemonThreadManager::instance().addThread(this); }

USBThread::~USBThread() {}

void USBThread::run() {
    TUSBProvider &provider = TUSBProvider::instance();
    const auto origin = OriginTimestamp::instance().value();
    const auto steady_origin = OriginTimestamp::instance().steady_value();

    int64_t now = origin;

    {
        QMutexLocker locker(&_mutex);

        if (usbtenki_provider_init()) {
            return;
        }

        usbtenki_provider_poll();

        const int n = usbtenki_provider_get_source_count();
        now = timestamp_now();

        for (int8_t i = 0; i < n; ++i) {
            Source *source = usbtenki_provider_get_source(i);
            source->timestamp = now;
            _sourcesBySerialNumber.insert(source->device->serial_number, source);
            provider.addSource(source);
        }
    }

    poll(now);

    dracal_info("usbtenki providers has been initialized");

    chrono::milliseconds current_interval{};

    while (_state.load() != SHUTDOWN) {
        const auto interval = _interval.load();

        if (current_interval != interval) {
            dracal_info("usb thread interval changed from {} to: {}", current_interval, interval);
            current_interval = interval;
        }

        const auto now_system = timestamp_now();
        const int64_t elapsed_us = now_system - origin;
        const int64_t interval_us = chrono::duration_cast<chrono::microseconds>(current_interval).count();
        const int64_t intervals_passed = elapsed_us / interval_us;
        const auto target = origin + ((intervals_passed + 1) * interval_us);

        const auto number_steady_intervals = (chrono::steady_clock::now() - steady_origin) / current_interval;
        const auto target_steady = steady_origin + ((number_steady_intervals + 1) * current_interval);

        bool shutdown_requested = false;

        while (chrono::steady_clock::now() < target_steady) {
            std::this_thread::sleep_for(chrono::milliseconds{1});

            if (_state.load() == SHUTDOWN) {
                shutdown_requested = true;
                break;
            }
        }

        if (shutdown_requested) {
            break;
        }

        poll(target);
    }

    dracal_info("usb thread main loop finished");

    {
        QMutexLocker locker(&_mutex);
        usbtenki_provider_shutdown();
    }

    dracal_info("usbtenki has been shutdown");
}

void USBThread::poll(const int64_t timestamp) {
    TUSBProvider &provider = TUSBProvider::instance();

    {
        QMutexLocker locker(&_mutex);
        usbtenki_provider_poll();
    }

    provider.tick(timestamp);

    {
        QMutexLocker locker(&_mutex);
        const int n = usbtenki_provider_get_source_count();

        for (int i = 0; i < n; ++i) {
            Source *source = usbtenki_provider_get_source(i);
            source->timestamp = timestamp;
            provider.updateSource(source);
        }
    }
}

void USBThread::shutdown() {
    dracal_info("usb thread shutdown requested");
    _state.store(SHUTDOWN);
}

void USBThread::setPollInterval(const unsigned int ms) {
    const auto interval = ms == 0 ? chrono::milliseconds{1000} : chrono::milliseconds{ms};
    _interval.store(interval);
    dracal_info("polling interval set {}", interval);
}

void USBThread::setVirtualOptions(const unsigned int flags, const double slp) {
    QMutexLocker locker(&_mutex);

    usbtenki_virtual_options.flags = flags;
    usbtenki_virtual_options.standard_sea_level_pressure = slp;
}

void USBThread::setUserCalibration(const QString &serialNumber, const int channelIndex, const bool on) {
    QMutexLocker locker(&_mutex);

    Source *source = _sourcesBySerialNumber.value(serialNumber);
    if (!source) {
        return;
    }

    source_set_user_calibration(source, channelIndex, on);
}
