/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2024 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.
 */

#include "DelegateEditors.h"

#include <QApplication>

#include <U2Core/AppContext.h>
#include <U2Core/Counter.h>
#include <U2Core/DocumentModel.h>
#include <U2Core/L10n.h>
#include <U2Core/QObjectScopedPointer.h>
#include <U2Core/SaveDocumentTask.h>
#include <U2Core/U2SafePoints.h>

#include <U2Gui/LastUsedDirHelper.h>
#include <U2Gui/ScriptEditorDialog.h>

#include <U2Lang/WorkflowUtils.h>

#include "PropertyWidget.h"

namespace U2 {

DelegateEditor::DelegateEditor(const DelegateEditor& other)
    : ConfigurationEditor(other) {
    foreach (const QString& id, other.delegates.keys()) {
        delegates[id] = other.delegates[id]->clone();
    }
}

void DelegateEditor::updateDelegates() {
    foreach (PropertyDelegate* delegate, delegates.values()) {
        delegate->update();
    }
}

void DelegateEditor::updateDelegate(const QString& name) {
    if (delegates.contains(name)) {
        delegates[name]->update();
    }
}

/********************************
 * SpinBoxDelegate
 ********************************/
PropertyWidget* SpinBoxDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return new SpinBoxWidget(getProperties(), parent);
}

QWidget* SpinBoxDelegate::createEditor(QWidget* parent,
                                       const QStyleOptionViewItem& /* option */,
                                       const QModelIndex& /* index */) const {
    auto editor = new SpinBoxWidget(getProperties(), parent);
    connect(editor, SIGNAL(valueChanged(int)), SIGNAL(si_valueChanged(int)));
    connect(editor, SIGNAL(valueChanged(int)), SLOT(sl_commit()));

    currentEditor = editor;

    return editor;
}

void SpinBoxDelegate::setEditorData(QWidget* editor,
                                    const QModelIndex& index) const {
    int value = index.model()->data(index, ConfigurationEditor::ItemValueRole).toInt();
    auto spinBox = static_cast<SpinBoxWidget*>(editor);
    spinBox->setValue(value);
}

void SpinBoxDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto spinBox = static_cast<SpinBoxWidget*>(editor);
    int value = spinBox->value().toInt();
    model->setData(index, value, ConfigurationEditor::ItemValueRole);
}

QVariant SpinBoxDelegate::getDisplayValue(const QVariant& v) const {
    QSpinBox editor;
    WorkflowUtils::setQObjectProperties(editor, getProperties());
    editor.setValue(v.toInt());
    return editor.text();
}

void SpinBoxDelegate::setEditorProperty(const char* name, const QVariant& val) {
    spinProperties[name] = val;
    if (!currentEditor.isNull()) {
        currentEditor->setProperty(name, val);
    }
}

void SpinBoxDelegate::getItems(QVariantMap& items) const {
    items = this->spinProperties;
}

QVariantMap SpinBoxDelegate::getProperties() const {
    QVariantMap result = spinProperties;
    DelegateTags* t = tags();
    CHECK(t != nullptr, result);
    foreach (const QString& tagName, t->names()) {
        result[tagName] = t->get(tagName);
    }
    return result;
}

void SpinBoxDelegate::sl_commit() {
    auto editor = static_cast<SpinBoxWidget*>(sender());
    CHECK(editor != nullptr, );
    emit commitData(editor);
}

/********************************
 * DoubleSpinBoxDelegate
 ********************************/
const int DoubleSpinBoxDelegate::DEFAULT_DECIMALS_VALUE = 5;

DoubleSpinBoxDelegate::DoubleSpinBoxDelegate(const QVariantMap& props, QObject* parent, const QSharedPointer<DoubleFormatter>& formatter)
    : PropertyDelegate(parent), spinProperties(props), valueFormatter(formatter) {
    if (!spinProperties.contains("decimals")) {
        spinProperties["decimals"] = DEFAULT_DECIMALS_VALUE;
    }
}

PropertyWidget* DoubleSpinBoxDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return (PropertyWidget*)createEditor(parent, QStyleOptionViewItem(), QModelIndex());
}

PropertyDelegate* DoubleSpinBoxDelegate::clone() {
    return new DoubleSpinBoxDelegate(spinProperties, parent(), valueFormatter);
}

QWidget* DoubleSpinBoxDelegate::createEditor(QWidget* parent,
                                             const QStyleOptionViewItem& /* option */,
                                             const QModelIndex& /* index */) const {
    auto editor = new DoubleSpinBoxWidget(spinProperties, parent);
    connect(editor, SIGNAL(si_valueChanged(QVariant)), SLOT(sl_commit()));
    return editor;
}

void DoubleSpinBoxDelegate::setEditorData(QWidget* editor,
                                          const QModelIndex& index) const {
    QVariant value = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto spinBox = static_cast<DoubleSpinBoxWidget*>(editor);
    spinBox->setValue(value);
}

void DoubleSpinBoxDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto spinBox = static_cast<DoubleSpinBoxWidget*>(editor);
    double value = spinBox->value().toDouble();
    model->setData(index, value, ConfigurationEditor::ItemValueRole);
}

QVariant DoubleSpinBoxDelegate::getDisplayValue(const QVariant& v) const {
    double doubleValue = v.toDouble();
    if (!valueFormatter.isNull()) {
        QString formatterValue = valueFormatter->format(doubleValue);
        if (!formatterValue.isEmpty()) {
            return formatterValue;
        }
    }
    QDoubleSpinBox editor;
    WorkflowUtils::setQObjectProperties(editor, spinProperties);
    editor.setValue(doubleValue);
    return editor.text();
}

void DoubleSpinBoxDelegate::getItems(QVariantMap& items) const {
    items = this->spinProperties;
}

void DoubleSpinBoxDelegate::sl_commit() {
    auto editor = static_cast<DoubleSpinBoxWidget*>(sender());
    CHECK(editor != nullptr, );
    emit commitData(editor);
}

///////////////////////////////////////
/// ComboBoxBaseDelegate
ComboBoxBaseDelegate::ComboBoxBaseDelegate(QObject* parent)
    : PropertyDelegate(parent) {
}

void ComboBoxBaseDelegate::setItemTextFormatter(const QSharedPointer<StringFormatter>& newFormatter) {
    itemTextFormatter = newFormatter;
}

ComboBoxBaseDelegate* ComboBoxBaseDelegate::initClonedDelegate(ComboBoxBaseDelegate* delegate) const {
    delegate->setItemTextFormatter(itemTextFormatter);
    delegate->setSortFlag(isSorted);
    return delegate;
}

void ComboBoxBaseDelegate::setSortFlag(bool flag) {
    isSorted = flag;
}

QString ComboBoxBaseDelegate::getFormattedItemText(const QString& itemKey) const {
    return itemTextFormatter.isNull() ? itemKey : itemTextFormatter->format(itemKey);
}

/********************************
 * ComboBoxDelegate
 ********************************/
ComboBoxDelegate::ComboBoxDelegate(const QVariantMap& items, QObject* parent)
    : ComboBoxBaseDelegate(parent) {
    foreach (QString key, items.keys()) {
        comboItems.append(qMakePair(key, items.value(key)));
    }
}

ComboBoxDelegate::ComboBoxDelegate(const QList<ComboItem>& items, QObject* parent)
    : ComboBoxBaseDelegate(parent),
      comboItems(items) {
}

PropertyDelegate* ComboBoxDelegate::clone() {
    return initClonedDelegate(new ComboBoxDelegate(comboItems, parent()));
}

PropertyWidget* ComboBoxDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return new ComboBoxWidget(comboItems, parent, itemTextFormatter, isSorted);
}

QWidget* ComboBoxDelegate::createEditor(QWidget* parent,
                                        const QStyleOptionViewItem& /* option */,
                                        const QModelIndex& /* index */) const {
    QList<ComboItem> l;
    QVariantMap m = getAvailableItems();
    if (m.isEmpty()) {
        l = comboItems;
    } else {
        foreach (QString key, m.keys()) {
            l.append(qMakePair(key, m.value(key)));
        }
    }
    auto editor = new ComboBoxWidget(l, parent, itemTextFormatter, isSorted);
    connect(editor, SIGNAL(valueChanged(const QString&)), SLOT(sl_commit()));
    connect(editor, SIGNAL(valueChanged(const QString&)), SIGNAL(si_valueChanged(const QString&)));

    return editor;
}

void ComboBoxDelegate::setEditorData(QWidget* editor,
                                     const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto box = static_cast<ComboBoxWidget*>(editor);
    box->setValue(val);
}

void ComboBoxDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto box = static_cast<ComboBoxWidget*>(editor);
    model->setData(index, box->value(), ConfigurationEditor::ItemValueRole);
}

QVariant ComboBoxDelegate::getDisplayValue(const QVariant& val) const {
    QVariantMap m;
    getItems(m);
    QString display = itemTextFormatter.isNull() ? m.key(val) : itemTextFormatter->format(m.key(val));
    emit si_valueChanged(display);
    return QVariant(display);
}

void ComboBoxDelegate::getItems(QVariantMap& items) const {
    items = getAvailableItems();
    if (items.isEmpty()) {
        foreach (ComboItem p, comboItems) {
            items.insert(p.first, p.second);
        }
    }
}

QVariantMap ComboBoxDelegate::getAvailableItems() const {
    DelegateTags* t = tags();
    if (t != nullptr) {
        if (t->get("AvailableValues") != QVariant()) {
            return t->get("AvailableValues").toMap();
        }
    }
    return QVariantMap();
}

void ComboBoxDelegate::sl_commit() {
    auto editor = static_cast<ComboBoxWidget*>(sender());

    if (editor) {
        emit commitData(editor);
    }
}

/********************************
 * ComboBoxWithUrlsDelegate
 ********************************/

PropertyWidget* ComboBoxWithUrlsDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return new ComboBoxWithUrlWidget(items, isPath, parent);
}

QWidget* ComboBoxWithUrlsDelegate::createEditor(QWidget* parent,
                                                const QStyleOptionViewItem& /* option */,
                                                const QModelIndex& /* index */) const {
    ComboBoxWithUrlWidget* editor = new ComboBoxWithUrlWidget(items, isPath, parent);
    connect(editor, SIGNAL(valueChanged(const QString&)), SLOT(sl_valueChanged(const QString&)));
    return editor;
}

void ComboBoxWithUrlsDelegate::sl_valueChanged(const QString& newVal) {
    emit si_valueChanged(newVal);
    auto editor = qobject_cast<QWidget*>(sender());
    SAFE_POINT(editor != nullptr, "Invalid editor", );
    emit commitData(editor);
}

void ComboBoxWithUrlsDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto box = static_cast<ComboBoxWithUrlWidget*>(editor);
    box->setValue(val);
}

void ComboBoxWithUrlsDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto box = static_cast<ComboBoxWithUrlWidget*>(editor);
    model->setData(index, box->value(), ConfigurationEditor::ItemValueRole);
}

QVariant ComboBoxWithUrlsDelegate::getDisplayValue(const QVariant& val) const {
    QString display = items.key(val);
    emit si_valueChanged(display);
    return QVariant(display);
}

/********************************
 * ComboBoxEditableDelegate
 ********************************/

PropertyWidget* ComboBoxEditableDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return new ComboBoxEditableWidget(items, parent);
}

QWidget* ComboBoxEditableDelegate::createEditor(QWidget* parent,
                                                const QStyleOptionViewItem& /* option */,
                                                const QModelIndex& /* index */) const {
    ComboBoxEditableWidget* editor = new ComboBoxEditableWidget(items, parent);
    connect(editor, SIGNAL(valueChanged(const QString&)), SLOT(sl_valueChanged(const QString&)));
    return editor;
}

void ComboBoxEditableDelegate::sl_valueChanged(const QString& newVal) {
    emit si_valueChanged(newVal);
    auto editor = qobject_cast<QWidget*>(sender());
    SAFE_POINT(editor != nullptr, "Invalid editor", );
    emit commitData(editor);
}

void ComboBoxEditableDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto box = static_cast<ComboBoxEditableWidget*>(editor);
    box->setValue(val);
}

void ComboBoxEditableDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto box = static_cast<ComboBoxEditableWidget*>(editor);
    model->setData(index, box->value(), ConfigurationEditor::ItemValueRole);
}

QVariant ComboBoxEditableDelegate::getDisplayValue(const QVariant& val) const {
    QString display = items.key(val);
    emit si_valueChanged(display);
    return QVariant(display);
}

/********************************
 * ComboBoxWithChecksDelegate
 ********************************/
ComboBoxWithChecksDelegate::ComboBoxWithChecksDelegate(const QVariantMap& _items, QObject* parent)
    : ComboBoxBaseDelegate(parent), items(_items) {
}

PropertyDelegate* ComboBoxWithChecksDelegate::clone() {
    return initClonedDelegate(new ComboBoxWithChecksDelegate(items, parent()));
}

PropertyWidget* ComboBoxWithChecksDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return new ComboBoxWithChecksWidget(items, parent, itemTextFormatter, isSorted);
}

QWidget* ComboBoxWithChecksDelegate::createEditor(QWidget* parent,
                                                  const QStyleOptionViewItem& /* option */,
                                                  const QModelIndex& /* index */) const {
    ComboBoxWithChecksWidget* editor = new ComboBoxWithChecksWidget(items, parent, itemTextFormatter, isSorted);
    connect(editor, SIGNAL(valueChanged(const QString&)), this, SIGNAL(si_valueChanged(const QString&)));
    connect(editor, SIGNAL(si_valueChanged(const QVariant&)), SLOT(sl_commit()));
    return editor;
}

void ComboBoxWithChecksDelegate::setEditorData(QWidget* editor,
                                               const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto box = static_cast<ComboBoxWithChecksWidget*>(editor);
    box->setValue(val);
}

void ComboBoxWithChecksDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto box = static_cast<ComboBoxWithChecksWidget*>(editor);
    model->setData(index, box->value(), ConfigurationEditor::ItemValueRole);
    setEditorData(editor, index);
}

void ComboBoxWithChecksDelegate::getItems(QVariantMap& items) const {
    items = this->items;
}

QVariant ComboBoxWithChecksDelegate::getDisplayValue(const QVariant& val) const {
    // ComboBoxWithChecksWidget serializes data as a list of comma separated values.
    QString listOfCommaSeparatedValues = val.toString();
    emit si_valueChanged(listOfCommaSeparatedValues);
    QStringList values = listOfCommaSeparatedValues.split(",");
    QStringList formattedValues;
    for (const QString& value : qAsConst(values)) {
        formattedValues << getFormattedItemText(value);
    }
    return formattedValues.join(",");
}

void ComboBoxWithChecksDelegate::sl_commit() {
    if (auto editor = static_cast<ComboBoxWithChecksWidget*>(sender())) {
        emit commitData(editor);
    }
}

/********************************
 * ComboBoxWithBoolsDelegate
 ********************************/

ComboBoxWithBoolsDelegate::ComboBoxWithBoolsDelegate(QObject* parent)
    : ComboBoxDelegate(boolMap(), parent) {
}

QVariantMap ComboBoxWithBoolsDelegate::boolMap() {
    QVariantMap map;
    map["False"] = false;
    map["True"] = true;
    return map;
}

/********************************
 * URLDelegate
 ********************************/
URLDelegate::URLDelegate(const QString& filter, const QString& type, const Options& _options, QObject* parent, const QString& format)
    : PropertyDelegate(parent),
      lastDirType(type),
      options(_options) {
    tags()->set(DelegateTags::FILTER, filter);
    tags()->set(DelegateTags::FORMAT, format);
}

URLDelegate::URLDelegate(const DelegateTags& _tags, const QString& type, const Options& _options, QObject* parent)
    : PropertyDelegate(parent),
      lastDirType(type),
      options(_options) {
    *tags() = _tags;
}

URLDelegate::URLDelegate(const QString& filter, const QString& type, bool multi, bool isPath, bool saveFile, QObject* parent, const QString& format, bool noFilesMode, bool doNotUseWorkflowOutputFolder)
    : PropertyDelegate(parent), lastDirType(type) {
    tags()->set(DelegateTags::FILTER, filter);
    tags()->set(DelegateTags::FORMAT, format);

    options |= multi ? AllowSelectSeveralFiles : None;
    options |= isPath ? AllowSelectOnlyExistingDir : None;
    options |= saveFile ? SelectFileToSave : None;
    options |= noFilesMode ? SelectParentDirInsteadSelectedFile : None;
    options |= doNotUseWorkflowOutputFolder ? DoNotUseWorkflowOutputFolder : None;
}

URLDelegate::URLDelegate(const DelegateTags& _tags, const QString& type, bool multi, bool isPath, bool saveFile, QObject* parent, bool noFilesMode, bool doNotUseWorkflowOutputFolder)
    : PropertyDelegate(parent),
      lastDirType(type) {
    *tags() = _tags;

    options |= multi ? AllowSelectSeveralFiles : None;
    options |= isPath ? AllowSelectOnlyExistingDir : None;
    options |= saveFile ? SelectFileToSave : None;
    options |= noFilesMode ? SelectParentDirInsteadSelectedFile : None;
    options |= doNotUseWorkflowOutputFolder ? DoNotUseWorkflowOutputFolder : None;
}

QVariant URLDelegate::getDisplayValue(const QVariant& v) const {
    return v.toString().isEmpty() ? QVariant(DelegateTags::getString(tags(), DelegateTags::PLACEHOLDER_TEXT)) : v;
}

URLWidget* URLDelegate::createWidget(QWidget* parent) const {
    URLWidget* result;
    if (options.testFlag(SelectParentDirInsteadSelectedFile)) {
        bool isPath = false;  // noFilesMode: choose a file but its dir will be committed
        result = new NoFileURLWidget(lastDirType,
                                     options.testFlag(AllowSelectSeveralFiles),
                                     isPath,
                                     options.testFlag(SelectFileToSave),
                                     tags(),
                                     parent);
    } else {
        result = new URLWidget(lastDirType,
                               options.testFlag(AllowSelectSeveralFiles),
                               options.testFlag(AllowSelectOnlyExistingDir),
                               options.testFlag(SelectFileToSave),
                               tags(),
                               parent);
    }
    if (!options.testFlag(DoNotUseWorkflowOutputFolder)) {
        result->setSchemaConfig(schemaConfig);
    }
    return result;
}

PropertyWidget* URLDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return createWidget(parent);
}

QWidget* URLDelegate::createEditor(QWidget* parent,
                                   const QStyleOptionViewItem& /* option */,
                                   const QModelIndex& /* index */) const {
    URLWidget* editor = createWidget(parent);
    connect(editor, &URLWidget::finished, this, &URLDelegate::sl_commit);
    return editor;
}

void URLDelegate::sl_commit() {
    auto editor = static_cast<URLWidget*>(sender());

    text = editor->value().toString();
    emit commitData(editor);
}

void URLDelegate::setEditorData(QWidget* editor,
                                const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto lineEdit = dynamic_cast<URLWidget*>(editor);
    SAFE_POINT(lineEdit != nullptr, "URLDelegate::setEditorData: lineEdit is null!", );
    lineEdit->setValue(val);
}

void URLDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto lineEdit = dynamic_cast<URLWidget*>(editor);
    SAFE_POINT(lineEdit != nullptr, "URLDelegate::setModelData: lineEdit is null!", );
    QString val = lineEdit->value().toString().replace('\\', '/').trimmed();
    QStringList urls = val.split(";", QString::SkipEmptyParts);
    val = urls.join(";");
    model->setData(index, val, ConfigurationEditor::ItemValueRole);
    if (options.testFlag(AllowSelectSeveralFiles)) {
        QVariantList vl;
        foreach (QString s, val.split(";")) {
            vl.append(s.trimmed());
        }
        model->setData(index, vl, ConfigurationEditor::ItemListValueRole);
    }
}

PropertyDelegate* URLDelegate::clone() {
    return new URLDelegate(*tags(), lastDirType, options, parent());
}

PropertyDelegate::Type URLDelegate::type() const {
    if (options.testFlag(AllowSelectOnlyExistingDir)) {
        return options.testFlag(SelectFileToSave) ? OUTPUT_DIR : INPUT_DIR;
    }
    return options.testFlag(SelectFileToSave) ? OUTPUT_FILE : INPUT_FILE;
}

/********************************
 * FileModeDelegate
 ********************************/
FileModeDelegate::FileModeDelegate(bool appendSupported, QObject* parent)
    : ComboBoxDelegate(QVariantMap(), parent) {
    comboItems.append(qMakePair(U2::WorkflowUtils::tr("Overwrite"), SaveDoc_Overwrite));
    comboItems.append(qMakePair(U2::WorkflowUtils::tr("Rename"), SaveDoc_Roll));
    if (appendSupported) {
        comboItems.append(qMakePair(U2::WorkflowUtils::tr("Append"), SaveDoc_Append));
    }
}

/********************************
 * SchemaRunModeDelegate
 ********************************/
SchemaRunModeDelegate::SchemaRunModeDelegate(QObject* parent)
    : ComboBoxDelegate(QVariantMap(), parent) {
    thisComputerOption = SchemaRunModeDelegate::tr("This computer");
    remoteComputerOption = SchemaRunModeDelegate::tr("Remote computer");

    comboItems.append(qMakePair(thisComputerOption, true));
    comboItems.append(qMakePair(remoteComputerOption, false));

    connect(this, SIGNAL(si_valueChanged(const QString&)), this, SLOT(sl_valueChanged(const QString&)));
}

void SchemaRunModeDelegate::sl_valueChanged(const QString& val) {
    emit si_showOpenFileButton(thisComputerOption == val);
}

/********************************
 * ScriptSelectionWidget
 ********************************/
const int NO_SCRIPT_ITEM_ID = 0;
const int USER_SCRIPT_ITEM_ID = 1;
const QPair<QString, int> NO_SCRIPT_ITEM_STR("no script", NO_SCRIPT_ITEM_ID);
const QPair<QString, int> USER_SCRIPT_ITEM_STR("user script", USER_SCRIPT_ITEM_ID);
const QString SCRIPT_PROPERTY = "combo_script_property";

ScriptSelectionWidget::ScriptSelectionWidget(QWidget* parent)
    : PropertyWidget(parent) {
    combobox = new QComboBox;
    combobox->addItem(NO_SCRIPT_ITEM_STR.first);
    combobox->addItem(USER_SCRIPT_ITEM_STR.first);
    connect(combobox, SIGNAL(currentIndexChanged(int)), SLOT(sl_comboCurrentIndexChanged(int)));
    addMainWidget(combobox);
}

void ScriptSelectionWidget::setValue(const QVariant& value) {
    AttributeScript attrScript = value.value<AttributeScript>();
    if (attrScript.isEmpty()) {
        combobox->setCurrentIndex(NO_SCRIPT_ITEM_STR.second);
    } else {
        combobox->setCurrentIndex(USER_SCRIPT_ITEM_STR.second);
    }
    combobox->setProperty(SCRIPT_PROPERTY.toLatin1().constData(), qVariantFromValue<AttributeScript>(attrScript));
}

QVariant ScriptSelectionWidget::value() {
    return combobox->itemData(USER_SCRIPT_ITEM_ID, ConfigurationEditor::ItemValueRole);
}

void ScriptSelectionWidget::sl_comboCurrentIndexChanged(int itemId) {
    switch (itemId) {
        case NO_SCRIPT_ITEM_ID: {
            combobox->setItemData(USER_SCRIPT_ITEM_ID, "", ConfigurationEditor::ItemValueRole);
            return;
        }
        case USER_SCRIPT_ITEM_ID: {
            GCOUNTER(cvar, "Script. Run Edit script of the element dialog for parameter");
            AttributeScript attrScript = combobox->property(SCRIPT_PROPERTY.toLatin1().constData()).value<AttributeScript>();
            QObjectScopedPointer<ScriptEditorDialog> dlg = new ScriptEditorDialog(QApplication::activeWindow(), AttributeScriptDelegate::createScriptHeader(attrScript));
            dlg->setScriptText(attrScript.getScriptText());

            int rc = dlg->exec();
            CHECK(!dlg.isNull(), );
            if (rc != QDialog::Accepted) {
                combobox->setItemData(USER_SCRIPT_ITEM_ID, qVariantFromValue<AttributeScript>(attrScript), ConfigurationEditor::ItemValueRole);
            } else {
                auto scriptText = dlg->getScriptText();
                if (!scriptText.isEmpty()) {
                    GCOUNTER(cvar1, "Script. Done Edit script of the element dialog for parameter with new script");
                }
                attrScript.setScriptText(scriptText);
                combobox->setItemData(USER_SCRIPT_ITEM_ID, qVariantFromValue<AttributeScript>(attrScript), ConfigurationEditor::ItemValueRole);
            }

            emit si_finished();
            return;
        }
        default: {
            FAIL("Unexpected item", );
        }
    }
}

/********************************
 * AttributeScriptDelegate
 ********************************/
AttributeScriptDelegate::AttributeScriptDelegate(QObject* parent)
    : PropertyDelegate(parent) {
}

AttributeScriptDelegate::~AttributeScriptDelegate() {
}

void AttributeScriptDelegate::sl_commit() {
    auto editor = static_cast<ScriptSelectionWidget*>(sender());
    emit commitData(editor);
}

QWidget* AttributeScriptDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const {
    ScriptSelectionWidget* editor = new ScriptSelectionWidget(parent);
    connect(editor, SIGNAL(si_finished()), SLOT(sl_commit()));
    return editor;
}

void AttributeScriptDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const {
    auto combo = qobject_cast<ScriptSelectionWidget*>(editor);
    assert(combo != nullptr);
    combo->setValue(index.model()->data(index, ConfigurationEditor::ItemValueRole));
}

void AttributeScriptDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto combo = qobject_cast<ScriptSelectionWidget*>(editor);
    assert(combo != nullptr);
    model->setData(index, combo->value(), ConfigurationEditor::ItemValueRole);
}

QVariant AttributeScriptDelegate::getDisplayValue(const QVariant& val) const {
    AttributeScript attrScript = val.value<AttributeScript>();
    QString ret = attrScript.isEmpty() ? NO_SCRIPT_ITEM_STR.first : USER_SCRIPT_ITEM_STR.first;
    return QVariant(ret);
}

QString AttributeScriptDelegate::createScriptHeader(const AttributeScript& attrScript) {
    QString header;
    foreach (const Descriptor& desc, attrScript.getScriptVars().keys()) {
        header += QString("var %1; // %2\n").arg(desc.getId()).arg(desc.getDisplayName());
    }
    return header;
}

/********************************
 * StringListDelegate
 ********************************/
void StingListEdit::sl_onExpand() {
    QObjectScopedPointer<QDialog> editor = new QDialog(this);
    editor->setWindowTitle(StringListDelegate::tr("Enter items"));

    QPushButton* accept = new QPushButton(StringListDelegate::tr("OK"), editor.data());
    connect(accept, SIGNAL(clicked()), editor.data(), SLOT(accept()));
    QPushButton* reject = new QPushButton(StringListDelegate::tr("Cancel"), editor.data());
    connect(reject, SIGNAL(clicked()), editor.data(), SLOT(reject()));

    QHBoxLayout* buttonsLayout = new QHBoxLayout(0);
    buttonsLayout->addStretch();
    buttonsLayout->addWidget(accept);
    buttonsLayout->addWidget(reject);

    QTextEdit* edit = new QTextEdit("", editor.data());

    foreach (const QString& item, text().split(";", QString::SkipEmptyParts)) {
        edit->append(item.trimmed());
    }

    QVBoxLayout* layout = new QVBoxLayout(editor.data());
    layout->addWidget(edit);
    layout->addLayout(buttonsLayout);

    editor->setLayout(layout);

    editor->exec();
    CHECK(!editor.isNull(), );

    if (editor->result() == QDialog::Accepted) {
        QString s = edit->toPlainText();
        s.replace("\n", "; ");
        setText(s);
        emit editingFinished();
    }
}

void StingListEdit::focusOutEvent(QFocusEvent*) {
    emit si_finished();
}

StingListWidget::StingListWidget(QWidget* parent)
    : PropertyWidget(parent) {
    edit = new StingListEdit(this);
    edit->setFrame(false);
    edit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    addMainWidget(edit);

    auto button = new QToolButton(this);
    button->setText("...");
    button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
    connect(button, SIGNAL(clicked()), edit, SLOT(sl_onExpand()));
    layout()->addWidget(button);

    connect(edit, SIGNAL(si_finished()), SIGNAL(finished()));
}

QVariant StingListWidget::value() {
    return edit->text();
}

void StingListWidget::setValue(const QVariant& value) {
    edit->setText(value.toString());
}

void StingListWidget::setRequired() {
    edit->setPlaceholderText(L10N::required());
}

PropertyWidget* StringListDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return new StingListWidget(parent);
}

QWidget* StringListDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const {
    auto widget = new StingListWidget(parent);
    connect(widget, SIGNAL(finished()), SLOT(sl_commit()));

    currentEditor = widget;
    return widget;
}

void StringListDelegate::sl_commit() {
    emit commitData(currentEditor);
}

void StringListDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto lineEdit = dynamic_cast<StingListWidget*>(editor);
    SAFE_POINT(lineEdit != nullptr, "StringListDelegate::setEditorData: lineEdit is null!", );
    lineEdit->setValue(val);
}

void StringListDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto lineEdit = dynamic_cast<StingListWidget*>(editor);
    SAFE_POINT(lineEdit != nullptr, "StringListDelegate::setModelData: lineEdit is null!", );

    QString val = lineEdit->value().toString();
    model->setData(index, val, ConfigurationEditor::ItemValueRole);

    QVariantList vl;
    foreach (const QString& s, val.split(";", QString::SkipEmptyParts)) {
        vl.append(s.trimmed());
    }

    model->setData(index, vl, ConfigurationEditor::ItemListValueRole);
}

/********************************
 * StringSelectorDelegate
 ********************************/
QWidget* StringSelectorDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem&, const QModelIndex&) const {
    auto editor = new QWidget(parent);
    valueEdit = new QLineEdit(editor);
    valueEdit->setObjectName("valueEdit");
    // valueEdit->setReadOnly(true);
    valueEdit->setFrame(false);
    valueEdit->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
    editor->setFocusProxy(valueEdit);
    auto toolButton = new QToolButton(editor);
    toolButton->setVisible(true);
    toolButton->setText("...");
    toolButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred));
    connect(toolButton, SIGNAL(clicked()), SLOT(sl_onClick()));

    auto layout = new QHBoxLayout(editor);
    layout->setSpacing(0);
    layout->setMargin(0);
    layout->addWidget(valueEdit);
    layout->addWidget(toolButton);

    currentEditor = editor;
    connect(valueEdit, SIGNAL(editingFinished()), SLOT(sl_commit()));

    return editor;
}

void StringSelectorDelegate::sl_commit() {
    emit commitData(currentEditor);
}

void StringSelectorDelegate::sl_onClick() {
    QObjectScopedPointer<QDialog> dlg = f->createSelectorDialog(initValue);

    const int dialogResult = dlg->exec();
    CHECK(!dlg.isNull(), );

    if (QDialog::Accepted == dialogResult) {
        valueEdit->setText(f->getSelectedString(dlg.data()));
        sl_commit();
    }
}

void StringSelectorDelegate::setEditorData(QWidget*, const QModelIndex& index) const {
    QString val = index.model()->data(index, ConfigurationEditor::ItemValueRole).toString();
    valueEdit->setText(val);
}

void StringSelectorDelegate::setModelData(QWidget*, QAbstractItemModel* model, const QModelIndex& index) const {
    QString val = valueEdit->text().trimmed();
    model->setData(index, val, ConfigurationEditor::ItemValueRole);
    if (multipleSelection) {
        QVariantList vl;
        foreach (QString s, val.split(",")) {
            vl.append(s.trimmed());
        }
        model->setData(index, vl, ConfigurationEditor::ItemListValueRole);
    }
}

/********************************
 * CharacterDelegate
 ********************************/
PropertyWidget* CharacterDelegate::createWizardWidget(U2OpStatus& /*os*/, QWidget* parent) const {
    return new IgnoreUpDownPropertyWidget(1, parent);
}

QWidget* CharacterDelegate::createEditor(QWidget* parent,
                                         const QStyleOptionViewItem& /* option */,
                                         const QModelIndex& /* index */) const {
    return new IgnoreUpDownPropertyWidget(1, parent);
}

void CharacterDelegate::setEditorData(QWidget* editor,
                                      const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto lineEdit = dynamic_cast<IgnoreUpDownPropertyWidget*>(editor);
    SAFE_POINT(lineEdit != nullptr, "CharacterDelegate::setEditorData: lineEdit is null", );
    lineEdit->setValue(val);
}

void CharacterDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto lineEdit = dynamic_cast<IgnoreUpDownPropertyWidget*>(editor);
    SAFE_POINT(lineEdit != nullptr, "CharacterDelegate::setModelData: lineEdit is null", );
    model->setData(index, lineEdit->value().toString(), ConfigurationEditor::ItemValueRole);
}

LineEditWithValidatorDelegate::LineEditWithValidatorDelegate(const QRegularExpression& _regExp, QObject* _parent)
    : PropertyDelegate(_parent),
      regExp(_regExp) {
}

QWidget* LineEditWithValidatorDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const {
    QScopedPointer<IgnoreUpDownPropertyWidget> editor(new IgnoreUpDownPropertyWidget(NO_LIMIT, parent));
    auto lineEdit = editor->findChild<QLineEdit*>("mainWidget");
    SAFE_POINT(lineEdit != nullptr, "Line edit is nullptr", nullptr);

    lineEdit->setValidator(new QRegularExpressionValidator(regExp, lineEdit));
    connect(editor.data(), SIGNAL(si_valueChanged(const QVariant&)), SLOT(sl_valueChanged()));
    return editor.take();
}

void LineEditWithValidatorDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const {
    QVariant val = index.model()->data(index, ConfigurationEditor::ItemValueRole);
    auto lineEdit = qobject_cast<IgnoreUpDownPropertyWidget*>(editor);
    lineEdit->setValue(val);
}

void LineEditWithValidatorDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const {
    auto lineEdit = qobject_cast<IgnoreUpDownPropertyWidget*>(editor);
    model->setData(index, lineEdit->value().toString(), ConfigurationEditor::ItemValueRole);
}

LineEditWithValidatorDelegate* LineEditWithValidatorDelegate::clone() {
    return new LineEditWithValidatorDelegate(regExp, parent());
}

void LineEditWithValidatorDelegate::sl_valueChanged() {
    auto editor = qobject_cast<IgnoreUpDownPropertyWidget*>(sender());
    CHECK(editor != nullptr, );

    auto lineEdit = editor->findChild<QLineEdit*>("mainWidget");
    SAFE_POINT(lineEdit != nullptr, "Line edit is nullptr", );

    const int cursorPos = lineEdit->cursorPosition();

    emit commitData(editor);

    lineEdit->setCursorPosition(cursorPos);
}

}  // namespace U2
