#include "CanController.h"

#include <core/MeasurementSetup.h>
#include <core/MeasurementNetwork.h>
#include <core/MeasurementInterface.h>
#include <core/CanTrace.h>
#include <core/Log.h>

#include <driver/CanInterface.h>
#include <driver/SLCANDriver/SLCANDriver.h>
#include <driver/CANBlastDriver/CANBlasterDriver.h>

#if defined(__linux__)
#include <driver/SocketCanDriver/SocketCanDriver.h>
#else
#include <driver/CandleApiDriver/CandleApiDriver.h>
#endif

#include <window/SetupDialog/SetupDialog.h>

#include <QDateTime>
#include <QDebug>
#include <QTimer>
#include <QSettings>
#include <QRandomGenerator>

CanController::CanController(QObject* parent)
    : QObject{parent}
    , lastPackageDateTime_(QDateTime::fromMSecsSinceEpoch(0))
    , reconnectTimer_(new QTimer(this))
{
    setupDrivers();
    setupDefaultParameters();

    auto& backend = Backend::instance();
    connect(backend.getTrace(), &CanTrace::messageEnqueued, this, &CanController::handlePackage);

    QSettings settings("settings.ini", QSettings::IniFormat);

    const auto sendParametersTimer = new QTimer(this);
    const auto sendParametersTimeout = settings.value("sendParametersTimeout", 50).toInt();
    sendParametersTimer->start(sendParametersTimeout);
    connect(sendParametersTimer, &QTimer::timeout, this, &CanController::sendParameters);

    const auto sendVkuClosureTimer = new QTimer(this);
    const auto sendVkuClosureTimeout = settings.value("sendVkuClosureTimeout", 50).toInt();
    sendVkuClosureTimer->start(sendVkuClosureTimeout);
    connect(sendVkuClosureTimer, &QTimer::timeout, this, &CanController::sendVkuClosure);

    const auto updateCanStatusTimer = new QTimer(this);
    updateCanStatusTimer->start(1000);
    connect(updateCanStatusTimer, &QTimer::timeout, this, &CanController::updateCanStatus);

    reconnectTimer_->start(5000);
    connect(reconnectTimer_, &QTimer::timeout, this, &CanController::tryConnectCan);

    // auto statusTimer = new QTimer(this);
    // statusTimer->start(1000);
    // statusTimer->callOnTimeout([this]
    // {
    //     showStatus(QRandomGenerator::global()->generate());
    // });

    QTimer::singleShot(0, this, &CanController::connectCan);
}

CanController::~CanController()
{
}

QObject* CanController::qmlInstance(QQmlEngine* /*engine*/, QJSEngine* /*scriptEngine*/)
{
    return new CanController;
}

void CanController::connectCan()
{
    auto& backend = Backend::instance();
    backend.clearTrace();

    QSettings settings("settings.ini", QSettings::IniFormat);
    auto bitrate = settings.value("bitrate", 100000).toInt();

    for (auto* network: backend.getSetup().getNetworks())
    {
        for (auto* mi: network->interfaces())
        {
            mi->setBitrate(bitrate);
        }
    }

    backend.startMeasurement();

    QTimer::singleShot(150, this, [this]{ updateCanStatus(); });
}

void CanController::disconnectCan()
{
    Backend::instance().stopMeasurement();
}

void CanController::tryConnectCan()
{
    if (!isConnected_)
    {
        connectCan();
    }
}

void CanController::switchVkuClosure()
{
    setProperty("isVkuClosed", !isVkuClosed_);
    sendVkuClosure();
}

void CanController::emergencyReset()
{
    CanMessage message(0x201);
    message.setLength(8);
    message.setByte(0, 0x02);

    auto& backend = Backend::instance();
    for (const auto interfaceId: backend.getInterfaceList())
    {
        auto interface = backend.getInterfaceById(interfaceId);
        if (interface)
        {
            interface->sendMessage(message);
        }
    }
}

void CanController::sendParameters()
{
    CanMessage message(0x202);
    message.setLength(8);

    const auto maximumCurrent = maximumCurrent_.toUInt();
    message.setByte(0, maximumCurrent & 0xFF);

    const auto breakingDelay = breakingDelay_.toUInt();
    message.setByte(1, (breakingDelay >> 8) & 0xFF);
    message.setByte(2, breakingDelay & 0xFF);

    const auto breakingCurrent = breakingCurrent_.toUInt();
    message.setByte(3, breakingCurrent & 0xFF);

    const auto emergencyDelay = qRound(emergencyDelay_.toDouble() * 2.0);
    message.setByte(4, emergencyDelay & 0xFF);

    const auto retriesAfterEmergencyBreak = retriesAfterEmergencyBreak_.toUInt();
    message.setByte(5, retriesAfterEmergencyBreak & 0xFF);

    // qDebug() << "Sending parameters: " << maximumCurrent;

    auto& backend = Backend::instance();
    for (const auto interfaceId: backend.getInterfaceList())
    {
        auto interface = backend.getInterfaceById(interfaceId);
        if (interface)
        {
            interface->sendMessage(message);
        }
    }
}

void CanController::sendVkuClosure()
{
    CanMessage message(0x201);
    message.setLength(8);
    message.setByte(0, isVkuClosed_ ? 0x01 : 0x00);

    auto& backend = Backend::instance();
    for (const auto interfaceId: backend.getInterfaceList())
    {
        auto interface = backend.getInterfaceById(interfaceId);
        if (interface)
        {
            interface->sendMessage(message);
        }
    }
}

void CanController::handlePackage(int index)
{
    lastPackageDateTime_ = QDateTime::currentDateTime();
    if (isFirstCheck_)
    {
        updateCanStatus();
    }

    auto& backend = Backend::instance();
    auto message = backend.getTrace()->getMessage(index);
    if (!message)
    {
        return;
    }

    if (message->getId() == 0x501)
    {
        handleCurrentState(message);
    }

    if (message->getId() == 0x502)
    {
        handleStatus(message);
    }
}

void CanController::handleCurrentState(const CanMessage* message)
{
    quint16 inputVoltage = (message->getByte(0) << 8) + message->getByte(1);
    setProperty("inputVoltage", QString::number(inputVoltage));

    quint16 outputVoltage = (message->getByte(2) << 8) + message->getByte(3);
    setProperty("outputVoltage", QString::number(outputVoltage));

    quint8 inputCurrent = message->getByte(4);
    setProperty("inputCurrent", QString::number(inputCurrent));

    qint16 radiatorTemperature = (message->getByte(5) << 8) + message->getByte(6);
    setProperty("radiatorTemperature", QString::number(radiatorTemperature));
}

void CanController::handleStatus(const CanMessage* message)
{
    quint8 status = message->getByte(0);
    showStatus(status);

    quint8 emergencyCounter = message->getByte(1);
    setProperty("emergencyCounter", QString::number(emergencyCounter));

    // bool isVkuClosed = (message->getByte(2) & 0x1) == 1;
    // setProperty("isVkuClosed", isVkuClosed);
}

void CanController::showStatus(quint32 status)
{
    QMap<quint32, QString> statusDescriptionMap =
    {
        {0x00, tr("Emergency reset")},
        {0x01, tr("No incoming messages via CAN interface")},
        {0x02, tr("Pause after breaking")},
        {0x03, tr("Pause after emergency")},
        {0x04, tr("Waiting for command for closure")},
        {0x05, tr("VKU is closed")},
        {0x18, tr("Exceeding radiator temperature")},
        {0x20, tr("Exceeding input voltage")},
        {0x21, tr("Fault signal")},
        {0x22, tr("Exceeding maximum current")},
        {0x23, tr("Exceeding switching current")},
        {0x25, tr("Voltage 5V is not normal")},
        {0x80, tr("Blocking due to emergency")},
        {0x0100, tr("Can connected")},
        {0x0200, tr("Can disconnected")},
    };

    auto statusDescription = statusDescriptionMap.value(status, tr("Unknown status"));

    QVariantMap statusMap;
    statusMap.insert("time", QDateTime::currentDateTime().toString());
    statusMap.insert("status", "0x" + QString::number(status, 16).toUpper().rightJustified(2, '0'));
    statusMap.insert("description", statusDescription);

    if (statuses_.isEmpty() || statuses_.first().toMap()["status"] != statusMap["status"])
    {
        statuses_.prepend(statusMap);
        emit statusesChanged();
    }
}

void CanController::updateCanStatus()
{
    auto isConnected = lastPackageDateTime_.msecsTo(QDateTime::currentDateTime()) < 2000;
    if (isConnected_ != isConnected || isFirstCheck_)
    {
        isConnected_ = isConnected;
        isFirstCheck_ = false;
        showStatus(isConnected ? 0x0100 : 0x0200);
    }
}

void CanController::setupDrivers()
{
    auto& backend = Backend::instance();

#if defined(__linux__)
    backend.addCanDriver(*(new SocketCanDriver(backend)));
#else
    backend.addCanDriver(*(new CandleApiDriver(backend)));
#endif
    // backend.addCanDriver(*(new SLCANDriver(backend)));
    // backend.addCanDriver(*(new CANBlasterDriver(backend)));

    backend.setDefaultSetup();
}

void CanController::setupDefaultParameters()
{
    setProperty("maximumCurrent", QString::number(95));
    setProperty("emergencyDelay", QString::number(2));
    setProperty("breakingDelay", QString::number(0));
    setProperty("retriesAfterEmergencyBreak", QString::number(2));
    setProperty("breakingCurrent", QString::number(100));
}
