#include "evideo.h"
#include "cfg.h"
#include "tools.h"
#include "base/waitingdlg.h"
#include "base/ffutil.h"
#include "videosplitthread.h"
#include <QLineEdit>
#include <QMessageBox>
#include <QMetaEnum>
#include <QProcess>
#include <QSettings>
#include <QSpinBox>

EVideo *EVideo::create(const QString &file, PageListItem *pageItem, EBase *multiWin) {
    int64_t dur;
    AVCodecID codecId;
    QImage img;
    QString err = videoInfo(file.toUtf8(), img, &dur, &codecId);
    if(! err.isEmpty()) {
        QMessageBox::critical(gMainWin, "Video Error", err+"\n"+file);
        return 0;
    }
    QFileInfo rawInfo(file);
    QString rawName = rawInfo.fileName();
    QString outFile = transcoding(file, rawName, pageItem->mPageDir, img.width(), img.height(), codecId);
    if(outFile.isEmpty()) return 0;
    QFileInfo outInfo(outFile);
    if(! outInfo.isFile() || outInfo.size()==0) return 0;
    return new EVideo(outInfo.absolutePath(), outInfo.fileName(), rawInfo.absolutePath(), rawName, img, dur/1000000, pageItem, multiWin);
}
EVideo *EVideo::create(const QJsonObject &json, PageListItem *pageItem, EBase *multiWin) {
    auto widget = json["widget"];
    auto dir = widget["path"].toString();
    auto name = widget["file"].toString();
    if(! QFileInfo::exists(dir)) dir = pageItem->mPageDir;
    QString file = dir + "/" + name;
    if(QFileInfo::exists(file)) ;
    else if(QFileInfo::exists(file = pageItem->mPageDir + "/" + name)) dir = pageItem->mPageDir;
    else return nullptr;
    int64_t dur;
    QImage img;
    QString err = videoInfo(file.toUtf8(), img, &dur, 0);
    if(! err.isEmpty()) return nullptr;
    auto ins = new EVideo(dir, name, widget["pathRaw"].toString(), widget["fileRaw"].toString(), img, dur/1000000, pageItem, multiWin);
    ins->setBaseAttr(json);
    auto play = json["play"];
    ins->playDuration = play["playDuration"].toInt();
    ins->playTimes = play["playTimes"].toInt();
    if(ins->playDuration<10) ins->playDuration = dur/1000000;
    return ins;
}
QJsonObject EVideo::genProg(const QJsonObject &ele, const QString &dstDir, ProgItem *progItem) {
    auto widget = ele["widget"];
    auto path = widget["path"].toString();
    auto name = widget["file"].toString();
    if(progItem->mMaxWidth) name += "-square.mp4";
    QString srcFile = path + "/" + name;
    QFileInfo srcInfo(srcFile);
    if(! srcInfo.isFile()) return QJsonObject();
    QString id = Tools::fileMd5(srcFile);
    if(id.isEmpty()) return QJsonObject();
    QFile::copy(srcFile, dstDir+"/"+id);
    QJsonObject oRes;
    oRes["_type"] = "Video";
    oRes["id"] = id;
    oRes["md5"] = id;
    oRes["name"] = name;
    oRes["size"] = srcInfo.size();
    QString suffix = srcInfo.suffix().toLower();
    oRes["fileExt"] = suffix;
    oRes["mime"] = "video/"+suffix;
    auto play = ele["play"];
    oRes["timeSpan"] = play["playDuration"].toInt() * play["playTimes"].toInt();
    oRes["enabled"] = true;
    oRes["entryEffect"] = "None";
    oRes["exitEffect"] = "None";
    oRes["entryEffectTimeSpan"] = 0;
    oRes["exitEffectTimeSpan"] = 0;
    return oRes;
}

EVideo::EVideo(const QString &dir, const QString &name, const QString &rawDir, const QString &rawName, QImage &coverImg, int dur, PageListItem *pageItem, EBase *multiWin)
    : EBase(multiWin), mDir(dir), mName(name), mRawDir(rawDir), mRawName(rawName), mCoverImg(coverImg), playDuration(dur), mPageItem(pageItem) {
    mType = EBase::Video;
}

void EVideo::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
    auto inner = innerRect();
    painter->save();
    painter->setRenderHints(QPainter::SmoothPixmapTransform);
    painter->drawImage(inner, mCoverImg);
    double maskW = mask().width()/2, maskH = mask().height()/2;
    if(maskW>inner.width() || maskH>inner.height()) {
        double rate = qMin(inner.width() / maskW, inner.height() / maskH);
        maskW *= rate;
        maskH *= rate;
    }
    painter->drawImage(QRectF(inner.left()+(inner.width() - maskW)/2, inner.top()+(inner.height() - maskH)/2, maskW, maskH), mask());
    painter->restore();
    EBase::paint(painter, option, widget);
}

QWidget* EVideo::attrWgt() {
    auto wgtAttr = new QWidget();
    auto vBox = new QVBoxLayout(wgtAttr);
    vBox->setContentsMargins(6, 0, 6, 0);
    if(mMultiWin!=nullptr) vBox->setSpacing(3);

    addBaseAttrWgt(vBox);

    auto hBox = new QHBoxLayout();
    hBox->addWidget(new QLabel(tr("Basic Properties")));

    auto line = new QFrame();
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Sunken);
    hBox->addWidget(line, 1);

    vBox->addLayout(hBox);

    hBox = new QHBoxLayout();
    hBox->addSpacing(6);
    hBox->addWidget(new QLabel(tr("File")));

    auto fdFileName = new QLineEdit(mRawName);
    fdFileName->setReadOnly(true);
    hBox->addWidget(fdFileName);

    auto bnSelectFile = new QPushButton("...");
    bnSelectFile->setFixedWidth(30);
    bnSelectFile->setObjectName("bnSelectFile");
    hBox->addWidget(bnSelectFile);

    vBox->addLayout(hBox);

//    hBox = new QHBoxLayout();
//    hBox->addSpacing(6);
//    hBox->addWidget(new QLabel(tr("AspectRatioMode")));

//    auto wAspectRatioMode = new QComboBox();
//    wAspectRatioMode->addItem(tr("IgnoreAspectRatio"));
//    wAspectRatioMode->addItem(tr("KeepAspectRatio"));
//    wAspectRatioMode->addItem(tr("KeepAspectRatioByExpanding"));
//    wAspectRatioMode->setCurrentIndex(aspectRatioMode);
//    connect(wAspectRatioMode, (void(QComboBox::*)(int))&QComboBox::currentIndexChanged, this, [this](int value) {
//        aspectRatioMode = value;
//    });
//    hBox->addWidget(wAspectRatioMode);
//    hBox->addStretch();

//    vBox->addLayout(hBox);

    hBox = new QHBoxLayout();
    hBox->addWidget(new QLabel(tr("Play Properties")));

    line = new QFrame();
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Sunken);
    hBox->addWidget(line, 1);

    vBox->addLayout(hBox);

    hBox = new QHBoxLayout();
    hBox->addSpacing(6);
    hBox->addWidget(new QLabel(tr("Play Duration")));

    auto fdDuration = new QSpinBox();
    fdDuration->setRange(1, 99999);
    fdDuration->setValue(playDuration);
    connect(fdDuration, (void(QSpinBox::*)(int))&QSpinBox::valueChanged, this, [this](int value) {
        playDuration = value;
    });
    connect(bnSelectFile, &QPushButton::clicked, this, [this, fdFileName, fdDuration] {
        auto rawFile = QFileDialog::getOpenFileName(gMainWin, tr("Select File"), gFileHome, EVideo::filters());
        if(rawFile.isEmpty()) return;
        QFileInfo rawInfo(rawFile);
        int64_t dur;
        AVCodecID codecId;
        QString err = videoInfo(rawFile.toUtf8(), mCoverImg, &dur, &codecId);
        if(! err.isEmpty()) {
            QMessageBox::critical(gMainWin, "Video Error", err+"\n"+rawFile);
            return;
        };
        mRawDir = rawInfo.absolutePath();
        mRawName = rawInfo.fileName();
        gFileHome = mRawDir;
        fdFileName->setText(mRawName);
        playDuration = dur/1000000;
        fdDuration->setValue(playDuration);
        QString outFile = transcoding(rawFile, mRawName, mPageItem->mPageDir, mCoverImg.width(), mCoverImg.height(), codecId);
        if(outFile.isEmpty()) return;
        QFile oldfile(mDir+"/"+mName);
        if(oldfile.exists()) oldfile.remove();
        QFileInfo outInfo(outFile);
        mDir = outInfo.absolutePath();
        mName = outInfo.fileName();
    });
    hBox->addWidget(fdDuration);

    hBox->addWidget(new QLabel(tr("s")));
    hBox->addStretch();

    vBox->addLayout(hBox);

    hBox = new QHBoxLayout();
    hBox->addSpacing(6);
    hBox->addWidget(new QLabel(tr("Play Times")));

    auto wPlayTimes = new QSpinBox();
    wPlayTimes->setRange(1, 99999);
    wPlayTimes->setValue(playTimes);
    connect(wPlayTimes, (void(QSpinBox::*)(int))&QSpinBox::valueChanged, this, [this](int value) {
        playTimes = value;
    });
    hBox->addWidget(wPlayTimes);
    hBox->addStretch();

    vBox->addLayout(hBox);
    vBox->addStretch();
    return wgtAttr;
}

bool EVideo::save(const QString &pageDir) {
    QString saveFile = pageDir + "/" + mName;
    if(QFile::exists(saveFile)) return true;
    QString oldFile = mDir + PAGEDEL_SUFFIX "/" + mName;
    if(QFile::exists(oldFile)) ;
    else if(QFile::exists(oldFile = mDir + "/" + mName)) ;
    else return false;
    QFile(oldFile).copy(saveFile);
    mDir = pageDir;
    if(gProgItem->mMaxWidth) {
        auto waitingDlg = new WaitingDlg(gProgEditorWin, "正在转码视频 ...");
        auto thread = new VideoSplitThread(mWidth, mHeight, gProgItem->mMaxWidth, gProgItem->mHeight, gProgItem->mSplitWidths, pos(), saveFile.toUtf8());
        connect(thread, &VideoSplitThread::emErr, this, [saveFile, waitingDlg](QString err) {
            waitingDlg->close();
            if(! err.isEmpty()) QMessageBox::critical(gProgEditorWin, "Video trans error", err+"\n"+saveFile);
        });
        connect(thread, &VideoSplitThread::emProgress, this, [saveFile, waitingDlg](int progress) {
            waitingDlg->fdText->setText(QString("正在转码视频 %1%").arg(progress));
        });
        thread->start();
        waitingDlg->exec();
    }
    return true;
}

QJsonObject EVideo::attrJson() const {
    QJsonObject oRoot;
    addBaseAttr(oRoot);
    oRoot["elementType"] = "Movie";
    oRoot["widget"] = QJsonObject{
        {"path", mDir},
        {"file", mName},
        {"pathRaw", mRawDir},
        {"fileRaw", mRawName}
    };
    oRoot["play"] = QJsonObject{
        {"playDuration", playDuration},
        {"playTimes", playTimes}
    };
    return oRoot;
}

QString EVideo::transcoding(QString rawFile, QString rawName, QString dir, int w, int h, AVCodecID codec_id) {
    QSettings settings;
    int rawMax = qMax(w, h);
    if(settings.value("VideoCompress", true).toBool() && rawMax > 1360 && (w > gProgItem->mWidth || h > gProgItem->mHeight)) {
        double rate = 1280.0 / rawMax;
        w *= rate;
        h *= rate;
    } else if(! settings.value("VideoTranscoding", true).toBool() || codec_id == AV_CODEC_ID_H264) {
        QString outFile = dir+"/"+rawName;
        QFile::copy(rawFile, outFile);
        return outFile;
    }
    QString outFile = dir+"/"+Tools::fileMd5(rawFile)+".mp4";
    if(QFileInfo::exists(outFile)) return outFile;

    QProcess process;
    process.setWorkingDirectory(QApplication::applicationDirPath());
    QMessageBox msgBox(gMainWin);
    msgBox.setWindowFlag(Qt::WindowCloseButtonHint, false);
    msgBox.setStandardButtons(QMessageBox::NoButton);
    msgBox.setWindowTitle(tr("Video Transcoding"));
    msgBox.setText(tr("Video Transcoding Progress")+": 0%    ");
    connect(&process, (void(QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished, &msgBox, [&msgBox] {
        msgBox.accept();
    });
    connect(&process, &QProcess::errorOccurred, &msgBox, [&msgBox](QProcess::ProcessError error) {
        msgBox.accept();
        QMessageBox::critical(gMainWin, tr("Error"), QString(QMetaEnum::fromType<QProcess::ProcessError>().valueToKey(error))+" ("+QString::number(error)+")");
    });
    connect(&process, &QProcess::readyReadStandardOutput, &msgBox, [&process] {
        auto data = process.readAllStandardOutput();
        qInfo()<<"EVideo::transcoding StandardOutput";
        qInfo()<<data.data();
    });
    int dur = 0;
    QString err;
    connect(&process, &QProcess::readyReadStandardError, &msgBox, [&process, &msgBox, &dur, &err] {
        auto data = process.readAllStandardError();
        qInfo()<<"EVideo::transcoding ffmpeg Output";
        qInfo()<<data.data();
        auto txt = QString::fromUtf8(data);
        if(dur==0) {
            int idx = txt.indexOf("Duration: ");
            if(idx > -1) dur = QTime::fromString(txt.mid(idx+10, 11)+"0", "HH:mm:ss.zzz").msecsSinceStartOfDay();
        } else {
            int idx = txt.indexOf("time=");
            if(idx > -1) {
                auto cur = QTime::fromString(txt.mid(idx+5, 11)+"0", "HH:mm:ss.zzz").msecsSinceStartOfDay();
                if(dur > 0) msgBox.setText(tr("Video Transcoding Progress")+": "+QString::number(cur*100/dur)+"%    ");
            } else err.append(txt);
        }
    });
    process.start("ffmpeg", {"-y", "-i", rawFile, "-vcodec", "h264", "-s", QString::number(w)+"x"+QString::number(h), "-profile:v", "main", "-b:v", QString::number(w*h/150)+"k", outFile});
    msgBox.exec();
    if(err.right(32).contains("Conversion failed!")) {
        QMessageBox::critical(gMainWin, tr("Error"), err);
        return "";
    }
    return outFile;
}
