/***
   This file is part of fly-ocr.

   Copyright (C) 2016-2020 Sandro Mani <manisandro@gmail.com>
   Copyright (C) 2021 LLC "RusBITech-Astra" (https://astralinux.ru)

    Based on gImageReader source code by Sandro Mani under the GNU
    General Public License (GPL) version 3 or later

   Foobar 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.

   Foobar 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 fly-ocr. If not, see <https://www.gnu.org/licenses/>.
***/

#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QUrl>
#include <fly-ocr/ocrengine.h>
#include "configsettings.h"
#include "filedialogs.h"
#include "outputeditortext.h"
#include "recognizer.h"
#include "sourcemanager.h"
#include "utils.h"

void OutputEditorText::TextBatchProcessor::appendOutput(QIODevice* dev, OcrEngine* engine, const PageInfo& pageInfo, bool firstArea) const
{
    if (firstArea && m_prependPage) {
        dev->write(tr("Page: %1\n").arg(pageInfo.page).toUtf8());
    }
    dev->write(engine->getPageData(pageInfo.page, OcrEngineFactory::TXT));
    dev->write("\n");
}

OutputEditorText::OutputEditorText(QWidget* parent) : OutputEditor(parent)
{
    m_ui.setupUi(this);
    m_ui.actionOutputReplace->setShortcut(Qt::CTRL + Qt::Key_F);

    m_spell.setDecodeLanguageCodes(true);
    m_spell.setShowCheckSpellingCheckbox(true);
    m_spell.setTextEdit(m_ui.plainTextEditOutput);
    m_spell.setUndoRedoEnabled(true);

    connect(m_ui.toolButtonOutputPostproc, &QToolButton::clicked, this, &OutputEditorText::filterBuffer);
    connect(m_ui.actionOutputReplace, &QAction::toggled, m_ui.searchFrame, &SearchReplaceFrame::setVisible);
    connect(m_ui.actionOutputReplace, &QAction::toggled, m_ui.searchFrame, &SearchReplaceFrame::clear);
    connect(m_ui.actionOutputUndo, &QAction::triggered, &m_spell, &QtSpell::TextEditChecker::undo);
    connect(m_ui.actionOutputRedo, &QAction::triggered, &m_spell, &QtSpell::TextEditChecker::redo);
    connect(m_ui.actionOutputClear, &QAction::triggered, this, &OutputEditorText::clear);
    connect(m_ui.searchFrame, &SearchReplaceFrame::findReplace, this, &OutputEditorText::findReplace);
    connect(m_ui.searchFrame, &SearchReplaceFrame::replaceAll, this, &OutputEditorText::replaceAll);
    connect(m_ui.searchFrame, &SearchReplaceFrame::applySubstitutions, this, &OutputEditorText::applySubstitutions);
    connect(&m_spell, &QtSpell::TextEditChecker::undoAvailable, m_ui.actionOutputUndo, &QAction::setEnabled);
    connect(&m_spell, &QtSpell::TextEditChecker::redoAvailable, m_ui.actionOutputRedo, &QAction::setEnabled);
    connect(ConfigSettings::get<FontSetting>("customoutputfont"), &FontSetting::changed, this, &OutputEditorText::setFont);
    connect(ConfigSettings::get<SwitchSetting>("systemoutputfont"), &SwitchSetting::changed, this, &OutputEditorText::setFont);
    connect(m_ui.actionOutputPostprocDrawWhitespace, &QAction::toggled, m_ui.plainTextEditOutput, &OutputTextEdit::setDrawWhitespace);

    ADD_SETTING(ActionSetting("keepdot", m_ui.actionOutputPostprocKeepEndMark, true));
    ADD_SETTING(ActionSetting("keepquote", m_ui.actionOutputPostprocKeepQuote));
    ADD_SETTING(ActionSetting("joinhyphen", m_ui.actionOutputPostprocJoinHyphen, true));
    ADD_SETTING(ActionSetting("joinspace", m_ui.actionOutputPostprocCollapseSpaces, true));
    ADD_SETTING(ActionSetting("keepparagraphs", m_ui.actionOutputPostprocKeepParagraphs, true));
    ADD_SETTING(ActionSetting("drawwhitespace", m_ui.actionOutputPostprocDrawWhitespace));

    setFont();
}

OutputEditorText::~OutputEditorText()
{}

void OutputEditorText::read(int pageNumber, const SourceDocumentPage* page)
{
    Q_UNUSED(page);
    if (engine()) {
        m_ui.plainTextEditOutput->setPlainText(QString::fromUtf8(engine()->getPageData(pageNumber, OcrEngineFactory::TXT)));
        m_ui.plainTextEditOutput->document()->setModified(false);
        m_pageNumber = pageNumber;
    }
}

void OutputEditorText::setFont()
{
    if (ConfigSettings::get<SwitchSetting>("systemoutputfont")->getValue()) {
        m_ui.plainTextEditOutput->setFont(QFont());
    } else {
        m_ui.plainTextEditOutput->setFont(ConfigSettings::get<FontSetting>("customoutputfont")->getValue());
    }
}

void OutputEditorText::filterBuffer()
{
    QTextCursor cursor = m_ui.plainTextEditOutput->regionBounds();
    QString txt = cursor.selectedText();

    Utils::busyTask([this, &txt] {
        // Always remove trailing whitespace
        txt.replace(QRegExp("\\s+$"), QString());

        if (m_ui.actionOutputPostprocJoinHyphen->isChecked())
        {
            txt.replace(QRegExp("[-\u2014]\\s*\u2029\\s*"), QString());
        }
        QString preChars, sucChars;
        if (m_ui.actionOutputPostprocKeepParagraphs->isChecked())
        {
            preChars += "\u2029"; // Keep if preceded by line break
        }
        if (m_ui.actionOutputPostprocKeepEndMark->isChecked())
        {
            preChars += "\\.\\?!"; // Keep if preceded by end mark (.?!)
        }
        if (m_ui.actionOutputPostprocKeepQuote->isChecked())
        {
            preChars += "'\"\u00BB\u00AB"; // Keep if preceded by quote
            sucChars += "'\"\u00AB\u00BB"; // Keep if succeeded by quote
        }
        if (m_ui.actionOutputPostprocKeepParagraphs->isChecked())
        {
            sucChars += "\u2029"; // Keep if succeeded by line break
        }
        if (!preChars.isEmpty())
        {
            preChars = "([^" + preChars + "])";
        }
        if (!sucChars.isEmpty())
        {
            sucChars = "(?![" + sucChars + "])";
        }
        QString expr = preChars + "\u2029" + sucChars;
        txt.replace(QRegExp(expr), preChars.isEmpty() ? " " : "\\1 ");

        if (m_ui.actionOutputPostprocCollapseSpaces->isChecked())
        {
            txt.replace(QRegExp("[ \t]+"), " ");
        }
        return true;
    }, tr("Stripping line breaks..."));
    m_ui.plainTextEditOutput->setTextCursor(cursor);
    cursor.insertText(txt);
    cursor.setPosition(cursor.position() - txt.length(), QTextCursor::KeepAnchor);
    m_ui.plainTextEditOutput->setTextCursor(cursor);
}

void OutputEditorText::findReplace(const QString& searchstr, const QString& replacestr, bool matchCase, bool backwards, bool replace)
{
    m_ui.searchFrame->clearErrorState();
    if (!m_ui.plainTextEditOutput->findReplace(backwards, replace, matchCase, searchstr, replacestr)) {
        m_ui.searchFrame->setErrorState();
    }
}

void OutputEditorText::replaceAll(const QString& searchstr, const QString& replacestr, bool matchCase)
{
    MAIN->pushState(MainWindow::State::Busy, tr("Replacing..."));
    if (!m_ui.plainTextEditOutput->replaceAll(searchstr, replacestr, matchCase)) {
        m_ui.searchFrame->setErrorState();
    }
    MAIN->popState();
}

void OutputEditorText::applySubstitutions(const QMap<QString, QString>& substitutions, bool matchCase)
{
    MAIN->pushState(MainWindow::State::Busy, tr("Applying substitutions..."));
    QTextCursor cursor = m_ui.plainTextEditOutput->regionBounds();
    int end = cursor.position();
    cursor.setPosition(cursor.anchor());
    QTextDocument::FindFlags flags;
    if (matchCase) {
        flags = QTextDocument::FindCaseSensitively;
    }
    int start = cursor.position();
    for (auto it = substitutions.begin(), itEnd = substitutions.end(); it != itEnd; ++it) {
        QString search = it.key();
        QString replace = it.value();
        int diff = replace.length() - search.length();
        cursor.setPosition(start);
        while (true) {
            cursor = m_ui.plainTextEditOutput->document()->find(search, cursor, flags);
            if (cursor.isNull() || std::max(cursor.anchor(), cursor.position()) > end) {
                break;
            }
            cursor.insertText(replace);
            end += diff;
        }
        QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
    }
    MAIN->popState();
}

//void OutputEditorText::read(OcrEngine* engine, ReadSessionData* data)
//{
//    QString text = QString::fromUtf8(engine->getPageData(data->pageInfo.page, OcrEngineFactory::TXT));
//    if (!text.endsWith('\n')) {
//        text.append('\n');
//    }
//    if (data->prependFile || data->prependPage) {
//        QStringList prepend;
//        if (data->prependFile) {
//            prepend.append(tr("File: %1").arg(data->pageInfo.filename));
//        }
//        if (data->prependPage) {
//            prepend.append(tr("Page: %1").arg(data->pageInfo.page));
//        }
//        text.prepend(QString("[%1]\n").arg(prepend.join("; ")));
//    }
//    bool& insertText = static_cast<TextReadSessionData*>(data)->insertText;
//    QMetaObject::invokeMethod(this, "addText", Qt::QueuedConnection, Q_ARG(QString, text), Q_ARG(bool, insertText));
//    insertText = true;
//}

//void OutputEditorText::readError(const QString& errorMsg, ReadSessionData* data)
//{
//    bool& insertText = static_cast<TextReadSessionData*>(data)->insertText;
//    QMetaObject::invokeMethod(this, "addText", Qt::QueuedConnection, Q_ARG(QString, errorMsg), Q_ARG(bool, insertText));
//    insertText = true;
//}

/*void OutputEditorText::addText(const QString& text, bool insert)
{
    if (insert) {
        ui.plainTextEditOutput->textCursor().insertText(text);
    } else {
        QTextCursor cursor = ui.plainTextEditOutput->textCursor();
        if (m_insertMode == InsertMode::Append) {
            cursor.movePosition(QTextCursor::End);
        } else if (m_insertMode == InsertMode::Cursor) {
            // pass
        } else if (m_insertMode == InsertMode::Replace) {
            cursor.movePosition(QTextCursor::Start);
            cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
        }
        cursor.insertText(text);
        ui.plainTextEditOutput->setTextCursor(cursor);
    }
    MAIN->setOutputPaneVisible(true);
}*/

//bool OutputEditorText::open(const QString& filename)
//{
//    if (!clear(false)) {
//        return false;
//    }
//    QFile file(filename);
//    if (file.open(QIODevice::ReadOnly)) {
//        ui.plainTextEditOutput->setPlainText(file.readAll());
//        MAIN->setOutputPaneVisible(true);
//        return true;
//    } else {
//        QString errorMsg(tr("The following files could not be opened:\n%1").arg(filename));
//        QMessageBox::critical(MAIN, tr("Unable to open files"), errorMsg);
//        return false;
//    }
//}

bool OutputEditorText::isModified() const
{
    return m_ui.plainTextEditOutput->document()->isModified();
}

void OutputEditorText::commit()
{
    if (isModified() && engine() && m_pageNumber >= 0) {
        engine()->setPageData(m_pageNumber, OcrEngineFactory::TXT, m_ui.plainTextEditOutput->toPlainText().toUtf8());
        m_ui.plainTextEditOutput->document()->setModified(false);
    }
}

void OutputEditorText::clear()
{
    m_ui.plainTextEditOutput->clear();
    m_spell.clearUndoRedo();
    m_ui.plainTextEditOutput->document()->setModified(false);
}

void OutputEditorText::setLanguage(const QString& lang)
{
    m_spell.setLanguage(lang);
}
