/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2023 UniPro <ugene@unipro.ru>
 * http://ugene.net
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#pragma once

#include <QAction>
#include <QComboBox>
#include <QCoreApplication>
#include <QDialog>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLineEdit>
#include <QListWidget>
#include <QModelIndex>
#include <QPair>
#include <QPointer>
#include <QPushButton>
#include <QSpinBox>
#include <QTextEdit>
#include <QToolButton>
#include <QVBoxLayout>

#include <U2Core/Formatters.h>

#include <U2Designer/URLLineEdit.h>

#include <U2Lang/ConfigurationEditor.h>

#include "PropertyWidget.h"

namespace U2 {

/**
 * simple realization of configuration editor
 */
class U2DESIGNER_EXPORT DelegateEditor : public ConfigurationEditor {
    Q_OBJECT
public:
    DelegateEditor(const QMap<QString, PropertyDelegate*>& map)
        : delegates(map) {
    }
    DelegateEditor(const QString& s, PropertyDelegate* d) {
        delegates.insert(s, d);
    }
    DelegateEditor(const DelegateEditor& other);
    virtual ~DelegateEditor() {
        qDeleteAll(delegates.values());
    }
    virtual PropertyDelegate* getDelegate(const QString& name) {
        return delegates.value(name);
    }
    virtual PropertyDelegate* removeDelegate(const QString& name) {
        return delegates.take(name);
    }
    virtual void updateDelegates();
    virtual void updateDelegate(const QString& name);
    virtual void addDelegate(PropertyDelegate* del, const QString& name) {
        delegates.insert(name, del);
    }
    virtual void commit() {
    }
    virtual ConfigurationEditor* clone() {
        return new DelegateEditor(*this);
    }

protected:
    QMap<QString, PropertyDelegate*> delegates;

private:
    DelegateEditor& operator=(const DelegateEditor&);
};  // DelegateEditor

/**
 * filter - a file filter string in the format for QFileDialog.
 * type - the domain name for the LastUsedDirHelper
 * multi - allow to select several files. Ignored, if isPath is true.
 * isPath - if it is true, allows to select only existing directory. Otherwise, files can be selected (existing or not).
 * saveFile - if it is true, allows to select file to save. File can be existing or not. Ignored, if isPath or multi is true.
 * format - the format ID. It is used only if saveFile is true. The selected file will have an appropriate extension.
 * noFilesMode - user can select files, but the directory will be commited as the selected item. It is not possible to select the directory in this mode, isPath is ignored.
 */
class U2DESIGNER_EXPORT URLDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    enum Option {
        None = 0,
        AllowSelectSeveralFiles = 1 << 0,  // allows to select several files. Ignored, if AllowSelectOnlyExistingDir is set.
        AllowSelectOnlyExistingDir = 1 << 1,  // allows to select only existing directory. Otherwise, files can be selected (existing or not).
        SelectFileToSave = 1 << 2,  // allows to select file to save. File can be existing or not. Ignored, if AllowSelectOnlyExistingDir or AllowSelectSeveralFiles is set.
        SelectParentDirInsteadSelectedFile = 1 << 3,  // user can select files, but the directory will be committed as the selected item. It is not possible to select the directory in this mode, AllowSelectOnlyExistingDir flag is ignored.
        DoNotUseWorkflowOutputFolder = 1 << 4  // do not offer to save file to the workflow output folder, show the default save dialog. Only if SelectFileToSave flag is set.
    };
    Q_DECLARE_FLAGS(Options, Option)

    URLDelegate(const QString& filter, const QString& type, const Options& options, QObject* parent = nullptr, const QString& format = "");
    URLDelegate(const DelegateTags& tags, const QString& type, const Options& options, QObject* parent = nullptr);
    URLDelegate(const QString& filter, const QString& type, bool multi = false, bool isPath = false, bool saveFile = true, QObject* parent = nullptr, const QString& format = "", bool noFilesMode = false, bool doNotUseWorkflowOutputFolder = false);
    URLDelegate(const DelegateTags& tags, const QString& type, bool multi = false, bool isPath = false, bool saveFile = true, QObject* parent = nullptr, bool noFilesMode = false, bool doNotUseWorkflowOutputFolder = false);

    QVariant getDisplayValue(const QVariant& v) const;

    virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;

    virtual void setEditorData(QWidget* editor, const QModelIndex& index) const;
    virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;

    virtual PropertyDelegate* clone();
    virtual Type type() const;

private slots:
    void sl_commit();

private:
    URLWidget* createWidget(QWidget* parent) const;

    QString lastDirType;
    Options options;
    QString text;
};

class U2DESIGNER_EXPORT SpinBoxDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    SpinBoxDelegate(const QVariantMap& props = QVariantMap(), QObject* parent = 0)
        : PropertyDelegate(parent), spinProperties(props), currentEditor(nullptr) {
    }
    virtual ~SpinBoxDelegate() {
    }

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;

    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
    QVariant getDisplayValue(const QVariant&) const;

    void setEditorProperty(const char* name, const QVariant& val);

    virtual PropertyDelegate* clone() {
        return new SpinBoxDelegate(spinProperties, parent());
    }

    void getItems(QVariantMap& items) const;

    QVariantMap getProperties() const;

signals:
    void si_valueChanged(int);
private slots:
    void sl_commit();

private:
    QVariantMap spinProperties;
    mutable QPointer<SpinBoxWidget> currentEditor;
};

class U2DESIGNER_EXPORT DoubleSpinBoxDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    DoubleSpinBoxDelegate(const QVariantMap& props = {}, QObject* parent = nullptr, const QSharedPointer<DoubleFormatter>& formatter = nullptr);

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const override;

    void setEditorData(QWidget* editor, const QModelIndex& index) const override;

    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;

    QVariant getDisplayValue(const QVariant&) const override;

    PropertyDelegate* clone() override;

    void getItems(QVariantMap& items) const override;

    static const int DEFAULT_DECIMALS_VALUE;

private slots:
    void sl_commit();

private:
    QVariantMap spinProperties;
    QSharedPointer<DoubleFormatter> valueFormatter;
};

/** Base class for all combo-box delegates. Includes item name formatter and other common features. */
class U2DESIGNER_EXPORT ComboBoxBaseDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    ComboBoxBaseDelegate(QObject* parent = nullptr);

    /** Updates item text formatter. */
    void setItemTextFormatter(const QSharedPointer<StringFormatter>& formatter);

    /** Returns formatted value for the item with the given name. */
    QString getFormattedItemText(const QString& itemKey) const;

    /** Enables/disables sorting of the value in the combo-box. */
    void setSortFlag(bool flag);

protected:
    /** Assigns common properties like itemTextFormatter to the cloned delegate. */
    ComboBoxBaseDelegate* initClonedDelegate(ComboBoxBaseDelegate* delegate) const;

    /** Formatter for combo-box values. */
    QSharedPointer<StringFormatter> itemTextFormatter;

    /** Makes combo-box list sorted. The sorting is case insensitive. */
    bool isSorted = false;
};

class U2DESIGNER_EXPORT ComboBoxDelegate : public ComboBoxBaseDelegate {
    Q_OBJECT
public:
    ComboBoxDelegate(const QVariantMap& comboItems, QObject* parent = 0);  // items: visible name -> value
    ComboBoxDelegate(const QList<ComboItem>& comboItems, QObject* parent = 0);  // items: visible name -> value

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;

    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
    QVariant getDisplayValue(const QVariant&) const;

    PropertyDelegate* clone() override;

    void getItems(QVariantMap& items) const;

protected:
    QVariantMap getAvailableItems() const;

signals:
    void si_valueChanged(const QString& newVal) const;

private slots:
    void sl_commit();

protected:
    QList<ComboItem> comboItems;
};

class U2DESIGNER_EXPORT ComboBoxEditableDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    ComboBoxEditableDelegate(const QVariantMap& items, QObject* parent = 0)
        : PropertyDelegate(parent), items(items) {
    }
    virtual ~ComboBoxEditableDelegate() {
    }

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;

    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;

    QVariant getDisplayValue(const QVariant&) const;

    virtual PropertyDelegate* clone() {
        return new ComboBoxEditableDelegate(items, parent());
    }

signals:
    void si_valueChanged(const QString& newVal) const;

private slots:
    void sl_valueChanged(const QString& newVal);

protected:
    QVariantMap items;
};

class U2DESIGNER_EXPORT ComboBoxWithUrlsDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    ComboBoxWithUrlsDelegate(const QVariantMap& items, bool _isPath = false, QObject* parent = 0)
        : PropertyDelegate(parent), items(items), isPath(_isPath) {
    }
    virtual ~ComboBoxWithUrlsDelegate() {
    }

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;

    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
    QVariant getDisplayValue(const QVariant&) const;

    virtual PropertyDelegate* clone() {
        return new ComboBoxWithUrlsDelegate(items, isPath, parent());
    }

signals:
    void si_valueChanged(const QString& newVal) const;

private slots:
    void sl_valueChanged(const QString& newVal);

protected:
    QVariantMap items;
    bool isPath;
};

class U2DESIGNER_EXPORT ComboBoxWithChecksDelegate : public ComboBoxBaseDelegate {
    Q_OBJECT
public:
    ComboBoxWithChecksDelegate(const QVariantMap& items, QObject* parent = 0);

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;

    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
    QVariant getDisplayValue(const QVariant&) const;

    PropertyDelegate* clone() override;

    void getItems(QVariantMap& items) const;

signals:
    void si_valueChanged(const QString& newVal) const;

private slots:
    void sl_commit();

protected:
    QVariantMap items;
};

class U2DESIGNER_EXPORT ComboBoxWithBoolsDelegate : public ComboBoxDelegate {
    Q_OBJECT
public:
    ComboBoxWithBoolsDelegate(QObject* parent = 0);
    virtual PropertyDelegate* clone() {
        return new ComboBoxWithBoolsDelegate(parent());
    }

private:
    static QVariantMap boolMap();
};

class U2DESIGNER_EXPORT FileModeDelegate : public ComboBoxDelegate {
public:
    FileModeDelegate(bool appendSupported, QObject* parent = 0);
    virtual ~FileModeDelegate() {
    }

    virtual PropertyDelegate* clone() {
        return new FileModeDelegate(3 == comboItems.size(), parent());
    }
};

class U2DESIGNER_EXPORT SchemaRunModeDelegate : public ComboBoxDelegate {
    Q_OBJECT
private:
    QString thisComputerOption;
    QString remoteComputerOption;

public:
    SchemaRunModeDelegate(QObject* parent = 0);
    virtual ~SchemaRunModeDelegate() {
    }

    virtual PropertyDelegate* clone() {
        return new SchemaRunModeDelegate(parent());
    }

public slots:
    void sl_valueChanged(const QString& val);

signals:
    void si_showOpenFileButton(bool show);

};  // SchemaRunModeDelegate

class ScriptSelectionWidget : public PropertyWidget {
    Q_OBJECT
public:
    ScriptSelectionWidget(QWidget* parent = nullptr);
    QVariant value();

public slots:
    void setValue(const QVariant& value);

private slots:
    void sl_comboCurrentIndexChanged(int itemId);

signals:
    void si_finished();

private:
    QComboBox* combobox;
};

class U2DESIGNER_EXPORT AttributeScriptDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    AttributeScriptDelegate(QObject* parent = 0);
    virtual ~AttributeScriptDelegate();

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;
    QVariant getDisplayValue(const QVariant&) const;

    static QString createScriptHeader(const AttributeScript& attrScript);

    virtual PropertyDelegate* clone() {
        return new AttributeScriptDelegate(parent());
    }

private slots:
    void sl_commit();
};  // AttributeScriptDelegate

class U2DESIGNER_EXPORT StingListEdit : public QLineEdit {
    Q_OBJECT

public:
    StingListEdit(QWidget* parent)
        : QLineEdit(parent) {
    }

protected:
    void focusOutEvent(QFocusEvent* event);

signals:
    void si_finished();

private slots:
    void sl_onExpand();
};

class StingListWidget : public PropertyWidget {
    Q_OBJECT
public:
    StingListWidget(QWidget* parent = nullptr);
    virtual QVariant value();
    virtual void setValue(const QVariant& value);
    virtual void setRequired();

signals:
    void finished();

private:
    StingListEdit* edit;
};

class U2DESIGNER_EXPORT StringListDelegate : public PropertyDelegate {
    Q_OBJECT

public:
    StringListDelegate(QObject* parent = 0)
        : PropertyDelegate(parent), currentEditor(nullptr) {
    }
    virtual ~StringListDelegate() {
    }

    virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;

    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;

    virtual PropertyDelegate* clone() {
        return new StringListDelegate(parent());
    }

public slots:
    void sl_commit();

private:
    mutable QWidget* currentEditor;
};

class SelectorDialogHandler {
public:
    virtual QDialog* createSelectorDialog(const QString& init) = 0;
    virtual QString getSelectedString(QDialog* dlg) = 0;
};

class U2DESIGNER_EXPORT StringSelectorDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    StringSelectorDelegate(const QString& _initValue, SelectorDialogHandler* _f, QObject* o = nullptr)
        : PropertyDelegate(o), valueEdit(nullptr), currentEditor(nullptr), initValue(_initValue), multipleSelection(false), f(_f) {
    }
    virtual ~StringSelectorDelegate() {
    }

    virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;

    void setEditorData(QWidget* editor, const QModelIndex& index) const;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;

    virtual PropertyDelegate* clone() {
        return new StringSelectorDelegate(initValue, f, parent());
    }

private slots:
    void sl_onClick();
    void sl_commit();

private:
    mutable QLineEdit* valueEdit;
    mutable QWidget* currentEditor;
    QString initValue;
    bool multipleSelection;
    SelectorDialogHandler* f;
};

class U2DESIGNER_EXPORT CharacterDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    CharacterDelegate(QObject* parent = 0)
        : PropertyDelegate(parent) {
    }
    virtual ~CharacterDelegate() {
    }

    virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
    virtual PropertyWidget* createWizardWidget(U2OpStatus& os, QWidget* parent) const;
    virtual void setEditorData(QWidget* editor, const QModelIndex& index) const;
    virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const;

    virtual PropertyDelegate* clone() {
        return new CharacterDelegate(parent());
    }

};  // CharacterDelegate

class U2DESIGNER_EXPORT LineEditWithValidatorDelegate : public PropertyDelegate {
    Q_OBJECT
public:
    LineEditWithValidatorDelegate(const QRegularExpression& regExp, QObject* parent = nullptr);

    QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    void setEditorData(QWidget* editor, const QModelIndex& index) const override;
    void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;

    virtual LineEditWithValidatorDelegate* clone() override;

private slots:
    void sl_valueChanged();

private:
    const QRegularExpression regExp;
};

}  // namespace U2

Q_DECLARE_OPERATORS_FOR_FLAGS(U2::URLDelegate::Options)
