#include <QDebug>
#include <QMutexLocker>
#include <QSettings>

#include "ConfigPanel.h"
#include "OriginTimestamp.h"
#include "QuantityFormat.h"
#include "TDeviceManager.h"

#define WATCHDOG_DEFAULT_INTERVAL (3 * TIMESTAMP_ONE_SECOND)
#define WATCHDOG_GRACE_INTERVAL (3 * TIMESTAMP_ONE_SECOND)

static QString getDefaultAlias(TChannel *channel) {

    QString defaultAlias = channel->id + QString(" -- ") + channel->description;
    return defaultAlias;
}

TDeviceManager::TDeviceManager()
    : loadFactor(-1), // unsigned, so max value
      previousLoadFactor(1), previousLoadPerSecond(0), watchdogInterval(WATCHDOG_DEFAULT_INTERVAL),
      watchdogTimestamp(OriginTimestamp::instance().value()) {

    queue_init(&events);
    queue_grow(&events, 256);

    connect(this, &TDeviceManager::__event__, this, &TDeviceManager::onEvent);
}

TDeviceManager::~TDeviceManager() {
    // Singleton lives forever
}

QVector<TDevice *> TDeviceManager::getDevices() {

    QMutexLocker locker(&mutex);

    QVector<TDevice *> results(devicesBySerialNumber.size());
    int i = 0;

    QMapIterator<QString, TDevice *> it(devicesBySerialNumber);
    while (it.hasNext()) {
        it.next();
        results[i++] = it.value();
    }

    return results;
}

TDevice *TDeviceManager::getDevice(const QString &serialNumber) {

    QMutexLocker locker(&mutex);

    return devicesBySerialNumber.value(serialNumber);
}

TDeviceSignal *TDeviceManager::getDeviceSignal(const QString &serialNumber) {

    QMutexLocker locker(&mutex);

    return signalsBySerialNumber.value(serialNumber);
}

TChannel *TDeviceManager::getChannel(const QString &id) {

    QMutexLocker locker(&mutex);

    return channelsByID.value(id);
}

QString TDeviceManager::getAlias(const QString &id) {

    QMutexLocker locker(&mutex);

    return aliasesByID.contains(id) ? aliasesByID.value(id) : QString("N/A");
}

void TDeviceManager::setChannelAlias(QString id, QString alias) {

    {
        QMutexLocker locker(&mutex);

        TChannel *channel = channelsByID.value(id);
        if (!channel) {
            return;
        }

        alias = alias.trimmed();

        if (alias.isEmpty()) {
            alias = getDefaultAlias(channel);
        }

        aliasesByID.insert(id, alias);
    }

    QSettings settings;
    settings.setValue("sourcesAliases/" + id, alias);

    emit channelAliasUpdated(id, alias);
}

void TDeviceManager::addWatchdogListener(WatchdogListener *listener) { watchdogListeners.append(listener); }

void TDeviceManager::removeWatchdogListener(WatchdogListener *listener) { watchdogListeners.removeOne(listener); }

void TDeviceManager::addDevice(const TDevice *device) { addEvent(TDevice::ADD, device); }

void TDeviceManager::removeDevice(const TDevice *device) { addEvent(TDevice::REMOVE, device); }

void TDeviceManager::connectDevice(const TDevice *device) { addEvent(TDevice::CONNECT, device); }

void TDeviceManager::disconnectDevice(const TDevice *device) { addEvent(TDevice::DISCONNECT, device); }

void TDeviceManager::updateDevice(const TDevice *device) { addEvent(TDevice::UPDATE, device); }

void TDeviceManager::addEvent(TDevice::Event code, const TDevice *device) {

    TDevice *copy = new TDevice(*device);
    copy->event = code;

    mutex.lock();
    queue_add(&events, copy);
    mutex.unlock();

    emit __event__();
}

bool TDeviceManager::onDeviceAdded(TDevice *eventDevice) {

    QVector<int> userCalibrationDisabled;
    QString serialNumber(eventDevice->serialNumber);

    {
        QMutexLocker locker(&mutex);

        TDevice *device = devicesBySerialNumber.value(serialNumber);

        if (device) {
            device->update(eventDevice);
            return true;
        }

        device = new TDevice(*eventDevice);
        devicesBySerialNumber.insert(serialNumber, device);
        signalsBySerialNumber.insert(serialNumber, new TDeviceSignal());

        QSettings settings;
        QVector<TChannel *> &channels = device->channels;

        for (int c = 0; c < channels.size(); c++) {

            TChannel *channel = channels[c];

            const QString &id = channel->id;
            QString alias = settings.value("sourcesAliases/" + id, getDefaultAlias(channel)).toString();

            channelsByID.insert(id, channel);
            aliasesByID.insert(id, alias);

            bool noCalibration = settings.value(QString("disableCalibration/") + id, false).toBool();
            bool isLocal = device->isLocal();
            bool isSpecial = device->isSpecial();

            // qDebug() << "noCalibration: " << noCalibration << " id  " << QString("disableCalibration/") + id;

            // WORKAROUND: Force user calibration to always be enabled for Tenkinet devices
            //
            // Background: Toggling user calibration on/off for Tenkinet devices creates complex
            // thread safety issues. To avoid these complications, user calibration is now
            // permanently enabled for all Tenkinet devices.
            //
            // Migration issue: Users who previously disabled user calibration on Tenkinet devices
            // will have this setting persisted as enabled in QSettings. We must override this
            // stored value and reset it to enabled to ensure consistent behavior.
            //
            // Side effect: A USB device with user calibration disabled will have its setting
            // automatically enabled if it's temporarily connected to a Tenkinet device. The user
            // can manually disable it again once the device is plugged back into USB.
            if (!isLocal & !isSpecial && noCalibration) {
                settings.setValue(QString("disableCalibration/") + id, false);
                noCalibration = false;

                // qDebug() << "noCalibration: " << noCalibration << " id  " << QString("disableCalibration/") + id
                //          << " local " << isLocal << " set to false"
                //          << settings.value(QString("disableCalibration/") + id);
            }

            if (noCalibration) {
                userCalibrationDisabled.append(c);
            }
        }
    }

    // Invoke provider methods outside the synchronized block, because providers often call our methods
    // and we want to avoid deadlocks.

    TProvider *provider = eventDevice->provider;

    for (int i = 0; i < userCalibrationDisabled.size(); i++) {
        provider->setUserCalibration(serialNumber, userCalibrationDisabled[i], false);
    }

    return true;
}

bool TDeviceManager::onDeviceUpdated(TDevice *eventDevice) {

    QMutexLocker locker(&mutex);

    const QString &serialNumber = eventDevice->serialNumber;

    TDevice *device = devicesBySerialNumber.value(serialNumber);
    if (!device) {
        return false;
    }

    device->update(eventDevice);

    return true;
}

void TDeviceManager::watchdog(int64_t timestamp) {

    int64_t dt = timestamp - watchdogTimestamp;
    if (dt < watchdogInterval) {
        return;
    }

    watchdogTimestamp = timestamp;

    int level = 0;
    float lps = 0.0;
    float accel = 0.0;

    // If the event queue could not be cleared during the interval, the application is thrashing.
    // In that case, lighten the load, and set a grace interval for processing backlogged events.

    if (loadFactor > 0) {

        size_t dl = loadFactor - previousLoadFactor;
        lps = TIMESTAMP_ONE_SECOND * ((float)dl) / ((float)dt);                   // load per second
        accel = TIMESTAMP_ONE_SECOND * (lps - previousLoadPerSecond) / (float)dt; // load acceleration

        float lps_next = lps + accel;

        if (lps_next >= 1024.0) {
            level = 4;
        } else if (lps_next >= 512.0) {
            level = 3;
        } else if (lps_next >= 128.0) {
            level = 2;
        } else if (lps_next >= 0.0) {
            level = 1;
        } else {
            float pps = lps_next / loadFactor; // proportion of load per second
            if (pps > -0.02) {
                level = 1;
            }
        }
    }

    if (level == 0) {
        watchdogInterval = WATCHDOG_DEFAULT_INTERVAL;
    } else {
        watchdogInterval = WATCHDOG_GRACE_INTERVAL;
        for (int i = 0; i < watchdogListeners.size(); i++) {
            watchdogListeners[i]->onThrash(level);
        }
    }

    previousLoadFactor = loadFactor;
    loadFactor = -1; // unsigned, so max value
    previousLoadPerSecond = lps;
}

void TDeviceManager::onEvent() {

    TDevice *device;
    size_t qn;

    mutex.lock();
    device = (TDevice *)queue_remove(&events);
    qn = queue_size(&events);
    mutex.unlock();

    if (qn < loadFactor) {
        loadFactor = qn;
    }

    int event = device->event;

    if (device->isTicker()) {
        if (event == TDevice::UPDATE) {
            watchdog(device->timestamp);
        }
    } else if (!device->isMath()) {
        device->convertUnits();
    }

    switch (event) {

    case TDevice::ADD:
        if (onDeviceAdded(device)) {
            emit deviceAdded(device);
        }
        break;

    case TDevice::REMOVE:
        if (onDeviceUpdated(device)) {
            emit deviceRemoved(device);
        }
        break;

    case TDevice::CONNECT:
        if (onDeviceUpdated(device)) {
            TDeviceSignal *signal = signalsBySerialNumber.value(device->serialNumber);
            emit signal->deviceConnected(device);
        }
        break;

    case TDevice::DISCONNECT:
        if (onDeviceUpdated(device)) {
            TDeviceSignal *signal = signalsBySerialNumber.value(device->serialNumber);
            emit signal->deviceDisconnected(device);
        }
        break;

    case TDevice::UPDATE:
        if (onDeviceUpdated(device)) {
            TDeviceSignal *signal = signalsBySerialNumber.value(device->serialNumber);
            emit signal->deviceUpdated(device);
        }
        break;
    }

    delete device;
}
