/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of maveod.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "ledcontroller.h"
#include "loggingcategories.h"

#include <QDataStream>

extern "C" {
#include <sys/ioctl.h>
#include <i2c/smbus.h>
#include <linux/i2c-dev.h>
}

LedController::LedController(const QString &i2cBusFileName, quint8 i2cAddress, QObject *parent) :
    QObject(parent),
    m_i2cBusFileName(i2cBusFileName),
    m_i2cAddress(i2cAddress)
{
    // Init the led controller and read the current mode
    qCDebug(dcLedController()) << "Initialize I2C bus" << m_i2cBusFileName << QString("0x%1").arg(m_i2cAddress, 2, 16, QLatin1Char('0'));
    m_i2cBusFile.setFileName(m_i2cBusFileName);
    if (!m_i2cBusFile.exists()) {
        qCWarning(dcLedController()) << "The given I2C bus file descriptor does not exist:" << m_i2cBusFile.fileName();
        return;
    }

    if (!m_i2cBusFile.open(QFile::ReadWrite)) {
        qCWarning(dcLedController()) << "Could not open the given I2C bus file descriptor:" << m_i2cBusFile.fileName() << m_i2cBusFile.errorString();
        return;
    }

    readData();
}

QString LedController::i2cBusFileName() const
{
    return m_i2cBusFileName;
}

quint8 LedController::i2cAddress() const
{
    return m_i2cAddress;
}

LedController::LedCommand LedController::command() const
{
    return m_command;
}

LedController::LedAnimation LedController::animation() const
{
    return m_animation;
}

quint16 LedController::duration() const
{
    return m_duration;
}

QColor LedController::color() const
{
    return m_color;
}

QString LedController::controllerVersion() const
{
    return m_controllerVersion;
}

bool LedController::setLed(LedController::LedCommand command, LedController::LedAnimation animation, quint16 duration, const QColor &color)
{
    if (!m_i2cBusFile.isOpen()) {
        qCWarning(dcLedController()) << "Could not send command to led controller. File not open" << m_i2cBusFile.fileName();
        return false;
    }

    qCDebug(dcLedController()) << "Set LED:" << command << animation << duration << "[ms]" << color;
    // Build data package

    unsigned char data[9];
    data[0] = 0x01;
    data[1] = static_cast<quint8>(command);
    data[2] = static_cast<quint8>(animation);
    data[3] = static_cast<quint8>((duration >> 8) & 0xff);
    data[4] = static_cast<quint8>(duration  & 0xff);
    data[5] = static_cast<quint8>(color.red());
    data[6] = static_cast<quint8>(color.green());
    data[7] = static_cast<quint8>(color.blue());
    data[8] = 0x17;

    QString byteString;
    for (int i = 0; i < 9; i++) {
        byteString += QString("0x%1 ").arg(data[i], 2, 16, QLatin1Char('0'));
    }

    // Set configuration
    if (ioctl(m_i2cBusFile.handle(), I2C_SLAVE, m_i2cAddress) < 0) {
        qCWarning(dcLedController()) << "Could not set I2C into slave mode" << m_i2cBusFileName << QString("0x%1").arg(m_i2cAddress, 2, 16, QLatin1Char('0'));
        return false;
    }

    qCDebug(dcLedController()) << "-->" << byteString;
    i2c_smbus_write_i2c_block_data(m_i2cBusFile.handle(), 0x00, 9, data);

    return readData();
}

bool LedController::readData()
{
    qCDebug(dcLedController()) << "Read controller data...";
    if (!m_i2cBusFile.isOpen()) {
        qCWarning(dcLedController()) << "Cannot read data from the controller. File not open" << m_i2cBusFile.fileName();
        return false;
    }

    if (ioctl(m_i2cBusFile.handle(), I2C_SLAVE, m_i2cAddress) < 0) {
        qCWarning(dcLedController()) << "Could not set I2C into slave mode" << m_i2cBusFileName << QString("0x%1").arg(m_i2cAddress, 2, 16, QLatin1Char('0'));
        return false;
    }

    unsigned char data[13];
    int result = i2c_smbus_read_i2c_block_data(m_i2cBusFile.handle(), 0x00, 13, data);
    if (result != 13) {
        qCWarning(dcLedController()) << "Could not read data" << result << "!=" << "13";
        return false;
    }

    QString byteString;
    for (int i = 0; i < 13; i++) {
        byteString += QString("0x%1 ").arg(data[i], 2, 16, QLatin1Char('0'));
    }
    qCDebug(dcLedController()) << "<--" << byteString;

    // Parse information - RW
    quint8 startPackage = data[0];
    LedCommand command = static_cast<LedCommand>(data[1]);
    LedAnimation animation = static_cast<LedAnimation>(data[2]);
    quint16 duration = static_cast<quint16>(data[3] << 8);
    duration |= data[4];
    quint8 red = data[5];
    quint8 green = data[6];
    quint8 blue = data[7];
    quint8 endPackage = data[8];

    // RO
    LedStatus status = static_cast<LedStatus>(data[9]);
    quint8 majorVersion = data[10];
    quint8 minorVersion = data[11];
    quint8 patchVersion = data[12];

    if (startPackage != 0x01 || endPackage != 0x17) {
        qCWarning(dcLedController()) << "Invalid package format from the LED controller.";
        return false;
    }

    qCDebug(dcLedController()) << command << animation << duration << "[ms]";
    qCDebug(dcLedController()) << QColor(red, green, blue).toRgb();
    qCDebug(dcLedController()) << "Status" << status;
    qCDebug(dcLedController()) << "Version:" << QString("%1.%2.%3").arg(majorVersion).arg(minorVersion).arg(patchVersion);

    if (static_cast<LedStatus>(status) != LedStatusSuccess) {
        qCWarning(dcLedController()) << "The LED controller returned error on last execution. Set the data either way";
    }

    return true;
}

void LedController::setCommand(LedController::LedCommand command)
{
    if (m_command == command)
        return;

    qCDebug(dcLedController()) << "Command changed" << command;
    m_command = command;
    emit commandChanged(m_command);
}

void LedController::setAnimation(LedController::LedAnimation animation)
{
    if (m_animation == animation)
        return;

    qCDebug(dcLedController()) << "Animation changed" << animation;
    m_animation = animation;
    emit animationChanged(m_animation);
}

void LedController::setDuration(quint16 duration)
{
    if (m_duration == duration)
        return;

    qCDebug(dcLedController()) << "Duration changed" << duration << "[ms]";
    m_duration = duration;
    emit durationChanged(m_duration);
}

void LedController::setColor(const QColor &color)
{
    if (m_color == color)
        return;

    qCDebug(dcLedController()) << "Color changed" << color;
    m_color = color;
    emit colorChanged(m_color);
}
