#include "ambeosoundbar.h"
#include "streamunlimitedrequest.h"

#include <QJsonDocument>
#include <QLoggingCategory>

Q_DECLARE_LOGGING_CATEGORY(dcSennheiser);

const QHash<QString, int> ambeoInputSourceMap = {
    { "HDMI 1", 0x0 },
    { "HDMI 2", 0x1 },
    { "HDMI 3" , 0x2 },
    { "HDMI TV", 0x3 },
    { "Bluetooth", 0x4 },
    { "Google Cast", 0x5 },
    { "Media", 0x6 }, // UPnP/DLNA
    { "Optical", 0x7 },
    { "Aux", 0x8 },
    { "Spotify", 0x9 },
    { "Airplay", 0xC },
    { "Tidal", 0xE },
    { "Toggle_Next", 0x80 },
    { "Toggle_Prev", 0x81}
};

AmbeoSoundBar::AmbeoSoundBar(NetworkAccessManager *nam, const QHash<QString, QUuid> idMap, QObject *parent) : StreamUnlimitedDevice(nam, idMap, "/ui", parent)
{
    addSubscriptions({"settings:/espresso/audioInputID", "settings:/espresso/nightMode", "settings:/espresso/equalizerPreset", "settings:/espresso/ambeoMode"});

    connect(this, &StreamUnlimitedDevice::connectionStatusChanged, [this](ConnectionStatus status){
        if (status == ConnectionStatusConnected) {
            refreshAmbeoMode();
            refreshEqualizerPreset();
            refreshNightMode();
            refreshInputSource();
        }
    });

    connect(this, &StreamUnlimitedDevice::changeNotification, [this](const QString &path){
        if (path == "settings:/espresso/nightMode") {
            refreshNightMode();
        } else if (path == "settings:/espresso/equalizerPreset") {
            refreshEqualizerPreset();
        } else if (path == "settings:/espresso/ambeoMode") {
            refreshAmbeoMode();
        } else if (path == "settings:/espresso/audioInputID") {
            refreshInputSource();
        }
    });
}

bool AmbeoSoundBar::nightMode() const
{
    return m_nightMode;
}

QUuid AmbeoSoundBar::setNightMode(bool nightMode)
{
    QUuid commandId = QUuid::createUuid();

    QString path;
    QString role;
    QVariantMap params;
    path = "settings:/espresso/nightMode";
    role = "value";
    params.insert("type", "i32_");
    params.insert("i32_", nightMode ? 0x1 : 0x0);

    qCDebug(dcSennheiser()) << "Selecting input source:" << path << role << params;
    StreamUnlimitedSetRequest *request = new StreamUnlimitedSetRequest(m_nam, m_address, m_port, path, role, params, this);
    connect(request, &StreamUnlimitedSetRequest::error, this, [=](const QNetworkReply::NetworkError &error) {
        qCWarning(dcSennheiser()) << "select night mode error" << error;
        emit commandCompleted(commandId, false);
    });
    connect(request, &StreamUnlimitedSetRequest::finished, this, [=](const QByteArray &response) {
        qCDebug(dcSennheiser()) << "Select night mode response" << response;
        QJsonParseError error;
        QVariantMap reply = QJsonDocument::fromJson(response, &error).toVariant().toMap();
        emit commandCompleted(commandId, error.error == QJsonParseError::NoError && reply.value("value").toMap().value("i32_").toInt() == (nightMode ? 0x1 : 0x0));
    });
    return commandId;
}

AmbeoSoundBar::AmbeoMode AmbeoSoundBar::ambeoMode() const
{
    return m_ambeoMode;
}

QUuid AmbeoSoundBar::setAmbeoMode(AmbeoSoundBar::AmbeoMode ambeoMode)
{
    QUuid commandId = QUuid::createUuid();

    QString path;
    QString role;
    QVariantMap params;
    path = "settings:/espresso/ambeoMode";
    role = "value";
    params.insert("type", "i32_");
    params.insert("i32_", ambeoMode);

    qCDebug(dcSennheiser()) << "Selecting ambeo mode:" << path << role << params;
    StreamUnlimitedSetRequest *request = new StreamUnlimitedSetRequest(m_nam, m_address, m_port, path, role, params, this);
    connect(request, &StreamUnlimitedSetRequest::error, this, [=](const QNetworkReply::NetworkError &error) {
        qCWarning(dcSennheiser()) << "Select ambeo mode error" << error;
        emit commandCompleted(commandId, false);
    });
    connect(request, &StreamUnlimitedSetRequest::finished, this, [=](const QByteArray &response) {
        qCDebug(dcSennheiser()) << "Select ambeo mode response" << response;
        QJsonParseError error;
        QVariantMap reply = QJsonDocument::fromJson(response, &error).toVariant().toMap();
        emit commandCompleted(commandId, error.error == QJsonParseError::NoError && reply.value("value").toMap().value("i32_").toInt() == ambeoMode);
    });
    return commandId;
}

QUuid AmbeoSoundBar::setInputSource(const QString &inputSource)
{
    QUuid commandId = QUuid::createUuid();

    QString path;
    if (deviceVersion() < "1.0.237.0xb344b2d") {
        path = "settings:/espresso/audioInputID";
    } else {
        path = "espresso:audioInputID";
    }

    QString role = "value";

    QVariantMap params = QVariantMap();
    params.insert("type", "i32_");
    params.insert("i32_", ambeoInputSourceMap.value(inputSource));

    qCDebug(dcSennheiser()) << "Selecting input source:" << path << role << params;
    StreamUnlimitedSetRequest *request = new StreamUnlimitedSetRequest(m_nam, m_address, m_port, path, role, params, this);
    connect(request, &StreamUnlimitedSetRequest::error, this, [=](const QNetworkReply::NetworkError &error) {
        qCWarning(dcSennheiser()) << "selectSource error" << error;
        emit commandCompleted(commandId, false);
    });
    connect(request, &StreamUnlimitedSetRequest::finished, this, [=](const QByteArray &response) {
        qCDebug(dcSennheiser()) << "Select source response" << response;
        bool success = false;
        if (deviceVersion() < "1.0.237.0xb344b2d") {
            QJsonParseError error;
            QVariantMap reply = QJsonDocument::fromJson(response, &error).toVariant().toMap();
            success = error.error == QJsonParseError::NoError && reply.value("value").toMap().value("i32_").toInt() == ambeoInputSourceMap.value(inputSource);
        } else {
            success = response == "true";
        }
        emit commandCompleted(commandId, success);
    });
    return commandId;

}

uint AmbeoSoundBar::volume() const
{
    return StreamUnlimitedDevice::volume() / 2;
}

QUuid AmbeoSoundBar::setVolume(uint volume)
{
    return StreamUnlimitedDevice::setVolume(volume * 2);
}

AmbeoSoundBar::EqualizerPreset AmbeoSoundBar::equalizerPreset() const
{
    return m_equalizerPreset;
}

QUuid AmbeoSoundBar::setEqualizerPreset(EqualizerPreset equalizerPreset)
{
    QUuid commandId = QUuid::createUuid();

    QString path;
    QString role;
    QVariantMap params;
    path = "settings:/espresso/equalizerPreset";
    role = "value";
    params.insert("type", "i32_");
    params.insert("i32_", equalizerPreset);

    qCDebug(dcSennheiser()) << "Selecting equalizer preset:" << path << role << params;
    StreamUnlimitedSetRequest *request = new StreamUnlimitedSetRequest(m_nam, m_address, m_port, path, role, params, this);
    connect(request, &StreamUnlimitedSetRequest::error, this, [=](const QNetworkReply::NetworkError &error) {
        qCWarning(dcSennheiser()) << "Select equalizer preset error" << error;
        emit commandCompleted(commandId, false);
    });
    connect(request, &StreamUnlimitedSetRequest::finished, this, [=](const QByteArray &response) {
        qCDebug(dcSennheiser()) << "Select equalizer preset response" << response;
        QJsonParseError error;
        QVariantMap reply = QJsonDocument::fromJson(response, &error).toVariant().toMap();
        emit commandCompleted(commandId, error.error == QJsonParseError::NoError && reply.value("value").toMap().value("i32_").toInt() == equalizerPreset);
    });
    return commandId;
}

void AmbeoSoundBar::refreshNightMode()
{
    QString path = "settings:/espresso/nightMode";
    StreamUnlimitedGetRequest *request = new StreamUnlimitedGetRequest(m_nam, m_address, m_port, path, {"value"}, this);
    connect(request, &StreamUnlimitedGetRequest::finished, this, [=](const QVariantMap &result){
        QVariantMap valueMap = result.value("value").toMap();
        m_nightMode = valueMap.value(valueMap.value("type").toString()).toInt() == 1;
        qCDebug(dcSennheiser()) << "Night mode changed to:" << m_nightMode;
        emit nightModeChanged(m_nightMode);
    });
}

void AmbeoSoundBar::refreshEqualizerPreset()
{
    QString path = "settings:/espresso/equalizerPreset";
    StreamUnlimitedGetRequest *request = new StreamUnlimitedGetRequest(m_nam, m_address, m_port, path, {"value"}, this);
    connect(request, &StreamUnlimitedGetRequest::finished, this, [=](const QVariantMap &result){
        QVariantMap valueMap = result.value("value").toMap();
        m_equalizerPreset = static_cast<EqualizerPreset>(valueMap.value(valueMap.value("type").toString()).toInt());
        qCDebug(dcSennheiser()) << "Equalizer preset changed to:" << m_equalizerPreset << result;
        emit equalizerPresetChanged(m_equalizerPreset);
    });
}

void AmbeoSoundBar::refreshAmbeoMode()
{
    QString path = "settings:/espresso/ambeoMode";
    StreamUnlimitedGetRequest *request = new StreamUnlimitedGetRequest(m_nam, m_address, m_port, path, {"value"}, this);
    connect(request, &StreamUnlimitedGetRequest::finished, this, [=](const QVariantMap &result){
        QVariantMap valueMap = result.value("value").toMap();
        m_ambeoMode = static_cast<AmbeoMode>(valueMap.value(valueMap.value("type").toString()).toInt());
        qCDebug(dcSennheiser()) << "AMBEO mode changed to:" << m_ambeoMode << result;
        emit ambeoModeChanged(m_ambeoMode);
    });
}

void AmbeoSoundBar::refreshInputSource()
{
    QString path = "settings:/espresso/audioInputID";
    StreamUnlimitedGetRequest *request = new StreamUnlimitedGetRequest(m_nam, m_address, m_port, path, {"value"}, this);
    connect(request, &StreamUnlimitedGetRequest::finished, this, [=](const QVariantMap &result){
        qCDebug(dcSennheiser()) << "Input source get result:" << result;
        QVariantMap valueMap = result.value("value").toMap();
        int inputId = valueMap.value(valueMap.value("type").toString()).toInt();
        m_inputSource = ambeoInputSourceMap.key(inputId);
        qCDebug(dcSennheiser()) << "Input changed to:" << inputId << m_inputSource;
        emit inputSourceChanged(m_inputSource);
    });
}
