/***
   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 <QCloseEvent>
#include <QDesktopServices>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include <QProgressBar>
#include <QStatusBar>
#include <QUrl>
#include <QtConcurrentRun>
#include <QDomDocument>
#include <QDomElement>
#include <QClipboard>
#include <csignal>
#include <iostream>
#include <sys/prctl.h>
#include <QtSpell.hpp>
#include <KSaneWidget>
#include <KConfig>
#include <KSharedConfig>
#include <KConfigGroup>
#include <KAboutData>
#include <KAboutApplicationDialog>
#include <fly-ocr/ocrengine.h>
#include "hocrdocument.h"
#include "hocrpdfexporter.h"
#include "hocrodtexporter.h"
#include "filedialogs.h"
#include "mainwindow.h"
#include "configsettings.h"
#include "displayer.h"
#include "displayertoolselect.h"
#include "displayertoolhocr.h"
#include "outputeditortext.h"
#include "outputeditorhocr.h"
#include "outputviewerhtml.h"
#include "recognitionmenu.h"
#include "recognizer.h"
#include "sourcemanager.h"
#include "utils.h"
#include "pageitemdelegate.h"
#include "mainwindow_ui.h"

MainWindow* MainWindow::instance()
{
    return s_instance;
}

void MainWindow::signalHandler(int signal)
{
    signalHandlerExec(signal, false);
}

void MainWindow::tesseractCrash(int signal)
{
    signalHandlerExec(signal, true);
}

void MainWindow::signalHandlerExec(int signal, bool tesseractCrash)
{
    std::signal(signal, nullptr);

    QString filename;
    if (MAIN->outputEditor() && MAIN->outputEditor()->isModified()) {
        filename = QDir(Utils::documentsFolder()).absoluteFilePath(QString("%1_crash-save.txt").arg(PACKAGE_NAME));
        int i = 0;
        while (QFile(filename).exists()) {
            ++i;
            filename = QDir(Utils::documentsFolder()).absoluteFilePath(QString("%1_crash-save_%2.txt").arg(PACKAGE_NAME).arg(i));
        }
//        MAIN->outputEditor()->save(filename);
    }

    QProcess process;
    process.start(QApplication::applicationFilePath(), QStringList() << "crashhandle" << QString::number(QApplication::applicationPid()) << QString::number(tesseractCrash) << filename);
    // Allow crash handler spawned debugger to attach to the crashed process
    prctl(PR_SET_PTRACER, process.processId(), 0, 0, 0);
    process.waitForFinished(-1);
    std::raise(signal);
}

void MainWindow::openScanner()
{
    if (!m_saneWidget->openDevice(m_saneDeviceName, m_saneDeviceLabel))
        return;

    QMap<QString, QString> opts = readScannerOptions();
    m_saneWidget->setOptVals(opts);

    KConfigGroup general(KSharedConfig::openConfig("fly-scanrc"), "General");
    general.writeEntry("CurrentDevice", m_saneDeviceName);
    general.writeEntry("CurrentDeviceLabel", m_saneDeviceLabel);
    general.sync();
}

void MainWindow::exportAs(const QString& path, OcrEngineFactory::Format format)
{
    HOCRDocument document;
    OcrEngine* engine = m_recognizer->engine();
    for (int i = 0; i < m_displayer->pageCount(); ++i) {
        if (engine->isRecognized(i)) {
            auto page = m_sourceManager->page(i);

            QByteArray data = engine->getPageData(i, OcrEngineFactory::HOCR);

            QDomDocument doc;
            doc.setContent(QString::fromUtf8(data));
            QDomElement pageDiv = doc.firstChildElement("div");
            QMap<QString, QString> attrs = HOCRItem::deserializeAttrGroup(pageDiv.attribute("title"));
            attrs["image"] = QString("'%1'").arg(page->path);
            attrs["ppageno"] = QString::number(page->page);
            attrs["rot"] = QString::number(page->angle);
            attrs["res"] = QString::number(page->resolution);
            pageDiv.setAttribute("title", HOCRItem::serializeAttrGroup(attrs));
            document.insertPage(document.pageCount(), pageDiv, true);
        }
    }

    if (format == OcrEngineFactory::HTML) {
        QFile file(path);
        file.open(QIODevice::WriteOnly);
        file.write(document.toHTML().toUtf8());
    } else if (format == OcrEngineFactory::PDF) {
        HOCRPdfExportDialog dialog(nullptr, &document, nullptr, this);
        if (dialog.exec() != QDialog::Accepted)
            return;

        HOCRPdfExporter::PDFSettings settings = dialog.getPdfSettings();
        HOCRPdfExporter().run(&document, path, &settings);
    } else if (format == OcrEngineFactory::ODT) {
        HOCROdtExporter().run(&document, path);
    }
}

QMap<QString, QString> MainWindow::readScannerOptions()
{
    KConfigGroup scannerOptions(KSharedConfig::openConfig("fly-scanrc"), QString::fromLatin1("Options For %1").arg(m_saneDeviceName));
    return scannerOptions.entryMap();
}

void MainWindow::saveScannerOptions()
{
    KConfigGroup options(KSharedConfig::openConfig("fly-scanrc"), QString::fromLatin1("Options For %1").arg(m_saneDeviceName));
    QMap <QString, QString> opts;
    m_saneWidget->getOptVals(opts);

    QMap<QString, QString>::const_iterator it = opts.constBegin();
    while (it != opts.constEnd()) {
        options.writeEntry(it.key(), it.value());
        ++it;
    }
    options.sync();
}

#if !(defined(__ARMEL__) || defined(__LCC__) && __LCC__ <= 121)
static void terminateHandler()
{
    std::set_terminate(nullptr);
    std::exception_ptr exptr = std::current_exception();
    if (exptr != nullptr) {
        try {
            std::rethrow_exception(exptr);
        } catch (std::exception& ex) {
            std::cerr << "Terminated due to exception: " << ex.what() << std::endl;
        } catch (...) {
            std::cerr << "Terminated due to unknown exception" << std::endl;
        }
    } else {
        std::cerr << "Terminated due to unknown reason:" << std::endl;
    }
    MainWindow::signalHandler(SIGABRT);
}
#endif

MainWindow* MainWindow::s_instance = nullptr;

MainWindow::MainWindow()
    : m_ui(new MainWindowUi), m_idleActions(nullptr)
{
    s_instance = this;

    std::signal(SIGSEGV, signalHandler);
    std::signal(SIGABRT, signalHandler);
#if !(defined(__ARMEL__) || defined(__LCC__) && __LCC__ <= 121)
    std::set_terminate(terminateHandler);
#endif

    qRegisterMetaType<MainWindow::State>();

    m_ui->setupUi(this);

    m_config = new ConfigDialog(this);
    m_displayer = new Displayer(m_ui);
    m_recognitionMenu = new RecognitionMenu(this);
    m_recognizer = new Recognizer(m_ui);
    m_sourceManager = new SourceManager(m_ui);

    m_ui->centralwidget->layout()->addWidget(m_displayer);
    QWidget* spacer1 = new QWidget(this);
    spacer1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    QWidget* spacer2 = new QWidget(this);
    spacer2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    QToolBar* displayerToolBar = new QToolBar(m_ui->centralwidget);
    displayerToolBar->addWidget(spacer1);
    displayerToolBar->addAction(m_ui->actionZoomIn);
    displayerToolBar->addAction(m_ui->actionZoomOut);
    displayerToolBar->addAction(m_ui->actionOriginalSize);
    displayerToolBar->addAction(m_ui->actionBestFit);
    displayerToolBar->addAction(m_ui->actionFitWidth);
    displayerToolBar->addSeparator();
    displayerToolBar->addAction(m_ui->actionRotateLeft);
    displayerToolBar->addAction(m_ui->actionRotateRight);
    displayerToolBar->addAction(m_ui->actionRotate);
    displayerToolBar->addAction(m_ui->actionPage);
    m_ui->actionPage->setVisible(false);
    displayerToolBar->addAction(m_ui->actionAutodetectLayout);
    displayerToolBar->addWidget(spacer2);

    m_ui->centralwidget->layout()->addWidget(displayerToolBar);
    m_ui->toolBarMain->setLayoutDirection(Qt::LeftToRight);
    m_ui->sourceDocumentView->setItemDelegate(new PageItemDelegate(this));

    m_ui->menuView->addSeparator();
    m_ui->menuView->addAction(m_ui->dockWidgetSources->toggleViewAction());
    m_ui->menuView->addAction(m_ui->dockWidgetOutput->toggleViewAction());
    m_ui->dockWidgetSources->toggleViewAction()->setIcon(QIcon::fromTheme("insert-image"));
    m_ui->dockWidgetOutput->toggleViewAction()->setIcon(QIcon::fromTheme("document-edit"));

    m_idleActions.setExclusive(false);
    m_idleActions.addAction(m_ui->actionZoomIn);
    m_idleActions.addAction(m_ui->actionZoomOut);
    m_idleActions.addAction(m_ui->actionRotateLeft);
    m_idleActions.addAction(m_ui->actionRotateRight);
    m_idleActions.addAction(m_ui->actionAutodetectLayout);
    m_idleActions.addAction(m_ui->actionSaveAs);

    m_idleWidgets.append(m_ui->spinBoxRotation);
    m_idleWidgets.append(m_ui->spinBoxPage);
    m_idleWidgets.append(m_ui->spinBoxBrightness);
    m_idleWidgets.append(m_ui->spinBoxContrast);
    m_idleWidgets.append(m_ui->spinBoxResolution);
    m_idleWidgets.append(m_ui->toolButtonRecognize);

    m_recognitionMenu->rebuild();

    connect(m_ui->actionExit, &QAction::triggered, [ = ] { qApp->exit(); });
    connect(m_ui->actionRedetectLanguages, &QAction::triggered, m_recognitionMenu, &RecognitionMenu::rebuild);
    connect(m_ui->actionPreferences, &QAction::triggered, this, &MainWindow::showConfig);
    connect(m_ui->actionContents, &QAction::triggered, this, [this] { showHelp(); });
    connect(m_ui->actionAboutOCR, &QAction::triggered, this, &MainWindow::showAboutOCR);
    connect(m_ui->actionAbout, &QAction::triggered, this, &MainWindow::showAbout);
    connect(m_sourceManager, &SourceManager::selectedPageChanged, this, &MainWindow::onSelectedPageChanged);
    connect(m_recognitionMenu, &RecognitionMenu::selectedLanguagesChanged, this, &MainWindow::onSelectedLanguagesChanged);
    connect(m_ui->actionAutodetectLayout, &QAction::triggered, m_displayer, &Displayer::autodetectOCRAreas);
    connect(m_recognizer, &Recognizer::outputModeChanged, this, &MainWindow::onSelectedPageChanged);
    connect(m_recognitionMenu, &RecognitionMenu::selectedLanguagesChanged, this, &MainWindow::updateLanguages);

    ADD_SETTING(VarSetting<QByteArray>("wingeom"));
    ADD_SETTING(VarSetting<QByteArray>("winstate"));

    m_progressWidget = new QWidget(this);
    m_progressWidget->setLayout(new QHBoxLayout());
    m_progressWidget->layout()->setContentsMargins(0, 0, 0, 0);
    m_progressWidget->layout()->setSpacing(2);
    m_progressWidget->layout()->addWidget(new QLabel());
    m_progressBar = new QProgressBar();
    m_progressBar->setRange(0, 100);
    m_progressBar->setMaximumWidth(200);
    m_progressTimer.setSingleShot(false);
    connect(&m_progressTimer, &QTimer::timeout, this, &MainWindow::progressUpdate);
    m_progressWidget->layout()->addWidget(m_progressBar);
    m_progressCancelButton = new QToolButton();
    m_progressCancelButton->setIcon(QIcon::fromTheme("dialog-close"));
    connect(m_progressCancelButton, &QToolButton::clicked, this, &MainWindow::progressCancel);
    m_progressWidget->layout()->addWidget(m_progressCancelButton);
    statusBar()->addPermanentWidget(m_progressWidget);
    m_progressWidget->setVisible(false);
    m_engineInfoLabel = new QLabel(this);
    m_engineInfoLabel->setText("...");
    m_engineInfoLabel->setMargin(5);
    statusBar()->addPermanentWidget(m_engineInfoLabel);
    updateLanguages();

    pushState(State::Idle, tr("Select an image to begin..."));

#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) //QDockWidget bug workarround
    if (ConfigSettings::get<VarSetting<QByteArray>>("winstate")->getValue().isEmpty())
        ConfigSettings::get<VarSetting<QByteArray>>("winstate")->setValue(saveState());
#endif

    restoreGeometry(ConfigSettings::get<VarSetting<QByteArray>>("wingeom")->getValue());
    restoreState(ConfigSettings::get<VarSetting<QByteArray>>("winstate")->getValue());
    m_ui->dockWidgetOutput->setVisible(false);

    m_saneWidget = new KSaneIface::KSaneWidget(this);
    m_saneWidget->setSimpleModeEnabled(true);
    m_saneWidget->initGetDeviceList();
    m_ui->tabWidget->addTab(m_saneWidget, tr("Scan"));
    KConfigGroup general(KSharedConfig::openConfig("fly-scanrc"), "General");
    m_saneDeviceName = general.readEntry("CurrentDevice", QString());
    m_saneDeviceLabel = general.readEntry("CurrentDeviceLabel", QString());
    openScanner();

    connect(m_saneWidget, &KSaneIface::KSaneWidget::changeDevice, this, &MainWindow::changeScannerDevice);
    connect(m_saneWidget, &KSaneIface::KSaneWidget::scannedImageReady, [ = ](const QImage & image) {
        m_sourceManager->add(image);
        m_sourceManager->page(m_displayer->pageCount() - 1)->resolution = m_saneWidget->currentDPI();
        m_sourceManager->selectPage(m_displayer->pageCount() - 1);
    });
    connect(m_saneWidget, &KSaneIface::KSaneWidget::scanDone, this, &MainWindow::scanDone);
    connect(m_saneWidget, &KSaneIface::KSaneWidget::userMessage, this, &MainWindow::alertUser);

    QAction* pasteAction = new QAction(this);
    pasteAction->setShortcut(QKeySequence::Paste);
    addAction(pasteAction);
    connect(pasteAction, &QAction::triggered, this, &MainWindow::pasteImage);

    QPalette p = m_ui->sourceDocumentView->palette();
    p.setBrush(QPalette::Base, Qt::gray);
    p.setBrush(QPalette::Text, Qt::white);
    m_ui->sourceDocumentView->setPalette(p);

    updateActions();
}

MainWindow::~MainWindow()
{
    if (!m_saneDeviceName.isEmpty()) {
        saveScannerOptions();
        m_saneWidget->closeDevice();
    }

    m_versionWatcher.waitForFinished();
    delete m_outputEditor;
    delete m_sourceManager;
    m_displayer->setTool(nullptr);
    delete m_displayerTool;
    delete m_displayer;
    delete m_recognizer;
    delete m_config;
    delete m_ui;
    s_instance = nullptr;
}

ConfigDialog* MainWindow::config() const
{
    return m_config;
}

Displayer* MainWindow::displayer() const
{
    return m_displayer;
}

OutputEditor* MainWindow::outputEditor() const
{
    return m_outputEditor;
}

Recognizer* MainWindow::recognizer() const
{
    return m_recognizer;
}

SourceManager* MainWindow::sourceManager() const
{
    return m_sourceManager;
}

RecognitionMenu* MainWindow::recognitionMenu() const
{
    return m_recognitionMenu;
}

void MainWindow::setOutputPaneVisible(bool visible)
{
    m_ui->dockWidgetOutput->setVisible(visible);
}

void MainWindow::pushState(MainWindow::State state, const QString& msg)
{
    m_stateStack.push(QPair<State, QString>(state, msg));
    m_ui->statusbar->showMessage(msg);
    setState(state);
    if (state == State::Busy) {
        QApplication::setOverrideCursor(Qt::WaitCursor);
    }
}

void MainWindow::popState()
{
    if (m_stateStack.top().first == State::Busy) {
        QApplication::restoreOverrideCursor();
    }
    m_stateStack.pop();
    const QPair<State, QString>& pair = m_stateStack.top();
    m_ui->statusbar->showMessage(pair.second);
    setState(pair.first);
}

void MainWindow::setState(State state)
{
    bool isIdle = state == State::Idle;
    m_idleActions.setEnabled(!isIdle);
    m_ui->actionOriginalSize->setEnabled(!isIdle);
    m_ui->actionFitWidth->setEnabled(!isIdle);
    m_ui->actionBestFit->setEnabled(!isIdle);
    for (QWidget* widget : m_idleWidgets) {
        widget->setEnabled(!isIdle);
    }
}

void MainWindow::closeEvent(QCloseEvent* ev)
{
    if (m_stateStack.top().first == State::Busy) {
        ev->ignore();
    } else {
        if (m_outputEditor)
            m_outputEditor->clear();
        if (!isMaximized()) {
            ConfigSettings::get<VarSetting<QByteArray>>("wingeom")->setValue(saveGeometry());
        }
        ConfigSettings::get<VarSetting<QByteArray>>("winstate")->setValue(saveState());
    }
}

void MainWindow::onSelectedPageChanged()
{
    std::shared_ptr<SourceDocumentPage> page = m_sourceManager->selectedPage();

    if (m_outputEditor) {
        if (m_outputEditor->isModified())
            m_outputEditor->commit();

        m_outputEditor->clear();
    }

    Recognizer::OutputMode mode = m_recognizer->outputMode();

    if (((dynamic_cast<OutputEditorHOCR*>(m_outputEditor) || dynamic_cast<OutputViewerHtml*>(m_outputEditor))
            && mode != Recognizer::FormattedText) ||
            (dynamic_cast<OutputEditorText*>(m_outputEditor) && mode != Recognizer::PlainText)) {
        m_ui->dockWidgetOutput->setWidget(nullptr);
        delete m_displayerTool;
        delete m_outputEditor;
        m_displayerTool = nullptr;
        m_outputEditor = nullptr;
    }

    if (!m_outputEditor) {
        if (mode == Recognizer::PlainText) {
            m_displayerTool = new DisplayerToolSelect(m_displayer);
            m_outputEditor = new OutputEditorText();
        } /*else if (OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR) {
            m_displayerTool = new DisplayerToolHOCR(m_displayer);
            m_outputEditor = new OutputEditorHOCR(static_cast<DisplayerToolHOCR*>(m_displayerTool));
        } */else if (OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HTML || OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR) {
            m_displayerTool = new DisplayerToolHOCR(m_displayer);
            m_outputEditor = new OutputViewerHtml();
        } else {
            setEnabled(false);
            return;
        }
        m_ui->actionAutodetectLayout->setVisible(m_displayerTool->allowAutodetectOCRAreas());
        m_displayer->setTool(m_displayerTool);
        m_outputEditor->setLanguage(m_recognitionMenu->selectedLanguageCode());
        //connect(m_ui->actionToggleOutputPane, &QAction::toggled, m_outputEditor, &OutputEditor::onVisibilityChanged);
        m_ui->dockWidgetOutput->setWidget(m_outputEditor);
    }

    if (page && m_displayer->setPage(page)) {
        setWindowTitle(QString("%1 - %2").arg(page->displayName).arg(PACKAGE_NAME));
        if (m_stateStack.top().first == State::Idle) {
            pushState(State::Normal, tr("Ready"));
        }

        if (m_recognizer->engine() && m_recognizer->engine()->isRecognized(m_displayer->currentPage())) {
            m_outputEditor->initialize(m_recognizer->engine());
            m_outputEditor->read(m_displayer->currentPage(), page.get());
            m_ui->dockWidgetOutput->setVisible(true);
        }

    } else {
        if (!m_stateStack.isEmpty() && m_stateStack.top().first == State::Normal) {
            popState();
        }
        m_displayer->setPage(nullptr);
        setWindowTitle(PACKAGE_NAME);
    }
}

void MainWindow::showAbout()
{
    KAboutData aboutData(qApp->applicationName(),
                         tr("fly-ocr"), // displayName, k4: programName
                         QLatin1String(PACKAGE_VERSION), // version
                         tr("Optical character recognition utility."), // shortDescription
                         KAboutLicense::GPL_V3, // licenseType
                         tr("Copyright © 2009 LLC \"RusBITech-Astra\""), // copyrightStatement
                         tr("Based on gImageReader source code by Sandro Mani"), // other Text
                         QStringLiteral("https://astralinux.ru") // homePageAddress
                        );
    KAboutApplicationDialog dlg(aboutData, this);
    dlg.exec();
}

void MainWindow::showAboutOCR()
{
    OcrEngineFactory* f = OcrEngine::selected();
    if (f) {
        QMessageBox::information(this, tr("About %1").arg(f->name()), f->about());
    }
}

void MainWindow::showHelp()
{
    QProcess::startDetached(QStringLiteral("/usr/lib/qt5/bin/assistant"), {
                                QStringLiteral("-enableRemoteControl"),
                                QStringLiteral("-collectionFile"),
                                QStringLiteral("/usr/share/doc/fly/qthelp/ru/flyhelp.qhc"),
                                QStringLiteral("-showUrl"),
                                QStringLiteral("qthelp://flyhelp.ru/doc/fly-ocr/fly-ocr.html") });
}

void MainWindow::showConfig()
{
    if (m_config->exec() == QDialog::Accepted) {
        m_recognitionMenu->rebuild();
        m_recognizer->destroyEngine();
        updateActions();
        updateLanguages();
    }
}

void MainWindow::addNotification(const QString& title, const QString& message, const QList<NotificationAction>& actions, MainWindow::Notification* handle)
{
    QFrame* frame = new QFrame();
    frame->setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
    frame->setStyleSheet("background: #FFD000;");
    QHBoxLayout* layout = new QHBoxLayout(frame);
    layout->addWidget(new QLabel(QString("<b>%1</b>").arg(title), frame));
    QLabel* msgLabel = new QLabel(message, frame);
    msgLabel->setWordWrap(true);
    layout->addWidget(msgLabel, 1);
    for (const NotificationAction& action : actions) {
        QToolButton* btn = new QToolButton(frame);
        btn->setText(action.text);
        connect(btn, &QToolButton::clicked, action.slot);
        if (action.close) {
            btn->setProperty("handle", QVariant::fromValue(reinterpret_cast<void*>(handle)));
            btn->setProperty("frame", QVariant::fromValue(reinterpret_cast<void*>(frame)));
            connect(btn, &QToolButton::clicked, this, [this] { hideNotification(); });
        }
        layout->addWidget(btn);
    }
    QToolButton* closeBtn = new QToolButton();
    closeBtn->setIcon(QIcon::fromTheme("dialog-close"));
    closeBtn->setProperty("handle", QVariant::fromValue(reinterpret_cast<void*>(handle)));
    closeBtn->setProperty("frame", QVariant::fromValue(reinterpret_cast<void*>(frame)));
    connect(closeBtn, &QToolButton::clicked, this, [this] { hideNotification(); });
    layout->addWidget(closeBtn);
    m_ui->centralwidget->layout()->addWidget(frame);
    if (handle) {
        *handle = frame;
    }
}

void MainWindow::hideNotification(Notification handle)
{
    if (!handle && QObject::sender()) {
        handle = static_cast<QFrame*>(static_cast<QToolButton*>(QObject::sender())->property("frame").value<void*>());
        Notification* h = reinterpret_cast<void**>(static_cast<QToolButton*>(QObject::sender())->property("handle").value<void*>());
        if (h) {
            *h = nullptr;
        }
    }
    if (handle) {
        static_cast<QFrame*>(handle)->deleteLater();
    }
}

void MainWindow::changeScannerDevice()
{
    if (!m_saneDeviceName.isEmpty()) {
        saveScannerOptions();
        m_saneWidget->closeDevice();
    }

    const KSaneIface::KSaneWidget::DeviceInfo newDeviceInfo = m_saneWidget->selectDeviceExt(this);

    if (!newDeviceInfo.name.isEmpty()) {
        m_saneDeviceName = newDeviceInfo.name;
        m_saneDeviceLabel = newDeviceInfo.label;
        openScanner();
    }
}

void MainWindow::scanDone(int status, const QString& strStatus)
{
    Q_UNUSED(status);
    Q_UNUSED(strStatus);
    pushState(State::Normal, tr("Scanning has been finished"));
}

void MainWindow::alertUser(int type, const QString& strStatus)
{
    switch (type) {
    case KSaneIface::KSaneWidget::ErrorGeneral:
        QMessageBox::warning(this, tr("Error"), strStatus);
        break;
    default:
        QMessageBox::information(this, tr("Information"), strStatus);
    }
}

void MainWindow::on_actionSaveAs_triggered()
{
    if (m_outputEditor && m_outputEditor->isModified())
        m_outputEditor->commit();

    QStringList filters;

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::TXT)
        filters << QString("%1 (*.txt)").arg(tr("Text Files"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::HTML || OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR)
        filters << QString("%1 (*.html)").arg(tr("HTML Files"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::PDF || OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR)
        filters << QString("%1 (*.pdf)").arg(tr("PDF Files"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::ODT || OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR)
        filters << QString("%1 (*.odt)").arg(tr("OpenDocument Text Documents"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::DOCX)
        filters << QString("%1 (*.docx)").arg(tr("Microsoft Word Open Documents"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::RTF)
        filters << QString("%1 (*.rtf)").arg(tr("RTF Files"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::CSV)
        filters << QString("%1 (*.csv)").arg(tr("CSV Files"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::CSV)
        filters << QString("%1 (*.ods)").arg(tr("ODS Files"));

    if (OcrEngine::supportedExportFormats() & OcrEngineFactory::XML)
        filters << QString("%1 (*.xml)").arg(tr("XML Files"));


    QString path = FileDialogs::saveDialog(tr("Save Recognized Text..."), tr("output") + ".txt", "outputdir", filters.join(";;"));
    if (path.isEmpty()) {
        return;
    }

    if (path.endsWith(".txt", Qt::CaseInsensitive)) {
        m_recognizer->exportAs(OcrEngineFactory::TXT, path);
    } else if (path.endsWith(".html", Qt::CaseInsensitive)) {
        if (OcrEngine::supportedExportFormats() & OcrEngineFactory::HTML) {
            m_recognizer->exportAs(OcrEngineFactory::HTML, path);
        } else if (OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR) {
            exportAs(path, OcrEngineFactory::HTML);
        }
    } else if (path.endsWith(".pdf", Qt::CaseInsensitive)) {
        if (OcrEngine::supportedExportFormats() & OcrEngineFactory::PDF) {
            m_recognizer->exportAs(OcrEngineFactory::PDF, path);
        } else if (OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR) {
            exportAs(path, OcrEngineFactory::PDF);
        }
    } else if (path.endsWith(".odt", Qt::CaseInsensitive)) {
        if (OcrEngine::supportedExportFormats() & OcrEngineFactory::ODT) {
            m_recognizer->exportAs(OcrEngineFactory::PDF, path);
        } else if (OcrEngine::supportedPageDataFormats() & OcrEngineFactory::HOCR) {
            exportAs(path, OcrEngineFactory::ODT);
        }
    } else if (path.endsWith(".docx", Qt::CaseInsensitive)) {
        m_recognizer->exportAs(OcrEngineFactory::DOCX, path);
    } else if (path.endsWith(".rtf", Qt::CaseInsensitive)) {
        m_recognizer->exportAs(OcrEngineFactory::RTF, path);
    } else if (path.endsWith(".csv", Qt::CaseInsensitive)) {
        m_recognizer->exportAs(OcrEngineFactory::CSV, path);
    } else if (path.endsWith(".ods", Qt::CaseInsensitive)) {
        QString csvFilePath = path.mid(0, path.size() - 4) + QLatin1String(".csv");
        m_recognizer->exportAs(OcrEngineFactory::CSV, csvFilePath);
        const QStringList args = {
            QStringLiteral("--headless"),
            QStringLiteral("--convert-to"),
            QStringLiteral("ods"),
            QStringLiteral("--infilter=CSV:44,34,76,1"),
            csvFilePath,
            QStringLiteral("--outdir"),
            QFileInfo(csvFilePath).path()
        };
        QProcess::execute(QStringLiteral("libreoffice"), args);
        QFile::remove(csvFilePath);
    } else if (path.endsWith(".xml", Qt::CaseInsensitive)) {
        m_recognizer->exportAs(OcrEngineFactory::XML, path);
    }
}

void MainWindow::pasteImage()
{
    const QClipboard* clipboard = QApplication::clipboard();
    QImage image = clipboard->image();
    if (!image.isNull()) {
        m_sourceManager->add(image);
        m_sourceManager->selectPage(m_displayer->pageCount() - 1);
    }
}

void MainWindow::updateLanguages()
{
    const QList<FlyOcr::Lang> langs = m_recognitionMenu->selectedLanguages();

    if (!langs.isEmpty()) {
        QStringList names;
        for (const auto& lang : qAsConst(langs))
            names << lang.name;

        OcrEngineFactory* f = OcrEngine::selected();

        if (f) {
            m_engineInfoLabel->setText(tr("%1 <b>%2</b>").arg(names.join("+")).arg(f->name()));
            m_ui->actionAboutOCR->setText(tr("About %1").arg(f->name()));
            m_engineInfoLabel->setVisible(true);
            m_ui->actionAboutOCR->setVisible(true);

        } else {
            m_engineInfoLabel->setVisible(false);
            m_ui->actionAboutOCR->setVisible(false);
        }

    } else {
        m_engineInfoLabel->clear();
    }
}

void MainWindow::updateActions()
{
    while (!m_extraActions.isEmpty()) {
        QAction* act = m_extraActions.takeFirst();
        m_ui->menuEdit->removeAction(act);
        act->deleteLater();
    }

    OcrEngineFactory* f = OcrEngine::selected();
    if (f) {
        m_extraActions = f->createActions(m_ui->menuEdit);
        if (!m_extraActions.isEmpty()) {
            QAction* separator = m_ui->menuEdit->addSeparator();
            for (QAction* a : qAsConst(m_extraActions))
                m_ui->menuEdit->addAction(a);
            m_extraActions << separator;
        }
    }
}

void MainWindow::showProgress(ProgressMonitor* monitor, int updateInterval)
{
    m_progressMonitor = monitor;
    m_progressTimer.start(updateInterval);
    m_progressCancelButton->setEnabled(true);
    m_progressBar->setValue(0);
    m_progressWidget->show();
    m_engineInfoLabel->hide();
}

void MainWindow::hideProgress()
{
    m_progressWidget->hide();
    m_engineInfoLabel->show();
    m_progressTimer.stop();
    m_progressMonitor = nullptr;
}

void MainWindow::progressCancel()
{
    if (m_progressMonitor) {
        m_progressCancelButton->setEnabled(false);
        m_progressMonitor->cancel();
    }
}

void MainWindow::progressUpdate()
{
    if (m_progressMonitor) {
        m_progressBar->setValue(m_progressMonitor->getProgress());
    }
}

void MainWindow::onSelectedLanguagesChanged(const QList<FlyOcr::Lang>& langs)
{
    QString code;
    if (langs.count() == 1)
        code = langs.first().code;

    if (m_outputEditor) {
        m_outputEditor->setLanguage(code);
    }
    hideNotification(m_notifierHandle);
    m_notifierHandle = nullptr;

    if (!code.isEmpty() && !QtSpell::checkLanguageInstalled(code) && ConfigSettings::get<SwitchSetting>("dictinstall")->getValue()) {
        NotificationAction actionDontShowAgain = {tr("Don't show again"), [this]{ m_config->disableDictInstall(); }, true};
        addNotification(tr("Spelling dictionary missing"), tr("The spellcheck dictionary for %1 is not installed").arg(code), { actionDontShowAgain }, &m_notifierHandle);
    }
}
