/*
 * Kylin-video
 *
 * Copyright (C) 2021, 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 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: Liu Cong <liucong1@kylinos.cn>
 *
 */

#include "globalsetup.h"
#include "global/global.h"
#include "global/globalsignal.h"
#include "global/functions.h"

using namespace Global;

GlobalSetup* GlobalSetup::instance = nullptr;

GlobalSetup *GlobalSetup::getInstance()
{
    if(instance == nullptr)
        instance = new GlobalSetup;
    return instance;
}

void GlobalSetup::clearChange()
{
    miniToTray.second           = miniToTray.first;
    pauseWhenMini.second        = pauseWhenMini.first;
    canRunMultiple.second       = canRunMultiple.first;
    keepStateWhenWakeup.second  = keepStateWhenWakeup.first;

    playLastPos.second          = playLastPos.first;
    playRelationFile.second     = playRelationFile.first;
    clearListWhenExit.second    = clearListWhenExit.first;
    fullScreenWhenPlay.second   = fullScreenWhenPlay.first;

    screenShotPath.second           = screenShotPath.first;
    screenShotFormat.second         = screenShotFormat.first;
    screenShotSaveToClip.second     = screenShotSaveToClip.first;
    screenShotCurrentSize.second    = screenShotCurrentSize.first;

    subDir.second           = subDir.first;
    subFontSize.second      = subFontSize.first;
    subFontFamily.second    = subFontFamily.first;
    loadSameNameSub.second  = loadSameNameSub.first;
    loadAllSubInDir.second  = loadAllSubInDir.first;

    audioOut.second         = audioOut.first;
    globalVolume.second     = globalVolume.first;
    standardVolume.second   = standardVolume.first;

    videoDecoder.second         = videoDecoder.first;
    videoOutput.second          = videoOutput.first;
    videoDecodeThreads.second   = videoDecodeThreads.first;

    seamlessBrowsing.second = seamlessBrowsing.first;
    audioChannel.second = audioChannel.first;
}

void GlobalSetup::flushChange()
{
    // 是否最小化到托盘
    if (miniToTray.first != miniToTray.second) {
        miniToTray.first = miniToTray.second;
        g_settings->setValue("General/mini_to_tray", miniToTray.second);
    }

    // 最小化时暂停播放
    if (pauseWhenMini.first != pauseWhenMini.second) {
        pauseWhenMini.first = pauseWhenMini.second;
        g_settings->setValue("General/pause_when_mini", pauseWhenMini.second);
    }

    if (keepStateWhenWakeup.first != keepStateWhenWakeup.second) {
        keepStateWhenWakeup.first = keepStateWhenWakeup.second;
        g_settings->setValue("General/keep_state_when_wakeup", keepStateWhenWakeup.second);
    }

    // 是否可运行多个播放器
    if (canRunMultiple.first != canRunMultiple.second) {
        canRunMultiple.first = canRunMultiple.second;
        g_settings->setValue("General/can_run_multiple", canRunMultiple.second);
    }

    // 播放时自动全屏
    if (fullScreenWhenPlay.first != fullScreenWhenPlay.second) {
        fullScreenWhenPlay.first = fullScreenWhenPlay.second;
        g_settings->setValue("General/fullscreen_when_play", fullScreenWhenPlay.second);
    }

    // 退出时清空播放列表
    if (clearListWhenExit.first != clearListWhenExit.second) {
        clearListWhenExit.first = clearListWhenExit.second;
        g_settings->setValue("General/clearlist_when_exit", clearListWhenExit.second);
    }

    // 从上次停止的地方播放
    if (playLastPos.first != playLastPos.second) {
        playLastPos.first = playLastPos.second;
        g_settings->setValue("General/play_last_pos", playLastPos.second);
    }

    // 自动播放相关文件
    if (playRelationFile.first != playRelationFile.second) {
        playRelationFile.first = playRelationFile.second;
        g_settings->setValue("General/play_relation_file", playRelationFile.second);
    }

    // 截图保存为文件
    if (screenShotSaveToClip.first != screenShotSaveToClip.second) {
       screenShotSaveToClip.first = screenShotSaveToClip.second;
       g_settings->setValue("General/screenshot_save_to_file", screenShotSaveToClip.second);
    }

    // 截图保存路径
    if (screenShotPath.first != screenShotPath.second) {
        screenShotPath.first = screenShotPath.second;
        g_settings->setValue("General/screenshot_path", screenShotPath.second);
        g_user_signal->screenShotDir(screenShotPath.second);
    }

    // 截图格式
    if (screenShotFormat.first != screenShotFormat.second) {
        screenShotFormat.first = screenShotFormat.second;
        g_settings->setValue("General/screenshot_format", screenShotFormat.second);
        g_user_signal->screenShotFormat(screenShotFormat.second);
    }

    // 截图尺寸
    if (screenShotCurrentSize.first != screenShotCurrentSize.second) {
        screenShotCurrentSize.first = screenShotCurrentSize.second;
        g_settings->setValue("General/screenshot_size", screenShotCurrentSize.second);
    }

    // 自动载入同名字幕
    if (loadSameNameSub.first != loadSameNameSub.second) {
        loadSameNameSub.first = loadSameNameSub.second;
        g_settings->setValue("General/load_same_name_subtitle", loadSameNameSub.second);
    }

    // 自动载入文件夹下其他字幕
    if (loadAllSubInDir.first != loadAllSubInDir.second) {
        loadAllSubInDir.first = loadAllSubInDir.second;
        g_settings->setValue("General/load_all_dir_subtitle", loadAllSubInDir.second);
    }

    // 字幕载入路径
    if (subDir.first != subDir.second) {
        subDir.first = subDir.second;
        g_settings->setValue("General/subtitle_dir", subDir.second);
    }

    // 字幕字体
    if (subFontFamily.first != subFontFamily.second) {
        subFontFamily.first = subFontFamily.second;
        g_settings->setValue("General/subtitle_font_family", subFontFamily.second);
        g_user_signal->setSubFont(subFontFamily.first);
    }

    // 字幕字体大小
    if (subFontSize.first != subFontSize.second) {
        subFontSize.first = subFontSize.second;
        g_settings->setValue("General/subtitle_font_size", subFontSize.second);
        g_user_signal->setSubSize(subFontSize.first);
    }

    // 音频输出驱动
    if (audioOut.first != audioOut.second) {
        // 输出驱动无法在播放的时候修改
        audioOut.first = audioOut.second;
        g_settings->setValue("General/audio_output_device", audioOut.second);
        g_user_signal->restart();
    }

    // 全局音量
    if (globalVolume.first != globalVolume.second) {
        globalVolume.first = globalVolume.second;
        g_settings->setValue("General/global_volume", globalVolume.second);
    }

    // 默认音量标准化
    if (standardVolume.first != standardVolume.second) {
        standardVolume.first = standardVolume.second;
        g_settings->setValue("General/standard_volume", standardVolume.second);
    }

    // 视频解码器
    if (videoDecoder.first != videoDecoder.second) {
        videoDecoder.first = videoDecoder.second;
        g_settings->setValue("General/video_decoder", videoDecoder.second);
        g_user_signal->setVideoDecoder(videoDecoder.second);
    }

    // 视频输出驱动
    if (videoOutput.first != videoOutput.second) {
        videoOutput.first = videoOutput.second;
        g_settings->setValue("General/video_output", videoOutput.second);
        g_user_signal->setVideoOutput(videoOutput.second);
    }

    if (videoDecodeThreads.first != videoDecodeThreads.second) {
        videoDecodeThreads.first = videoDecodeThreads.second;
        g_settings->setValue("General/video_decode_threads", videoDecodeThreads.second);
        g_user_signal->setVideoDecodeThreads(videoDecodeThreads.second);
    }

    //无痕浏览
    if (seamlessBrowsing.first != seamlessBrowsing.second) {
        seamlessBrowsing.first = seamlessBrowsing.second;
        g_settings->setValue("General/seamless_browsing", seamlessBrowsing.second);
    }

    // 声道
    if (audioChannel.first != audioChannel.second) {
        audioChannel.first = audioChannel.second;
        g_settings->setValue("General/audio_channel", audioChannel.second);
    }
}

GlobalSetup::GlobalSetup(QObject *parent) : QObject(parent)
{
    reset();
}

void GlobalSetup::updatePredefinedList()
{
    //# 对于PCIe设备的预处理
    //# 格式："VID:PID:ClassID:预设分数:描述信息"
    //# 一行一条规则
    //# VID、PID中，-1或者0xFFFF代表匹配所有
    //# ClassID中，-1或者0xFFFFFF代表匹配所有
    //# 例如："0x8086:0x1901:0x0:0:Intel PCIe Controller (x16)"

    QStringList itemList;
    itemList.append("1002:-1:-1:300:AMD Graphics Card");
    itemList.append("10de:-1:-1:0:Nvidia Graphics Card");
    itemList.append("0731:7200:-1:0:JINGJIA MICRO JM7200 Graphics Card");
    itemList.append("126f:-1:-1:0:SM750/SM768");
    itemList.append("0709:0001:-1:0:709 GP101 Graphics Card");
    itemList.append("1a03:-1:-1:0:BMCd");

    foreach (QString item, itemList) {
        item = item.trimmed();
        if (item.startsWith("#"))
            continue;
        if (item.startsWith("//"))
            continue;

        PciePredefined device;
        QStringList stringList = item.split(":");
        bool ok;

        if (stringList.count() < 5)
            continue;

        device.vid = stringList[0].toInt(&ok, 16);
        if (device.vid == -1)
            device.vid = 0xFFFF;
        stringList.removeAt(0);

        device.pid = stringList[0].toInt(&ok, 16);
        if (device.pid == -1)
            device.pid = 0xFFFF;
        stringList.removeAt(0);

        device.cid = stringList[0].toInt(&ok, 16);
        if (device.cid == -1)
            device.cid = 0xFFFFFF;
        stringList.removeAt(0);

        device.score = stringList[0].toInt(&ok, 10);
        stringList.removeAt(0);

        device.description = stringList.join(":").trimmed();
        predefinedList.append(device);
    }
}

void GlobalSetup::updatePcieList()
{
    QDir dir("/sys/bus/pci/devices/");
    if (!dir.exists())
            return;

    dir.setFilter(QDir::Dirs);
    QStringList busList = dir.entryList();
    busList.removeOne(".");
    busList.removeOne("..");

    foreach(QString bus, busList) {
        PcieInfo info;
        QString path;
        QFile file;
        QByteArray charArray;
        bool ok;
        int id;

        path = dir.absoluteFilePath(bus + "/" + "vendor");
        file.setFileName(path);
        file.open(QIODevice::ReadOnly | QIODevice::Text);
        charArray = file.readAll();
        file.close();
        id = QString(charArray).toInt(&ok, 16);
        info.vid = id;

        path = dir.absoluteFilePath(bus + "/" + "device");
        file.setFileName(path);
        file.open(QIODevice::ReadOnly | QIODevice::Text);
        charArray = file.readAll();
        file.close();
        id = QString(charArray).toInt(&ok, 16);
        info.pid = id;

        path = dir.absoluteFilePath(bus + "/" + "class");
        file.setFileName(path);
        file.open(QIODevice::ReadOnly | QIODevice::Text);
        charArray = file.readAll();
        file.close();
        id = QString(charArray).toInt(&ok, 16);
        info.cid = id;

        pcieList.append(info);
    }
}

GlobalSetup::HardwareDecodecType GlobalSetup::getHardwareDecodingType()
{
    //默认值为-1,表示使用软解
    HardwareDecodecType type = DEFAULT_SOFTWARE;

    updatePredefinedList();

    int size = predefinedList.size();
    if (size > 0) {
        updatePcieList();

        foreach (PciePredefined predefined, predefinedList) {
            foreach(PcieInfo info, pcieList) {
                if ( ((predefined.vid == info.vid) || (predefined.vid == 0xFFFF))
                         && ((predefined.pid == info.pid) || (predefined.pid == 0xFFFF))
                         && ((predefined.cid == info.cid) || (predefined.cid == 0xFFFFFF))) {
                    qDebug("Find %s device(%04x:%04x.%04x), "
                               "use predefine score: %d\n",
                               predefined.description.toUtf8().data(),
                               predefined.vid, predefined.pid,
                               predefined.cid, predefined.score);
                    char vidstr[128] = {0};
                    snprintf(vidstr, sizeof(vidstr), "%04x", predefined.vid);
                    //printf("vidstr:%s\n", vidstr);
                    QString vid = QString::fromStdString(std::string(vidstr));
                    //qDebug() << "vid:" << vid;
                    //Find AMD Graphics Card device(1002:ffff.ffffff), use predefine score: 300
                    if (predefined.description == "AMD Graphics Card" && vid == "1002") {
                        type = AMD_VDPAU;
                    }
                    else if (predefined.description == "JINGJIA MICRO JM7200 Graphics Card" && vid == "0731") {
                        type = JM7200_VDPAU;
                    }
                    else if (predefined.description == "709 GP101 Graphics Card" && vid == "0709") {
                        type = GP101_SOFTWARE;
                    }
                    else if (predefined.description == "SM750/SM768" && vid == "126f") {
                        type = Sm768_SOFTWARE;
                    }
                    else if (predefined.description == "Zhaoxin Device 3d00" && vid == "1d17") {
//                        type = zxe3d00; // zxe3d00 == AmdVdpau, 默认走 VDPAU 硬件解码
                    }
                    else if (predefined.description == "Nvidia Graphics Card" && vid == "10de") {
                        type = Nvidia_VDPAU_COPY;
                    }
                    return type;
                }
            }
        }
    }

    return type;
}

void GlobalSetup::reset()
{
    miniToTray.first            = miniToTray.second             = g_settings->value("General/mini_to_tray").toBool();
    pauseWhenMini.first         = pauseWhenMini.second          = g_settings->value("General/pause_when_mini").toBool();
    keepStateWhenWakeup.first   = keepStateWhenWakeup.second    = g_settings->value("General/keep_state_when_wakeup").toBool();
    canRunMultiple.first        = canRunMultiple.second         = g_settings->value("General/can_run_multiple").toBool();

    fullScreenWhenPlay.first    = fullScreenWhenPlay.second = g_settings->value("General/fullscreen_when_play").toBool();
    clearListWhenExit.first     = clearListWhenExit.second  = g_settings->value("General/clearlist_when_exit").toBool();
    playLastPos.first           = playLastPos.second        = g_settings->value("General/play_last_pos").toBool();
    playRelationFile.first      = playRelationFile.second   = g_settings->value("General/play_relation_file").toBool();

    screenShotSaveToClip.first  = screenShotSaveToClip.second   = g_settings->value("General/screenshot_save_to_file").toBool();
    screenShotPath.first        = screenShotPath.second         = g_settings->value("General/screenshot_path").toString();
    screenShotFormat.first      = screenShotFormat.second       = g_settings->value("General/screenshot_format").toString();
    screenShotCurrentSize.first = screenShotCurrentSize.second  = g_settings->value("General/screenshot_size").toBool();

    loadSameNameSub.first   = loadSameNameSub.second    = g_settings->value("General/load_same_name_subtitle").toBool();
    loadAllSubInDir.first   = loadAllSubInDir.second    = g_settings->value("General/load_all_dir_subtitle").toBool();
    subDir.first            = subDir.second             = g_settings->value("General/subtitle_dir").toString();
    subFontFamily.first     = subFontFamily.second      = g_settings->value("General/subtitle_font_family").toString();
    subFontSize.first       = subFontSize.second        = g_settings->value("General/subtitle_font_size").toInt();

    audioOut.first          = audioOut.second           = g_settings->value("General/audio_output_device").toString();
    globalVolume.first      = globalVolume.second       = g_settings->value("General/global_volume").toBool();
    standardVolume.first    = standardVolume.second     = g_settings->value("General/standard_volume").toBool();

    videoDecoder.first = videoDecoder.second = g_settings->value("General/video_decoder").toString();
    videoOutput.first = videoOutput.second = g_settings->value("General/video_output").toString();
    if (videoDecoder.first == "") {
        HardwareDecodecType decodeType = getHardwareDecodingType();
        if (decodeType == AMD_VDPAU) {
            videoDecoder.second = "vdpau";
            videoOutput.second = "vdpau";
        }
        else if (decodeType == JM7200_VDPAU) {
            videoOutput.second = "vdpau";
            if (Functions::isQingsongDevice()) {
                videoDecoder.second = "no";
            }
            else {
                videoDecoder.second = "vdpau";
            }
        }
        else if (decodeType == Sm768_SOFTWARE) {
            videoOutput.second = "xv";
            videoDecoder.second = "no";
        }
        else if (decodeType == GP101_SOFTWARE) {
            videoOutput.second = "x11";
            videoDecoder.second = "no";
        }
        else if (decodeType == Nvidia_VDPAU_COPY) {
            videoOutput.second = "vdpau";
            videoDecoder.second = "vdpau-copy";
        }
        else {
            videoOutput.second = "auto";
            videoDecoder.second = "default";
        }
#if defined(__loongarch__)
        videoOutput.second = "x11";
        videoDecoder.second = "no";
#endif
        flushChange();
    }
    videoDecodeThreads.first = videoDecodeThreads.second = g_settings->value("General/video_decode_threads").toInt();
    if (videoDecodeThreads.first == 0) {
#if defined(__loongarch__)
        videoDecodeThreads.second = sysconf(_SC_NPROCESSORS_ONLN) * 2 + 1;
#else
        videoDecodeThreads.second = 4;
#endif
        flushChange();
    }

    seamlessBrowsing.first = seamlessBrowsing.second = g_settings->value("General/seamless_browsing").toBool();
    audioChannel.first = audioChannel.second = g_settings->value("General/audio_channel").toInt();
}
