#include "streamsdkdevboard.h"
#include "streamunlimitedrequest.h"
#include "extern-plugininfo.h"

#include <QJsonDocument>
#include <QRegularExpression>

const QHash<QString, QString> inputSourceMap = {
    {"SPDIFIN", "SPDIF in"},
    {"AUX", "Line-in (AUX)"},
    {"spotify", "Spotify"},
    {"airableRadios", "Airable"},
    {"airablePodcasts", "Airable"},
    {"tuneIn", "TuneIn"}
};

StreamSDKDevBoard::StreamSDKDevBoard(NetworkAccessManager *nam, const QHash<QString, QUuid> idMap, QObject *parent) : StreamUnlimitedDevice(nam, idMap, "ui:", parent)
{
    connect(this, &StreamUnlimitedDevice::connectionStatusChanged, [this](ConnectionStatus status) {
        if (status == ConnectionStatusConnected) {
            refreshInputSource();
        }
    });

    connect(this, &StreamUnlimitedDevice::changeNotification, [this](const QString &path){
        if (path == "player:player/data") {
            refreshInputSource();
        }
    });
}

QUuid StreamSDKDevBoard::executeContextMenu(const QString &itemId, const ActionTypeId &actionTypeId)
{
    QUuid commandId = QUuid::createUuid();

    if (actionTypeId == streamSDKdevBoardFavoriteAirableBrowserItemActionTypeId
            || actionTypeId == streamSDKdevBoardUnfavoriteAirableBrowserItemActionTypeId) {
        // First we need to get the context for this item
        QString containerData = itemId;
        containerData.remove(QRegularExpression("(container|audio):"));
        qCDebug(dcStreamSDK()) << "Path data" << containerData;
        QVariantMap container = QJsonDocument::fromJson(containerData.toUtf8()).toVariant().toMap();
        QString path;
        if (itemId.startsWith("container")) {
            path = container.value("context").toMap().value("path").toString();
        } else if (itemId.startsWith("audio")) {
            path = container.value("mediaRoles").toMap().value("context").toMap().value("path").toString();
        }

        StreamUnlimitedBrowseRequest *contextRequest = new StreamUnlimitedBrowseRequest(m_nam, m_address, m_port, path, {
                                                                                            "path","id","title","icon","type","containerType","personType","albumType","imageType","audioType","videoType","epgType","modifiable","disabled","flags","value","valueOperation()","edit","mediaData","query","activate","likeIt","rowsOperation","setRoles","timestamp","valueUnit","context","description","longDescription","search","prePlay","activity","cancel","accept","risky","preferred","httpRequest","encrypted","encryptedValue","rating","fillParent","autoCompletePath","busyText","sortKey","renderAsButton","doNotTrack","persistentMetaData","containerPlayable","releaseDate"
                                                                                            }, this);
        // If we fail, bail out
        connect(contextRequest, &StreamUnlimitedBrowseRequest::error, this, [=](){
            qCWarning(dcStreamSDK()) << "Error fetching context for item" << itemId;
            emit commandCompleted(commandId, false);
        });

        // Context is here, find the action path.
        connect(contextRequest, &StreamUnlimitedBrowseRequest::finished, this, [=](const QVariantMap &contextResult){
            qCDebug(dcStreamSDK()) << "Context menu item" << qUtf8Printable(QJsonDocument::fromVariant(contextResult).toJson());

            QVariantList contextItems = contextResult.value("rows").toList();
            QString contextActionId;
            if (actionTypeId == streamSDKdevBoardFavoriteAirableBrowserItemActionTypeId) {
                contextActionId = "airable://airable/action/favorite.insert";
            } else if (actionTypeId == streamSDKdevBoardUnfavoriteAirableBrowserItemActionTypeId) {
                contextActionId = "airable://airable/action/favorite.remove";
            }
            // Note: connecte Actions don't provide the ID field... so we'll pick the first one... afaict there is always only one anyways
            foreach (const QVariant &contextRow, contextItems) {
                QStringList roles = contextRow.toStringList();
                QString contextItemPath = roles.takeFirst();
                QString id = roles.takeFirst();
                if (id == contextActionId) {

                    // We've found it! Execute it!
                    StreamUnlimitedSetRequest *request = new StreamUnlimitedSetRequest(m_nam, m_address, m_port, contextItemPath, "activate", "", this);
                    connect(request, &StreamUnlimitedSetRequest::error, this, [=](){
                        qCWarning(dcStreamSDK()) << "Failed to execute browser item context menu action";
                        emit commandCompleted(commandId, false);
                    });
                    connect(request, &StreamUnlimitedSetRequest::finished, this, [=](const QByteArray &data){
                        qCDebug(dcStreamSDK()) << "Context menu execution result:" << data;
                        QJsonParseError error;
                        QJsonDocument result = QJsonDocument::fromJson(data, &error);
                        emit commandCompleted(commandId, error.error == QJsonParseError::NoError && !result.toVariant().toMap().contains("error"));
                    });
                    return;
                }
            }
            qCWarning(dcStreamSDK()) << "Could not find context action" << contextActionId << "on this item";
            emit commandCompleted(commandId, false);
        });

    } else {
        commandId = StreamUnlimitedDevice::executeContextMenu(itemId, actionTypeId);
    }


    return commandId;
}

QString StreamSDKDevBoard::inputSource() const
{
    return m_inputSource;
}

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

    QString path;
    QString role = "activate";
    QVariantMap params = QVariantMap();

    if (inputSource == "Line-in (AUX)" || inputSource == "SPDIF in") {
        path = "player:player/control";
        params = composeComplexInputSourcePayload(inputSource);
    } else if (inputSource == "Spotify") {
        path = "spotify:/resume";
    } else {
        qCWarning(dcStreamSDK()) << "Switching to input source" << inputSource << "is not supported.";
        return QUuid();
    }

    qCDebug(dcStreamSDK()) << "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(dcStreamSDK()) << "selectSource error" << error;
        emit commandCompleted(commandId, false);
    });
    connect(request, &StreamUnlimitedSetRequest::finished, this, [=](const QByteArray &response) {
        qCDebug(dcStreamSDK()) << "Select source response" << response;
        emit commandCompleted(commandId, true);
    });
    return commandId;
}

void StreamSDKDevBoard::refreshInputSource()
{
    StreamUnlimitedGetRequest *request = new StreamUnlimitedGetRequest(m_nam, m_address, m_port, "player:player/data", {"value"}, this);
    connect(request, &StreamUnlimitedGetRequest::finished, this, [=](const QVariantMap &result){
        QString inputSource = result.value("value").toMap().value("mediaRoles").toMap().value("mediaData").toMap().value("metaData").toMap().value("serviceID").toString();
        if (inputSource.isEmpty()) {
            inputSource = result.value("value").toMap().value("trackRoles").toMap().value("mediaData").toMap().value("metaData").toMap().value("serviceID").toString();
        }
        if (!inputSourceMap.contains(inputSource)) {
            qCWarning(dcStreamSDK()) << "Unknown input source:" << inputSource;
            return;
        }
        qCDebug(dcStreamSDK()) << "Input source is" << inputSource;
        m_inputSource = inputSourceMap.value(inputSource);
        emit inputSourceChanged(m_inputSource);
    });

}

QVariantMap StreamSDKDevBoard::composeComplexInputSourcePayload(const QString &inputSource)
{
    QVariantMap params;
    params.insert("control", "play");

    QVariantMap mediaRoles;
    mediaRoles.insert("type", "audio");
    mediaRoles.insert("audioType", "audioBroadcast");

    QVariantMap mediaData;
    QVariantMap metaData;
    QVariantList resources;
    QVariantMap resource;
    resource.insert("bitsPerSample", 16);
    resource.insert("mimeType", "audio/unknown");
    resource.insert("nrAudioChannels", 2);
    resource.insert("sampleFrequency", 48000);
    if (inputSource == "Line-in (AUX)") {
        mediaRoles.insert("path", "ui:/auxaux_plug");
        metaData.insert("serviceID", "AUX");
        resource.insert("uri", "alsa://aux_plug?rate=48000?channels=2?format=S16LE?latency-time=5000?buffer-time=50000");
        mediaRoles.insert("title", "Line-in (AUX)");
    } else if (inputSource == "SPDIF in") {
        mediaRoles.insert("path", "ui:/spdifinspdifin_plug");
        metaData.insert("serviceID", "SPDIFIN");
        resource.insert("uri", "alsa://spdifin_plug?rate=48000?channels=2?format=S16LE");
        mediaRoles.insert("title", "SPDIF in");
    } else {
        qCWarning(dcStreamSDK()) << "Cannot compose input source for source:" << inputSource;
        return QVariantMap();
    }

    resources.append(resource);
    mediaData.insert("resources", resources);

    mediaData.insert("metaData", metaData);
    mediaRoles.insert("mediaData", mediaData);
    params.insert("mediaRoles", mediaRoles);

    return params;
}
