#include <QMutexLocker>

#include "TDeviceManager.h"
#include "TenkinetProvider.h"
#include "TenkinetThread.h"
#include "chip.h"

// TODO dynamic description
static const QString DESCRIPTION = QString("Tenkinet Devices");

const QString TenkinetProvider::DEFAULT_SERVER_PORT(TENKINET_SERVER_PORT_STR);

TenkinetProvider::TenkinetProvider(const QString &host, const QString &port)
    : TProvider(DESCRIPTION), host(host), port(port), hostAndPort(host + ":" + port),
      settingsKey("tenkinet/" + hostAndPort) {
    QSettings settings;
    QString enabledKey = settingsKey + "/enabled";
    favorite = settings.contains(enabledKey);
    on = settings.value(enabledKey, true).toBool();
}

TenkinetProvider::~TenkinetProvider() {

    QMutexLocker locker(&mutex);

    QMapIterator<QString, Source *> itSources(sourcesBySerialNumber);

    while (itSources.hasNext()) {
        itSources.next();
        Source *source = itSources.value();
        device_delete(source->device);
        source_delete(source);
    }

    QMapIterator<QString, TDevice *> itDevices(devicesBySerialNumber);

    while (itDevices.hasNext()) {
        itDevices.next();
        TDevice *device = itDevices.value();
        if (device->isAlive()) {
            TDeviceManager::instance().disconnectDevice(device);
        }
        TDeviceManager::instance().removeDevice(device);
        delete device;
    }
}

int TenkinetProvider::getDeviceCount() {

    QMutexLocker locker(&mutex);

    return devicesBySerialNumber.size();
}

QVector<TDevice *> TenkinetProvider::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 *TenkinetProvider::findDevice(const QString &serialNumber) {

    QMutexLocker locker(&mutex);

    return devicesBySerialNumber.value(serialNumber);
}

const QString &TenkinetProvider::getServerHost() { return host; }

const QString &TenkinetProvider::getServerPort() { return port; }

const QString &TenkinetProvider::getServerHostAndPort() { return hostAndPort; }

QString TenkinetProvider::getServerAddress() {

    QMutexLocker locker(&mutex);
    return address.isEmpty() ? host : address;
}

QString TenkinetProvider::getServerAddressAndPort() {

    QMutexLocker locker(&mutex);
    return (address.isEmpty() ? host : address) + ":" + port;
}

QString TenkinetProvider::getServerSerialNumber() {

    QMutexLocker locker(&mutex);
    return serialNumber;
}

QString TenkinetProvider::getServerName() {

    QMutexLocker locker(&mutex);
    return name;
}

Version TenkinetProvider::getServerVersion() {

    QMutexLocker locker(&mutex);
    return version;
}

uint64_t TenkinetProvider::getPing() {

    QMutexLocker locker(&mutex);
    return ping;
}

TenkinetProvider::State TenkinetProvider::getState() {

    QMutexLocker locker(&mutex);
    return state;
}

bool TenkinetProvider::isFavorite() {

    QMutexLocker locker(&mutex);
    return favorite;
}

bool TenkinetProvider::isEnabled() {

    QMutexLocker locker(&mutex);
    return on;
}

int TenkinetProvider::start() {

    bool on;

    {
        QMutexLocker locker(&mutex);
        if (state != DISCONNECTED) {
            return 1;
        }
        state = CONNECTING;
        on = this->on;
    }

    emit connecting(this);

    TenkinetThread::instance().add(this);
    TenkinetThread::instance().setSubscribed(this, on);
    TenkinetThread::instance().setPollInterval(this, pollInterval);
    TenkinetThread::instance().connectClient(this);

    return 0;
}

void TenkinetProvider::shutdown() { TenkinetThread::instance().disconnectClient(this); }

void TenkinetProvider::setFavorite(bool favorite) {

    QMutexLocker locker(&mutex);

    if (this->favorite == favorite) {
        return;
    }

    this->favorite = favorite;

    QSettings settings;

    if (favorite) {
        settings.setValue(settingsKey + "/host", getServerHost());
        settings.setValue(settingsKey + "/port", getServerPort());
        settings.setValue(settingsKey + "/enabled", on);
    } else {
        settings.remove(settingsKey);
    }
}

void TenkinetProvider::setEnabled(bool on) {

    {
        QMutexLocker locker(&mutex);

        if (this->on == on) {
            return;
        }

        this->on = on;

        if (favorite) {
            QSettings settings;
            settings.setValue(settingsKey + "/enabled", on);
        }
    }

    if (on) {
        emit enabled(this);
    } else {
        emit disabled(this);
    }

    TenkinetThread::instance().setSubscribed(this, on);
}

void TenkinetProvider::setPollInterval(int ms) {

    pollInterval = ms;
    TenkinetThread::instance().setPollInterval(this, ms);
}

void TenkinetProvider::setVirtualOptions(unsigned int flags, double slp) {

    QMutexLocker locker(&mutex);

    virtualOptions.flags = flags;
    virtualOptions.standard_sea_level_pressure = slp;
}

void TenkinetProvider::setUserCalibration(const QString &serialNumber, int channelIndex, bool on) {

    QMutexLocker locker(&mutex);

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

    source_set_user_calibration(source, channelIndex, on);
}

void TenkinetProvider::setServerAddress(const QString &address) {

    QMutexLocker locker(&mutex);
    this->address = address;
}

void TenkinetProvider::setServerSerialNumber(const QString &serialNumber) {

    QMutexLocker locker(&mutex);
    this->serialNumber = serialNumber;
}

void TenkinetProvider::setServerName(const QString &name) {

    QMutexLocker locker(&mutex);
    this->name = name;
}

void TenkinetProvider::setServerVersion(const Version &version) {

    QMutexLocker locker(&mutex);
    this->version = version;
}

void TenkinetProvider::refresh() {

    {
        QMutexLocker locker(&mutex);
        if (state != State::CONNECTED) {
            return;
        }
    }

    TenkinetThread::instance().refresh(this);

    emit refreshing(this);
}

void TenkinetProvider::onConnect() {

    mutex.lock();
    state = State::CONNECTED;
    mutex.unlock();

    emit connected(this);
}

void TenkinetProvider::onDisconnect() {

    mutex.lock();
    state = State::DISCONNECTED;
    mutex.unlock();

    emit disconnected(this);
}

void TenkinetProvider::onError() {

    mutex.lock();
    state = State::BROKEN;
    mutex.unlock();

    emit error(this);
}

void TenkinetProvider::onList(List *tenkinetDevices) {

    {
        QMutexLocker locker(&mutex);

        LIST_FOR(tenkinetDevices) {

            Device *tenkinetDevice = LIST_CUR(Device);
            QString serialNumber = tenkinetDevice->serial_number;

            Source *source = sourcesBySerialNumber.value(serialNumber);
            if (!source) {
                source = source_new(tenkinetDevice, &virtualOptions);
                sourcesBySerialNumber.insert(serialNumber, source);
            }

            source_refresh(source);

            TDevice *device = devicesBySerialNumber.value(serialNumber);

            if (device) {
                device->update(source);
            } else {
                device = new TDevice(this, source);

                if (host == "127.0.0.1") {
                    device->setLocalTenkinet();
                }

                devicesBySerialNumber.insert(serialNumber, device);
                if (on) {
                    TDeviceManager::instance().addDevice(device);
                }
            }
        }
    }

    emit refreshed(this);
}

void TenkinetProvider::onData(int64_t timestamp, List *tenkinetDevices) {

    QMutexLocker locker(&mutex);

    LIST_FOR(tenkinetDevices) {
        Device *tenkinetDevice = LIST_CUR(Device);
        TDevice *device = updateDevice(timestamp, tenkinetDevice);
        if (device && on && device->isAlive()) {
            TDeviceManager::instance().updateDevice(device);
        }
    }
}

void TenkinetProvider::onDeviceConnect(int64_t timestamp, Device *tenkinetDevice) {

    QMutexLocker locker(&mutex);

    TDevice *device = updateDevice(timestamp, tenkinetDevice);
    if (device && on) {
        TDeviceManager::instance().connectDevice(device);
    }
}

void TenkinetProvider::onDeviceDisconnect(int64_t timestamp, Device *tenkinetDevice) {

    QMutexLocker locker(&mutex);

    TDevice *device = updateDevice(timestamp, tenkinetDevice);
    if (device && on) {
        TDeviceManager::instance().disconnectDevice(device);
    }
}

void TenkinetProvider::onPing(uint64_t ping) {

    QMutexLocker locker(&mutex);
    this->ping = ping;
}

TDevice *TenkinetProvider::updateDevice(int64_t timestamp, Device *tenkinetDevice) {

    // mutex must already be locked before invoking this method

    QString serialNumber = tenkinetDevice->serial_number;

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

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

    source->timestamp = timestamp;
    source_refresh(source);
    device->update(source);

    return device;
}
