#include "vexporter.h"

#include <QDebug>
#include <QWidget>
#include <QWebChannel>
#include <QWebEngineProfile>
#include <QRegExp>
#include <QProcess>
#include <QTemporaryDir>
#include <QScopedPointer>
#include <QCoreApplication>

#include "vconfigmanager.h"
#include "vfile.h"
#include "vwebview.h"
#include "utils/vutils.h"
#include "vpreviewpage.h"
#include "vconstants.h"
#include "vmarkdownconverter.h"
#include "vdocument.h"
#include "utils/vwebutils.h"

extern VConfigManager *g_config;

extern VWebUtils *g_webUtils;

VExporter::VExporter(QWidget *p_parent)
    : QObject(p_parent),
      m_webViewer(NULL),
      m_state(ExportState::Idle),
      m_askedToStop(false)
{
}

static QString marginToStrMM(qreal p_margin)
{
    return QString("%1mm").arg(p_margin);
}

void VExporter::prepareExport(const ExportOption &p_opt)
{
    bool isPdf = p_opt.m_format == ExportFormat::PDF
                 || p_opt.m_format == ExportFormat::OnePDF
                 || (p_opt.m_format == ExportFormat::Custom
                     && p_opt.m_customOpt.m_pdfLike);
    bool extraToc = isPdf
                    && !p_opt.m_pdfOpt.m_wkhtmltopdf
                    && p_opt.m_pdfOpt.m_enableTableOfContents;

    m_htmlTemplate = VUtils::generateHtmlTemplate(p_opt.m_renderer,
                                                  p_opt.m_renderBg,
                                                  p_opt.m_renderStyle,
                                                  p_opt.m_renderCodeBlockStyle,
                                                  isPdf,
                                                  isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf,
                                                  extraToc);

    bool outline = p_opt.m_htmlOpt.m_outlinePanel
                   && !isPdf
                   && (p_opt.m_format == ExportFormat::HTML
                       || p_opt.m_format == ExportFormat::Custom);
    m_exportHtmlTemplate = VUtils::generateExportHtmlTemplate(p_opt.m_renderBg,
                                                              isPdf && p_opt.m_pdfOpt.m_wkhtmltopdf,
                                                              outline);

    m_pageLayout = *(p_opt.m_pdfOpt.m_layout);

    prepareWKArguments(p_opt.m_pdfOpt);
}

void VExporter::prepareWKArguments(const ExportPDFOption &p_opt)
{
    m_wkArgs.clear();
    m_wkArgs << "--page-size" << m_pageLayout.pageSize().key();
    m_wkArgs << "--orientation"
             << (m_pageLayout.orientation() == QPageLayout::Portrait ? "Portrait" : "Landscape");

    QMarginsF marginsMM = m_pageLayout.margins(QPageLayout::Millimeter);
    m_wkArgs << "--margin-bottom" << marginToStrMM(marginsMM.bottom());
    m_wkArgs << "--margin-left" << marginToStrMM(marginsMM.left());
    m_wkArgs << "--margin-right" << marginToStrMM(marginsMM.right());
    m_wkArgs << "--margin-top" << marginToStrMM(marginsMM.top());

    QString footer;
    switch (p_opt.m_wkPageNumber) {
    case ExportPageNumber::Left:
        footer = "--footer-left";
        break;

    case ExportPageNumber::Center:
        footer = "--footer-center";
        break;

    case ExportPageNumber::Right:
        footer = "--footer-right";
        break;

    default:
        break;
    }

    if (!footer.isEmpty()) {
        m_wkArgs << footer << "[page]"
                 << "--footer-spacing" << QString::number(marginsMM.bottom() / 3, 'f', 2);
    }

    // Title.
    if (!p_opt.m_wkTitle.isEmpty()) {
        m_wkArgs << "--title" << p_opt.m_wkTitle;
    }

    m_wkArgs << "--encoding" << "utf-8";
    m_wkArgs << (p_opt.m_wkEnableBackground ? "--background" : "--no-background");

    // Delay for MathJax.
    if (p_opt.m_wkhtmltopdf) {
        m_wkArgs << "--javascript-delay" << "10000";
    }

    // Append additional global option.
    if (!p_opt.m_wkExtraArgs.isEmpty()) {
        m_wkArgs.append(VUtils::parseCombinedArgString(p_opt.m_wkExtraArgs));
    }

    // TOC option.
    if (p_opt.m_enableTableOfContents) {
        m_wkArgs << "toc" << "--toc-text-size-shrink" << "1.0";
    }
}

bool VExporter::exportPDF(VFile *p_file,
                          const ExportOption &p_opt,
                          const QString &p_outputFile,
                          QString *p_errMsg)
{
    return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg);
}

bool VExporter::exportHTML(VFile *p_file,
                           const ExportOption &p_opt,
                           const QString &p_outputFile,
                           QString *p_errMsg)
{
    return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg);
}

static void replaceArgument(QString &p_cmd, const QString &p_arg, const QString &p_val)
{
    if (p_val.startsWith("\"")) {
        // Check if the arg has been already surrounded by ".
        int pos = 0;
        while (pos < p_cmd.size()) {
            int idx = p_cmd.indexOf(p_arg, pos);
            if (idx == -1) {
                break;
            }

            int len = p_arg.size();
            int nidx = idx;
            if (idx > 0 && p_cmd[idx - 1] == '"') {
                --nidx;
                len += 1;
            }

            if (idx + p_arg.size() < p_cmd.size()
                && p_cmd[idx + p_arg.size()] == '"') {
                len += 1;
            }

            p_cmd.replace(nidx, len, p_val);
            pos = nidx + p_val.size() - len;
        }
    } else {
        p_cmd.replace(p_arg, p_val);
    }
}

static QString evaluateCommand(const ExportCustomOption &p_opt,
                               const QString &p_input,
                               const QString &p_inputFolder,
                               const QString &p_output)
{
    QString cmd(p_opt.m_cmd);
    replaceArgument(cmd, "%0", p_input);
    replaceArgument(cmd, "%1", p_output);
    replaceArgument(cmd, "%2", QDir::toNativeSeparators(p_opt.m_cssUrl));
    replaceArgument(cmd, "%3", p_inputFolder);
    replaceArgument(cmd, "%4", QDir::toNativeSeparators(p_opt.m_codeBlockCssUrl));

    return cmd;
}

bool VExporter::exportCustom(VFile *p_file,
                             const ExportOption &p_opt,
                             const QString &p_outputFile,
                             QString *p_errMsg)
{
    const ExportCustomOption &customOpt = p_opt.m_customOpt;
    if (customOpt.m_srcFormat == ExportCustomOption::Markdown) {
        // Use Markdown file as input.
        QList<QString> files;
        files.append(QDir::toNativeSeparators(p_file->fetchPath()));
        return convertFilesViaCustom(files, p_outputFile, customOpt, p_errMsg);
    } else {
        return exportViaWebView(p_file, p_opt, p_outputFile, p_errMsg);
    }
}

void VExporter::initWebViewer(VFile *p_file, const ExportOption &p_opt)
{
    Q_ASSERT(!m_webViewer);

    m_webViewer = new VWebView(p_file, static_cast<QWidget *>(parent()));
    m_webViewer->hide();

    VPreviewPage *page = new VPreviewPage(m_webViewer);
    m_webViewer->setPage(page);
    connect(page, &VPreviewPage::loadFinished,
            this, &VExporter::handleLoadFinished);
    connect(page->profile(), &QWebEngineProfile::downloadRequested,
            this, &VExporter::handleDownloadRequested);

    m_webDocument = new VDocument(p_file, m_webViewer);
    connect(m_webDocument, &VDocument::logicsFinished,
            this, &VExporter::handleLogicsFinished);

    QWebChannel *channel = new QWebChannel(m_webViewer);
    channel->registerObject(QStringLiteral("content"), m_webDocument);
    page->setWebChannel(channel);

    // Need to generate HTML using Hoedown.
    if (p_opt.m_renderer == MarkdownConverterType::Hoedown) {
        VMarkdownConverter mdConverter;
        QString toc;
        QString html = mdConverter.generateHtml(p_file->getContent(),
                                                g_config->getMarkdownExtensions(),
                                                toc);
        bool isPdf = p_opt.m_format == ExportFormat::PDF
                     || p_opt.m_format == ExportFormat::OnePDF;
        bool extraToc = isPdf
                        && !p_opt.m_pdfOpt.m_wkhtmltopdf
                        && p_opt.m_pdfOpt.m_enableTableOfContents;
        if (extraToc && !toc.isEmpty()) {
            // Add toc to html.
            QString div = "<div class=\"vnote-toc\">" + toc + "</div>\n";
            html = div + html;
        }

        m_webDocument->setHtml(html);
    }

    m_baseUrl = p_file->getBaseUrl();
    m_webViewer->setHtml(m_htmlTemplate, m_baseUrl);
}

void VExporter::handleLogicsFinished()
{
    Q_ASSERT(!(m_noteState & NoteState::WebLogicsReady));
    m_noteState = NoteState(m_noteState | NoteState::WebLogicsReady);
}

void VExporter::handleLoadFinished(bool p_ok)
{
    Q_ASSERT(!(m_noteState & NoteState::WebLoadFinished));
    m_noteState = NoteState(m_noteState | NoteState::WebLoadFinished);

    if (!p_ok) {
        m_noteState = NoteState(m_noteState | NoteState::Failed);
    }
}

void VExporter::clearWebViewer()
{
    // m_webDocument will be freeed by QObject.
    delete m_webViewer;
    m_webViewer = NULL;
    m_webDocument = NULL;
    m_baseUrl.clear();
}

bool VExporter::exportToPDF(VWebView *p_webViewer,
                            const QString &p_filePath,
                            const QPageLayout &p_layout)
{
    int pdfPrinted = 0;
    p_webViewer->page()->printToPdf([&, this](const QByteArray &p_result) {
        if (p_result.isEmpty() || this->m_state == ExportState::Cancelled) {
            pdfPrinted = -1;
            return;
        }

        V_ASSERT(!p_filePath.isEmpty());

        if (!VUtils::writeFileToDisk(p_filePath, p_result)) {
            pdfPrinted = -1;
            return;
        }

        pdfPrinted = 1;
    }, p_layout);

    while (pdfPrinted == 0) {
        VUtils::sleepWait(100);

        if (m_state == ExportState::Cancelled) {
            break;
        }
    }

    return pdfPrinted == 1;
}

bool VExporter::exportToPDFViaWK(VDocument *p_webDocument,
                                 const ExportPDFOption &p_opt,
                                 const QString &p_filePath,
                                 QString *p_errMsg)
{
    int pdfExported = 0;

    connect(p_webDocument, &VDocument::htmlContentFinished,
            this, [&, this](const QString &p_headContent,
                            const QString &p_styleContent,
                            const QString &p_bodyContent) {
                if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) {
                    pdfExported = -1;
                    return;
                }

                Q_ASSERT(!p_filePath.isEmpty());

                // Save HTML to a temp dir.
                QTemporaryDir tmpDir;
                if (!tmpDir.isValid()) {
                    pdfExported = -1;
                    return;
                }

                QString htmlPath = tmpDir.filePath("vnote_tmp.html");
                QString title = p_webDocument->getFile()->getName();
                title = QFileInfo(title).completeBaseName();
                if (!outputToHTMLFile(htmlPath,
                                      title,
                                      p_headContent,
                                      p_styleContent,
                                      p_bodyContent,
                                      true,
                                      true,
                                      false)) {
                    pdfExported = -1;
                    return;
                }

                // Convert via wkhtmltopdf.
                QList<QString> files;
                files.append(htmlPath);
                if (!htmlsToPDFViaWK(files, p_filePath, p_opt, p_errMsg)) {
                    pdfExported = -1;
                } else {
                    pdfExported = 1;
                }
            });

    p_webDocument->getHtmlContentAsync();

    while (pdfExported == 0) {
        VUtils::sleepWait(100);

        if (m_state == ExportState::Cancelled) {
            break;
        }
    }

    return pdfExported == 1;
}

bool VExporter::exportToCustom(VDocument *p_webDocument,
                               const ExportCustomOption &p_opt,
                               const QString &p_filePath,
                               QString *p_errMsg)
{
    int exported = 0;

    connect(p_webDocument, &VDocument::htmlContentFinished,
            this, [&, this](const QString &p_headContent,
                            const QString &p_styleContent,
                            const QString &p_bodyContent) {
                if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) {
                    exported = -1;
                    return;
                }

                Q_ASSERT(!p_filePath.isEmpty());

                // Save HTML to a temp dir.
                QTemporaryDir tmpDir;
                if (!tmpDir.isValid()) {
                    exported = -1;
                    return;
                }

                QString htmlPath = tmpDir.filePath("vnote_tmp.html");
                QString title = p_webDocument->getFile()->getName();
                title = QFileInfo(title).completeBaseName();
                if (!outputToHTMLFile(htmlPath,
                                      title,
                                      p_headContent,
                                      p_styleContent,
                                      p_bodyContent,
                                      true,
                                      true,
                                      false)) {
                    exported = -1;
                    return;
                }

                // Convert via custom command.
                QList<QString> files;
                files.append(htmlPath);
                if (!convertFilesViaCustom(files, p_filePath, p_opt, p_errMsg)) {
                    exported = -1;
                } else {
                    exported = 1;
                }
            });

    p_webDocument->getHtmlContentAsync();

    while (exported == 0) {
        VUtils::sleepWait(100);

        if (m_state == ExportState::Cancelled) {
            break;
        }
    }

    return exported == 1;
}

bool VExporter::exportViaWebView(VFile *p_file,
                                 const ExportOption &p_opt,
                                 const QString &p_outputFile,
                                 QString *p_errMsg)
{
    Q_UNUSED(p_errMsg);

    bool ret = false;

    bool isOpened = p_file->isOpened();
    if (!isOpened && !p_file->open()) {
        goto exit;
    }

    Q_ASSERT(m_state == ExportState::Idle);
    m_state = ExportState::Busy;

    clearNoteState();

    initWebViewer(p_file, p_opt);

    while (!isNoteStateReady()) {
        VUtils::sleepWait(100);

        if (m_state == ExportState::Cancelled) {
            goto exit;
        }

        if (isNoteStateFailed()) {
            m_state = ExportState::Failed;
            goto exit;
        }

        if (m_askedToStop) {
            m_state = ExportState::Cancelled;
            goto exit;
        }
    }

    // Wait to ensure Web side is really ready.
    VUtils::sleepWait(200);

    if (m_state == ExportState::Cancelled) {
        goto exit;
    }

    {

    bool exportRet = false;
    switch (p_opt.m_format) {
    case ExportFormat::PDF:
        V_FALLTHROUGH;
    case ExportFormat::OnePDF:
        if (p_opt.m_pdfOpt.m_wkhtmltopdf) {
            exportRet = exportToPDFViaWK(m_webDocument,
                                         p_opt.m_pdfOpt,
                                         p_outputFile,
                                         p_errMsg);
        } else {
            exportRet = exportToPDF(m_webViewer,
                                    p_outputFile,
                                    m_pageLayout);
        }

        break;

    case ExportFormat::HTML:
        if (p_opt.m_htmlOpt.m_mimeHTML) {
            exportRet = exportToMHTML(m_webViewer,
                                      p_opt.m_htmlOpt,
                                      p_outputFile);
        } else {
            exportRet = exportToHTML(m_webDocument,
                                     p_opt.m_htmlOpt,
                                     p_outputFile);
        }

        break;

    case ExportFormat::Custom:
        exportRet = exportToCustom(m_webDocument,
                                   p_opt.m_customOpt,
                                   p_outputFile,
                                   p_errMsg);
        break;

    default:
        break;
    }

    clearNoteState();

    if (!isOpened) {
        p_file->close();
    }

    if (exportRet) {
        m_state = ExportState::Successful;
    } else {
        m_state = ExportState::Failed;
    }

    }

exit:
    clearWebViewer();

    if (m_state == ExportState::Successful) {
        ret = true;
    }

    m_state = ExportState::Idle;

    return ret;
}

bool VExporter::exportToHTML(VDocument *p_webDocument,
                             const ExportHTMLOption &p_opt,
                             const QString &p_filePath)
{
    int htmlExported = 0;

    connect(p_webDocument, &VDocument::htmlContentFinished,
            this, [&, this](const QString &p_headContent,
                            const QString &p_styleContent,
                            const QString &p_bodyContent) {
                if (p_bodyContent.isEmpty() || this->m_state == ExportState::Cancelled) {
                    htmlExported = -1;
                    return;
                }

                Q_ASSERT(!p_filePath.isEmpty());

                QString title = p_webDocument->getFile()->getName();
                title = QFileInfo(title).completeBaseName();
                if (!outputToHTMLFile(p_filePath,
                                      title,
                                      p_headContent,
                                      p_styleContent,
                                      p_bodyContent,
                                      p_opt.m_embedCssStyle,
                                      p_opt.m_completeHTML,
                                      p_opt.m_embedImages)) {
                    htmlExported = -1;
                    return;
                }

                htmlExported = 1;
            });

    p_webDocument->getHtmlContentAsync();

    while (htmlExported == 0) {
        VUtils::sleepWait(100);

        if (m_state == ExportState::Cancelled) {
            break;
        }
    }

    return htmlExported == 1;
}

bool VExporter::fixStyleResources(const QString &p_folder,
                                  QString &p_html)
{
    bool altered = false;
    QRegExp reg("\\burl\\(\"((file|qrc):[^\"\\)]+)\"\\);");

    int pos = 0;
    while (pos < p_html.size()) {
        int idx = p_html.indexOf(reg, pos);
        if (idx == -1) {
            break;
        }

        QString targetFile = g_webUtils->copyResource(QUrl(reg.cap(1)), p_folder);
        if (targetFile.isEmpty()) {
            pos = idx + reg.matchedLength();
        } else {
            // Replace the url string in html.
            QString newUrl = QString("url(\"%1\");").arg(getResourceRelativePath(targetFile));
            p_html.replace(idx, reg.matchedLength(), newUrl);
            pos = idx + newUrl.size();
            altered = true;
        }
    }

    return altered;
}

bool VExporter::embedStyleResources(QString &p_html)
{
    bool altered = false;
    QRegExp reg("\\burl\\(\"((file|qrc):[^\"\\)]+)\"\\);");

    int pos = 0;
    while (pos < p_html.size()) {
        int idx = p_html.indexOf(reg, pos);
        if (idx == -1) {
            break;
        }

        QString dataURI = g_webUtils->dataURI(QUrl(reg.cap(1)), false);
        if (dataURI.isEmpty()) {
            pos = idx + reg.matchedLength();
        } else {
            // Replace the url string in html.
            QString newUrl = QString("url('%1');").arg(dataURI);
            p_html.replace(idx, reg.matchedLength(), newUrl);
            pos = idx + newUrl.size();
            altered = true;
        }
    }

    return altered;
}

bool VExporter::fixBodyResources(const QUrl &p_baseUrl,
                                 const QString &p_folder,
                                 QString &p_html)
{
    bool altered = false;
    if (p_baseUrl.isEmpty()) {
        return altered;
    }

    QRegExp reg("<img ([^>]*)src=\"([^\"]+)\"([^>]*)>");

    int pos = 0;
    while (pos < p_html.size()) {
        int idx = p_html.indexOf(reg, pos);
        if (idx == -1) {
            break;
        }

        if (reg.cap(2).isEmpty()) {
            pos = idx + reg.matchedLength();
            continue;
        }

        QUrl srcUrl(p_baseUrl.resolved(reg.cap(2)));
        QString targetFile = g_webUtils->copyResource(srcUrl, p_folder);
        if (targetFile.isEmpty()) {
            pos = idx + reg.matchedLength();
        } else {
            // Replace the url string in html.
            QString newUrl = QString("<img %1src=\"%2\"%3>").arg(reg.cap(1))
                                                            .arg(getResourceRelativePath(targetFile))
                                                            .arg(reg.cap(3));
            p_html.replace(idx, reg.matchedLength(), newUrl);
            pos = idx + newUrl.size();
            altered = true;
        }
    }

    return altered;
}

bool VExporter::embedBodyResources(const QUrl &p_baseUrl, QString &p_html)
{
    bool altered = false;
    if (p_baseUrl.isEmpty()) {
        return altered;
    }

    QRegExp reg("<img ([^>]*)src=\"([^\"]+)\"([^>]*)>");

    int pos = 0;
    while (pos < p_html.size()) {
        int idx = p_html.indexOf(reg, pos);
        if (idx == -1) {
            break;
        }

        if (reg.cap(2).isEmpty()) {
            pos = idx + reg.matchedLength();
            continue;
        }

        QUrl srcUrl(p_baseUrl.resolved(reg.cap(2)));
        QString dataURI = g_webUtils->dataURI(srcUrl);
        if (dataURI.isEmpty()) {
            pos = idx + reg.matchedLength();
        } else {
            // Replace the url string in html.
            QString newUrl = QString("<img %1src='%2'%3>").arg(reg.cap(1))
                                                          .arg(dataURI)
                                                          .arg(reg.cap(3));
            p_html.replace(idx, reg.matchedLength(), newUrl);
            pos = idx + newUrl.size();
            altered = true;
        }
    }

    return altered;
}

QString VExporter::getResourceRelativePath(const QString &p_file)
{
    int idx = p_file.lastIndexOf('/');
    int idx2 = p_file.lastIndexOf('/', idx - 1);
    Q_ASSERT(idx > 0 && idx2 < idx);
    return "." + p_file.mid(idx2);
}

bool VExporter::exportToMHTML(VWebView *p_webViewer,
                              const ExportHTMLOption &p_opt,
                              const QString &p_filePath)
{
    Q_UNUSED(p_opt);

    m_downloadState = QWebEngineDownloadItem::DownloadRequested;

    p_webViewer->page()->save(p_filePath, QWebEngineDownloadItem::MimeHtmlSaveFormat);

    while (m_downloadState == QWebEngineDownloadItem::DownloadRequested
           || m_downloadState == QWebEngineDownloadItem::DownloadInProgress) {
        VUtils::sleepWait(100);
    }

    return m_downloadState == QWebEngineDownloadItem::DownloadCompleted;
}

void VExporter::handleDownloadRequested(QWebEngineDownloadItem *p_item)
{
    if (p_item->savePageFormat() == QWebEngineDownloadItem::MimeHtmlSaveFormat) {
        connect(p_item, &QWebEngineDownloadItem::stateChanged,
                this, [this](QWebEngineDownloadItem::DownloadState p_state) {
                    m_downloadState = p_state;
                });
    }
}

static QString combineArgs(QStringList &p_args)
{
    QString str;
    for (const QString &arg : p_args) {
        QString tmp;
        if (arg.contains(' ')) {
            tmp = '"' + arg + '"';
        } else {
            tmp = arg;
        }

        if (str.isEmpty()) {
            str = tmp;
        } else {
            str = str + ' ' + tmp;
        }
    }

    return str;
}

bool VExporter::htmlsToPDFViaWK(const QList<QString> &p_htmlFiles,
                                const QString &p_filePath,
                                const ExportPDFOption &p_opt,
                                QString *p_errMsg)
{
    // Note: system's locale settings (Language for non-Unicode programs) is important to wkhtmltopdf.
    // Input file could be encoded via QUrl::fromLocalFile(p_htmlFile).toString(QUrl::EncodeUnicode) to
    // handle non-ASCII path.

    QStringList args(m_wkArgs);

    for (auto const & it : p_htmlFiles) {
        args << QDir::toNativeSeparators(it);
    }

    args << QDir::toNativeSeparators(p_filePath);

    QString cmd = p_opt.m_wkPath + " " + combineArgs(args);
    emit outputLog(cmd);
    qDebug() << "wkhtmltopdf cmd:" << cmd;
    int ret = startProcess(p_opt.m_wkPath, args);
    qDebug() << "wkhtmltopdf returned" << ret;
    if (m_askedToStop) {
        return ret == 0;
    }

    switch (ret) {
    case -2:
        VUtils::addErrMsg(p_errMsg, tr("Fail to start wkhtmltopdf (%1).").arg(cmd));
        break;

    case -1:
        VUtils::addErrMsg(p_errMsg, tr("wkhtmltopdf crashed (%1).").arg(cmd));
        break;

    default:
        break;
    }

    return ret == 0;
}

bool VExporter::convertFilesViaCustom(const QList<QString> &p_files,
                                      const QString &p_filePath,
                                      const ExportCustomOption &p_opt,
                                      QString *p_errMsg)
{
    QString input;
    QString inputFolder;
    for (auto const & it : p_files) {
        if (!input.isEmpty()) {
            input += " ";
        }

        if (!inputFolder.isEmpty()) {
            inputFolder += p_opt.m_folderSep;
        }

        QString tmp = QDir::toNativeSeparators(it);
        input += ("\"" + tmp + "\"");
        inputFolder += ("\"" + VUtils::basePathFromPath(tmp) + "\"");
    }

    QString output = QDir::toNativeSeparators(p_filePath);
    QString cmd = evaluateCommand(p_opt,
                                  input,
                                  inputFolder,
                                  output);
    emit outputLog(cmd);
    qDebug() << "custom cmd:" << cmd;
    int ret = startProcess(cmd);
    qDebug() << "custom cmd returned" << ret;
    if (m_askedToStop) {
        return ret == 0;
    }

    switch (ret) {
    case -2:
        VUtils::addErrMsg(p_errMsg, tr("Fail to start custom command (%1).").arg(cmd));
        break;

    case -1:
        VUtils::addErrMsg(p_errMsg, tr("Custom command crashed (%1).").arg(cmd));
        break;

    default:
        break;
    }

    return ret == 0;
}

int VExporter::exportPDFInOne(const QList<QString> &p_htmlFiles,
                              const ExportOption &p_opt,
                              const QString &p_outputFile,
                              QString *p_errMsg)
{
    if (!htmlsToPDFViaWK(p_htmlFiles, p_outputFile, p_opt.m_pdfOpt, p_errMsg)) {
        return 0;
    }

    return p_htmlFiles.size();
}

int VExporter::startProcess(const QString &p_program, const QStringList &p_args)
{
    int ret = 0;
    QScopedPointer<QProcess> process(new QProcess(this));
    process->start(p_program, p_args);
    bool finished = false;
    bool started = false;
    while (true) {
        QProcess::ProcessError err = process->error();
        if (err == QProcess::FailedToStart
            || err == QProcess::Crashed) {
            emit outputLog(tr("QProcess error %1.").arg(err));
            if (err == QProcess::FailedToStart) {
                ret = -2;
            } else {
                ret = -1;
            }

            break;
        }

        if (started) {
            if (process->state() == QProcess::NotRunning) {
                finished = true;
            }
        } else {
            if (process->state() != QProcess::NotRunning) {
                started = true;
            }
        }

        if (process->waitForFinished(500)) {
            // Finished.
            finished = true;
        }

        QByteArray outBa = process->readAllStandardOutput();
        QByteArray errBa = process->readAllStandardError();
        QString msg;
        if (!outBa.isEmpty()) {
            msg += QString::fromLocal8Bit(outBa);
        }

        if (!errBa.isEmpty()) {
            msg += QString::fromLocal8Bit(errBa);
        }

        if (!msg.isEmpty()) {
            emit outputLog(msg);
        }

        if (finished) {
            QProcess::ExitStatus sta = process->exitStatus();
            if (sta == QProcess::CrashExit) {
                ret = -1;
                break;
            }

            ret = process->exitCode();
            break;
        }

        QCoreApplication::processEvents();

        if (m_askedToStop) {
            process->kill();
            ret = -1;
            break;
        }
    }

    return ret;
}

int VExporter::startProcess(const QString &p_cmd)
{
    QStringList args = VUtils::parseCombinedArgString(p_cmd);
    if (args.isEmpty()) {
        return -2;
    }

    return startProcess(args.first(), args.mid(1));
}

bool VExporter::outputToHTMLFile(const QString &p_file,
                                 const QString &p_title,
                                 const QString &p_headContent,
                                 const QString &p_styleContent,
                                 const QString &p_bodyContent,
                                 bool p_embedCssStyle,
                                 bool p_completeHTML,
                                 bool p_embedImages)
{
    QFile file(p_file);
    if (!file.open(QFile::WriteOnly)) {
        return false;
    }

    QString resFolder = QFileInfo(p_file).completeBaseName() + "_files";
    QString resFolderPath = QDir(VUtils::basePathFromPath(p_file)).filePath(resFolder);

    qDebug() << "HTML files folder" << resFolderPath;

    QString html(m_exportHtmlTemplate);
    if (!p_title.isEmpty()) {
        html.replace(HtmlHolder::c_headTitleHolder,
                     "<title>" + VUtils::escapeHtml(p_title) + "</title>");
    }

    if (!p_styleContent.isEmpty() && p_embedCssStyle) {
        QString content(p_styleContent);
        embedStyleResources(content);
        html.replace(HtmlHolder::c_styleHolder, content);
    }

    if (!p_headContent.isEmpty()) {
        html.replace(HtmlHolder::c_headHolder, p_headContent);
    }

    if (p_completeHTML) {
        QString content(p_bodyContent);
        if (p_embedImages) {
            embedBodyResources(m_baseUrl, content);
        } else {
            fixBodyResources(m_baseUrl, resFolderPath, content);
        }

        html.replace(HtmlHolder::c_bodyHolder, content);
    } else {
        html.replace(HtmlHolder::c_bodyHolder, p_bodyContent);
    }

    file.write(html.toUtf8());
    file.close();

    // Delete empty resource folder.
    QDir dir(resFolderPath);
    if (dir.isEmpty()) {
        dir.cdUp();
        dir.rmdir(resFolder);
    }

    return true;
}

int VExporter::exportCustomInOne(const QList<QString> &p_files,
                                 const ExportOption &p_opt,
                                 const QString &p_outputFile,
                                 QString *p_errMsg)
{
    if (!convertFilesViaCustom(p_files, p_outputFile, p_opt.m_customOpt, p_errMsg)) {
        return 0;
    }

    return p_files.size();
}
