/*
* Copyright (C) 2024, KylinSoft Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <https://www.gnu.org/licenses/>.
*
* Authors: zhouyong <zhouyong01@kylinos.cn>
*
*/
#include "ukui-audio-sink-control.h"

#include "Util.h"
#include "../model/ukui-audio-sink-model.h"
#include "./ukui-audio-control.h"
#include "../common/common.h"

using namespace UkuiAudioFramwork;

UKUIAudioSinkControl& UKUIAudioSinkControl::getInstance()
{
    static UKUIAudioSinkControl instance;
    return instance;
}

bool UKUIAudioSinkControl::initData()
{
    bool ret = true;

    return ret;
}

bool UKUIAudioSinkControl::initSlots()
{
    bool ret = true;
    ret = connect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&)));
    ret = connect(&DBusClient::getInstance(), SIGNAL(muteChangedSignal(int, int, bool)), this, SLOT(muteChangedSlots(int, int, bool)));
    ret = connect(&DBusClient::getInstance(), SIGNAL(deviceChangedSignal(int, const QString&, const QString&)), this, SLOT(deviceChangedSlots(int, const QString&, const QString&)));
    ret = connect(&DBusClient::getInstance(), SIGNAL(deviceAdjustSignal(int)), this, SLOT(deviceAdjustSlots(int)));
    ret = connect(&DBusClient::getInstance(), SIGNAL(addStreamSignal(int, const QString&, const QString&)), this, SLOT(addStreamSlots(int, const QString&, const QString&)));
    ret = connect(&DBusClient::getInstance(), SIGNAL(removeStreamSignal(int)), this, SLOT(removeStreamSlots(int)));
    ret = connect(&DBusClient::getInstance(), SIGNAL(settingsChangedSignal(QString,QDBusVariant)), this, SLOT(settingsChangedSlots(QString,QDBusVariant)));
    //start
    ret = QDBusConnection::sessionBus().connect(QString(), QString("/"), QString("org.kylin.music"), QString("sinkInputVolumeChanged"),
                                                this, SLOT(streamVolumeChangedSlots(QString,int,bool)));
    ret = QDBusConnection::sessionBus().connect(QString(), QString("/"), QString("org.kylin.video"), QString("sinkInputVolumeChanged"),
                                                this, SLOT(streamVolumeChangedSlots(QString,int,bool)));
    //end
    return ret;
}

bool UKUIAudioSinkControl::initView()
{
    bool ret = true;
    UKUIAudioSinkModel::getInstance().addDeviceHeader();
    UKUIAudioSinkModel::getInstance().addStreamHeader();

    return ret;
}

void UKUIAudioSinkControl::volumeChangedSlots(int type, int idx, const QDBusVariant& value)
{
    qDebug() << __func__ << "type:" << type << "idx:" << idx << "value:" << value.variant();
    if (type == EnumToInt(NodeType::OUTPUT)) {
        setSinkVolumeValue(value.variant().toInt());
    }
}

void UKUIAudioSinkControl::muteChangedSlots(int type, int index, bool mute)
{
    qDebug() << __func__ << "type:" << type << "index:" << index << "mute:" << mute;
    if (type == EnumToInt(NodeType::OUTPUT)) {
        setSinkMuteStatus(mute);
    }
}

void UKUIAudioSinkControl::deviceChangedSlots(int type, const QString& portName, const QString& cardName)
{
    qDebug() << __func__ << "type:" << type << "portName:" << portName << "cardName:" << cardName;
    switch (IntToEnum<NodeType>(type)) {
    case NodeType::OUTPUT: {
        emit sinkDeviceChanged(portName, cardName);
    }
        break;
    case NodeType::INPUT:
    case NodeType::SINK_INPUT:
    case NodeType::SOURCE_OUTPUT:
    default:
        break;
    }
}

void UKUIAudioSinkControl::deviceAdjustSlots(int type)
{
    qDebug() << __func__ << "type:" << type;
    switch (IntToEnum<NodeType>(type)) {
    case NodeType::OUTPUT: {
        emit sinkDeviceAdjust();
    }
        break;
    case NodeType::INPUT:
    case NodeType::SINK_INPUT:
    case NodeType::SOURCE_OUTPUT:
    default:
        break;
    }
}

void UKUIAudioSinkControl::addStreamSlots(int idx, const QString& iconName, const QString& descName)
{
    qDebug() << __func__ << "idx:" << idx << "iconName:" << iconName << "descName:" << descName;
}

void UKUIAudioSinkControl::removeStreamSlots(int idx)
{
    qDebug() << __func__ << "idx:" << idx;
}

void UKUIAudioSinkControl::settingsChangedSlots(const QString& key, const QDBusVariant& value)
{
    qDebug() << __func__ << "key:" << key << "value:" << value.variant();
    if (key.compare(VOLUME_BOOST_KEY) == 0)
    {
        setSinkVolumeBoostStatus(value.variant().toBool());
    }
}

void UKUIAudioSinkControl::streamVolumeChangedSlots(QString name, int value, bool muteStatus)
{
    qDebug() << __func__ << "name" << name << "value" << value << "muteStatus" << muteStatus;
    m_sinkInputName = name;
    m_sinkInputValue = value;
    m_sinkInputMuteStatus = muteStatus;

    if (nullptr == m_debounceTimer) {
        m_debounceTimer = new QTimer(this);
        m_debounceTimer->setSingleShot(true);
        QObject::connect(m_debounceTimer, &QTimer::timeout, this, &UKUIAudioSinkControl::debounceTimerSlots);
    } else {
        m_debounceTimer->stop();
        m_debounceTimer->start(200);
    }
}

void UKUIAudioSinkControl::debounceTimerSlots()
{
    qDebug() << __func__ << "m_sinkInputName" << m_sinkInputName << "m_sinkInputValue" << m_sinkInputValue << "m_sinkInputMuteStatus" << m_sinkInputMuteStatus;
    int streamIndex = UKUIAudioSinkModel::getInstance().getStreamIndexByStreamIconName(m_sinkInputName);
    if (streamIndex == -1) {
        qDebug() << __func__ << "streamIndex invaild .";
        return;
    }

    UKUIAudioControl::getInstance().startTask(Common::TaskID_SetSinkInputVolume, streamIndex, m_sinkInputValue);
}

bool UKUIAudioSinkControl::getSinkVolume(int index, QVariant& retValue)
{
    qDebug() << __func__ << "index" << index;
    bool ret = true;
    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                     AUDIO_OBJECT_PATH,
                                                                     AUDIO_INTERFACE,
                                                                     "getVolume",
                                                                     EnumToInt(NodeType::OUTPUT), index);
    if (reply.arguments().isEmpty())
    {
        qDebug() << __func__ << "arguments is nullptr...";
        ret = false;
        return ret;
    }

    retValue.setValue(reply.arguments());

    return ret;
}

void UKUIAudioSinkControl::updateSinkVolume(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }

    setSinkVolumeValue(retValue.value<QList<QVariant>>().at(0).toInt());
}

bool UKUIAudioSinkControl::setSinkVolume(int index, int value)
{
    qDebug() << __func__ << "index" << index << "value" << value;
    setSinkVolumeValue(value);

    bool ret = true;
    DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                             AUDIO_OBJECT_PATH,
                                             AUDIO_INTERFACE,
                                             "setVolume",
                                             EnumToInt(NodeType::OUTPUT), index, value);

    return ret;
}

bool UKUIAudioSinkControl::getSinkMute(int index, QVariant& retValue)
{
    qDebug() << __func__ << "index" << index;
    bool ret = true;
    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                      AUDIO_OBJECT_PATH,
                                                                      AUDIO_INTERFACE,
                                                                      "getMute",
                                                                      EnumToInt(NodeType::OUTPUT), index);
    if (reply.arguments().isEmpty())
    {
        qDebug() << __func__ << "arguments is nullptr...";
        ret = false;
        return ret;
    }

    retValue.setValue(reply.arguments());

    return ret;
}

void UKUIAudioSinkControl::updateSinkMute(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }

    setSinkMuteStatus(retValue.value<QList<QVariant>>().at(0).toBool());
}

bool UKUIAudioSinkControl::setSinkMute(int index, bool muted)
{
    qDebug() << __func__ << "index" << index << "muted" << muted;
    setSinkMuteStatus(muted);

    bool ret = true;
    DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                             AUDIO_OBJECT_PATH,
                                             AUDIO_INTERFACE,
                                             "setMute",
                                             EnumToInt(NodeType::OUTPUT), index, muted);

    return ret;
}

bool UKUIAudioSinkControl::getSinkAvailablePortList(QVariant& retValue)
{
    qDebug() << __func__ << "enter";
    bool ret = true;
    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                     AUDIO_OBJECT_PATH,
                                                                     AUDIO_INTERFACE,
                                                                     "getAvailablePortList",
                                                                     EnumToInt(NodeType::OUTPUT));
    if (reply.arguments().isEmpty())
    {
        qDebug() << __func__ << "arguments is nullptr...";
        ret = false;
        return ret;
    }

    retValue.setValue(reply.arguments());

    isExitMultiAudioDevice();

    return ret;
}

void UKUIAudioSinkControl::updateSinkAvailablePortList(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }

    UKUIAudioSinkModel::getInstance().clearDevice();
    const QDBusArgument& dbusArgs = retValue.value<QList<QVariant>>().at(0).value<QDBusArgument>();
    dbusArgs.beginArray();
    while (!dbusArgs.atEnd()) {
        ClientDeviceInfo info;
        dbusArgs >> info;
        UKUIAudioSinkModel::getInstance().addDevice(info);
    }
    dbusArgs.endArray();

    if (UKUIAudioSinkModel::getInstance().getDeviceCount() <= 0) {
        UKUIAudioSinkModel::getInstance().addEmptyDevice();
    } else if (UKUIAudioSinkModel::getInstance().getEnabledDeviceCount() <= 0) {
        UKUIAudioSinkModel::getInstance().addEmptyDevice();
    }

    if (m_isExitMultiAudioDevice) {
        UKUIAudioSinkModel::getInstance().addDevice(m_SinkMutiAudioDeviceInfo);
    }
}

bool UKUIAudioSinkControl::getSinkInputList(QVariant& retValue)
{
    qDebug() << __func__ << "enter";
    bool ret = true;
    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                  AUDIO_OBJECT_PATH,
                                                                  AUDIO_INTERFACE,
                                                                  "getStreamMediaList");
    if (reply.arguments().isEmpty())
    {
        qDebug() << __func__ << "arguments is nullptr...";
        ret = false;
        return ret;
    }

    retValue.setValue(reply.arguments());

    return ret;
}

void UKUIAudioSinkControl::updateSinkInputList(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }

    UKUIAudioSinkModel::getInstance().clearStream();
    qRegisterMetaType<ClientMediaInfo>();
    qRegisterMetaType<ClientStreamMediaInfo>();
    qDBusRegisterMetaType<ClientMediaInfo>();
    qDBusRegisterMetaType<ClientStreamMediaInfo>();
    QList<ClientStreamMediaInfo> list;

    const QDBusArgument& dbusArgs = retValue.value<QList<QVariant>>().at(0).value<QDBusArgument>();
    dbusArgs.beginArray();
    while (!dbusArgs.atEnd()) {
        ClientStreamMediaInfo info;
        dbusArgs >> info;
        list.push_back(info);
        UKUIAudioSinkModel::getInstance().addStream(info);
    }
    dbusArgs.endArray();

    if (UKUIAudioSinkModel::getInstance().getStreamCount() <= 0) {
        UKUIAudioSinkModel::getInstance().addEmptyStream();
    }
}

void UKUIAudioSinkControl::updateSinkInputVolume(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }
}

bool UKUIAudioSinkControl::setSinkInputVolume(int index, int value)
{
    qDebug() << __func__ << "index" << index << "value" << value;

    bool ret = true;
    DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                             AUDIO_OBJECT_PATH,
                                             AUDIO_INTERFACE,
                                             "setVolume",
                                             EnumToInt(NodeType::SINK_INPUT), index, value);

    return ret;
}

bool UKUIAudioSinkControl::sendSinkInputVolumeChanged(const QString& iconName, int value, bool muteStatus)
{
    qDebug() << __func__ << "iconName" << iconName << "value" << value << "muteStatus" << muteStatus;

    bool ret = true;
    if ((iconName.compare(QString("kylin-music")) == 0)
        || (iconName.compare(QString("kylin-video")) == 0)) {
        QDBusMessage msg = QDBusMessage::createSignal(QString("/"), QString("org.ukui.media"), QString("sinkVolumeChanged"));
        msg << iconName << value << muteStatus;
        QDBusConnection::sessionBus().send(msg);
        qDebug() << __func__ << "iconName:" << iconName << "value:" << value;
    }

    return ret;
}

bool UKUIAudioSinkControl::getSinkDefaultDevice(int index, QVariant& retValue)
{
    qDebug() << __func__ << "enter" << index;
    bool ret = true;
    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                      AUDIO_OBJECT_PATH,
                                                                      AUDIO_INTERFACE,
                                                                      "getDefaultDevice",
                                                                      EnumToInt(NodeType::OUTPUT), index);
    if (reply.arguments().isEmpty()) {
        qDebug() << __func__ << "arguments is nullptr...";
        ret = false;
        return ret;
    }

    retValue.setValue(reply.arguments());

    return ret;
}

void UKUIAudioSinkControl::updateSinkDefaultDevice(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }

    const QDBusArgument& dbusArgs = retValue.value<QList<QVariant>>().at(0).value<QDBusArgument>();
    ClientDefaultDeviceInfo info;
    dbusArgs >> info;
    UKUIAudioSinkModel::getInstance().setDeviceActivePort(info.activePortName, info.cardName);
}

void UKUIAudioSinkControl::updateSinkDefaultDevice(const QString& portName, const QString& cardName)
{
    qDebug() << __func__ << "enter" << "portName:" << portName << "cardName:" << cardName;
    UKUIAudioSinkModel::getInstance().setDeviceActivePort(portName, cardName);
}

bool UKUIAudioSinkControl::setSinkDefaultDevice(int index, const QString &portName, const QString&cardName)
{
    qDebug() << __func__ << "enter" << index << "portName:" << portName << "cardName:" << cardName;
    bool ret = true;
    DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                             AUDIO_OBJECT_PATH,
                                             AUDIO_INTERFACE,
                                             "setDefaultDevice",
                                             EnumToInt(NodeType::OUTPUT), index, portName, cardName);

    return ret;
}

bool UKUIAudioSinkControl::getSinkVolumeBoostStatus(QVariant& retValue)
{
    qDebug() << __func__ << "enter";
    bool ret = true;
    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                  AUDIO_OBJECT_PATH,
                                                                  SETTINGS_INTERFACE,
                                                                  "getVolumeBoostStatus");
    if (reply.arguments().isEmpty()) {
        qDebug() << __func__ << "arguments is nullptr...";
        ret = false;
        return ret;
    }

    retValue.setValue(reply.arguments());

    return ret;
}

void UKUIAudioSinkControl::updateSinkVolumeBoostStatus(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }

    setSinkVolumeBoostStatus(retValue.value<QList<QVariant>>().at(0).toBool());
}

bool UKUIAudioSinkControl::getSinkVolumeBoostValue(QVariant& retValue)
{
    qDebug() << __func__ << "enter";
    bool ret = true;
    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                  AUDIO_OBJECT_PATH,
                                                                  SETTINGS_INTERFACE,
                                                                  "getVolumeBoostValue");
    if (reply.arguments().isEmpty()) {
        qDebug() << __func__ << "arguments is nullptr...";
        ret = false;
        return ret;
    }

    retValue.setValue(reply.arguments());

    return ret;
}

void UKUIAudioSinkControl::updateSinkVolumeBoostValue(QVariant retValue)
{
    qDebug() << __func__ << "enter";
    if (!retValue.isValid()) {
        qDebug() << __func__ << "retValue is invalid...";
        return;
    }

    setSinkVolumeBoostValue(retValue.value<QList<QVariant>>().at(0).toInt());
}

bool UKUIAudioSinkControl::isExitMultiAudioDevice()
{
    m_isExitMultiAudioDevice = false;
    auto status = getSinkMultiAudioCombineStatus();
    if (status) {
        auto sinkList = getSinkList();
        auto it = std::find_if(sinkList.begin(), sinkList.end(), [](const ClientDefaultDeviceInfo& info) {
            return info.name.contains("combine");
        });

        if (it != sinkList.end()) {
            m_isExitMultiAudioDevice = true;
            m_SinkMutiAudioDeviceInfo = *it;
            qDebug() << __func__ << "Add Multiple Bluetooth outputs, (*it).activePortName " << (*it).activePortName <<
                "(*it).cardName" << (*it).cardName;
        }
    }

    return m_isExitMultiAudioDevice;
}

bool UKUIAudioSinkControl::getSinkMultiAudioCombineStatus() const
{
    QDBusReply<bool> reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                      AUDIO_OBJECT_PATH,
                                                                      AUDIO_INTERFACE,
                                                                      "getMultiAudioCombineStatus",
                                                                      EnumToInt(NodeType::OUTPUT));

    return reply;
}

QList<ClientDefaultDeviceInfo> UKUIAudioSinkControl::getSinkList() const
{
    QList<ClientDefaultDeviceInfo> list;

    QDBusMessage reply = DBusClient::getInstance().dbusMethodCall(AUDIO_OBJECT_SERVICE,
                                                                  AUDIO_OBJECT_PATH,
                                                                  AUDIO_INTERFACE,
                                                                  "getSinkList");

    if (reply.arguments().isEmpty()) {
        qDebug() << __func__ << "empty sinklist";
        return {};
    }

    const QDBusArgument& dbusArgs = reply.arguments().at(0).value<QDBusArgument>();

    dbusArgs.beginArray();
    while (!dbusArgs.atEnd()) {
        ClientDefaultDeviceInfo info;
        dbusArgs >> info;
        list.push_back(info);
    }
    dbusArgs.endArray();

    return list;
}

int UKUIAudioSinkControl::getSinkDeviceCount()
{
    return UKUIAudioSinkModel::getInstance().getDeviceCount();
}

int UKUIAudioSinkControl::getEnabledSinkDeviceCount()
{
    return UKUIAudioSinkModel::getInstance().getEnabledDeviceCount();
}

void UKUIAudioSinkControl::releaseData()
{
    qDebug() << "UKUIAudioSinkControl::releaseData" << "enter";
    m_sinkVolumeValue = -1;
    m_sinkMuteStatus = false;
    m_sinkVolumeStatusIcon = "";
    m_sinkVolumeBoostStatus = false;
    m_sinkVolumeBoostValue = -1;
    m_isExitMultiAudioDevice = false;
    UKUIAudioSinkModel::getInstance().releaseModel();
    qDebug() << "UKUIAudioSinkControl::releaseData" << "leave";
}

void UKUIAudioSinkControl::releaseSlots()
{
    qDebug() << "UKUIAudioSinkControl::releaseSlots" << "enter";
    disconnect(&DBusClient::getInstance(), SIGNAL(volumeChangedSignal(int, int, const QDBusVariant&)), this, SLOT(volumeChangedSlots(int, int, const QDBusVariant&)));
    disconnect(&DBusClient::getInstance(), SIGNAL(muteChangedSignal(int, int, bool)), this, SLOT(muteChangedSlots(int, int, bool)));
    disconnect(&DBusClient::getInstance(), SIGNAL(deviceChangedSignal(int, const QString&, const QString&)), this, SLOT(deviceChangedSlots(int, const QString&, const QString&)));
    disconnect(&DBusClient::getInstance(), SIGNAL(deviceAdjustSignal(int)), this, SLOT(deviceAdjustSlots(int)));
    disconnect(&DBusClient::getInstance(), SIGNAL(addStreamSignal(int, const QString&, const QString&)), this, SLOT(addStreamSlots(int, const QString&, const QString&)));
    disconnect(&DBusClient::getInstance(), SIGNAL(removeStreamSignal(int)), this, SLOT(removeStreamSlots(int)));
    qDebug() << "UKUIAudioSinkControl::releaseSlots" << "leave";
}

UKUIAudioSinkControl::~UKUIAudioSinkControl()
{
    qDebug() << __func__ << "enter";
    releaseSlots();
    releaseData();

    if (m_debounceTimer) {
        m_debounceTimer->deleteLater();
        m_debounceTimer = nullptr;
    }
}
