#include <QDebug>
#include <QGroupBox>
#include <QSettings>

#include "Config.h"
#include "TDeviceManager.h"
#include "TenkinetDiscoveryThread.h"
#include "TenkinetPanel.h"
#include "TenkinetThread.h"

#include "protocol.h"

#define RECONNECTION_TICK_INTERVAL 1000
#define RECONNECTION_TICK_COUNT 10

static const QString DEFAULT_PORT(TENKINET_SERVER_PORT_STR);

TenkinetPanel::TenkinetPanel() {

    QSettings settings;
    QLabel *label;

    QGridLayout *mainLayout = new QGridLayout();
    setLayout(mainLayout);

    QWidget *settingsWidget = new QWidget();
    QVBoxLayout *settingsLayout = new QVBoxLayout();
    settingsWidget->setLayout(settingsLayout);
    settingsWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
    mainLayout->addWidget(settingsWidget, 0, 0);

    QGroupBox *scanBox = new QGroupBox("Scan for SensGate Devices");
    QVBoxLayout *scanBoxLayout = new QVBoxLayout();
    scanBox->setLayout(scanBoxLayout);
    settingsLayout->addWidget(scanBox);

    label = new QLabel("Click the button below to scan your local<br>"
                       "networks for SensGate devices.");
    scanBoxLayout->addWidget(label);

    discoverIcon = new QIcon(QPixmap(":network.png"));
    discoverButton = new QPushButton(*discoverIcon, "  Scan");
    discoverButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
    connect(discoverButton, &QPushButton::clicked, this, &TenkinetPanel::onClickDiscover);
    scanBoxLayout->addWidget(discoverButton);

    label = new QLabel("This may not work on all networks, especially<br>"
                       "if firewalls or other restrictions are present.");
    scanBoxLayout->addWidget(label);

    discoverErrorIcon = new QIcon(":error.png");

    QGroupBox *addBox = new QGroupBox("Add a SensGate Device");
    QVBoxLayout *addBoxLayout = new QVBoxLayout();
    addBox->setLayout(addBoxLayout);
    settingsLayout->addWidget(addBox);

    label = new QLabel("If you know the host name or IP address of<br>"
                       "a SensGate device, you can add it manually below.");
    addBoxLayout->addWidget(label);

    hostEdit = new QLineEdit();
    hostEdit->setPlaceholderText("Host Name or IP Address");
    addBoxLayout->addWidget(hostEdit);

    addButton = new QPushButton("Add");
    addButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
    connect(addButton, &QPushButton::clicked, this, &TenkinetPanel::onClickAdd);
    addBoxLayout->addWidget(addButton);

    QGroupBox *convertLogBox = new QGroupBox("Convert a Binary Log");
    QVBoxLayout *convertLogLayout = new QVBoxLayout();
    convertLogBox->setLayout(convertLogLayout);
    settingsLayout->addWidget(convertLogBox);

    label = new QLabel("Data logs downloaded in binary format can be<br>"
                       "converted to text here.");
    convertLogLayout->addWidget(label);

    QPushButton *convertLogButton = new QPushButton("Convert Log");
    convertLogButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
    connect(convertLogButton, &QPushButton::clicked, this, &TenkinetPanel::onClickConvertLog);
    convertLogLayout->addWidget(convertLogButton);

    settingsLayout->addStretch();

    QWidget *devicesWidget = new QWidget();
    QVBoxLayout *devicesWidgetLayout = new QVBoxLayout();
    devicesWidget->setLayout(devicesWidgetLayout);
    mainLayout->addWidget(devicesWidget, 0, 1);

    QGroupBox *devicesBox = new QGroupBox("SensGate Devices");
    QVBoxLayout *devicesBoxLayout = new QVBoxLayout();
    devicesBox->setLayout(devicesBoxLayout);
    devicesWidgetLayout->addWidget(devicesBox);

    QWidget *devicesListWidget = new QWidget();
    devicesListWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
    devicesLayout = new QVBoxLayout();
    devicesListWidget->setLayout(devicesLayout);

    devicesBoxLayout->addWidget(devicesListWidget);
    devicesBoxLayout->addStretch();

    lockedLabel = new QLabel("<img src=':/attention.png'> "
                             "<b>Devices cannot be enabled or disabled while logging</b>");
    devicesLayout->addWidget(lockedLabel);
    lockedLabel->setVisible(false);

    logConversionDialog = new LogConversionDialog(this);

    QWidget *spacer = new QWidget();
    spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
    mainLayout->addWidget(spacer, 0, 2);

    discoverMovie = new QMovie();
    discoverMovie->setFileName(":loading_large.gif");
    connect(discoverMovie, &QMovie::frameChanged, this, &TenkinetPanel::refreshDiscoveryMovie);

    timer.setInterval(RECONNECTION_TICK_INTERVAL);
    connect(&timer, &QTimer::timeout, this, &TenkinetPanel::onTimerTick);

    TenkinetDiscoveryThread &tdt = TenkinetDiscoveryThread::instance();
    connect(&tdt, &TenkinetDiscoveryThread::discoveryInitialized, this, &TenkinetPanel::onDiscoveryInit);
    connect(&tdt, &TenkinetDiscoveryThread::discoveryError, this, &TenkinetPanel::onDiscoveryError);
    connect(&tdt, &TenkinetDiscoveryThread::discoveryStarted, this, &TenkinetPanel::onDiscoveryStart);
    connect(&tdt, &TenkinetDiscoveryThread::discoveryEnded, this, &TenkinetPanel::onDiscoveryEnd);
    connect(&tdt, &TenkinetDiscoveryThread::discovered, this,
            qOverload<TenkinetProvider *>(&TenkinetPanel::addProvider));

    localProvider = new TenkinetProvider("127.0.0.1", loadTenkiNetPort());
    addProvider(localProvider);

    connect(localProvider, &TenkinetProvider::refreshed, this, &TenkinetPanel::localProviderRefreshed);

    // Add providers from config

    settings.beginGroup("tenkinet");
    QStringList serverKeys = settings.childGroups();

    for (int i = 0; i < serverKeys.size(); i++) {
        const QString &serverKey = serverKeys.at(i);
        settings.beginGroup(serverKey);
        addProvider(settings.value("host").toString(), settings.value("port").toString());
        settings.endGroup();
    }
}

TenkinetPanel::~TenkinetPanel() {}

void TenkinetPanel::refreshLocalProvider() {
    if (localProvider != nullptr) {
        localProvider->refresh();
    }
}

void TenkinetPanel::start() {

    if (TenkinetThread::instance().isRunning()) {
        return;
    }

    TenkinetThread::instance().start();
    TenkinetDiscoveryThread::instance().start();
}

void TenkinetPanel::shutdown() {

    TenkinetThread::instance().shutdown();
    TenkinetDiscoveryThread::instance().shutdown();
}

void TenkinetPanel::addProvider(const QString &host, const QString &port) {

    TenkinetProvider *provider = new TenkinetProvider(host, port);
    addProvider(provider);
}

void TenkinetPanel::addProvider(TenkinetProvider *provider) {

    TenkinetProvider *oldProvider = providerMap.value(provider->getServerHostAndPort());

    if (!oldProvider) {
        oldProvider = providerMap.value(provider->getServerAddressAndPort());
    }

    if (oldProvider) {
        delete provider;
        TenkinetProviderBox *box = providerBoxMap.value(oldProvider);

        if (!box->isVisible()) {
            if (oldProvider->getServerHost() == "127.0.0.1") {
                box->setVisible(false);
            } else {
                box->setVisible(true);
            }

            devicesLayout->addWidget(box);
            oldProvider->setEnabled(true);
            oldProvider->setFavorite(true);
            oldProvider->start();
        }
        return;
    }

    providerMap.insert(provider->getServerHostAndPort(), provider);
    providerMap.insert(provider->getServerAddressAndPort(), provider);
    providers.append(provider);

    TenkinetProviderBox *box = new TenkinetProviderBox(provider, locked);
    boxes.append(box);
    providerBoxMap.insert(provider, box);
    devicesLayout->addWidget(box);

    if (provider->getServerHost() == "127.0.0.1") {
        box->setVisible(false);
    }

    connect(provider, &TenkinetProvider::connecting, this, &TenkinetPanel::onProviderConnecting);
    connect(provider, &TenkinetProvider::connected, this, &TenkinetPanel::onProviderConnected);
    connect(provider, &TenkinetProvider::disconnected, this, &TenkinetPanel::onProviderDisconnected);
    connect(provider, &TenkinetProvider::error, this, &TenkinetPanel::onProviderError);
    connect(provider, &TenkinetProvider::refreshing, this, &TenkinetPanel::onProviderRefreshing);
    connect(provider, &TenkinetProvider::refreshed, this, &TenkinetPanel::onProviderRefreshed);
    connect(provider, &TenkinetProvider::enabled, this, &TenkinetPanel::onProviderEnabled);
    connect(provider, &TenkinetProvider::disabled, this, &TenkinetPanel::onProviderDisabled);

    if (provider->isEnabled()) {
        QVector<TDevice *> devices = provider->getDevices();
        for (int i = 0; i < devices.size(); i++) {
            TDeviceManager::instance().addDevice(devices[i]);
        }
    }

    provider->setFavorite(true);
    provider->start();
}

void TenkinetPanel::removeProvider(TenkinetProvider *provider) {

    TenkinetProviderBox *box = providerBoxMap.value(provider);
    if (!(box != nullptr && box->isVisible())) {
        return;
    }

    devicesLayout->removeWidget(box);
    box->setVisible(false);

    provider->setFavorite(false);
    provider->setEnabled(false);
    provider->shutdown();
}

void TenkinetPanel::setLocked(bool locked) {

    this->locked = locked;

    for (int i = 0; i < boxes.size(); i++) {
        boxes.at(i)->setLocked(locked);
    }

    lockedLabel->setVisible(locked);
}

void TenkinetPanel::onClickAdd() { addProvider(hostEdit->text().trimmed(), DEFAULT_PORT); }

void TenkinetPanel::onClickConvertLog() { logConversionDialog->exec(); }

void TenkinetPanel::onClickDiscover() {

    if (discoveryRunning) {
        return;
    }

    for (int i = 0; i < providers.size(); i++) {
        providers[i]->refresh();
    }

    discoveryRunning = true;
    discoverButton->setEnabled(false);
    discoverMovie->start();

    TenkinetDiscoveryThread::instance().discover();
}

void TenkinetPanel::onDiscoveryInit() {}

void TenkinetPanel::onDiscoveryError(int error) {

    discoveryRunning = false;
    discoverMovie->stop();
    discoverButton->setIcon(*discoverErrorIcon);
    discoverButton->setEnabled(false);
}

void TenkinetPanel::onDiscoveryStart() {}

void TenkinetPanel::onDiscoveryEnd() {

    discoveryRunning = false;
    discoverMovie->stop();
    discoverButton->setIcon(*discoverIcon);
    discoverButton->setEnabled(true);
}

void TenkinetPanel::refreshDiscoveryMovie() { discoverButton->setIcon(discoverMovie->currentPixmap()); }

void TenkinetPanel::onProviderConnecting(TenkinetProvider *provider) {

    Counter *counter = reconnectionCountdown.value(provider);
    if (counter) {
        counter->n = -1;
    }
}

void TenkinetPanel::onProviderConnected(TenkinetProvider *provider) {}

void TenkinetPanel::onProviderDisconnected(TenkinetProvider *provider) {

    TenkinetProviderBox *providerBox = providerBoxMap.value(provider);

    if (!providerBox->isVisible()) {
        return;
    }

    reconnectionCountdown.insert(provider, new Counter(RECONNECTION_TICK_COUNT));

    if (!timer.isActive()) {
        timer.start();
    }
}

void TenkinetPanel::onProviderError(TenkinetProvider *provider) {}

void TenkinetPanel::onProviderRefreshing(TenkinetProvider *provider) {}

void TenkinetPanel::onProviderRefreshed(TenkinetProvider *provider) {}

void TenkinetPanel::onProviderEnabled(TenkinetProvider *provider) {

    TDeviceManager &dm = TDeviceManager::instance();
    QVector<TDevice *> devices = provider->getDevices();

    for (int i = 0; i < devices.size(); i++) {
        TDevice *device = devices[i];
        dm.addDevice(device);
        if (device->isAlive()) {
            dm.connectDevice(device);
        }
    }
}

void TenkinetPanel::onProviderDisabled(TenkinetProvider *provider) {

    TDeviceManager &dm = TDeviceManager::instance();
    QVector<TDevice *> devices = provider->getDevices();

    for (int i = 0; i < devices.size(); i++) {
        TDevice *device = devices[i];
        if (device->isAlive()) {
            dm.disconnectDevice(device);
        }
        dm.removeDevice(device);
    }
}

void TenkinetPanel::onTimerTick() {

    QMutableMapIterator<TenkinetProvider *, Counter *> it(reconnectionCountdown);

    while (it.hasNext()) {

        it.next();
        TenkinetProvider *provider = it.key();
        Counter *counter = it.value();
        int n = counter->n - 1;

        if (n < 0) {
            it.remove();
            delete counter;
            continue;
        }

        counter->n = n;
        TenkinetProviderBox *box = providerBoxMap.value(provider);
        box->setReconnection(n);

        if (n == 0) {
            provider->start();
        }
    }

    if (reconnectionCountdown.isEmpty()) {
        timer.stop();
    }
}
