#include "DataController.h"

#include <QTimer>
#include <QtEndian>
#include <QDateTime>
#include <QSettings>
#include <QSerialPort>
#include <QCoreApplication>

#include <QDebug>

DataController::DataController(QObject* parent)
    : QObject{parent}
    , serialPort_(new QSerialPort)
{
    connect(serialPort_.data(), &QSerialPort::readyRead, this, &DataController::serialReadyRead);
    connect(serialPort_.data(), &QSerialPort::errorOccurred, this, &DataController::serialError);

    // for correct qApp->exit();
    QTimer::singleShot(0, this, [this]{ openSerialPort(); });
}

DataController::~DataController()
{
}

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

void DataController::serialReadyRead()
{
    const auto data = serialPort_->readAll();
    qDebug() << "Serial data received (HEX):" << QDateTime::currentDateTime() << data.toHex();

    serialData_ += data;
    parseSerialData();
}

void DataController::serialError()
{
    const auto error = serialPort_->error();
    if (error == QSerialPort::NoError)
    {
        return;
    }

    qCritical() << "A serial port error has occurred. Port:" << serialPort_->portName() << "Error:" << serialPort_->errorString();
    qApp->exit(1);
}

QVariantMap DataController::readSettings()
{
    QSettings settings("settings.ini", QSettings::IniFormat);

    const auto keys = settings.allKeys();
    if (keys.isEmpty())
    {
        qCritical() << "File settings.ini is empty. Please put the file next to the executable file.";
        qApp->exit(1);
    }


    QVariantMap settingsMap;
    for (const auto& key: keys)
    {
        settingsMap[key] = settings.value(key);
    }

    return settingsMap;
}

void DataController::openSerialPort()
{
    const auto settings = readSettings();
    if (settings.isEmpty())
    {
        return;
    }

    const auto portName = settings["name"].toString();

    // for testing
    if (portName == "fake")
    {
        mockSerialData();
        parseSerialData();
        return;
    }

    serialPort_->setPortName(portName);

    if (settings.contains("baudRate"))
    {
        serialPort_->setBaudRate(settings["baudRate"].toInt());
    }

    if (settings.contains("dataBits"))
    {
        serialPort_->setDataBits((QSerialPort::DataBits)settings["dataBits"].toInt());
    }

    if (settings.contains("parity"))
    {
        serialPort_->setParity((QSerialPort::Parity)settings["parity"].toInt());
    }

    if (settings.contains("stopBits"))
    {
        serialPort_->setStopBits((QSerialPort::StopBits)settings["stopBits"].toInt());
    }

    if (settings.contains("flowControl"))
    {
        serialPort_->setFlowControl((QSerialPort::FlowControl)settings["flowControl"].toInt());
    }

    if (!serialPort_->open(QIODevice::ReadOnly))
    {
        qCritical() << "Can't open serial port. Port:" << serialPort_->portName() << "Error:" << serialPort_->errorString();
        qApp->exit(1);
    }
}

void DataController::parseSerialData()
{
    const char* startBytes = "\xD0\x0D";
    const auto startPosition = serialData_.indexOf(startBytes);
    if (startPosition == -1)
    {
        serialData_.clear();
        return;
    }

    if (startPosition > 0)
    {
        serialData_ = serialData_.mid(startPosition);
    }

    if (serialData_.size() < 36)
    {
        return;
    }

    auto extractInt = [](const QByteArray& array, int start, int length) -> uint32_t
    {
        uint32_t result = 0;
        if (length > 4)
        {
            return result;
        }

        // reversed byte order
        for (int i = 0; i < length; ++i)
        {
            memcpy(reinterpret_cast<char*>(&result) + i, array.data() + start + length - i - 1, 1);
        }

        // same byte order
//        memcpy(&result, array.data() + start, length);
        return result;
    };

    auto statusToString = [](uint32_t status) -> QString
    {
        auto hexStatus = "0x" + QString("%1").arg(status, 2, 16, QLatin1Char('0')).toUpper();
        if (hexStatus.compare("0xFF", Qt::CaseInsensitive) == 0)
        {
            hexStatus = " — ";
        }

        return hexStatus;
    };

    auto shortIntToString = [](uint16_t parameter) -> QString
    {
        return parameter == std::numeric_limits<uint16_t>::max() ? " — " : QString::number(parameter);
    };

    auto shortDoubleToString = [](uint16_t parameter) -> QString
    {
        return parameter == std::numeric_limits<uint16_t>::max() ? " — " : QString::number(parameter / 10.0, 'f', 1);
    };

    const auto package = serialData_.mid(0, 36);
    serialData_ = serialData_.mid(36);

    quint16 calculatedChecksum = 0;
    for (int i = 0; i < 34; ++i)
    {
        calculatedChecksum += static_cast<quint8>(package.at(i));
    }

    const auto checksum = extractInt(package, 34, 2);
    if (calculatedChecksum != checksum)
    {
        qWarning() << "incorrect checksum";
        return;
    }

    setProperty("firstStatus", statusToString(extractInt(package, 2, 1)));
    setProperty("secondStatus", statusToString(extractInt(package, 3, 1)));

    setProperty("psnInputVoltage", shortIntToString(extractInt(package, 4, 2)));
    setProperty("mainVoltage", shortIntToString(extractInt(package, 6, 2)));

    setProperty("ainMode", extractInt(package, 8, 1));
    setProperty("ainReservedMask", extractInt(package, 9, 1));

    setProperty("ain1Voltage", shortIntToString(extractInt(package, 10, 2)));
    setProperty("ain1Current", shortDoubleToString(extractInt(package, 12, 2)));

    setProperty("ain2Voltage", shortIntToString(extractInt(package, 14, 2)));
    setProperty("ain2Current", shortDoubleToString(extractInt(package, 16, 2)));

    setProperty("ain3Voltage", shortIntToString(extractInt(package, 18, 2)));
    setProperty("ain3Current", shortDoubleToString(extractInt(package, 20, 2)));

    setProperty("ain4Voltage", shortIntToString(extractInt(package, 22, 2)));
    setProperty("ain4Current", shortDoubleToString(extractInt(package, 24, 2)));

    setProperty("ain5Voltage", shortIntToString(extractInt(package, 26, 2)));
    setProperty("ain5Current", shortDoubleToString(extractInt(package, 28, 2)));

    setProperty("ain6Voltage", shortIntToString(extractInt(package, 30, 2)));
    setProperty("ain6Current", shortDoubleToString(extractInt(package, 32, 2)));
}

void DataController::mockSerialData()
{
    serialData_.clear();

    serialData_.append((char)0xD0);
    serialData_.append((char)0x0D);

    char status1 = 0x0;
    char status2 = 0x0;
    serialData_.append(status1);
    serialData_.append(status2);

    uint16_t inputVoltage = qToBigEndian((uint16_t)3010);
    uint16_t mainVoltage = qToBigEndian((uint16_t)384);
    serialData_.append(reinterpret_cast<char*>(&inputVoltage), 2);
    serialData_.append(reinterpret_cast<char*>(&mainVoltage), 2);

    char mode = 0;
    char reservedMask = 0x12;
    serialData_.append(mode);
    serialData_.append(reservedMask);

    uint16_t voltage1 = qToBigEndian((uint16_t)380);
    uint16_t current1 = qToBigEndian((uint16_t)535);
    serialData_.append(reinterpret_cast<char*>(&voltage1), 2);
    serialData_.append(reinterpret_cast<char*>(&current1), 2);

    uint16_t voltage2 = qToBigEndian((uint16_t)381);
    uint16_t current2 = qToBigEndian((uint16_t)527);
    serialData_.append(reinterpret_cast<char*>(&voltage2), 2);
    serialData_.append(reinterpret_cast<char*>(&current2), 2);

    uint16_t voltage3 = qToBigEndian((uint16_t)383);
    uint16_t current3 = qToBigEndian((uint16_t)729);
    serialData_.append(reinterpret_cast<char*>(&voltage3), 2);
    serialData_.append(reinterpret_cast<char*>(&current3), 2);

    uint16_t voltage4 = qToBigEndian((uint16_t)381);
    uint16_t current4 = qToBigEndian((uint16_t)536);
    serialData_.append(reinterpret_cast<char*>(&voltage4), 2);
    serialData_.append(reinterpret_cast<char*>(&current4), 2);

    uint16_t voltage5 = qToBigEndian((uint16_t)382);
    uint16_t current5 = qToBigEndian((uint16_t)528);
    serialData_.append(reinterpret_cast<char*>(&voltage5), 2);
    serialData_.append(reinterpret_cast<char*>(&current5), 2);

    uint16_t voltage6 = qToBigEndian((uint16_t)383);
    uint16_t current6 = qToBigEndian((uint16_t)730);
    serialData_.append(reinterpret_cast<char*>(&voltage6), 2);
    serialData_.append(reinterpret_cast<char*>(&current6), 2);

    quint16 checksum = 0;
    for (int i = 0; i < serialData_.size(); ++i)
    {
        checksum += static_cast<quint8>(serialData_.at(i));
    }

    checksum = qToBigEndian(checksum);
    serialData_.append(reinterpret_cast<char*>(&checksum), 2);
}
