/*
 * 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 "playlist.h"

#include <QFileInfo>
#include <QDateTime>
#include <QProcess>
#include <QVector>
#include <QThread>
#include <QUuid>
#include <QMap>

#include <QDebug>
#include <ukui-log4qt.h>

#include "global/globalsignal.h"
#include "global/global.h"
#include "global/paths.h"
#include "ffutil.h"
#include "elog.h"
#define LOG_TAG "playlist"

using namespace Global;

bool markSort(MarkItem &a, MarkItem &b) {
    if (a.m_filePath != b.m_filePath) {
        return a.m_filePath < b.m_filePath;
    }
    return a.m_markPos < b.m_markPos;
}

PlayList::PlayList(QObject *parent) : QObject(parent),
    m_lastFileChange(0)
{
    m_needPlayFile = "";
    connect(this, &PlayList::sigAddItem, this, &PlayList::addItem);
    initGlobalSig();
}

PlayList::~PlayList()
{

}

/** ************************************************************************
 * 添加播放列表条目
    initGlobalSig();
 * @param:
 *      _file            :文件全路径
 *      _duration        :媒体时长
 *      _viewPath        :预览文件路径
 *      _lastStopTime    :上次停止时间
 *      _writeToDatabase :是否写如数据库
 *                        (添加新文件的时候需要写入数据库，初始化的时候不用)
 ***************************************************************************/
void PlayList::addItem(QString _file, int _duration, int _lastStopTime, QString _viewPath, bool _writeToDatabase)
{
    QString t_name;
    bool    t_isExist;
    int     t_index;

    m_duration = _duration;

    QString mark_character = _viewPath.split("/").last();
    mark_character.chop(4);
    mark_character.remove(0, 1);

    t_name = _file.split("/").last();
    // 是否需要写入数据库
    if(_writeToDatabase)
    {
        // 如果是无痕浏览的话不要添加到数据库和界面.
        if(gsetup->seamlessBrowsing.first)
        {
            if(m_needPlayFile == _file)
            {
                g_user_signal->stop();
                g_user_signal->open(m_needPlayFile, 0);
            }
            return;
        }
        g_sqlite->insertPlayList(_file, _file.split("/").last(), _duration, _viewPath);
    }

    PlayItem pi;
    {
        std::lock_guard<std::mutex> lg(m_mux);
        m_fileList << _file;

        // 判断文件是否存在
        QFileInfo t_fi(_file);
        if(t_fi.exists())
        {
            t_isExist   = true;
            t_index     = m_fileList.size()-1;
            if (m_availableIndexVec.indexOf(m_fileList.size()-1) < 0)
                m_availableIndexVec.push_back(m_fileList.size()-1);
        }
        else
        {
            t_isExist   = false;
            t_index     = -1;
            m_notExitFiles.insert(_file);
        }

        pi.m_isExit         = t_isExist;
        pi.m_isNewForDB     = _writeToDatabase;
        pi.m_fileName       = t_name;
        pi.m_mark           = mark_character;
        pi.m_indexOfList    = t_index;
        pi.m_filePath       = _file;
        pi.m_duration       = _duration;
        pi.m_previewPath    = _viewPath;
        pi.m_lastStopTime   = _lastStopTime;

        m_pathItemMap[_file] = pi;
    }

    // 告诉界面添加了一个 item
    emit itemAdded(pi);
    if(m_needPlayFile == _file)
    {
        g_user_signal->stop();
        g_user_signal->open(m_needPlayFile, 0);
    }
}

/** ************************************************************************
 * 添加播放列表条目
    initGlobalSig();
 * @param:
 *      file    :要删除的文件
 *      删除     :数据库、预览文件、临时map
 ***************************************************************************/
void PlayList::deleteFile(QString file)
{
    if(m_pathItemMap.find(file) != m_pathItemMap.end())
    {
        // 删除预览文件
        QString cmd_ = QString("rm -rf ").append(m_pathItemMap[file].m_previewPath);
        system(cmd_.toStdString().c_str());

        {
            // 删除的时候锁一下
            std::lock_guard<std::mutex> lg(m_mux);
            // 如果是当前播放的文件，直接播放下一首
            if(file == m_playFile) {
                if (m_availableIndexVec.size() > 1 && m_playState > 0)
                    g_user_signal->playNext(true);
                else {
                    g_user_signal->stop();
                    // 没有可以播放的文件后就显示主页
                    g_user_signal->showStopFrame();
                }
            }
            m_pathItemMap.remove(file);
            for(int i=0; i<m_fileList.size(); i++)
            {
                if(m_fileList.at(i) == file)
                {
                    m_fileList.removeAt(i);
                    // 删除可用索引，之后全部可用索引-1
                    int t_rindex = -1;
                    for(int j=0; j<m_availableIndexVec.size(); j++)
                    {
                        if(m_availableIndexVec.at(j) == i)
                            t_rindex = j;
                        else if(m_availableIndexVec.at(j) > i)
                            m_availableIndexVec.replace(j, m_availableIndexVec.at(j)-1);
                    }
                    if(t_rindex >= 0)
                        m_availableIndexVec.remove(t_rindex);
                    break;
                }
            }
            // 从数据库中删除
            g_sqlite->deletePlayList(file);
            emit itemDelete(file);

            // 删除列表中书签内容
            QString markPath = "";
            for (int i=0; i<m_bookmarks.size();) {
                if (m_bookmarks.at(i).m_filePath == file) {
                    if (markPath == "") {
                        QStringList sl = m_bookmarks.at(i).m_previewPath.split("/");
                        markPath = sl.at(sl.size()-2);
                    }
                    m_bookmarks.remove(i);
                    continue;
                }
                i++;
            }
            emit sigMarkUpdate(m_bookmarks);
            // 删除书签文件夹
            if (markPath != "") {
                QString cmd = QString("rm -rf %1/%2").arg(Paths::configPath()).arg(markPath);
                qDebug() << cmd;
                QProcess::execute(cmd);
            }
        }
    }
}

/** ***********************************************
 * 删除文件不存在的项
 * 说明：
 *      遍历所有项，判断文件是否存在，如果不存在的话删除
 **************************************************/
void PlayList::deleteInvalidItems()
{
    QStringList tmp_list(m_fileList);
    for (int i=0; i<m_fileList.size(); i++) {
        if (m_availableIndexVec.indexOf(i) < 0) {
            QString file = tmp_list.at(i);
            QThread::create([this, file](){
                this->deleteFile(file);
            })->start();
        }
    }
}

void PlayList::clearList()
{
    m_availableIndexVec.clear();
    QStringList t_tmp_list(m_fileList);
    foreach (QString t_file, t_tmp_list)
        deleteFile(t_file);
    g_user_signal->stop();
    // 列表清空之后需要显示默认界面
    g_user_signal->showStopFrame();
}

/** **********************************************
 * 更新播放索引
 * 说明：播放列表内容改变索引
 *************************************************/
void PlayList::updateIndex()
{
    // 通过正在播放的文件全路径获取播放索引
    // 如果没有找到的话将索引设置为-1
    m_playIndex = -1;
    int i = 0;
    for(; i<m_fileList.size(); i++)
    {
        if(m_fileList.at(i) == m_playFile)
        {
            break;
        }
    }
    setPlayIndex(i >= m_fileList.size() ? -1 : i);
}

/** **********************************************
 * 设置当前索引
 * @param: _index 索引
 *************************************************/
void PlayList::setPlayIndex(int _index)
{
    if(m_availableIndexVec.size() == 0)
        return;
    if(m_availableIndexVec.back() < _index)
        return;
    m_playIndex = _index;
    emit sigIndexChange(m_playIndex);
    g_settings->setValue("History/playlist_index", m_playIndex);
}

/** **********************************************
 * 添加文件
 * @param: _files 要添加的文件
 *************************************************/
void PlayList::addFiles(QStringList _files)
{
    Extensions t_e;
    QRegExp t_rx_ext(t_e.multimedia().forRegExp());
    t_rx_ext.setCaseSensitivity(Qt::CaseInsensitive);
    // 添加后播放第一个添加的文件
    m_needPlayFile = _files.first();

    foreach (QString t_filename, _files) {
        QFileInfo t_fi(t_filename);
        if (!t_fi.isDir()) {
            if (t_rx_ext.indexIn(t_fi.suffix()) > -1) {
                // 如果列表存在的话不重复添加
                if (m_pathItemMap.find(t_filename) != m_pathItemMap.end())
                {
                    if (!m_pathItemMap[t_filename].m_isExit)
                    {
                        m_pathItemMap[t_filename].m_isExit = true;
                        setAvaliable(t_filename);
                    }
                    if (m_needPlayFile == t_filename)
                    {
                        g_user_signal->stop();
                        g_user_signal->open(t_filename, 0);
                    }
                    continue;
                }
                // 添加文件后需要获取媒体时长，如果是第一个文件的话需要播放
                QThread::create([this, t_filename](){newFile(t_filename);})->start();
            }
        }
    }
}

/** **********************************************
 * 添加文件夹
 * 说明：非递归添加，只添加符合规则的媒体文件
 * @param: _dir 要添加的文件夹
 *************************************************/
void PlayList::addDirectory(QString _dir)
{
    m_needPlayFile = "";
    Extensions t_e;
    QRegExp t_rx_ext(t_e.multimedia().forRegExp());
    t_rx_ext.setCaseSensitivity(Qt::CaseInsensitive);

    QStringList t_dir_list = QDir(_dir).entryList();

    QString t_filename;
    QStringList::Iterator t_it = t_dir_list.begin();
    while(t_it != t_dir_list.end())
    {
        t_filename = _dir;
        if (t_filename.right(1)!="/")
        {
            t_filename += "/";
        }
        t_filename += (*t_it);
        QFileInfo t_fi(t_filename);
        if (!t_fi.isDir()) {
            if (t_rx_ext.indexIn(t_fi.suffix()) > -1)
            {
                if(m_needPlayFile == "") {
                    m_needPlayFile = t_filename;
                }
                // 如果列表存在的话不重复添加
                if(m_pathItemMap.find(t_filename) != m_pathItemMap.end())
                {
                    if(m_needPlayFile == t_filename)
                    {
                        g_user_signal->stop();
                        g_user_signal->open(t_filename, 0);
                    }
                    ++t_it;
                    continue;
                }
                // 添加文件后需要获取媒体时长
                newFile(t_filename);
//                QThread::create([this, t_filename](){newFile(t_filename);})->start();
            }
        }
        ++t_it;
    }
}

/** **********************************************
 * 播放下一个(设置手动最快300ms切换一次)
 * 说明：播放下一个具体文件依据播放顺序和播放列表
 * @param : manual 如果是手动的话直接按列表循环播放下一个
 *************************************************/
void PlayList::playNext(bool manual)
{
    if (m_availableIndexVec.size() == 0 || (QDateTime::currentMSecsSinceEpoch()-m_lastFileChange < 300 && manual)) {
        KyInfo() << " available index size " << m_availableIndexVec.size();
        return;
    }
    g_user_signal->play();
    if(m_playOrder == ONE_LOOP)
    {
        if (m_availableIndexVec.indexOf(m_playIndex) < 0)
            manual = true;
        if (manual) {
            do
            {
                if(m_availableIndexVec.indexOf(++m_playIndex) >= 0)
                    break;
            }while(m_playIndex < m_availableIndexVec.last());
            m_playIndex = m_playIndex > m_availableIndexVec.last() ? m_availableIndexVec.first() : m_playIndex;
        }
    }
    else
    {
        switch (m_playOrder) {
        case ONE_LOOP:
            m_playIndex = m_playIndex < 0 ? 0 : m_playIndex;
            break;
        case SEQUENCE:
            // 顺序播放，只播放一遍
            if(m_playIndex == m_availableIndexVec.last())
                // 停止播放
                return;
            do
            {
                if(m_availableIndexVec.indexOf(++m_playIndex) >= 0)
                    break;
            }while(m_playIndex < m_availableIndexVec.last());
            break;
        case LIST_LOOP:
            do
            {
                if(m_availableIndexVec.indexOf(++m_playIndex) >= 0)
                    break;
            }while(m_playIndex < m_availableIndexVec.last());
            m_playIndex = m_playIndex > m_availableIndexVec.last() ? m_availableIndexVec.first() : m_playIndex;
            break;
        case RANDOM:
            m_playIndex = m_availableIndexVec.at(rand() % m_availableIndexVec.size());
            break;
        default:
            break;
        }
    }
    KyInfo() << " play index " << m_playIndex;
    if(m_fileList.size() > 0 && m_playIndex >= 0 && m_playIndex < m_fileList.size())
    {
        QString t_file_path = m_fileList.at(m_playIndex);

        g_user_signal->stop();
        g_user_signal->open(t_file_path, 0);
    }
    m_lastFileChange = QDateTime::currentMSecsSinceEpoch();
}

/** **********************************************
 * 播放上一个
 *************************************************/
void PlayList::playPrev(bool manual)
{
    if (m_availableIndexVec.size() == 0 || QDateTime::currentMSecsSinceEpoch()-m_lastFileChange < 300)
        return;
    g_user_signal->play();
    if(manual && m_playOrder == ONE_LOOP)
    {
        do
        {
            if(m_availableIndexVec.indexOf(--m_playIndex) >= 0)
                break;
        }while(m_playIndex > m_availableIndexVec.first());
        m_playIndex = m_playIndex < m_availableIndexVec.first() ? m_availableIndexVec.last() : m_playIndex;
    }
    else
    {
        switch (m_playOrder) {
        case ONE_LOOP:
            break;
        case SEQUENCE:
            // 顺序播放，只播放一遍
            if(m_playIndex == m_availableIndexVec.first())
                // 停止播放
                return;
            do
            {
                if(m_availableIndexVec.indexOf(--m_playIndex) >= 0)
                    break;
            }while(m_playIndex > m_availableIndexVec.first());
            break;
        case LIST_LOOP:
            do
            {
                if(m_availableIndexVec.indexOf(--m_playIndex) >= 0)
                    break;
            }while(m_playIndex > m_availableIndexVec.first());
            m_playIndex = m_playIndex < m_availableIndexVec.first() ? m_availableIndexVec.last() : m_playIndex;
            break;
        case RANDOM:
            m_playIndex = m_availableIndexVec.at(rand() % m_availableIndexVec.size());
            break;
        default:
            break;
        }
    }
    if(m_fileList.size() > 0 && m_playIndex >= 0 && m_playIndex < m_fileList.size())
    {
        QString t_file_path = m_fileList.at(m_playIndex);
        g_user_signal->stop();
        g_user_signal->open(t_file_path, 0);
    }
    m_lastFileChange = QDateTime::currentMSecsSinceEpoch();
}

void PlayList::setNotAvaliable(QString _file)
{
    if (m_pathItemMap.find(_file) != m_pathItemMap.end()) {
        // 当前只是把不可用认为是不存在
        m_pathItemMap[_file].m_isExit = false;
    }
    for (int i=0; i<m_fileList.size(); i++)
        if(m_fileList.at(i) == _file)
            for(int j=0; j<m_availableIndexVec.size(); j++)
                if(m_availableIndexVec.at(j) == i)
                    m_availableIndexVec.remove(j);
}

void PlayList::setAvaliable(QString file)
{
    for(int i=0; i<m_fileList.size(); i++) {
        if(m_fileList.at(i) == file) {
            if (m_availableIndexVec.indexOf(i) < 0)
                m_availableIndexVec.push_back(i);
            qSort(m_availableIndexVec.begin(), m_availableIndexVec.end());
        }
    }
}

/** **********************************************
 * 添加书签
 * @param:  file 要添加的书签文件路径
 *          pos  书签位置秒
 *          desc 书签描述
 *          view 书签预览图
 *************************************************/
void PlayList::addMark(QString file, int pos, QString desc, QString view)
{
    // 如果书签存在的话就不要添加了
    for (MarkItem it : m_bookmarks) {
        if (it.m_filePath == file && it.m_markPos == pos) {
            return;
        }
    }

    g_sqlite->insertBookMark(file, m_duration, pos, desc, view);

    MarkItem item;
    item.m_filePath = file;
    item.m_duration = m_duration;
    item.m_markPos = pos;
    item.m_describe = desc;
    item.m_previewPath = view;
    item.m_fileExit = true;

    m_bookmarks.push_back(item);
    qSort(m_bookmarks.begin(), m_bookmarks.end(), markSort);
    emit sigMarkUpdate(m_bookmarks);
    m_playingFileMarks.push_back(item);
    emit sigInsertMark(item);
}

void PlayList::deleteMark(QString file, int pos)
{
    g_sqlite->deleteBookMark(file, pos);
    for (int i=0; i<m_bookmarks.size(); i++)
    {
        if (m_bookmarks.at(i).m_filePath == file && m_bookmarks.at(i).m_markPos == pos) {
            emit sigDeleteMark(m_bookmarks.at(i).m_markPos);
            m_bookmarks.remove(i);
            break;
        }
    }
}

void PlayList::slotFileInfoChange(Mpv::FileInfo _fi)
{
    if(_fi.file_path == QString())
    {
        return;
    }

    if(gsetup->seamlessBrowsing.first)
    {
        m_playFile = _fi.file_path;
        updateIndex();
        // 如果是无痕浏览的话不要做添加操作,但是需要更新进度条上的书签
        goto update_mark;
    }
    m_duration = _fi.length;
    m_playFile = _fi.file_path;
    if(m_pathItemMap.find(m_playFile) == m_pathItemMap.end())
    {
        newFile(m_playFile);
    }
    updateIndex();

update_mark:
    // 文件改变之后书签列表重新排序
    // 文件改变之后需要刷新进度条书签
    m_playingFileMarks.clear();
    int index = -1, num = 0, i = 0;
    for (MarkItem item : m_bookmarks) {
        if (item.m_filePath == _fi.file_path) {
            m_playingFileMarks.push_back(item);
            if (index == -1)
                index = i;
            num++;
        }
        i++;
    }
    if (index >= 0)
        m_bookmarks.remove(index, num);
    qSort(m_bookmarks.begin(), m_bookmarks.end(), markSort);
    m_bookmarks = m_playingFileMarks + m_bookmarks;
    emit sigMarkUpdate(m_bookmarks);
    emit sigPlayingFileMarkUpdate(m_playingFileMarks);
}

void PlayList::initData()
{
    // 读取播放列表
    QVector<KPlaylistRecord> vecRec = g_sqlite->getPlayList();
    for(KPlaylistRecord rec : vecRec)
    {
        addItem(rec.path, rec.duration, rec.lastTime, rec.viewPath);
    }
    // 获取书签列表
    QVector<MarkRecord> vec_mark = g_sqlite->getMarks();
    for (MarkRecord mark : vec_mark) {
        MarkItem item;
        item.m_filePath = mark.path;
        item.m_duration = mark.duration;
        item.m_markPos = mark.pos;
        item.m_describe = mark.desc;
        item.m_previewPath = mark.preview;
        item.m_fileExit = (m_notExitFiles.find(mark.path) == m_notExitFiles.end());
        m_bookmarks.push_back(item);
    }
    qSort(m_bookmarks.begin(), m_bookmarks.end(), markSort);
    emit sigMarkUpdate(m_bookmarks);
    initIndex();
}


/** **********************************************
 * 新文件
 * 说明：新文件需要获取时长，然后添加至播放列表，如果文件获
 *      取内容失败需要做什么处理（当前未做处理）
 * @param: _file 新文件绝对路径
 *************************************************/
void PlayList::newFile(QString _file)
{
    /** **********************************
     * ffmpeg 接口获取时长和一个关键帧
     *************************************/
    // 预览关键帧保存为图片,名字为自动生成 uuid
    QUuid   t_id = QUuid::createUuid();
    QString name = t_id.toString().remove(0, 1);
    name.chop(1);
    QStringList sl = name.split("-");
    name = "";
    foreach (auto ts, sl) {
        name += ts;
    };

    QString t_file_path = Paths::previewPath().append("/").append(name.append(".jpg"));
    FFUtil fu;
    fu.open(_file);
    int t_duration = fu.getDuration();
    t_duration = t_duration > 0 ? t_duration : 0;
    // 添加 item
    emit sigAddItem(_file, t_duration, 0, t_file_path, true);
    QThread::create([this, _file, t_file_path, &fu](){
        FFUtil fuz;
        fuz.open(_file);
        fuz.saveIFrame(t_file_path);
        emit sigReloadView(_file);
    })->start();
}

void PlayList::initIndex()
{
    // 获取配置文件中但前列表索引
    if(g_settings->contains("History/playlist_index"))
        m_playIndex = g_settings->value("History/playlist_index").toInt();
    else
        m_playIndex = -1;

    // 判断下文件是否存在，不存在就不要设置当前选中行了
    if (m_fileList.size() > 0 && m_playIndex > 0) {
        QFileInfo fi(m_fileList.at(m_playIndex));
        if (fi.exists())
            setPlayIndex(m_playIndex);
    }
}

void PlayList::initGlobalSig()
{
    // 修改播放列表 主要是删除要用
    connect(g_user_signal, &GlobalUserSignal::sigListItemChange, [&](QString _file, int _duration, bool _isAdd){
        if(_isAdd)
            addItem(_file, _duration, 0, " ");
        else
            deleteFile(_file);
    });
    connect(g_user_signal, &GlobalUserSignal::sigClearPlayList  , this, &PlayList::clearList);

    connect(g_user_signal, &GlobalUserSignal::sigAddFiles       , this, &PlayList::addFiles);
    connect(g_user_signal, &GlobalUserSignal::sigAddDir         , [this](QString dir){
        QThread::create([this, dir](){addDirectory(dir);})->start();
    });
    connect(g_user_signal, &GlobalUserSignal::sigPlayNext       , this, &PlayList::playNext);
    connect(g_user_signal, &GlobalUserSignal::sigPlayPrev       , this, &PlayList::playPrev);

    connect(g_user_signal, &GlobalUserSignal::sigChangePlayOrder, [&](){
        m_playOrder = m_playOrder==RANDOM ? (PlayOrder)0 : (PlayOrder)((int)m_playOrder+1);
        g_user_signal->setPlayOrder(m_playOrder);
    });

    connect(g_user_signal, &GlobalUserSignal::sigPlayOrder, [&](PlayOrder _order){
        m_playOrder = _order;
        g_settings->setValue("General/play_order", (int)_order);
    });

    connect(g_core_signal, &GlobalCoreSignal::sigFileInfoChange , this, &PlayList::slotFileInfoChange);
    connect(g_core_signal, &GlobalCoreSignal::sigStateChange, [this](Mpv::PlayState state){
        m_playState = state;
    });

    connect(g_core_signal, &GlobalCoreSignal::sigMarkAdded, this, &PlayList::addMark);
    connect(g_core_signal, &GlobalCoreSignal::sigDuration, this, [this](QString file, int duration){
        if (m_pathItemMap.find(file) != m_pathItemMap.end()) {
            if (m_pathItemMap[file].m_isNewForDB) {
                // 更新数据库中时长
                g_sqlite->updateDuration(file, duration);
                m_pathItemMap[file].m_isNewForDB = false;
                m_pathItemMap[file].m_duration = duration;
                emit sigResetDuration(file, duration);
            }
        }
    });
}

#if 0
// 添加一个书签，添加完成之后排序然后重新加载界面
MarkList::MarkList(const MarkList &other)
{
    m_fileName = other.m_fileName;
    m_duration = other.m_duration;
    m_itemMap = other.m_itemMap;
}

void MarkList::addItem(int pos, QString desc, QString view)
{
    MarkItem item;
    item.m_duration = m_duration;
    item.m_filePath = m_fileName;
    item.m_previewPath = view;
    item.m_describe = desc;
    item.m_markPos = pos;

    m_itemMap.insert(pos, item);
    emit sigUpdateUI(m_itemMap);
}

void MarkList::deleteItem(int pos)
{
    // 应该删除之后再去通知界面
    if (m_itemMap.find(pos) != m_itemMap.end()) {
        m_itemMap.remove(pos);
    }
}

MarkList &MarkList::operator =(const MarkList other)
{
    m_fileName = other.m_fileName;
    m_duration = other.m_duration;
    m_itemMap = other.m_itemMap;
    return *this;
}
#endif
