/************************************************************************
 *									*
 *  This file is part of Kooka, a scanning/OCR application using	*
 *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.	*
 *									*
 *  Copyright (C) 2021      Jonathan Marten <jjm@keelhaul.me.uk>	*
 *									*
 *  Kooka is free software; you can redistribute it and/or modify it	*
 *  under the terms of the GNU Library General Public License as	*
 *  published by the Free Software Foundation and appearing in the	*
 *  file COPYING included in the packaging of this file;  either	*
 *  version 2 of the License, or (at your option) any later version.	*
 *									*
 *  As a special exception, permission is given to link this program	*
 *  with any version of the KADMOS OCR/ICR engine (a product of		*
 *  reRecognition GmbH, Kreuzlingen), and distribute the resulting	*
 *  executable without including the source code for KADMOS in the	*
 *  source distribution.						*
 *									*
 *  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;  see the file COPYING.  If		*
 *  not, see <http://www.gnu.org/licenses/>.				*
 *									*
 ************************************************************************/

#include "destinationshare.h"

#include <qcombobox.h>
#include <qjsonarray.h>

#include <kpluginfactory.h>
#include <klocalizedstring.h>
#include <kmessagebox.h>

#include <kio/global.h>
#include <kio/deletejob.h>
#include <kio/jobuidelegatefactory.h>

#include <purpose/alternativesmodel.h>
#include <purpose/menu.h>

#include "scanparamspage.h"
#include "kookasettings.h"
#include "multiscanoptions.h"
#include "destination_logging.h"


K_PLUGIN_FACTORY_WITH_JSON(DestinationShareFactory, "kookadestination-share.json", registerPlugin<DestinationShare>();)
#include "destinationshare.moc"


DestinationShare::DestinationShare(QObject *pnt, const QVariantList &args)
    : AbstractDestination(pnt, "DestinationShare")
{
    // The list of available share destinations can be obtained simply from
    // an AlternativesModel, but we create a Purpose::Menu so as to be able
    // to also use it for launching the share job.  Otherwise the whole of
    // Purpose::MenuPrivate::trigger() would need to be duplicated in
    // imageScanned().
    mMenu = new Purpose::Menu(parentWidget());
    connect(mMenu, &Purpose::Menu::finished, this, &DestinationShare::slotShareFinished);

    // This is the Purpose::AlternativesModel created by the Purpose::Menu.
    mModel = mMenu->model();
}


bool DestinationShare::imageScanned(ScanImage::Ptr img)
{
    const QString mimeName = mFormatCombo->currentData().toString();
    qCDebug(DESTINATION_LOG) << "received image size" << img->size() << "type" << img->imageType() << "mime" << mimeName;

    ImageFormat fmt = getSaveFormat(mimeName, img);	// get format for saving image
    if (!fmt.isValid()) return (false);			// must have this now
    const QUrl saveUrl = saveTempImage(fmt, img);	// save to temporary file
    if (!saveUrl.isValid()) return (false);		// could not save image

    // See DestinationApplication::imageScanned() for explanation.
    mSaveUrls.append(saveUrl);
    if (!multiScanOptions()->flags().testFlag(MultiScanOptions::BatchMultiple)) launchShare();
    return (true);
}


void DestinationShare::createGUI(ScanParamsPage *page)
{
    qCDebug(DESTINATION_LOG);

    // The MIME types that can be selected for sharing the image.
    QStringList mimeTypes;
    mimeTypes << "image/png" << "image/jpeg" << "image/tiff";
    mFormatCombo = createFormatCombo(mimeTypes, KookaSettings::destinationShareMime());
    connect(mFormatCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DestinationShare::slotUpdateShareCombo);
    page->addRow(i18n("Image format:"), mFormatCombo);

    // The share destinations that are available depend on the MIME type
    // as selected above.
    mShareCombo = new QComboBox;
    slotUpdateShareCombo();
    page->addRow(i18n("Share to:"), mShareCombo);
}


KLocalizedString DestinationShare::scanDestinationString()
{
    // Can't say "Sharing to..." or anything like that, because it
    // looks clumsy when it says "Sharing to Send via Bluetooth".
    return (kxi18n("<application>%1</application>").subs(mShareCombo->currentText()));
}


void DestinationShare::saveSettings() const
{
    KookaSettings::setDestinationShareDest(mShareCombo->currentData().toString());
    KookaSettings::setDestinationShareMime(mFormatCombo->currentData().toString());
}


void DestinationShare::slotUpdateShareCombo()
{
    QString mimeType = mFormatCombo->currentData().toString();
    // If the MIME type is "Other" then we do not have the type
    // available at this stage, so accept plugins that can share
    // any image type.
    if (mimeType.isEmpty()) mimeType = "image/*";
    qCDebug(DESTINATION_LOG) << "for MIME" << mimeType;

    QString configuredShare = mShareCombo->currentData().toString();
    if (configuredShare.isEmpty()) configuredShare = KookaSettings::destinationShareDest();
    int configuredIndex = -1;
    qCDebug(DESTINATION_LOG) << "current" << configuredShare;

    QJsonObject dataObject;
    dataObject.insert("mimeType", QJsonValue(mimeType));
    dataObject.insert("urls", QJsonArray());		// not relevant at this stage
    mModel->setInputData(dataObject);
    mModel->setPluginType(QStringLiteral("Export"));
    qCDebug(DESTINATION_LOG) << "have" << mModel->rowCount() << "share destinations";

    mShareCombo->clear();
    for (int i = 0; i<mModel->rowCount(); ++i)
    {
        QModelIndex idx = mModel->index(i, 0);
        const QString key = mModel->data(idx, Purpose::AlternativesModel::PluginIdRole).toString();
        qCDebug(DESTINATION_LOG) << "  " << i << key << mModel->data(idx, Qt::DisplayRole).toString();

        if (!configuredShare.isEmpty() && key==configuredShare) configuredIndex = mShareCombo->count();

        mShareCombo->addItem(mModel->data(idx, Qt::DecorationRole).value<QIcon>(),
                             mModel->data(idx, Qt::DisplayRole).toString(), key);
    }

    if (configuredIndex!=-1) mShareCombo->setCurrentIndex(configuredIndex);
}


void DestinationShare::batchStart(const MultiScanOptions *opts)
{
    AbstractDestination::batchStart(opts);		// remember the options
    mSaveUrls.clear();					// clear file list

    mShareService = mShareCombo->currentData().toString();
    mMimeName = mFormatCombo->currentData().toString();
    qCDebug(DESTINATION_LOG) << "share service" << mShareService << "mime" << mMimeName;
}


void DestinationShare::batchEnd(bool ok)
{
    if (mSaveUrls.isEmpty()) return;			// no files collected
    qCDebug(DESTINATION_LOG) << "have" << mSaveUrls.count() << "files, ok?" << ok;

    if (!ok)						// problem while scanning
    {
        // Since we are not sending the files anywhere, they can be
        // deleted immediately here.
        KIO::DeleteJob *job = KIO::del(mSaveUrls);
        job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, parentWidget()));
        job->start();
        return;
    }

    launchShare();
}


void DestinationShare::launchShare()
{
    qCDebug(DESTINATION_LOG) << "have" << mSaveUrls.count() << "files";

    // Because we did not know the specific MIME type at the time, the
    // original menu and hence the share destination combo box will have
    // been filled with actions for the generic "image/*" MIME type.
    // Now the MIME type is known and we need to specify it.
    //
    // It is assumed that all of the scans delivered here in a batch
    // have the same MIME type.  This is reasonable, because the scan
    // format GUI is disabled during scanning.
    //
    // Hopefully the list of shared destinations will not change as a
    // result of the more specific MIME type.  Just in case it does,
    // note the selected share ID before setting the MIME type and
    // find it again before triggering the menu action.  This has to
    // be done on the first pass through the loop, after the first URL
    // is available.
    QJsonObject dataObject;
    dataObject.insert("mimeType", QJsonValue(mMimeName));

    // See DestinationApplication::launchApplication() for why we can
    // assume batching here.
    QJsonArray dataUrls;
    for (const QUrl &url : mSaveUrls) dataUrls.append(url.url());
    dataObject.insert("urls", dataUrls);
    mSaveUrls.clear();					// don't need them any more

    mModel->setInputData(dataObject);			// set MIME type and URL
    mMenu->reload();					// regenerate the menu

    int menuRow = -1;
    for (int i = 0; i<mModel->rowCount(); ++i)		// search through new model
    {
        QModelIndex idx = mModel->index(i, 0);
        const QString key = mModel->data(idx, Purpose::AlternativesModel::PluginIdRole).toString();
        if (key==mShareService)
        {
            menuRow = i;
            break;
        }
    }

    if (menuRow==-1)					// couldn't find share in new menu
    {
        qCWarning(DESTINATION_LOG) << "Cannot find service for updated MIME type, count" << mModel->rowCount();
        return;
    }

    QAction *act = mMenu->actions().value(menuRow);	// get action from menu
    Q_ASSERT(act!=nullptr);
    act->trigger();					// do the share action

    // There is nothing more to do here, the temporary files will eventually
    // be deleted in slotShareFinished().
}


void DestinationShare::slotShareFinished(const QJsonObject &output, int error, const QString &errorMessage)
{
    // Based on the lambda in ShareFileItemAction::ShareFileItemAction()
    // The finished() signal is emitted by purpose/src/widgets/JobDialog.qml
    qCDebug(DESTINATION_LOG) << "error" << error << "output" << output;
    if (error==0 || error==KIO::ERR_USER_CANCELED)
    {
        // Report the result URL if there is one in the share output.
        // Test case for this is Imgur (no account/password needed).
        if (output.contains(QLatin1String("url")))
        {
            QString url = output.value(QLatin1String("url")).toString();

            // It may be more friendly to use a KMessageWidget (including
            // a "Copy link" button) for this, but vertical space in the
            // ScanParams area is already at a premium.
            KMessageBox::information(parentWidget(),
                                     xi18ncp("@info",
                                             "The scan was shared to<nl/><link>%2</link>", 
                                             "The scans were shared to<nl/><link>%2</link>",
                                             mSaveUrls.count(), url),
                                     i18n("Scan Shared"),
                                     QString(),
                                     KMessageBox::Notify|KMessageBox::AllowLink);
        }
    }
    else
    {
        qCWarning(DESTINATION_LOG) << "job failed with error" << error << errorMessage << output;
        KMessageBox::error(parentWidget(),
                           xi18ncp("@info",
                                   "Cannot share the scanned image<nl/><nl/><message>%2</message>",
                                   "Cannot share the %1 scanned images<nl/><nl/><message>%2</message>",
                                   mSaveUrls.count(), errorMessage));
    }

    delayedDelete(mSaveUrls);				// delete temporary file, eventually
    mSaveUrls.clear();					// have dealt with them now
}


MultiScanOptions::Capabilities DestinationShare::capabilities() const
{
    return (MultiScanOptions::AcceptBatch|MultiScanOptions::DefaultBatch);
}
