/*
 * Copyright (C) 2022, 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: iaom <zhangpengfei@kylinos.cn>
 *
 */
#include "volume-shortcut.h"
#include <QDebug>
#include <QApplication>
#include <QTranslator>
#include <QtMath>
#include "ukui-quick/platform/dbus-connector.h"

#define USD_PLUGIN_MEDIA_KEY_STATE                "org.ukui.SettingsDaemon.plugins.media-keys-state"
#define VOLUME_KEY                                "sinkVolume"
#define MUTE_KEY                                  "sinkMute"
#define AUDIO_VOLUME_HIGH_SYMBOLIC                "audio-volume-high-symbolic"
#define AUDIO_VOLUME_LOW_SYMBOLIC                 "audio-volume-low-symbolic"
#define AUDIO_VOLUME_MEDIUM_SYMBOLIC              "audio-volume-medium-symbolic"
#define AUDIO_VOLUME_MUTED_SYMBOLIC               "audio-volume-muted-symbolic"

#define UKUI_SOUND_SCHEMA                         "org.ukui.sound"
#define UKUI_SOUND_INCREASE_KEY                   "volumeIncrease"
#define UKUI_SOUND_INCREASE_VALUE                 "volumeIncreaseValue"

#define UKUI_VOLUME_CONTROL_NAME                  "org.ukui.volume.control"
#define UKUI_VOLUME_CONTROL_PATH                  "/org/ukui/volume/control"
#define UKUI_VOLUME_CONTROL_INTERFACE             "org.ukui.volume.control"

QDBusArgument &operator<<(QDBusArgument &arg, const DeviceInfo &s) {
    arg.beginStructure();
    arg << s.cardName << s.cardDesc << s.portName << s.portLabel << s.priority << s.direction << s.available << s.enable;
    arg.endStructure();
    return arg;
}

const QDBusArgument &operator>>(const QDBusArgument &arg, DeviceInfo &s) {
    arg.beginStructure();
    arg >> s.cardName >> s.cardDesc >> s.portName >> s.portLabel >> s.priority >> s.direction >> s.available >> s.enable;
    arg.endStructure();
    return arg;
}

using namespace UkuiShortcut;
using namespace UkuiQuick;

VolumeShortcut::VolumeShortcut(QObject *parent) : Shortcut(parent)
{
    qDBusRegisterMetaType<DeviceInfo>();
    qDBusRegisterMetaType<QList<DeviceInfo>>();
    initMetaData();

    m_currentStatusInfo.setName(tr("Audio"));
    m_currentStatusInfo.setValue(0);
    m_currentStatusInfo.setIcon(AUDIO_VOLUME_MUTED_SYMBOLIC);
    m_currentStatusInfo.setToolTip(tr("Volume"));
    m_currentStatusInfo.setWidgetId("org.ukui.shortcut.audio");

    const QByteArray id(USD_PLUGIN_MEDIA_KEY_STATE);
    if (QGSettings::isSchemaInstalled(id)) {
        m_gsettings = new QGSettings(id);

        initVolume();
        initGSettingsConnect();
    } else {
        qWarning() << "VolumeShortcut:" << "can't find gsettings :" << USD_PLUGIN_MEDIA_KEY_STATE;
        m_isEnable = false;
    }

    initVolumeIncrease();
    DbusConnector* dbusConnector = new DbusConnector(UKUI_VOLUME_CONTROL_NAME,
                                                     UKUI_VOLUME_CONTROL_PATH,
                                                     UKUI_VOLUME_CONTROL_INTERFACE,
                                                     QDBusConnection::sessionBus());
    connect(dbusConnector, &DbusConnector::ready, this, &VolumeShortcut::onDbusConnectorReady);
    dbusConnector->start();
}

void VolumeShortcut::initVolumeIncrease()
{
    const QByteArray id(UKUI_SOUND_SCHEMA);
    if (QGSettings::isSchemaInstalled(id)) {
        QGSettings *gsettings = new QGSettings(id, QByteArray(), this);

        QStringList keys = gsettings->keys();
        if (!keys.contains(UKUI_SOUND_INCREASE_KEY, Qt::CaseInsensitive)
            || !keys.contains(UKUI_SOUND_INCREASE_VALUE, Qt::CaseInsensitive))
        {
            delete gsettings;
            return;
        }
        m_isIncrease = gsettings->get(UKUI_SOUND_INCREASE_KEY).toBool();
        m_volumeMaxValue = gsettings->get(UKUI_SOUND_INCREASE_VALUE).toInt();

        changeValue(m_currentVolume);
        connect(gsettings, &QGSettings::changed, this, [=] (const QString &key) {
            if (!m_volumeAvailable || !m_isEnable) return;
            if (key == UKUI_SOUND_INCREASE_KEY || key == UKUI_SOUND_INCREASE_VALUE) {
                m_isIncrease = gsettings->get(UKUI_SOUND_INCREASE_KEY).toBool();
                m_volumeMaxValue = gsettings->get(UKUI_SOUND_INCREASE_VALUE).toInt();
                changeValue(m_currentVolume);
            }
        });
    }
}

void VolumeShortcut::initVolume()
{
    bool isEnable;
    if (m_gsettings->keys().contains(VOLUME_KEY, Qt::CaseInsensitive)) {
        m_currentVolume = m_gsettings->get(VOLUME_KEY).toDouble();
        changeValue(m_currentVolume);
        isEnable = true;
    } else {
        qWarning() << "VolumeShortcut:" << "can't find key :" << VOLUME_KEY;
        isEnable = false;
    }

    if (m_isEnable != isEnable) {
        m_isEnable = isEnable;
        Q_EMIT enableStatusChanged(m_isEnable);
    }

    if (m_gsettings->keys().contains(MUTE_KEY, Qt::CaseInsensitive)) {
        mute(m_gsettings->get(MUTE_KEY).toBool());
    } else {
        qWarning() << "VolumeShortcut:" << "can't find key :" << MUTE_KEY;
    }
}

void VolumeShortcut::initGSettingsConnect()
{
    connect(m_gsettings, &QGSettings::changed, this, [ & ](const QString & key) {
        if (!m_volumeAvailable || !m_isEnable) return;
        if (key == VOLUME_KEY) {
            m_currentVolume = m_gsettings->get(VOLUME_KEY).toDouble();
            changeValue(m_currentVolume);

        } else if (key == MUTE_KEY) {
            mute(m_gsettings->get(MUTE_KEY).toBool());
        }
    });
}

VolumeShortcut::~VolumeShortcut()
{
    if (m_gsettings) {
        delete m_gsettings;
        m_gsettings = nullptr;
    }
    if (m_volumeDeviceInterface) {
        delete m_volumeDeviceInterface;
        m_volumeDeviceInterface = nullptr;
    }
}

void VolumeShortcut::active(PluginMetaType::Action action)
{
    switch (action) {
        case PluginMetaType::Click:
            if (!m_volumeAvailable || !m_isEnable) return;
            if (m_gsettings) {
                m_gsettings->set(MUTE_KEY, !m_mute);
            }
            break;
        case PluginMetaType::LongClick:
            break;
        case PluginMetaType::MenuRequest:
            break;
        default:
            break;
    }
}

void VolumeShortcut::setValue(int value)
{
    if (m_isIncrease) {
        value = qFloor((double)value * (double)m_volumeMaxValue / 100.0);
    }

    if (m_gsettings && m_isEnable && m_volumeAvailable) {
        if (m_gsettings->keys().contains(VOLUME_KEY, Qt::CaseInsensitive)) {
            m_gsettings->set(VOLUME_KEY, value);
            if (value > 0 && m_mute) {
                if (m_gsettings->keys().contains(MUTE_KEY, Qt::CaseInsensitive)) {
                    m_gsettings->set(MUTE_KEY, false);
                }
            }
        }
    }
}

const StatusInfo VolumeShortcut::currentStatus()
{
    return m_currentStatusInfo;
}

bool VolumeShortcut::isEnable()
{
    return m_isEnable;
}

void VolumeShortcut::onDeviceAdjust(qint32 type)
{
    QDBusPendingReply<QList<DeviceInfo>> getReply = m_volumeDeviceInterface->asyncCall(QStringLiteral("getAvailablePortList"), 1);
    auto watcher = new QDBusPendingCallWatcher(getReply);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, getReply] (QDBusPendingCallWatcher *self) {
        if (self) {
            if (self->isError()) {
                qDebug() << "VolumeShortcut: asyncCall getAvailablePortList failed !!! " << self->error().message();
            } else {
                if (getReply.isValid()) {
                    m_volumeAvailable = false;
                    QList<DeviceInfo> deviceInfoList = getReply.value();
                    for (const auto &item : deviceInfoList) {
                        m_volumeAvailable = item.enable;
                        if (m_volumeAvailable) break;
                    }
                    if (!m_volumeAvailable) {
                        m_currentStatusInfo.setValue(0);
                        m_currentStatusInfo.setIcon(AUDIO_VOLUME_MUTED_SYMBOLIC);
                        m_currentStatusInfo.setDisable(true);
                        Q_EMIT statusChanged(m_currentStatusInfo);
                    } else {
                        if (!m_gsettings) {
                            if (QGSettings::isSchemaInstalled(QByteArray(USD_PLUGIN_MEDIA_KEY_STATE))) {
                                m_gsettings = new QGSettings(QByteArray(USD_PLUGIN_MEDIA_KEY_STATE));
                                initVolume();
                                initGSettingsConnect();
                            }
                        } else {
                            initVolume();
                        }
                    }
                } else {
                    qDebug() << "VolumeShortcut: get the list of DeviceInfo failed " << getReply.error();
                }
            }
            self->deleteLater();
        }
    });
}

void VolumeShortcut::onDbusConnectorReady(QDBusInterface *interface)
{
    if (!interface || !interface->isValid()) return;
    if (m_volumeDeviceInterface) {
        delete m_volumeDeviceInterface;
        m_volumeDeviceInterface = nullptr;
    }
    m_volumeDeviceInterface = interface;
    onDeviceAdjust();
    if (!QDBusConnection::sessionBus().connect(UKUI_VOLUME_CONTROL_NAME,
                                               UKUI_VOLUME_CONTROL_PATH,
                                               UKUI_VOLUME_CONTROL_INTERFACE,
                                               "deviceAdjust", this, SLOT(onDeviceAdjust(qint32)))) {
        qDebug() << "VolumeShortcut: QDBusConnection::sessionBus() connect deviceAdjust failed";
    }
}

void VolumeShortcut::changeValue(double volume)
{
    if (m_isIncrease) {
        volume *= (100.0 / (double)m_volumeMaxValue);
    }

    updateStatus(qFloor(volume));
}

QMap<PluginMetaType::SystemMode, PluginMetaData> VolumeShortcut::pluginMetaData()
{
    return m_metaData;
}

void VolumeShortcut::initMetaData()
{
    PluginMetaData pc {true, 1, PluginMetaType::PluginType::ProgressBar};
    PluginMetaData tablet {true, 1, PluginMetaType::PluginType::ProgressBar};
    m_metaData.insert(PluginMetaType::SystemMode::PC, pc);
    m_metaData.insert(PluginMetaType::SystemMode::Tablet, tablet);
}

void VolumeShortcut::mute(bool mute)
{
    m_mute = mute;
    updateStatus(m_currentStatusInfo.getValue());
}

void VolumeShortcut::updateStatus(int volume)
{
    if (0 <= volume && volume <= 100) {
        if (volume == 0 || m_mute) {
            m_currentStatusInfo.setIcon(AUDIO_VOLUME_MUTED_SYMBOLIC);
        } else if (volume <= 33) {
            m_currentStatusInfo.setIcon(AUDIO_VOLUME_LOW_SYMBOLIC);
        } else if (volume <= 66) {
            m_currentStatusInfo.setIcon(AUDIO_VOLUME_MEDIUM_SYMBOLIC);
        } else {
            m_currentStatusInfo.setIcon(AUDIO_VOLUME_HIGH_SYMBOLIC);
        }
        m_currentStatusInfo.setDisable(false);
        m_currentStatusInfo.setValue(volume);

    } else {
        qWarning() << "VolumeShortcut value error: " << volume;
        m_currentStatusInfo.setDisable(true);
        m_currentStatusInfo.setIcon(AUDIO_VOLUME_MUTED_SYMBOLIC);
    }

    Q_EMIT statusChanged(m_currentStatusInfo);
}

VolumeShortcutPlugin::VolumeShortcutPlugin(QObject *parent) : UkuiShortcutPlugin(parent)
{

}

QStringList VolumeShortcutPlugin::translations()
{
    return {QStringLiteral("volume-shortcut")};
}

Shortcut *VolumeShortcutPlugin::createShortcut()
{
    return new VolumeShortcut;
}
