/*
 * Copyright (C) 2019 Tianjin KYLIN Information Technology 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, 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 <http://www.gnu.org/licenses/&gt;.
 *
 */

#include "ukmedia_volume_control.h"
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <QEventLoop>
#include <set>
#include <pulse/introspect.h>
#include <QIcon>
#include <QStyle>
#include <QLabel>
#include <QSettings>
#include <QDebug>
#include <QSlider>

//pa_sink_info *m_pDefaultSink;
/* Used for profile sorting */
struct profile_prio_compare {
    bool operator() (pa_card_profile_info2 const * const lhs, pa_card_profile_info2 const * const rhs) const {

        if (lhs->priority == rhs->priority)
            return strcmp(lhs->name, rhs->name) > 0;

        return lhs->priority > rhs->priority;
    }
};

struct sink_port_prio_compare {
    bool operator() (const pa_sink_port_info& lhs, const pa_sink_port_info& rhs) const {

        if (lhs.priority == rhs.priority)
            return strcmp(lhs.name, rhs.name) > 0;

        return lhs.priority > rhs.priority;
    }
};

struct source_port_prio_compare {
    bool operator() (const pa_source_port_info& lhs, const pa_source_port_info& rhs) const {

        if (lhs.priority == rhs.priority)
            return strcmp(lhs.name, rhs.name) > 0;

        return lhs.priority > rhs.priority;
    }
};

UkmediaVolumeControl::UkmediaVolumeControl():

    showSinkInputType(SINK_INPUT_CLIENT),
    showSinkType(SINK_ALL),
    showSourceOutputType(SOURCE_OUTPUT_CLIENT),
    showSourceType(SOURCE_NO_MONITOR),

    canRenameDevices(false),
    m_connected(false),
    m_config_filename(nullptr) {
    profileNameMap.clear();
    //创建声音初始化记录文件
    customSoundFile = new CustomSound();
    customSoundFile->createAudioFile();
    connectToPulse(this);

    if (reconnectTimeout >= 0) {

    }

    if (reconnectTimeout < 0)
        showError(QObject::tr("Fatal Error: Unable to connect to PulseAudio").toUtf8().constData());
}

/*
 *  设置输出设备静音
 */
void UkmediaVolumeControl::setSinkMute(bool status)
{
    pa_operation* o;

    if (!(o = pa_context_set_sink_mute_by_index(getContext(), sinkIndex, status, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
    }
}

/*
 *  设置输出设备音量
 */
void UkmediaVolumeControl::setSinkVolume(int index,int value)
{
    if(m_pDefaultSink == nullptr){
        return;
    }

    pa_cvolume v = m_pDefaultSink->volume;
    v.channels = channel;
    for (int i=0;i<v.channels;i++)
        v.values[i] = value;
    if (balance != 0) {
        pa_cvolume_set_balance(&v,&channelMap,balance);
    }

    pa_operation* o;
    if (sinkMuted) {
        if (!(o = pa_context_set_sink_mute_by_index(getContext(), index,false, nullptr, nullptr))) {
            showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
        }
    }
    if (!(o = pa_context_set_sink_volume_by_index(getContext(), index, &v, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
    }
    qDebug() << "set sink volume" << sinkIndex << "channel:" << v.channels << "balance:" << balance;
}

/*
 *  设置输入设备静音
 */
void UkmediaVolumeControl::setSourceMute(bool status)
{
    pa_operation* o;

    if (!(o = pa_context_set_source_mute_by_index(getContext(), sourceIndex, status, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
    }
}


/*
 * 设置输入设备音量
 */
void UkmediaVolumeControl::setSourceVolume(int index,int value)
{
    if(m_pDefaultSink == nullptr){
        return;
    }

    pa_cvolume v = m_pDefaultSink->volume;
    v.channels = inputChannel;
    for (int i=0;i<v.channels;i++)
        v.values[i] = value;

    qDebug() << "set source volume" << sourceIndex << v.channels << value;
    pa_operation* o;
    if (!(o = pa_context_set_source_volume_by_index(getContext(), index, &v, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
    }
}

/*
 * 获取输出设备的静音状态
 */
bool UkmediaVolumeControl::getSinkMute()
{
    return sinkMuted;
}

/*
 * 获取输出设备的音量值
 */
int UkmediaVolumeControl::getSinkVolume()
{
    return sinkVolume;
}

/*
 * 获取输入设备的静音状态
 */
bool UkmediaVolumeControl::getSourceMute()
{
    return sourceMuted;
}

/*
 * 获取输入设备的音量值
 */
int UkmediaVolumeControl::getSourceVolume()
{
    return sourceVolume;
}

int UkmediaVolumeControl::getDefaultSinkIndex()
{
    pa_operation *o;
    if (!(o = pa_context_get_server_info(getContext(), serverInfoIndexCb, this))) {
        showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData());
        return -1;
    }
    pa_operation_unref(o);

    return sinkIndex;
}

/*
 * 根据名称获取sink input静音状态
 */
bool UkmediaVolumeControl::getSinkInputMuted(QString description)
{
    int muteStatus = 1;

    QMap<QString,int>::iterator it;
    for (it=sinkInputMuteMap.begin();it!=sinkInputMuteMap.end();) {
        qDebug() << "getSinkInputMuted" << it.key() << description << it.value();
        if (it.key() == description) {
            muteStatus = it.value();
            break;
        }
        ++it;
    }

    return muteStatus==1?true:false;
}

/*
 * 根据模块名称获取对应模块索引
 */
int UkmediaVolumeControl::getModuleIndexByName(QString name)
{
    findModuleStr = name;
    pa_context_get_module_info_list(getContext(), moduleInfoCb,this);
}

/*
 *  滑动条更改设置sink input 音量值
 */
void UkmediaVolumeControl::setSinkInputVolume(int index, int value,int channel)
{
    pa_cvolume v = m_pDefaultSink->volume;

    v.channels = channel;
    for (int i=0;i<v.channels;i++)
        v.values[i] = value;

    qDebug() << "set sink input volume" << index << v.channels << value;

    pa_operation* o;
    if (!(o = pa_context_set_sink_input_mute(getContext(), index,false, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
    }

    if (!(o = pa_context_set_sink_input_volume(getContext(), index, &v, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
    }
}

/*
 *  滑动条更改设置sink input静音状态
 */
void UkmediaVolumeControl::setSinkInputMuted(int index, bool status)
{
    qDebug() << "set sink input muted" << index << status;

    pa_operation* o;
    if (!(o = pa_context_set_sink_input_mute(getContext(), index,status, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_volume_by_index() failed").toUtf8().constData());
    }
}

/*
 *  滑动条更改设置source output 音量值
 */
void UkmediaVolumeControl::setSourceOutputVolume(int index, int value, int channel)
{
    pa_cvolume v = m_pDefaultSink->volume;
    v.channels = channel;
    for (int i=0;i<v.channels;i++)
        v.values[i] = value;

    qDebug() << "set source output volume" << index << v.channels << value;

    pa_operation* o;
    if (!(o = pa_context_set_source_output_mute(getContext(), index,false, nullptr, nullptr))) {
        showError(tr("pa_context_set_source_output_volume() failed").toUtf8().constData());
    }

    if (!(o = pa_context_set_source_output_volume(getContext(), index, &v, nullptr, nullptr))) {
        showError(tr("pa_context_set_source_output_volume() failed").toUtf8().constData());
    }
}

/*
 *  滑动条更改设置source output静音状态
 */
void UkmediaVolumeControl::setSourceOutputMuted(int index, bool status)
{
    qDebug() << "set source output muted" << index << status;

    pa_operation* o;
    if (!(o = pa_context_set_source_output_mute(getContext(), index,status, nullptr, nullptr))) {
        showError(tr("pa_context_set_source_output_mute() failed").toUtf8().constData());
    }
}

/*
 * 设置声卡的配置文件
 */
bool UkmediaVolumeControl::setCardProfile(int index, const gchar *name)
{
    qDebug() << "setCardProfile" << index << name;

    pa_operation* o;
    if (!(o = pa_context_set_card_profile_by_index(getContext(), index, name, nullptr, nullptr))) {
        showError(tr("pa_context_set_card_profile_by_index() failed").toUtf8().constData());
        return false;
    }

    return true;
}

/*
 * 设置默认的输出设备
 */
bool UkmediaVolumeControl::setDefaultSink(const gchar *name)
{
    qDebug() << "setDefaultSink" << name;

    pa_operation* o;
    if (!(o = pa_context_set_default_sink(getContext(), name, nullptr, nullptr))) {
        showError(tr("pa_context_set_default_sink() failed").toUtf8().constData());
        return false;
    }

    return true;
}

/*
 * 设置默认的输入设备
 */
bool UkmediaVolumeControl::setDefaultSource(const gchar *name)
{
    qDebug() << "setDefaultSource" << name;

    pa_operation* o;
    if (!(o = pa_context_set_default_source(getContext(), name, nullptr, nullptr))) {
        showError(tr("pa_context_set_default_source() failed").toUtf8().constData());
        return false;
    }

    return true;
}

/*
 * 设置输出设备的端口
 */
bool UkmediaVolumeControl::setSinkPort(const gchar *sinkName, const gchar *portName)
{
    qDebug() << "setSinkPort" << "sinkname:" << sinkName << "portname:" << portName;

    pa_operation* o;
    if (!(o = pa_context_set_sink_port_by_name(getContext(), sinkName, portName, nullptr, nullptr))) {
        showError(tr("pa_context_set_sink_port_by_name() failed").toUtf8().constData());
        return false;
    }

    return true;
}

/*
 * 设置输入设备端口
 */
bool UkmediaVolumeControl::setSourcePort(const gchar *sourceName, const gchar *portName)
{
    qDebug() << "setSourcePort" << sourceName << portName;
    pa_operation* o;
    if (!(o = pa_context_set_source_port_by_name(getContext(), sourceName, portName, nullptr, nullptr))) {
        showError(tr("pa_context_set_source_port_by_name() failed").toUtf8().constData());
        return false;
    }
    return true;
}

/*
 * kill 索引为index 的sink input
 */
bool UkmediaVolumeControl::killSinkInput(int index)
{
    pa_operation* o;
    if (!(o = pa_context_kill_sink_input(getContext(), index, nullptr, nullptr))) {
        showError(tr("pa_context_kill_sink_input() failed").toUtf8().constData());
        return false;
    }
    return true;
}

UkmediaVolumeControl::~UkmediaVolumeControl() {

    while (!clientNames.empty()) {
        auto i = clientNames.begin();
        g_free(i->second);
        clientNames.erase(i);
    }
}

static void updatePorts(UkmediaVolumeControl *d, std::map<QByteArray, PortInfo> &ports) {
    std::map<QByteArray, PortInfo>::iterator it;
    PortInfo p;
    for (auto & port : d->dPorts) {
        QByteArray desc;
        it = ports.find(port.first);

        if (it == ports.end())
            continue;

        p = it->second;
        desc = p.description;

        if (p.available == PA_PORT_AVAILABLE_YES)
            desc +=  UkmediaVolumeControl::tr(" (plugged in)").toUtf8().constData();
        else if (p.available == PA_PORT_AVAILABLE_NO) {
            if (p.name == "analog-output-speaker" ||
                p.name == "analog-input-microphone-internal")
                desc += UkmediaVolumeControl::tr(" (unavailable)").toUtf8().constData();
            else
                desc += UkmediaVolumeControl::tr(" (unplugged)").toUtf8().constData();
        }
        port.second = desc;
    }
    qDebug() << "updatePorts" << p.name << p.description;

    Q_EMIT d->updatePortSignal();

    it = ports.find(d->activePort);

    if (it != ports.end()) {
        p = it->second;
//        d->setLatencyOffset(p.latency_offset);
    }
}

static void setIconByName(QLabel* label, const char* name) {
    QIcon icon = QIcon::fromTheme(name);
    int size = label->style()->pixelMetric(QStyle::PM_ToolBarIconSize);
    QPixmap pix = icon.pixmap(size, size);
    label->setPixmap(pix);
}

int UkmediaVolumeControl::boolToInt(bool b)
{
    if (b == true)
        return 1;
    else
        return 0;
}

void UkmediaVolumeControl::updateCard(UkmediaVolumeControl *c, const pa_card_info &info) {
    const char *description;
    QMap<QString,QString> tempInput;
    QMap<QString,QString> tempOutput;
    QList<QString> profileName;
    QMap<QString,QString>portMap;
    QMap<QString,QString> outputPortNameLabelMap;
    QMap<QString,QString>inputPortNameLabelMap;
    QMap<QString,int> profilePriorityMap;
    std::set<pa_card_profile_info2 *, profile_prio_compare> profile_priorities;

    description = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_DESCRIPTION);//获取device.description字符串

    hasSinks = c->hasSources = false;
    profile_priorities.clear();
    for (pa_card_profile_info2 ** p_profile = info.profiles2; *p_profile != nullptr; ++p_profile) {
//        c->hasSinks = c->hasSinks || ((*p_profile)->n_sinks > 0);
//        c->hasSources = c->hasSources || ((*p_profile)->n_sources > 0);
        profile_priorities.insert(*p_profile);
        profileName.append((*p_profile)->name);
        profilePriorityMap.insertMulti((*p_profile)->name,(*p_profile)->priority);
    }
    QList<QMap<QString,int>> cardProfilePriorityMapList;
    if(cardProfilePriorityMap.isEmpty())
        cardProfilePriorityMap.insertMulti(info.index,profilePriorityMap);

    cardProfilePriorityMapList = cardProfilePriorityMap.values();
    if(!cardProfilePriorityMapList.contains(profilePriorityMap))
        cardProfilePriorityMap.insertMulti(info.index,profilePriorityMap);

    c->ports.clear();
    for (uint32_t i = 0; i < info.n_ports; ++i) {
        PortInfo p;
        p.name = info.ports[i]->name;
        p.description = info.ports[i]->description;
        p.priority = info.ports[i]->priority;
        p.available = info.ports[i]->available;
        p.direction = info.ports[i]->direction;
        p.latency_offset = info.ports[i]->latency_offset;
        if (info.ports[i]->profiles2)//bug#92426 该蓝牙音响存在输入端口无输入配置文件会导致段错误
            for (pa_card_profile_info2 ** p_profile = info.ports[i]->profiles2; *p_profile != nullptr; ++p_profile) {
                p.profiles.push_back((*p_profile)->name);
            }
        if (p.direction == 1 && p.available != PA_PORT_AVAILABLE_NO) {
//            portMap.insertMulti(p.name,p.description.data());

            //新增UI设计，combobox显示portname+description
            QString outputPortName = p.description.data();//端口名(如：扬声器,模拟耳机..)
            QString outputPortName_and_description = outputPortName + "（" + description + "）";

            tempOutput.insertMulti(p.name,outputPortName_and_description);

            QList<QString> portProfileName;
            for (auto p_profile : p.profiles) {
                portProfileName.append(p_profile.data());
                outputPortNameLabelMap.insertMulti(p.description.data(),p_profile.data());
                qDebug() << "ctf profilename map insert -----------" << p.description.data() << p_profile.data();
            }
            profileNameMap.insert(info.index,outputPortNameLabelMap);
            cardProfileMap.insert(info.index,portProfileName);
        }
        else if (p.direction == 2 && p.available != PA_PORT_AVAILABLE_NO){

            qDebug() << " add source port name "<< info.index << p.name << p.description.data();
            QString inputPortName = p.description.data();//端口名(如：扬声器,模拟耳机..)
            QString inputPortName_and_description = inputPortName + "（" + description + "）";

            tempInput.insertMulti(p.name,inputPortName_and_description);

            for (auto p_profile : p.profiles) {
                inputPortNameLabelMap.insertMulti(p.description.data(),p_profile.data());
            }
            inputPortProfileNameMap.insert(info.index,inputPortNameLabelMap);
        }
        c->ports[p.name] = p;
    }
    inputPortMap.insert(info.index,tempInput);
    outputPortMap.insert(info.index,tempOutput);
    cardActiveProfileMap.insert(info.index,info.active_profile->name);
    c->profiles.clear();
    for (auto p_profile : profile_priorities) {
        bool hasNo = false, hasOther = false;
        std::map<QByteArray, PortInfo>::iterator portIt;
        QByteArray desc = p_profile->description;

        for (portIt = c->ports.begin(); portIt != c->ports.end(); portIt++) {
            PortInfo port = portIt->second;

            if (std::find(port.profiles.begin(), port.profiles.end(), p_profile->name) == port.profiles.end())
                continue;

            if (port.available == PA_PORT_AVAILABLE_NO)
                hasNo = true;
            else {
                hasOther = true;
                break;
            }
        }
        if (hasNo && !hasOther)
            desc += tr(" (unplugged)").toUtf8().constData();

        if (!p_profile->available)
            desc += tr(" (unavailable)").toUtf8().constData();

        c->profiles.push_back(std::pair<QByteArray,QByteArray>(p_profile->name, desc));
        if (p_profile->n_sinks == 0 && p_profile->n_sources == 0)
            c->noInOutProfile = p_profile->name;
    }
    c->activeProfile = info.active_profile ? info.active_profile->name : "";

    //bug 102001 针对hw990 9a0 panguv panguw，由于连上部分音箱后，创建蓝牙声卡时a2d_sink对应transport状态还是不可用的状态，因此进行延迟300ms的异常处理
    QString sessionType = getenv("XDG_SESSION_TYPE");
    if (sessionType.compare("wayland", Qt::CaseSensitive)) {
        if (strstr(info.name,"bluez_card") && strcmp(info.active_profile->name,"off") == 0) {
                qDebug() << "bluez card profile is off,need to set profile...";
                QTimer::singleShot(300,this,[=](){
                    setCardProfile(info.index,"a2dp_sink");
                });
            }
    }


    /* Because the port info for sinks and sources is discontinued we need
     * to update the port info for them here. */
    updatePorts(c,c->ports);

    Q_EMIT checkDeviceSelectionSianal(&info);
}

/*
 * Update output device when the default output device or port is updated
 */
bool UkmediaVolumeControl::updateSink(UkmediaVolumeControl *w,const pa_sink_info &info) {
    bool is_new = false;
    m_defaultSinkVolume = info.volume;
    QMap<QString,QString>temp;
    int volume;
    if (info.volume.channels >= 2)
        volume = MAX(info.volume.values[0],info.volume.values[1]);
    else
        volume = info.volume.values[0];
    if (info.active_port)
        sinkActivePortMap.insert(info.name,info.active_port->name);

    //默认的输出音量
    if (strcmp(defaultSinkName.data(),info.name) == 0) {
        channel = info.volume.channels;
        channelMap = info.channel_map;
        sinkIndex = info.index;
        if (info.active_port) {
            if((strcmp(info.active_port->name,sinkPortName.toLatin1().data()) != 0)){
                //拔出耳机转为扬声器时，音乐暂停信号
                if ((strstr(sinkPortName.toLatin1().data(),"headphone") || strstr(sinkPortName.toLatin1().data(),"Headphone")) \
                    && strstr(info.active_port->name,"speaker") || strstr(info.active_port->name,"Speaker")) {
                    QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sinkPortChanged");
                    message << "portChanged";
                    QDBusConnection::sessionBus().send(message);
                    qDebug() << "sinkPortChanged" << sinkPortName;
                }
//                if(sinkPortName == "")
//                {
//                    QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sinkPortChanged");
//                    message << "portChanged";
//                    QDBusConnection::sessionBus().send(message);
//                    qDebug() << "sinkPortChanged IN sinkindexCb with NO SINK" << w->sinkPortName;
//                    qDebug() << "sinkPortChanged" << sinkPortName;
//                }

                //defaultSinkName不变时不走serverinfo需要手动设置outputListwidgetItem项
                if(strcmp(sinkPortName.toLatin1().data(),info.active_port->name) != 0){
                    sinkPortName = info.active_port->name;
                    QTimer::singleShot(100,this,SLOT(timeoutSlot()));
                }
                else
                    sinkPortName = info.active_port->name;
                //updateSinkFirstEnterFlag用来规避第一次出现osd弹窗提示的情况
                if(updateSinkFirstEnterFlag==false  && !sinkPortName.contains("histen-algo",Qt::CaseInsensitive)){
                    if (sinkPortName.contains("speaker",Qt::CaseInsensitive))
                    {
                        Q_EMIT device_changed_signal("audio-volume-high-symbolic");
                    }
                    else
                    {
                        if(sinkPortName!="")
                            Q_EMIT device_changed_signal("ukui-headset-symbolic");
                    }
                }
                updateSinkFirstEnterFlag = false;
            }
        }
        else {
            if (sinkPortName != "") {

                sinkPortName = "";
                QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sinkPortChanged");//"sourcePortChanged");
                message << "portChanged";
                QDBusConnection::sessionBus().send(message);
                qDebug() << "sinkPortChanged" << sinkPortName;
            }
        }
        defaultOutputCard = info.card;
        QString temp = "";
        balance = pa_cvolume_get_balance(&info.volume,&info.channel_map);
/*
    bug:96232、95568、95523 解决 hw-panguw 机型，找不到对于sinkPortName导致音量无法同步、静音按钮只能按一次等问题
*/
        if (sinkPortName.contains("histen-algo",Qt::CaseInsensitive)) {
            sinkPortName = findSinkActivePortName(PANGUW_SINK);
            temp = "histen-algo";
        }

        if(sinkMuted != info.mute) {
            sinkMuted = info.mute;
            Q_EMIT updateMute(sinkMuted);
        }
        if (sinkVolume != volume) {
            if(temp != "")
                sinkPortName = temp;
            sinkVolume = volume;
            sinkMuted = info.mute;
            Q_EMIT updateVolume(sinkVolume);
        }
    }
    qDebug() << "UpdateSink--->" << "sinkIndex:" << sinkIndex << "sinkPortName:" << sinkPortName << "sinkVolume:" << sinkVolume << "channel:" << channel;

    if (info.ports) {
        for (pa_sink_port_info ** sinkPort = info.ports; *sinkPort != nullptr; ++sinkPort) {
            temp.insertMulti(info.name,(*sinkPort)->name);
        }
        QList <QMap<QString,QString>> sinkPortMapList;
        if(sinkPortMap.isEmpty())
            sinkPortMap.insertMulti(info.card,temp);
        sinkPortMapList = sinkPortMap.values();

        if(!sinkPortMapList.contains(temp))
            sinkPortMap.insertMulti(info.card,temp);

        const char *icon;
        std::set<pa_sink_port_info,sink_port_prio_compare> port_priorities;

        icon = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_ICON_NAME);

        port_priorities.clear();
        for (uint32_t i=0; i<info.n_ports; ++i) {
            port_priorities.insert(*info.ports[i]);
        }

        w->ports.clear();
    }
    return is_new;
}

QString UkmediaVolumeControl::stringRemoveUnrecignizedChar(QString str) {
    str.remove(" ");
    str.remove("/");
    str.remove("(");
    str.remove(")");
    str.remove("[");
    str.remove("]");
    return str;
}


/*
 * stream suspend callback
 */
static void suspended_callback(pa_stream *s, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (pa_stream_is_suspended(s)) {
//        w->updateVolumeMeter(pa_stream_get_device_index(s), PA_INVALID_INDEX, -1);
    }
}

static void read_callback(pa_stream *s, size_t length, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    const void *data;
    double v;

    if (pa_stream_peek(s, &data, &length) < 0) {
        w->showError(UkmediaVolumeControl::tr("Failed to read data from stream").toUtf8().constData());
        return;
    }

    if (!data) {
        /* nullptr data means either a hole or empty buffer.
         * Only drop the stream when there is a hole (length > 0) */
        if (length)
            pa_stream_drop(s);
        return;
    }

    assert(length > 0);
    assert(length % sizeof(float) == 0);

    v = ((const float*) data)[length / sizeof(float) -1];

    pa_stream_drop(s);

    if (v < 0)
        v = 0;
    if (v > 1)
        v = 1;
}

pa_stream* UkmediaVolumeControl::createMonitorStreamForSource(uint32_t source_idx, uint32_t stream_idx = -1, bool suspend = false) {
    pa_stream *s;
    char t[16];
    pa_buffer_attr attr;
    pa_sample_spec ss;
    pa_stream_flags_t flags;

    ss.channels = 1;
    ss.format = PA_SAMPLE_FLOAT32;
    ss.rate = 25;

    memset(&attr, 0, sizeof(attr));
    attr.fragsize = sizeof(float);
    attr.maxlength = (uint32_t) -1;

    snprintf(t, sizeof(t), "%u", source_idx);
    m_pPaContext = getContext();
    if (!(s = pa_stream_new(getContext(), tr("Peak detect").toUtf8().constData(), &ss, nullptr))) {
        showError(tr("Failed to create monitoring stream").toUtf8().constData());
        return nullptr;
    }

    if (stream_idx != (uint32_t) -1)
        pa_stream_set_monitor_stream(s, stream_idx);

    pa_stream_set_read_callback(s, read_callback, this);
    pa_stream_set_suspended_callback(s, suspended_callback, this);

    flags = (pa_stream_flags_t) (PA_STREAM_DONT_MOVE | PA_STREAM_PEAK_DETECT | PA_STREAM_ADJUST_LATENCY |
                                 (suspend ? PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND : PA_STREAM_NOFLAGS) /*|
                                 (!showVolumeMetersCheckButton->isChecked() ? PA_STREAM_START_CORKED : PA_STREAM_NOFLAGS)*/);

    if (pa_stream_connect_record(s, t, &attr, flags) < 0) {
        showError(tr("Failed to connect monitoring stream").toUtf8().constData());
        pa_stream_unref(s);
        return nullptr;
    }
    return s;
}

void UkmediaVolumeControl::updateSource(const pa_source_info &info) {
    int volume;
    if (info.volume.channels >= 2)
        volume = MAX(info.volume.values[0],info.volume.values[1]);
    else
        volume = info.volume.values[0];

    //默认的输出音量
    if (strcmp(defaultSourceName.data(),info.name) == 0) {
        inputChannel = info.volume.channels;
        sourceIndex = info.index;
        if (info.active_port) {
            if (strcmp(info.active_port->name,sourcePortName.toLatin1().data()) != 0) {
                sourcePortName = info.active_port->name;
                QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sourcePortChanged");
                message << "portChanged";
                QDBusConnection::sessionBus().send(message);
                qDebug() << "sourcePortChanged" << sourcePortName;
            }
        }
        else {
            if (sourcePortName != "") {
                sourcePortName = "";
                QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sourcePortChanged");
                message << "portChanged";
                QDBusConnection::sessionBus().send(message);
                qDebug() << "sourcePortChanged" << sourcePortName;
            }
        }

        defaultInputCard = info.card;

        if (sourceMuted != info.mute) {
            sourceMuted = info.mute;
            Q_EMIT updateSourceMute(sourceMuted);
        }

        if (sourceVolume != volume) {
            sourceMuted = info.mute;
            sourceVolume = volume;
            Q_EMIT updateSourceVolume(sourceVolume);
        }
    }

    QMap<QString,QString>temp;
    if(info.ports) {
        for (pa_source_port_info ** sourcePort = info.ports; *sourcePort != nullptr; ++sourcePort) {
            temp.insertMulti(info.name,(*sourcePort)->name);
        }
        QList <QMap<QString,QString>> sourcePortMapList;
        if(sourcePortMap.isEmpty())
            sourcePortMap.insertMulti(info.card,temp);
        sourcePortMapList = sourcePortMap.values();

        if(!sourcePortMapList.contains(temp))
            sourcePortMap.insertMulti(info.card,temp);

        qDebug() << "UpdateSource--->" << "sourceIndex:" << sourceIndex << "sourcePortName:" << sourcePortName << "sourceVolume:" << sourceVolume << "inputChannel:" << inputChannel;
    }
}


void UkmediaVolumeControl::setIconFromProplist(QLabel *icon, pa_proplist *l, const char *def) {
    const char *t;

    if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ICON_NAME)))
        goto finish;

    if ((t = pa_proplist_gets(l, PA_PROP_WINDOW_ICON_NAME)))
        goto finish;

    if ((t = pa_proplist_gets(l, PA_PROP_APPLICATION_ICON_NAME)))
        goto finish;

    if ((t = pa_proplist_gets(l, PA_PROP_MEDIA_ROLE))) {

        if (strcmp(t, "video") == 0 ||
            strcmp(t, "phone") == 0)
            goto finish;

        if (strcmp(t, "music") == 0) {
            t = "audio";
            goto finish;
        }

        if (strcmp(t, "game") == 0) {
            t = "applications-games";
            goto finish;
        }

        if (strcmp(t, "event") == 0) {
            t = "dialog-information";
            goto finish;
        }
    }

    t = def;

finish:

    setIconByName(icon, t);
}


void UkmediaVolumeControl::updateSinkInput(const pa_sink_input_info &info) {
    sinkInputChannel = info.volume.channels;
    const char *t;

    if ((t = pa_proplist_gets(info.proplist, "module-stream-restore.id"))) {
        if (t && strcmp(t, "sink-input-by-media-role:event") == 0) {
            g_debug("%s", tr("Ignoring sink-input due to it being designated as an event and thus handled by the Event widget").toUtf8().constData());
            return;
        }
    }

    const gchar *description = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_NAME);
    QString appId = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID);
    QString appIconName = pa_proplist_gets(info.proplist,PA_PROP_APPLICATION_ICON_NAME);\
    const gchar *appBinary = pa_proplist_gets(info.proplist,PA_PROP_APPLICATION_PROCESS_BINARY);

    if(appBinary && strcmp(appBinary,"qaxbrowser") == 0){
        appBinary = "qaxbrowser-safe";
    }
    else if (appBinary && strcmp(appBinary,"kylin-kmre-audio") == 0){
        appBinary = nullptr;
    }
    //网页版微信需要适配desktop文件
    else if (appBinary && strcmp(appBinary,"微信") == 0){
        appBinary = "wechat";
    }
    //fixed bug:100614、100610 无线投屏时应用音量界面里应用音量没有图标显示
    else if (description && (strcmp(description,"castplayer") == 0  || strcmp(description,"miraclecast") == 0 )) {
        appBinary = nullptr;
     }

    if(appBinary) {
        appId = appBinary;
        appIconName = appBinary;
        description = appBinary;
    }
    else {
        qDebug() << "updateSinkInput appBinary is null";
        return;
    }

    qDebug() << " updateSinkInput：" << "icon:"<< appIconName << "id" << appId <<"dess"<< description <<"bind"<< appBinary << info.volume.values[0];

    //没制定应用名称的不加入到应用音量中
    if(description && !strstr(description,"QtPulseAudio") && !strstr(description,"ALSA") && !strstr(description,"aplay"))
    {
        sinkInputIndexMap.insert(info.index,description);    //记录每个sinkinput的index
        sinkInputValueMap.insert(description,info.volume.values[0]);     //记录相同description的最新音量
        sinkInputMuteMap.insert(description,info.mute);
        if(!sinkInputList.contains(description)){
            sinkInputList.append(description);  //记录应用的个数 来增删应用widget
            Q_EMIT addSinkInputSignal(description,appId.toLatin1().data(),info.index,info.volume.values[0],info.volume.channels);
         }
        Q_EMIT sinkInputVolumeChangedSignal(description,appId.toLatin1().data(),info.volume.values[0]); //应用音量发生改变时发送信号更新滑动条的值
     }
}

void UkmediaVolumeControl::updateSourceOutput(const pa_source_output_info &info) {
    sourceOutputChannel = info.volume.channels;
    const char *app;
    bool is_new = false;

    if ((app = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID)))
        if (strcmp(app, "org.PulseAudio.pavucontrol") == 0
                || strcmp(app, "org.gnome.VolumeControl") == 0
                || strcmp(app, "org.kde.kmixd") == 0)
            return;

    const gchar *description = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_NAME);
    QString appId = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ID);
    QString appIconName = pa_proplist_gets(info.proplist, PA_PROP_APPLICATION_ICON_NAME);
    const gchar *appBinary = pa_proplist_gets(info.proplist,PA_PROP_APPLICATION_PROCESS_BINARY);

    //kmre没有设计对应的desktop图标，暂时不显示在应用音量窗口
    if (appBinary && strcmp(appBinary,"kylin-kmre-audio") == 0){
        appBinary = nullptr;
    }
    //fixed bug:100614、100610 无线投屏时应用音量界面里应用音量没有图标显示
    else if (description && (strcmp(description,"castplayer") == 0  || strcmp(description,"miraclecast") == 0 )) {
        appBinary = nullptr;
    }

    if(appBinary) {
        appId = appBinary;
        description = appBinary;
        appIconName = appBinary;
    }
    else {
        qDebug() << "updateSourceOutput appBinary is null";
        return;
    }

    qDebug() << "updateSourceOutput:" << "icon:"<< appIconName << "id" << appId <<"dess"<< description <<"bind"<< appBinary << info.volume.values[0];

    if(sinkInputValueMap.keys().contains(description)){
        qDebug() << description << "已产生了sink-input，不再对其source-output生成app widget";
        return;
    }

    if(description && !strstr(description,"QtPulseAudio") && !strstr(description,"ALSA") && !strstr(description,"Volume Control") && !strstr(description,"aplay"))
    {
        sourceOutputIndexMap.insert(info.index,description); //记录所有source-output index
        sourceOutputValueMap.insert(description,info.volume.values[0]); //记录相同description的最新音量
        sinkInputMuteMap.insert(description,info.mute);
        if(!sinkInputList.contains(description)){
            sinkInputList.append(description);
            Q_EMIT addSinkInputSignal(description,appId.toLatin1().data(),info.index,info.volume.values[0],info.volume.channels);
        }
        Q_EMIT sinkInputVolumeChangedSignal(description,appId.toLatin1().data(),info.volume.values[0]);
    }
}


void UkmediaVolumeControl::updateClient(const pa_client_info &info) {
    g_free(clientNames[info.index]);
    clientNames[info.index] = g_strdup(info.name);
}

void UkmediaVolumeControl::updateServer(const pa_server_info &info) {
    defaultSourceName = info.default_source_name ? info.default_source_name : "";
    defaultSinkName = info.default_sink_name ? info.default_sink_name : "";
    qDebug() << "updateServer" << "defaultSinkName " << defaultSinkName << "defaultSourceName" << defaultSourceName;

}

#if HAVE_EXT_DEVICE_RESTORE_API
void UkmediaVolumeControl::updateDeviceInfo(const pa_ext_device_restore_info &info) {
//    if (sinkWidgets.count(info.index)) {
//        SinkWidget *w;
//        pa_format_info *format;

//        w = sinkWidgets[info.index];

//        w->updating = true;

//        /* Unselect everything */
//        for (int j = 1; j < PAVU_NUM_ENCODINGS; ++j)
//            w->encodings[j].widget->setChecked(false);


//        for (uint8_t i = 0; i < info.n_formats; ++i) {
//            format = info.formats[i];
//            for (int j = 1; j < PAVU_NUM_ENCODINGS; ++j) {
//                if (format->encoding == w->encodings[j].encoding) {
//                    w->encodings[j].widget->setChecked(true);
//                    break;
//                }
//            }
//        }

//        w->updating = false;
//    }
}
#endif


void UkmediaVolumeControl::setConnectionState(gboolean connected) {
    if (m_connected != connected) {
        m_connected = connected;
        if (m_connected) {
//            connectingLabel->hide();
//            notebook->show();
        } else {
//            notebook->hide();
//            connectingLabel->show();
        }
    }
}


void UkmediaVolumeControl::removeCard(uint32_t index) {

}

void UkmediaVolumeControl::removeSink(uint32_t index) {
    QMap<int,QString>::iterator it;
    Q_EMIT removeSinkSignal();
    for (it=sinkMap.begin();it!=sinkMap.end();)  {
        if (it.key() == index) {
            qDebug() << "removeSink" << index;
            sinkMap.erase(it);
            break;
        }
        ++it;
    }
}

void UkmediaVolumeControl::removeSource(uint32_t index) {
    QMap<int,QString>::iterator it;
    for (it=sourceMap.begin();it!=sourceMap.end();)  {
        if (it.key() == index) {
            qDebug() << "removeSource" << index;
            sourceMap.erase(it);
            break;
        }
        ++it;
    }
}

void UkmediaVolumeControl::removeSinkInput(uint32_t index)
{
    QString description = "";
    qDebug() << "removeSinkInput" << index;

    description = sinkInputIndexMap.value(index); //先获取要移除sinkinput的description
    sinkInputIndexMap.remove(index);//移除对应index的sinkinput

    if(!sinkInputIndexMap.values().contains(description) || sinkInputIndexMap.isEmpty())
    {
        qDebug() << "移除" << description << "应用窗口";
        sinkInputValueMap.remove(description);//如果所有相同description的index都被移除，需要移除valueMap对应的description
        sinkInputMuteMap.remove(description);
        if(description != "")
            Q_EMIT removeSinkInputSignal(description.toLatin1().data());//并移除对应app widget
    }

}

void UkmediaVolumeControl::removeSourceOutput(uint32_t index)
{
    QString description = "";
    qDebug() << "removeSourceOutput" << index;

    description = sourceOutputIndexMap.value(index);
    sourceOutputIndexMap.remove(index);

    if(!sourceOutputIndexMap.values().contains(description) || sourceOutputIndexMap.isEmpty())
    {
        qDebug() << "移除" << description << "应用窗口";
        sourceOutputValueMap.remove(description);
        sinkInputMuteMap.remove(description);
        if(description != "")
            Q_EMIT removeSinkInputSignal(description.toLatin1().data());
    }

}

void UkmediaVolumeControl::removeClient(uint32_t index) {
    g_free(clientNames[index]);
    clientNames.erase(index);
}

void UkmediaVolumeControl::setConnectingMessage(const char *string) {
    QByteArray markup = "<i>";
    if (!string)
        markup += tr("Establishing connection to PulseAudio. Please wait...").toUtf8().constData();
    else
        markup += string;
    markup += "</i>";
}

void UkmediaVolumeControl::showError(const char *txt) {
    char buf[256];

    snprintf(buf, sizeof(buf), "%s: %s", txt, pa_strerror(pa_context_errno(context)));
    qDebug() <<QString::fromUtf8(buf);
}

void UkmediaVolumeControl::decOutstanding(UkmediaVolumeControl *w) {
    if (w->n_outstanding <= 0)
        return;

    if (--w->n_outstanding <= 0) {
        w->setConnectionState(true);
    }
}

void UkmediaVolumeControl::cardCb(pa_context *c, const pa_card_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Card callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
    w->cardMap.insert(i->index,i->name);

    w->signalCount += 1;
//    if(w->signalCount >= 2){
//        w->updateCard(w,*i);
//        w->signalCount = 1;
//    }
    w->updateCard(w,*i);
}

#if HAVE_EXT_DEVICE_RESTORE_API
static void ext_device_restore_subscribeCb(pa_context *c, pa_device_type_t type, uint32_t idx, void *userdata);
#endif

void UkmediaVolumeControl::sinkIndexCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Sink callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
    int volume;
    if(i->volume.channels >= 2)
        volume = MAX(i->volume.values[0],i->volume.values[1]);
    else
        volume = i->volume.values[0];
    w->channel = i->volume.channels;
    w->defaultOutputCard = i->card;
    w->sinkIndex = i->index;
    w->balance = pa_cvolume_get_balance(&i->volume,&i->channel_map);
    /*
        部分机器会出现sinkPort在updaeSink之前更新的情况
    */
    if(i->active_port)
    {
        QString portStr = i->active_port->name;
        if (w->sinkPortName != i->active_port->name)
        if (portStr.contains("speaker",Qt::CaseInsensitive))
        {
            Q_EMIT w->device_changed_signal("audio-volume-high-symbolic");
        }
        else
        {
            if(i->active_port->available)
                Q_EMIT w->device_changed_signal("ukui-headset-symbolic");
        }
        w->sinkPortName = i->active_port->name;
    }
    else
        w->sinkPortName = "";

    if(w->sinkMuted != i->mute){
        w->sinkMuted = i->mute;
        Q_EMIT w->updateMute(w->sinkMuted);
    }

    if(w->sinkVolume != volume){
        w->sinkVolume = volume;
        Q_EMIT w->updateVolume(w->sinkVolume);
    }

    qDebug() << "sinkIndexCB" << w->sinkPortName;

    //拔出耳机转为扬声器时，音乐暂停信号
    QString currentCardName = i->name;
    if(i->active_port && strcmp(i->active_port->name,"analog-output-speaker") == 0 && !currentCardName.contains("bluez",Qt::CaseInsensitive)
            &&!currentCardName.contains("usb",Qt::CaseInsensitive)){
        int count = -1;
        count = w->findOutputPort("analog-output-speaker");
        if(count == 0){
            if(w->firstgo == false){
                w->sendPortChangedSignal();
                qDebug() << "sinkPortChanged IN sinkindexCb" << w->sinkPortName;
            }
            w->firstgo = false;
        }
    }
    if( w->sinkPortName == "")
    {
        if(w->firstGoWithNoSink == false){
            w->sendPortChangedSignal();
            qDebug() << "sinkPortChanged IN sinkindexCb with NO SINK" << w->sinkPortName;
        }
        w->firstGoWithNoSink = false;
    }
}

void UkmediaVolumeControl::sourceIndexCb(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Source callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
    int volume;
    if(i->volume.channels >= 2)
        volume = MAX(i->volume.values[0],i->volume.values[1]);
    else
        volume = i->volume.values[0];

    w->inputChannel = i->volume.channels;
    w->defaultInputCard = i->card;
    w->sourceIndex = i->index;
    if(i->active_port)
        w->sourcePortName = i->active_port->name;
    else
        w->sourcePortName = "";

    if(w->sourceMuted != i->mute){
        w->sourceMuted = i->mute;
        Q_EMIT w->updateSourceMute(w->sourceMuted);
    }

    if(w->sourceVolume != volume){
        w->sourceVolume = volume;
        Q_EMIT w->updateSourceVolume(w->sourceVolume);
    }

    qDebug() << "sourceIndexCb---" << w->sourcePortName;
}

void UkmediaVolumeControl::sinkCb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Sink callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
    w->m_pDefaultSink = i;
    qDebug() << "SinkCb"  << "Volume:" << i->volume.values[0] << "isMute:" << i->mute << i->name;
    w->sinkMap.insert(i->index,i->name);
    w->updateSink(w,*i);
}

void UkmediaVolumeControl::sourceCb(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Source callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
    qDebug() << "SourceCb" << "Volume:" << i->volume.values[0] << "isMute:" << i->mute << i->name ;
//    w->sourceIndex = i->index;

    w->sourceMap.insert(i->index,i->name);
    w->updateSource(*i);
}

void UkmediaVolumeControl::sinkInputCb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Sink input callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }

    w->updateSinkInput(*i);
}

void UkmediaVolumeControl::sourceOutputCb(pa_context *c, const pa_source_output_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Source output callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0)  {
        decOutstanding(w);
        return;
    }
    qDebug() << "sourceOutputCb" << i->name;
    w->updateSourceOutput(*i);
}

void UkmediaVolumeControl::clientCb(pa_context *c, const pa_client_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Client callback failure").toUtf8().constData());
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
//    qDebug() << "clientCb" << i->name;
    w->updateClient(*i);
}

void UkmediaVolumeControl::serverInfoCb(pa_context *, const pa_server_info *i, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (!i) {
        w->showError(QObject::tr("Server info callback failure").toUtf8().constData());
        return;
    }

    pa_operation *o;
    //默认的输出设备改变时需要获取默认的输出音量
    if (strcmp(w->defaultSinkName.data(),i->default_sink_name) != 0) {
      //默认输出改变时才走sinkindexcb
        if(!(o = pa_context_get_sink_info_by_name(w->getContext(),i->default_sink_name,sinkIndexCb,w))) {
            w->showError(tr("pa_context_get_sink_info_by_name() failed").toUtf8().constData());
        }
    }
    if(!(o = pa_context_get_source_info_by_name(w->getContext(),i->default_source_name,sourceIndexCb,w))){
        w->showError(tr("pa_context_get_source_info_by_name() failed").toUtf8().constData());
    }

    qDebug() << "serverInfoCb" << i->default_sink_name << i->default_source_name;
    w->updateServer(*i);

    QTimer::singleShot(100, w, SLOT(timeoutSlot()));
    decOutstanding(w);
}

void UkmediaVolumeControl::serverInfoIndexCb(pa_context *, const pa_server_info *i, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (!i) {
        w->showError(QObject::tr("Server info callback failure").toUtf8().constData());
        return;
    }
    pa_operation *o;

//    qDebug() << "serverInfoIndexCb" << i->default_sink_name << i->default_source_name;
    w->updateServer(*i);
    decOutstanding(w);
}

void UkmediaVolumeControl::timeoutSlot()
{
    Q_EMIT deviceChangedSignal();
}

void UkmediaVolumeControl::extStreamRestoreReadCb(
        pa_context *c,
        const pa_ext_stream_restore_info *i,
        int eol,
        void *userdata) {

    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (eol < 0) {
        decOutstanding(w);
        g_debug(QObject::tr("Failed to initialize stream_restore extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(c)));
        return;
    }

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
}

void UkmediaVolumeControl::extStreamRestoreSubscribeCb(pa_context *c, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    pa_operation *o;

    if (!(o = pa_ext_stream_restore_read(c, extStreamRestoreReadCb, w))) {
        w->showError(QObject::tr("pa_ext_stream_restore_read() failed").toUtf8().constData());
        return;
    }
    qDebug() << "extStreamRestoreSubscribeCb" ;
    pa_operation_unref(o);
}

#if HAVE_EXT_DEVICE_RESTORE_API
void ext_device_restore_read_cb(
        pa_context *,
        const pa_ext_device_restore_info *i,
        int eol,
        void *userdata) {

    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (eol < 0) {
        w->decOutstanding(w);
        g_debug(QObject::tr("Failed to initialize device restore extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(context)));
        return;
    }

    if (eol > 0) {
        w->decOutstanding(w);
        return;
    }

    /* Do something with a widget when this part is written */
    w->updateDeviceInfo(*i);
}

static void ext_device_restore_subscribeCb(pa_context *c, pa_device_type_t type, uint32_t idx, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    pa_operation *o;

    if (type != PA_DEVICE_TYPE_SINK)
        return;

    if (!(o = pa_ext_device_restore_read_formats(c, type, idx, ext_device_restore_read_cb, w))) {
        w->showError(QObject::tr("pa_ext_device_restore_read_sink_formats() failed").toUtf8().constData());
        return;
    }

    pa_operation_unref(o);
}
#endif

void UkmediaVolumeControl::extDeviceManagerReadCb(
        pa_context *c,
        const pa_ext_device_manager_info *,
        int eol,
        void *userdata) {

    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (eol < 0) {
        decOutstanding(w);
        g_debug(QObject::tr("Failed to initialize device manager extension: %s").toUtf8().constData(), pa_strerror(pa_context_errno(c)));
        return;
    }

    w->canRenameDevices = true;

    if (eol > 0) {
        decOutstanding(w);
        return;
    }
    qDebug() << "extDeviceManagerReadCb";
    /* Do something with a widget when this part is written */
}

void UkmediaVolumeControl::extDeviceManagerSubscribeCb(pa_context *c, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    pa_operation *o;

    if (!(o = pa_ext_device_manager_read(c, extDeviceManagerReadCb, w))) {
        w->showError(QObject::tr("pa_ext_device_manager_read() failed").toUtf8().constData());
        return;
    }
    qDebug() << "extDeviceManagerSubscribeCb";
    pa_operation_unref(o);
}

void UkmediaVolumeControl::subscribeCb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
        case PA_SUBSCRIPTION_EVENT_SINK:
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                w->removeSink(index);
            else {
                    pa_operation *o;
                    if (!(o = pa_context_get_sink_info_by_index(c, index, sinkCb, w))) {
                        w->showError(QObject::tr("pa_context_get_sink_info_by_index() failed").toUtf8().constData());
                        return;
                    }
                    pa_operation_unref(o);
            }
            break;

        case PA_SUBSCRIPTION_EVENT_SOURCE:
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                w->removeSource(index);
            else {
                pa_operation *o;
                if (!(o = pa_context_get_source_info_by_index(c, index, sourceCb, w))) {
                    w->showError(QObject::tr("pa_context_get_source_info_by_index() failed").toUtf8().constData());
                    return;
                }
                pa_operation_unref(o);
            }
            break;

        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                w->removeSinkInput(index);
            else {
                pa_operation *o;
                if (!(o = pa_context_get_sink_input_info(c, index, sinkInputCb, w))) {
                    w->showError(QObject::tr("pa_context_get_sink_input_info() failed").toUtf8().constData());
                    return;
                }
                pa_operation_unref(o);
            }
            break;

        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                w->removeSourceOutput(index);
            else {
                pa_operation *o;
                if (!(o = pa_context_get_source_output_info(c, index, sourceOutputCb, w))) {
                    w->showError(QObject::tr("pa_context_get_sink_input_info() failed").toUtf8().constData());
                    return;
                }
                pa_operation_unref(o);
            }
            break;

        case PA_SUBSCRIPTION_EVENT_CLIENT:
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
                w->removeClient(index);
            else {
                pa_operation *o;
                if (!(o = pa_context_get_client_info(c, index, clientCb, w))) {
                    w->showError(QObject::tr("pa_context_get_client_info() failed").toUtf8().constData());
                    return;
                }
                pa_operation_unref(o);
            }
            break;

        case PA_SUBSCRIPTION_EVENT_SERVER: {
                pa_operation *o;
                if (!(o = pa_context_get_server_info(c, serverInfoCb, w))) {
                    w->showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData());
                    return;
                }
                pa_operation_unref(o);
            }
            break;

        case PA_SUBSCRIPTION_EVENT_CARD:
            if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
                qDebug() << "remove cards------";
                //移除outputPort
                w->removeSinkPortMap(index);
                w->removeOutputPortMap(index);
                w->removeInputPortMap(index);

                Q_EMIT w->updatePortSignal();

                w->removeCardMap(index);
                w->removeCardProfileMap(index);

                w->removeProfileMap(index);
                w->removeInputProfile(index);
                w->removeCard(index);
            }
            else {
                pa_operation *o;
                if (!(o = pa_context_get_card_info_by_index(c, index, cardCb, w))) {
                    w->showError(QObject::tr("pa_context_get_card_info_by_index() failed").toUtf8().constData());
                    return;
                }
                pa_operation_unref(o);
            }
            break;

    }
}

void UkmediaVolumeControl::contextStateCallback(pa_context *c, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    g_assert(c);

    switch (pa_context_get_state(c)) {
        case PA_CONTEXT_UNCONNECTED:
        case PA_CONTEXT_CONNECTING:
        case PA_CONTEXT_AUTHORIZING:
        case PA_CONTEXT_SETTING_NAME:

            break;

        case PA_CONTEXT_READY: {
            pa_operation *o;
            qDebug() << "pa_context_get_state" << "PA_CONTEXT_READY" << pa_context_get_state(c);
            w->reconnectTimeout = 1;

            /* Create event widget immediately so it's first in the list */

            pa_context_set_subscribe_callback(c, subscribeCb, w);

            if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
                                           (PA_SUBSCRIPTION_MASK_SINK|
                                            PA_SUBSCRIPTION_MASK_SOURCE|
                                            PA_SUBSCRIPTION_MASK_SINK_INPUT|
                                            PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
                                            PA_SUBSCRIPTION_MASK_CLIENT|
                                            PA_SUBSCRIPTION_MASK_SERVER|
                                            PA_SUBSCRIPTION_MASK_CARD), nullptr, nullptr))) {
                w->showError(QObject::tr("pa_context_subscribe() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);

            /* Keep track of the outstanding callbacks for UI tweaks */
            w->n_outstanding = 0;

            if (!(o = pa_context_get_server_info(c, serverInfoCb, w))) {
                w->showError(QObject::tr("pa_context_get_server_info() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);
            w->n_outstanding++;

            if (!(o = pa_context_get_client_info_list(c, clientCb, w))) {
                w->showError(QObject::tr("pa_context_client_info_list() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);
            w->n_outstanding++;

            if (!(o = pa_context_get_card_info_list(c, cardCb, w))) {
                w->showError(QObject::tr("pa_context_get_card_info_list() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);
            w->n_outstanding++;

            if (!(o = pa_context_get_sink_info_list(c, sinkCb, w))) {
                w->showError(QObject::tr("pa_context_get_sink_info_list() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);
            w->n_outstanding++;

            if (!(o = pa_context_get_source_info_list(c, sourceCb, w))) {
                w->showError(QObject::tr("pa_context_get_source_info_list() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);
            w->n_outstanding++;
            if (!(o = pa_context_get_sink_input_info_list(c, sinkInputCb, w))) {
                w->showError(QObject::tr("pa_context_get_sink_input_info_list() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);
            w->n_outstanding++;

            if (!(o = pa_context_get_source_output_info_list(c, sourceOutputCb, w))) {
                w->showError(QObject::tr("pa_context_get_source_output_info_list() failed").toUtf8().constData());
                return;
            }
            pa_operation_unref(o);
            w->n_outstanding++;
            Q_EMIT w->paContextReady();
            break;
        }

        case PA_CONTEXT_FAILED:
            w->setConnectionState(false);
            pa_context_unref(w->m_pPaContext);
            w->m_pPaContext = nullptr;

            if (w->reconnectTimeout > 0) {
                g_debug("%s", QObject::tr("Connection failed, attempting reconnect").toUtf8().constData());
//                g_timeout_add_seconds(reconnectTimeout, connectToPulse, w);
            }
            return;

        case PA_CONTEXT_TERMINATED:
        default:
            return;
    }
}

void UkmediaVolumeControl::moduleInfoCb(pa_context *c, const pa_module_info *i, int eol, void *userdata)
{
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);

    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("moduleInfoCb callback failure").toUtf8().constData());
        return;
    }
    if(i && strcmp(i->name,w->findModuleStr.toLatin1().data()) == 0) {
        w->findModuleIndex = i->index;
    }

}

pa_context* UkmediaVolumeControl::getContext(void) {
    return context;
}

gboolean UkmediaVolumeControl::connectToPulse(gpointer userdata) {
    pa_glib_mainloop *m = pa_glib_mainloop_new(g_main_context_default());
    api = pa_glib_mainloop_get_api(m);
    pa_proplist *proplist = pa_proplist_new();
    pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, QObject::tr("Ukui Media Volume Control").toUtf8().constData());
    pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "org.PulseAudio.pavucontrol");
    pa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "audio-card");
    pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "PACKAGE_VERSION");
    context = pa_context_new_with_proplist(api, nullptr, proplist);
    g_assert(context);

    pa_proplist_free(proplist);

    pa_context_set_state_callback(getContext(), contextStateCallback, this);
    if (pa_context_connect(getContext(), nullptr, PA_CONTEXT_NOFAIL, nullptr) < 0) {
        if (pa_context_errno(getContext()) == PA_ERR_INVALID) {
            /*w->setConnectingMessage(QObject::tr("Connection to PulseAudio failed. Automatic retry in 5s\n\n"
                "In this case this is likely because PULSE_SERVER in the Environment/X11 Root Window Properties\n"
                "or default-server in client.conf is misconfigured.\n"
                "This situation can also arrise when PulseAudio crashed and left stale details in the X11 Root Window.\n"
                "If this is the case, then PulseAudio should autospawn again, or if this is not configured you should\n"
                "run start-pulseaudio-x11 manually.").toUtf8().constData());*/
            qFatal("connect pulseaudio failed")  ;
        }
        else {
//            g_timeout_add_seconds(5,connectToPulse,w);
        }
    }

    return false;
}


/*
 * 根据名称获取sink input音量
 */
int UkmediaVolumeControl::getSinkInputVolume(const gchar *name)
{
    return sinkInputValueMap.value(name);
}

/*
 * 根据名称获取source output音量
 */
int UkmediaVolumeControl::getSourceOutputVolume(const gchar *name)
{
   return sourceOutputValueMap.value(name);
}


void UkmediaVolumeControl::sinkInputCallback(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
    UkmediaVolumeControl *w = static_cast<UkmediaVolumeControl*>(userdata);
    if (eol < 0) {
        if (pa_context_errno(c) == PA_ERR_NOENTITY)
            return;

        w->showError(QObject::tr("Sink input callback failure").toUtf8().constData());
        return;
    }
    if (eol > 0) {
        decOutstanding(w);
        return;
    }
    w->sinkInputMuted = i->mute;
    if (i->volume.channels >= 2)
        w->sinkInputVolume = MAX(i->volume.values[0],i->volume.values[1]);
    else
        w->sinkInputVolume = i->volume.values[0];
    qDebug() << "sinkInputCallback" <<w->sinkInputVolume <<i->name;
}

/*
 * 移除指定索引的output port
 */
void UkmediaVolumeControl::removeOutputPortMap(int index)
{
    QMap<int, QMap<QString,QString>>::iterator it;
    for (it=outputPortMap.begin();it!=outputPortMap.end();) {
        if (it.key() == index) {
            qDebug() << "removeoutputport" <<it.key() << it.value();
            outputPortMap.erase(it);
            break;
        }
        ++it;
    }
}

/*
 * 移除指定索引的input port
 */
void UkmediaVolumeControl::removeInputPortMap(int index)
{
    QMap<int, QMap<QString,QString>>::iterator it;
    for (it=inputPortMap.begin();it!=inputPortMap.end();) {
        if (it.key() == index) {
            inputPortMap.erase(it);
            break;
        }
        ++it;
    }
}

/*
 * 移除指定索引的card
 */
void UkmediaVolumeControl::removeCardMap(int index)
{
    QMap<int, QString>::iterator it;
    for (it=cardMap.begin();it!=cardMap.end();) {
        if (it.key() == index) {
            cardMap.erase(it);
            break;
        }
        ++it;
    }
}

void UkmediaVolumeControl::removeCardProfileMap(int index)
{
    qDebug() << "removeCardProfileMap";
    QMap<int, QList<QString>>::iterator it;
    QMap<int, QMap<QString,int>>::iterator at;
    for (it=cardProfileMap.begin();it!=cardProfileMap.end();) {
        if (it.key() == index) {
            cardProfileMap.erase(it);
            break;
        }
        ++it;
    }

    for (at=cardProfilePriorityMap.begin();at!=cardProfilePriorityMap.cend();) {
        if (at.key() == index) {
            cardProfilePriorityMap.erase(at);
            break;
        }
        ++at;
    }
}

void UkmediaVolumeControl::removeSinkPortMap(int index)
{
    qDebug() << "removeSinkPortMap///";
    QMap<int,QMap<QString,QString>>::iterator it;
    for(it=sinkPortMap.begin();it!=sinkPortMap.end();){
        if(it.key() == index) {
            sinkPortMap.erase(it);
            break;
        }
        ++it;
    }
}

void UkmediaVolumeControl::removeProfileMap(int index)
{
    QMap<int,QMap<QString,QString>>::iterator it;
    qDebug() << "removeProfileMap"  << index;

    for (it=profileNameMap.begin();it!=profileNameMap.end();) {
        if(it.key() == index)
        {
            profileNameMap.erase(it);
            break;
        }
        ++it;
    }

}

bool UkmediaVolumeControl::isExitOutputPort(QString name)
{
    QMap<int, QMap<QString,QString>>::iterator it;
    QMap<QString,QString>::iterator at;
    QMap<QString,QString> portMap;
    for (it=outputPortMap.begin();it!=outputPortMap.end();) {
        portMap = it.value();
        for (at=portMap.begin();at!=portMap.end();) {
            if (at.value() == name)
                return true;
            ++at;
        }
        ++it;
    }
    return false;
}

int UkmediaVolumeControl::findOutputPort(QString name){
    QMap<int, QMap<QString,QString>>::iterator it;
    QMap<QString,QString>::iterator at;
    QMap<QString,QString> portMap;
    int count = 0;
    for (it=outputPortMap.begin();it!=outputPortMap.end();) {
        portMap = it.value();
        for (at=portMap.begin();at!=portMap.end();) {
            if (at.key() != name) {
                ++count;
            }
            ++at;
        }
        ++it;
    }
    return count;
}

QString UkmediaVolumeControl::findSinkActivePortName(QString name)
{
    QString portName = "";
    QMap<QString,QString>::iterator at;
    for (at=sinkActivePortMap.begin();at!=sinkActivePortMap.end();++at) {
        if (at.key() == name) {
            portName = at.value();
            break;
        }
    }
    return portName;
}

void UkmediaVolumeControl::removeInputProfile(int index)
{
    QMap<int, QMap<QString,QString>>::iterator it;
    qDebug() << "removeInputProfile"  << index;

    for (it=inputPortProfileNameMap.begin();it!=inputPortProfileNameMap.end();) {
        if(it.key() == index){
            inputPortProfileNameMap.erase(it);
            break;
        }
        ++it;
    }
}

bool UkmediaVolumeControl::isExitInputPort(QString name)
{
    QMap<int, QMap<QString,QString>>::iterator it;
    QMap<QString,QString>::iterator at;
    QMap<QString,QString> portMap;
    for (it=inputPortMap.begin();it!=inputPortMap.end();) {
        portMap = it.value();
        for (at=portMap.begin();at!=portMap.end();) {
            if (at.value() == name)
                return true;
            ++at;
        }
        ++it;
    }
    return false;
}

void UkmediaVolumeControl::sendPortChangedSignal()
{
    QDBusMessage message = QDBusMessage::createSignal("/","org.ukui.media","sinkPortChanged");
    message<<"portChanged";
    QDBusConnection::sessionBus().send(message);
}
