/*
 * Copyright (c) 2012-2026 Meltytech, LLC
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

#include "meltjob.h"

#include "Logger.h"
#include "dialogs/textviewerdialog.h"
#include "mainwindow.h"
#include "settings.h"
#include "util.h"

#include <QAction>
#include <QApplication>
#include <QDialog>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QIODevice>
#include <QTimer>

MeltJob::MeltJob(const QString &name,
                 const QString &xml,
                 int frameRateNum,
                 int frameRateDen,
                 QThread::Priority priority)
    : AbstractJob(name, priority)
    , m_isStreaming(false)
    , m_previousPercent(0)
    , m_currentFrame(0)
    , m_useMultiConsumer(false)
{
    setTarget(name);
    if (!xml.isEmpty()) {
        QAction *action = new QAction(tr("View XML"), this);
        action->setToolTip(tr("View the MLT XML for this job"));
        connect(action, SIGNAL(triggered()), this, SLOT(onViewXmlTriggered()));
        m_standardActions << action;
        m_xml.reset(Util::writableTemporaryFile(name, "shotcut-XXXXXX.mlt"));
        if (m_xml->open()) {
            m_xml->write(xml.toUtf8());
            m_xml->close();
        }
    } else {
        // Not an EncodeJob
        QAction *action = new QAction(tr("Open"), this);
        action->setData("Open");
        action->setToolTip(tr("Open the output file in the Shotcut player"));
        connect(action, SIGNAL(triggered()), this, SLOT(onOpenTiggered()));
        m_successActions << action;

        action = new QAction(tr("Show In Files"), this);
        action->setToolTip(tr("Show In Files"));
        connect(action, SIGNAL(triggered()), this, SLOT(onShowInFilesTriggered()));
        m_successActions << action;

        action = new QAction(tr("Show In Folder"), this);
        action->setToolTip(tr("Show In Folder"));
        connect(action, SIGNAL(triggered()), this, SLOT(onShowFolderTriggered()));
        m_successActions << action;
    }
    if (frameRateNum > 0 && frameRateDen > 0)
        m_profile.set_frame_rate(frameRateNum, frameRateDen);
}

void MeltJob::onOpenTiggered()
{
    MAIN.open(objectName().toUtf8().constData());
}

void MeltJob::onShowFolderTriggered()
{
    Util::showInFolder(objectName());
}

void MeltJob::onShowInFilesTriggered()
{
    MAIN.showInFiles(objectName());
}

MeltJob::MeltJob(const QString &name,
                 const QString &xml,
                 const QStringList &args,
                 int frameRateNum,
                 int frameRateDen)
    : MeltJob(name, xml, frameRateNum, frameRateDen)
{
    m_args = args;
}

MeltJob::MeltJob(const QString &name, const QStringList &args, int frameRateNum, int frameRateDen)
    : MeltJob(name, QString(), frameRateNum, frameRateDen)
{
    m_args = args;
}

MeltJob::~MeltJob()
{
    LOG_DEBUG() << "begin";
}

void MeltJob::start()
{
    if (m_args.isEmpty() && !m_xml) {
        AbstractJob::start();
        LOG_ERROR() << "the job XML is empty!";
        appendToLog("Error: the job XML is empty!\n");
        QTimer::singleShot(0, this, [=]() { emit finished(this, false); });
        return;
    }
    QString shotcutPath = qApp->applicationDirPath();
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
    QFileInfo meltPath(shotcutPath, "melt-7");
#else
    QFileInfo meltPath(shotcutPath, "melt");
#endif
    setReadChannel(QProcess::StandardError);
    QStringList args;
    args << "-verbose";
    args << "-progress2";
    args << "-abort";
    if (!m_xml.isNull()) {
        if (m_useMultiConsumer) {
            args << "xml:" + QUrl::toPercentEncoding(xmlPath()) + "?multi:1";
        } else {
            args << "xml:" + QUrl::toPercentEncoding(xmlPath());
        }
    }
    if (m_args.size() > 0) {
        args.append(m_args);
    }
    if (m_in > -1) {
        args << QStringLiteral("in=%1").arg(m_in);
    }
    if (m_out > -1) {
        args << QStringLiteral("out=%1").arg(m_out);
    }
    LOG_DEBUG() << meltPath.absoluteFilePath() + " " + args.join(' ');
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
#ifndef Q_OS_MAC
    // These environment variables fix rich text rendering for high DPI
    // fractional or otherwise.
    env.insert("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
    env.insert("QT_SCALE_FACTOR_ROUNDING_POLICY", "PassThrough");
#endif
#ifdef Q_OS_MAC
    // Prevent melt from appearing in the Dock by using the offscreen Qt
    // platform instead of Cocoa, which would register it as a foreground app.
    // GPU processing requires OpenGL, so skip this when it is enabled.
    if (!Settings.playerGPU())
        env.insert("QT_QPA_PLATFORM", "offscreen");
#endif
    if (!Settings.encodeHardwareDecoder()) {
        env.remove("MLT_AVFORMAT_HWACCEL");
    }
    env.remove("MLT_AVFORMAT_HWACCEL_PPS");
    setProcessEnvironment(env);
#ifdef Q_OS_WIN
    if (m_isStreaming)
        args << "-getc";
#endif
    AbstractJob::start(meltPath.absoluteFilePath(), args);
}

QString MeltJob::xml()
{
    if (m_xml->open()) {
        QString s(m_xml->readAll());
        m_xml->close();
        return s;
    } else {
        return QString();
    }
}

void MeltJob::setIsStreaming(bool streaming)
{
    m_isStreaming = streaming;
}

void MeltJob::setUseMultiConsumer(bool multi)
{
    m_useMultiConsumer = multi;
}

void MeltJob::setInAndOut(int in, int out)
{
    m_in = in;
    m_out = out;
}

void MeltJob::onViewXmlTriggered()
{
    TextViewerDialog dialog(&MAIN, true);
    dialog.setWindowTitle(tr("MLT XML"));
    dialog.setText(xml());
    dialog.exec();
}

void MeltJob::onReadyRead()
{
    QString msg;
    do {
        msg = readLine();
        int index = msg.indexOf("Frame:");
        if (index > -1) {
            index += 6;
            int comma = msg.indexOf(',', index);
            m_currentFrame = msg.mid(index, comma - index).toInt();
        }
        index = msg.indexOf("percentage:");
        if (index > -1) {
            int percent = msg.mid(index + 11).toInt();
            if (percent > m_previousPercent) {
                emit progressUpdated(m_item, percent);
                QCoreApplication::processEvents();
                m_previousPercent = percent;
            }
        } else {
            appendToLog(msg);
        }
    } while (!msg.isEmpty());
}

void MeltJob::onFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    AbstractJob::onFinished(exitCode, exitStatus);
    if (exitStatus != QProcess::NormalExit && exitCode != 0 && !stopped()) {
        Mlt::Producer producer(m_profile, "colour:");
        QString time = QString::fromLatin1(producer.frames_to_time(m_currentFrame));
        emit finished(this, false, time);
    }
}
