/*******************************************************************************
  XDG desktop portal implementation for TDE
  Copyright © 2024 Mavridis Philippe <mavridisf@gmail.com>

  This program or library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public License as
  published by the Free Software Foundation; either version 2.1 of the License,
  or (at your option) any later version.

  This library 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 Lesser General Public License for more
  details.

  You should have received a copy of the GNU Lesser General Public License
  along with this library; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

  Improvements and feedback are welcome!
*******************************************************************************/

// TQt
#include <tqregexp.h>
#include <tqdbusobjectpath.h>
#include <tqapplication.h>
#include <tqthread.h>

// TDE
#include <tdeio/renamedlg.h>
#include <tdefiledialog.h>
#include <kpushbutton.h>
#include <kdebug.h>

// Portal
#include "file_chooser_portal.h"
#include "file_chooser_portal.moc"

class KMultiFileSaveDialog : public KFileDialog
{
    public:
        KMultiFileSaveDialog(TQString startDir, TQStringList filelist,
                             const char *name, bool modal)
            : KFileDialog(startDir, TQString::null, nullptr, name, modal),
              m_files(filelist)
        {}

        KURL::List urls()
        {
            return m_urls;
        }

    protected slots:
        // The below is a workaround to prevent the dialog from disappearing
        // until we are done handling conflicting files.
        // Don't kick it, it works.
        void done(int) {}
        void actuallyDone(int r) { KFileDialog::done(r); }

        void slotCancel()
        {
            actuallyDone(KFileDialog::Rejected);
        }

        void slotOk()
        {
            KFileDialog::slotOk();
            m_urls.clear();

            // Check if there are conflicting files in the target directory
            // and prompt for action
            TQDir d(selectedURL().path());
            for (TQStringList::Iterator it = m_files.begin(); it != m_files.end(); ++it)
            {
                TQString filename(*it);
                TQFileInfo fi(d, filename);
                KURL url(fi.absFilePath());
                if (fi.exists())
                {
                    TDEIO::RenameDlg rename(nullptr, caption(), TQString::null,
                                            fi.absFilePath(), TDEIO::M_OVERWRITE);
                    int result = rename.exec();

                    switch (result)
                    {
                        case TDEIO::R_RENAME:
                            m_urls << rename.newDestURL();
                            break;
                        case TDEIO::R_OVERWRITE:
                            m_urls << url.url();
                            break;
                        case TDEIO::R_CANCEL:
                        default:
                            actuallyDone(KFileDialog::Rejected);
                            break;
                    }
                }
                else
                {
                    m_urls << url.url();
                }
            }
            actuallyDone(KFileDialog::Accepted);
        }

    private:
        TQStringList m_files;
        KURL::List m_urls;
};

TDEFileChooserPortal::TDEFileChooserPortal(TQT_DBusConnection &connection)
    : m_connection(connection)
{}

TDEFileChooserPortal::~TDEFileChooserPortal()
{}

void TDEFileChooserPortal::OpenFileAsync(int asyncCallId,
                                         const TQT_DBusObjectPath& handle,
                                         const TQString& app_id,
                                         const TQString& parent_window,
                                         const TQString& title,
                                         const TQT_DBusVariantMap &options)
{
    FileDialogOpts opts;

    opts.caption = title;

    if (OPTION_VALID("accept_label", "s"))
        opts.okButtonText = options["accept_label"].value.toString();

    if (OPTION_VALID("directory", "b"))
        opts.directory = options["directory"].value.toBool();

    if (OPTION_VALID("multiple", "b"))
        opts.multiple = options["multiple"].value.toBool();

    if (OPTION_VALID("modal", "b"))
        opts.modal = options["modal"].value.toBool();

    if (OPTION_VALID("filters", "a(sa(us))"))
        opts.filters = parseFilterList(options["filters"], options["current_filter"]);

    opts.windowId = parse_window_id(parent_window);

    // Execute dialog
    execFileDialog(&TDEFileChooserPortal::OpenFileAsyncReply, asyncCallId, opts, handle);
}

void TDEFileChooserPortal::SaveFileAsync(int asyncCallId,
                                         const TQT_DBusObjectPath& handle,
                                         const TQString& app_id,
                                         const TQString& parent_window,
                                         const TQString& title,
                                         const TQT_DBusVariantMap &options)
{
    FileDialogOpts opts;

    opts.caption = title;

    if (OPTION_VALID("accept_label", "s"))
        opts.okButtonText = options["accept_label"].value.toString();

    if (OPTION_VALID("directory", "b"))
        opts.directory = options["directory"].value.toBool();

    if (OPTION_VALID("multiple", "b"))
        opts.multiple = options["multiple"].value.toBool();

    if (OPTION_VALID("modal", "b"))
        opts.modal = options["modal"].value.toBool();

    if (OPTION_VALID("filters", "a(sa(us))"))
        opts.filters = parseFilterList(options["filters"], options["current_filter"]);

    if (OPTION_VALID("current_folder", "ay"))
        opts.startDir = bytelist_to_string(options["current_folder"].value.toList().toByteList());

    if (OPTION_VALID("current_name", "s"))
        opts.startName = options["current_name"].value.toString();

    opts.windowId = parse_window_id(parent_window);

    execFileDialog(&TDEFileChooserPortal::SaveFileAsyncReply, asyncCallId, opts, handle);
}

// TODO move some of the dialog handling stuff to execFileDialog()
void TDEFileChooserPortal::SaveFilesAsync(int asyncCallId,
                                          const TQT_DBusObjectPath& handle,
                                          const TQString& app_id,
                                          const TQString& parent_window,
                                          const TQString& title,
                                          const TQMap<TQString, TQT_DBusVariant> &options)
{
    // Get list of files to save
    if (!OPTION_VALID("files", "aay"))
    {
        kdWarning() << "TDEFileChooserPortal::SaveFiles: "
                    << "Invalid or missing files option" << endl;

        SaveFilesAsyncReply(asyncCallId, 0, TQT_DBusVariantMap());
        return;
    }

    TQT_DBusValueList filelist = options["files"].value.toTQValueList();
    TQT_DBusValueList::iterator fi;
    TQStringList files;
    for (fi = filelist.begin(); fi != filelist.end(); ++fi)
    {
        files << bytelist_to_string((*fi).toList().toByteList());
    }

    // Parse options
    FileDialogOpts opts;
    opts.caption = title;

    if (OPTION_VALID("accept_label", "s"))
        opts.okButtonText = options["accept_label"].value.toString();

    if (OPTION_VALID("modal", "b"))
        opts.modal = options["modal"].value.toBool();

    if (OPTION_VALID("current_folder", "ay"))
        opts.startDir = bytelist_to_string(options["current_folder"].value.toList().toByteList());

    // We can't just use execFileDialog because we need to do some special processing
    KMultiFileSaveDialog *dialog = new KMultiFileSaveDialog(opts.startDir, files,
                                                            "xdg-tde-file-chooser",
                                                            opts.modal);

    dialog->setMode(KFile::LocalOnly | KFile::Directory);

    if (!opts.caption.isNull())
        dialog->setPlainCaption(opts.caption);

    if (!opts.okButtonText.isNull())
        dialog->okButton()->setText(opts.okButtonText);

    if (opts.windowId > 0) KWin::setMainWindow(dialog, opts.windowId);

    auto *sender = new DialogResultSender<TDEFileChooserPortal>
            (this, asyncCallId, dialog,
             &TDEFileChooserPortal::prepareSaveFilesReply,
             &TDEFileChooserPortal::SaveFilesAsyncReply);
    dialog->show();
    sender->start();

}

void TDEFileChooserPortal::handleMethodReply(const TQT_DBusMessage &reply)
{
    m_connection.send(reply);
}

bool TDEFileChooserPortal::handleSignalSend(const TQT_DBusMessage& reply) {
    handleMethodReply(reply);
    return true;
}

void TDEFileChooserPortal::execFileDialog(ResultSendCallback<TDEFileChooserPortal> callback,
                                          int asyncCallId,
                                          FileDialogOpts options,
                                          const TQT_DBusObjectPath& handle)
{
    KFileDialog *dialog = new KFileDialog(options.startDir, TQString::null,
                                          nullptr, "xdg-tde-file-chooser",
                                          options.modal);
    uint mode = KFile::LocalOnly;
    if (options.savingMode)
    {
        dialog->setOperationMode(KFileDialog::Saving);
    }
    else {
        mode |= KFile::ExistingOnly;
    }
    dialog->setMode(mode | options.mode());

    if (!options.caption.isNull())
    {
        dialog->setPlainCaption(options.caption);
    }

    if (!options.okButtonText.isNull())
    {
        dialog->okButton()->setText(options.okButtonText);
    }

    if (!options.filters.isNull())
    {
        dialog->setFilter(options.filters);
    }

    dialog->setSelection(options.startName);

    if (options.windowId > 0) KWin::setMainWindow(dialog, options.windowId);

    auto *sender = new DialogResultSender<TDEFileChooserPortal>
        (this, asyncCallId, dialog, &TDEFileChooserPortal::prepareReply, callback);
    dialog->show();
    sender->start();
}

DialogResult TDEFileChooserPortal::prepareReply(TQDialog *dlg)
{
    KFileDialog *fd = static_cast<KFileDialog*>(dlg);
    KURL::List urllist = fd->selectedURLs();

    DialogResult res;
    TQT_DBusDataList urls = kurl_list_to_datalist(urllist);
    TQT_DBusVariant var = TQT_DBusData::fromList(urls).getAsVariantData().toVariant();
    res.results.insert("uris", var);
    res.response = urllist.isEmpty() ? 1 : 0;
    return res;
}

// Special treatment for SaveFiles()
// TODO revise this and possibly merge with prepareReply()
DialogResult TDEFileChooserPortal::prepareSaveFilesReply(TQDialog *dlg)
{
    DialogResult res;
    if (dlg->result() == TQDialog::Accepted)
    {
        KMultiFileSaveDialog *fd = static_cast<KMultiFileSaveDialog*>(dlg);

        TQT_DBusDataList urls = kurl_list_to_datalist(fd->urls());
        TQT_DBusVariant var = TQT_DBusData::fromList(urls).getAsVariantData().toVariant();

        res.response = 0;
        res.results.insert("uris", var);
    }
    else res.response = 1;

    return res;
}

TQString TDEFileChooserPortal::parseFilter(const TQT_DBusData data)
{
    TQStringList patternList;

    TQT_DBusValueList filterData = data.toStruct();

    TQString label = filterData[0].toString();
    TQT_DBusValueList patterns = filterData[1].toTQValueList();

    TQT_DBusValueList::iterator fp;
    for (fp = patterns.begin(); fp != patterns.end(); ++fp)
    {
        TQT_DBusValueList patternData = (*fp).toStruct();
        bool isMime = (patternData[0].toUInt32() == 1);
        if (isMime) {
            // KFileDialog cannot handle both a mime and a simple
            // extension filter, so in case we get a mimetype,
            // we just get the associated extensions from the
            // MIME system
            TQString mime = patternData[1].toString();
            patternList += KMimeType::mimeType(mime)->patterns();
        }
        else {
            TQString patternString = patternData[1].toString();

            // Regex patterns like *.[hH][tT][mM][lL] are unnecessary for us
            // and causes a problem with the save dialog's extension autoselection
            TQString finalPattern = patternString;
            if (TQRegExp("[*.](?:[[](?:[A-Za-z]{2})[]])+").search(patternString) != -1)
            {
                finalPattern = "*.";
                int pos = patternString.find('[');
                while (pos != -1)
                {
                    finalPattern += patternString[pos + 1];
                    pos = patternString.find('[', patternString.find(']', pos));
                }
            }
            patternList += finalPattern;
        }
    }

    TQString patternString = patternList.join(" ");

    return TQString("%1|%2 (%3)").arg(patternString, label, patternString);
}

TQString TDEFileChooserPortal::parseFilterList(const TQT_DBusVariant filterData,
                                               const TQT_DBusVariant currentFilterData)
{
    TQStringList filterList;
    TQT_DBusValueList filters = filterData.value.toTQValueList();
    if (filters.count() > 0) {
        TQT_DBusValueList::iterator f;
        for (f = filters.begin(); f != filters.end(); ++f)
                filterList += parseFilter((*f));
    }

    if (check_variant(currentFilterData, "(sa(us))"))
    {
        TQString currentFilter = parseFilter(currentFilterData.value);
        if (filterList.contains(currentFilter))
        {
            // We have no way to affect the filter selection of the dialog,
            // so we just move the current filter to the top of the list
            // to get it selected automatically
            filterList.remove(currentFilter);
            filterList.prepend(currentFilter);
        }
    }

    return filterList.join("\n");
}

// kate: replace-tabs true; tab-width 4; indent-width 4;