/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/

#include "codestyleselectorwidget.h"
#include "ui_codestyleselectorwidget.h"
#include "icodestylepreferences.h"
#include "icodestylepreferencesfactory.h"
#include "codestylepool.h"
#include "tabsettings.h"

#include <utils/fileutils.h>

#include <QPushButton>
#include <QDialogButtonBox>
#include <QDialog>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#include <QFileDialog>

#include <QDebug>

using namespace TextEditor;
using namespace Utils;

namespace TextEditor {
namespace Internal {

class CodeStyleDialog : public QDialog
{
    Q_OBJECT
public:
    CodeStyleDialog(ICodeStylePreferencesFactory *factory,
                    ICodeStylePreferences *codeStyle, QWidget *parent = nullptr);
    ~CodeStyleDialog() override;
    ICodeStylePreferences *codeStyle() const;
private:
    void slotCopyClicked();
    void slotDisplayNameChanged();

    ICodeStylePreferences *m_codeStyle;
    QLineEdit *m_lineEdit;
    QDialogButtonBox *m_buttons;
    QLabel *m_warningLabel = nullptr;
    QPushButton *m_copyButton = nullptr;
    QString m_originalDisplayName;
};

CodeStyleDialog::CodeStyleDialog(ICodeStylePreferencesFactory *factory,
                ICodeStylePreferences *codeStyle, QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle(tr("Edit Code Style"));
    auto layout = new QVBoxLayout(this);
    QLabel *label = new QLabel(tr("Code style name:"));
    m_lineEdit = new QLineEdit(codeStyle->displayName(), this);
    auto nameLayout = new QHBoxLayout;
    nameLayout->addWidget(label);
    nameLayout->addWidget(m_lineEdit);
    layout->addLayout(nameLayout);

    if (codeStyle->isReadOnly()) {
        auto warningLayout = new QHBoxLayout;
        m_warningLabel = new QLabel(
                    tr("You cannot save changes to a built-in code style. "
                       "Copy it first to create your own version."), this);
        QFont font = m_warningLabel->font();
        font.setItalic(true);
        m_warningLabel->setFont(font);
        m_warningLabel->setWordWrap(true);
        m_copyButton = new QPushButton(tr("Copy Built-in Code Style"), this);
        m_copyButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        connect(m_copyButton, &QAbstractButton::clicked, this, &CodeStyleDialog::slotCopyClicked);
        warningLayout->addWidget(m_warningLabel);
        warningLayout->addWidget(m_copyButton);
        layout->addLayout(warningLayout);
    }

    m_originalDisplayName = codeStyle->displayName();
    m_codeStyle = factory->createCodeStyle();
    m_codeStyle->setTabSettings(codeStyle->tabSettings());
    m_codeStyle->setValue(codeStyle->value());
    m_codeStyle->setId(codeStyle->id());
    m_codeStyle->setDisplayName(m_originalDisplayName);
    QWidget *editor = factory->createEditor(m_codeStyle, this);

    m_buttons = new QDialogButtonBox(
                QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
    if (codeStyle->isReadOnly()) {
        QPushButton *okButton = m_buttons->button(QDialogButtonBox::Ok);
        okButton->setEnabled(false);
    }

    if (editor)
        layout->addWidget(editor);
    layout->addWidget(m_buttons);
    resize(850, 600);

    connect(m_lineEdit, &QLineEdit::textChanged, this, &CodeStyleDialog::slotDisplayNameChanged);
    connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
    connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}

ICodeStylePreferences *CodeStyleDialog::codeStyle() const
{
    return m_codeStyle;
}

void CodeStyleDialog::slotCopyClicked()
{
    if (m_warningLabel)
        m_warningLabel->hide();
    if (m_copyButton)
        m_copyButton->hide();
    QPushButton *okButton = m_buttons->button(QDialogButtonBox::Ok);
    okButton->setEnabled(true);
    if (m_lineEdit->text() == m_originalDisplayName)
        m_lineEdit->setText(tr("%1 (Copy)").arg(m_lineEdit->text()));
    m_lineEdit->selectAll();
}

void CodeStyleDialog::slotDisplayNameChanged()
{
    m_codeStyle->setDisplayName(m_lineEdit->text());
}

CodeStyleDialog::~CodeStyleDialog()
{
    delete m_codeStyle;
}

} // Internal

CodeStyleSelectorWidget::CodeStyleSelectorWidget(ICodeStylePreferencesFactory *factory, QWidget *parent) :
    QWidget(parent),
    m_factory(factory),
    m_ui(new Internal::Ui::CodeStyleSelectorWidget)
{
    m_ui->setupUi(this);
    m_ui->importButton->setEnabled(false);
    m_ui->exportButton->setEnabled(false);

    connect(m_ui->delegateComboBox, QOverload<int>::of(&QComboBox::activated),
            this, &CodeStyleSelectorWidget::slotComboBoxActivated);
    connect(m_ui->copyButton, &QAbstractButton::clicked,
            this, &CodeStyleSelectorWidget::slotCopyClicked);
    connect(m_ui->editButton, &QAbstractButton::clicked,
            this, &CodeStyleSelectorWidget::slotEditClicked);
    connect(m_ui->removeButton, &QAbstractButton::clicked,
            this, &CodeStyleSelectorWidget::slotRemoveClicked);
    connect(m_ui->importButton, &QAbstractButton::clicked,
            this, &CodeStyleSelectorWidget::slotImportClicked);
    connect(m_ui->exportButton, &QAbstractButton::clicked,
            this, &CodeStyleSelectorWidget::slotExportClicked);
}

CodeStyleSelectorWidget::~CodeStyleSelectorWidget()
{
    delete m_ui;
}

void CodeStyleSelectorWidget::setCodeStyle(ICodeStylePreferences *codeStyle)
{
    if (m_codeStyle == codeStyle)
        return; // nothing changes

    // cleanup old
    if (m_codeStyle) {
        CodeStylePool *codeStylePool = m_codeStyle->delegatingPool();
        if (codeStylePool) {
            disconnect(codeStylePool, &CodeStylePool::codeStyleAdded,
                    this, &CodeStyleSelectorWidget::slotCodeStyleAdded);
            disconnect(codeStylePool, &CodeStylePool::codeStyleRemoved,
                    this, &CodeStyleSelectorWidget::slotCodeStyleRemoved);
        }
        disconnect(m_codeStyle, &ICodeStylePreferences::currentDelegateChanged,
                this, &CodeStyleSelectorWidget::slotCurrentDelegateChanged);

        m_ui->exportButton->setEnabled(false);
        m_ui->importButton->setEnabled(false);
        m_ui->delegateComboBox->clear();
    }
    m_codeStyle = codeStyle;
    // fillup new
    if (m_codeStyle) {
        QList<ICodeStylePreferences *> delegates;
        CodeStylePool *codeStylePool = m_codeStyle->delegatingPool();
        if (codeStylePool) {
            delegates = codeStylePool->codeStyles();

            connect(codeStylePool, &CodeStylePool::codeStyleAdded,
                    this, &CodeStyleSelectorWidget::slotCodeStyleAdded);
            connect(codeStylePool, &CodeStylePool::codeStyleRemoved,
                    this, &CodeStyleSelectorWidget::slotCodeStyleRemoved);
            m_ui->exportButton->setEnabled(true);
            m_ui->importButton->setEnabled(true);
        }

        for (int i = 0; i < delegates.count(); i++)
            slotCodeStyleAdded(delegates.at(i));

        slotCurrentDelegateChanged(m_codeStyle->currentDelegate());

        connect(m_codeStyle, &ICodeStylePreferences::currentDelegateChanged,
                this, &CodeStyleSelectorWidget::slotCurrentDelegateChanged);
    }
}

void CodeStyleSelectorWidget::slotComboBoxActivated(int index)
{
    if (m_ignoreGuiSignals)
        return;

    if (index < 0 || index >= m_ui->delegateComboBox->count())
        return;
    auto delegate = m_ui->delegateComboBox->itemData(index).value<ICodeStylePreferences *>();

    QSignalBlocker blocker(this);
    m_codeStyle->setCurrentDelegate(delegate);
}

void CodeStyleSelectorWidget::slotCurrentDelegateChanged(ICodeStylePreferences *delegate)
{
    m_ignoreGuiSignals = true;
    m_ui->delegateComboBox->setCurrentIndex(m_ui->delegateComboBox->findData(QVariant::fromValue(delegate)));
    m_ui->delegateComboBox->setToolTip(m_ui->delegateComboBox->currentText());
    m_ignoreGuiSignals = false;

    const bool removeEnabled = delegate && !delegate->isReadOnly() && !delegate->currentDelegate();
    m_ui->removeButton->setEnabled(removeEnabled);
}

void CodeStyleSelectorWidget::slotCopyClicked()
{
    if (!m_codeStyle)
        return;

    CodeStylePool *codeStylePool = m_codeStyle->delegatingPool();
    ICodeStylePreferences *currentPreferences = m_codeStyle->currentPreferences();
    bool ok = false;
    const QString newName = QInputDialog::getText(this,
                                                  tr("Copy Code Style"),
                                                  tr("Code style name:"),
                                                  QLineEdit::Normal,
                                                  tr("%1 (Copy)").arg(currentPreferences->displayName()),
                                                  &ok);
    if (!ok || newName.trimmed().isEmpty())
        return;
    ICodeStylePreferences *copy = codeStylePool->cloneCodeStyle(currentPreferences);
    if (copy) {
        copy->setDisplayName(newName);
        m_codeStyle->setCurrentDelegate(copy);
    }
}

void CodeStyleSelectorWidget::slotEditClicked()
{
    if (!m_codeStyle)
        return;

    ICodeStylePreferences *codeStyle = m_codeStyle->currentPreferences();
    // check if it's read-only

    Internal::CodeStyleDialog dialog(m_factory, codeStyle, this);
    if (dialog.exec() == QDialog::Accepted) {
        ICodeStylePreferences *dialogCodeStyle = dialog.codeStyle();
        if (codeStyle->isReadOnly()) {
            CodeStylePool *codeStylePool = m_codeStyle->delegatingPool();
            codeStyle = codeStylePool->cloneCodeStyle(dialogCodeStyle);
            if (codeStyle)
                m_codeStyle->setCurrentDelegate(codeStyle);
            return;
        }
        codeStyle->setTabSettings(dialogCodeStyle->tabSettings());
        codeStyle->setValue(dialogCodeStyle->value());
        codeStyle->setDisplayName(dialogCodeStyle->displayName());
    }
}

void CodeStyleSelectorWidget::slotRemoveClicked()
{
    if (!m_codeStyle)
        return;

    CodeStylePool *codeStylePool = m_codeStyle->delegatingPool();
    ICodeStylePreferences *currentPreferences = m_codeStyle->currentPreferences();

    QMessageBox messageBox(QMessageBox::Warning,
                           tr("Delete Code Style"),
                           tr("Are you sure you want to delete this code style permanently?"),
                           QMessageBox::Discard | QMessageBox::Cancel,
                           this);

    // Change the text and role of the discard button
    auto deleteButton = static_cast<QPushButton*>(messageBox.button(QMessageBox::Discard));
    deleteButton->setText(tr("Delete"));
    messageBox.addButton(deleteButton, QMessageBox::AcceptRole);
    messageBox.setDefaultButton(deleteButton);

    connect(deleteButton, &QAbstractButton::clicked, &messageBox, &QDialog::accept);
    if (messageBox.exec() == QDialog::Accepted)
        codeStylePool->removeCodeStyle(currentPreferences);
}

void CodeStyleSelectorWidget::slotImportClicked()
{
    const FilePath fileName =
            FileUtils::getOpenFilePath(this, tr("Import Code Style"), {},
                                       tr("Code styles (*.xml);;All files (*)"));
    if (!fileName.isEmpty()) {
        CodeStylePool *codeStylePool = m_codeStyle->delegatingPool();
        ICodeStylePreferences *importedStyle = codeStylePool->importCodeStyle(fileName);
        if (importedStyle)
            m_codeStyle->setCurrentDelegate(importedStyle);
        else
            QMessageBox::warning(this, tr("Import Code Style"),
                                 tr("Cannot import code style from %1"), fileName.toUserOutput());
    }
}

void CodeStyleSelectorWidget::slotExportClicked()
{
    ICodeStylePreferences *currentPreferences = m_codeStyle->currentPreferences();
    const FilePath filePath = FileUtils::getSaveFilePath(this, tr("Export Code Style"),
                             FilePath::fromString(QString::fromUtf8(currentPreferences->id() + ".xml")),
                             tr("Code styles (*.xml);;All files (*)"));
    if (!filePath.isEmpty()) {
        CodeStylePool *codeStylePool = m_codeStyle->delegatingPool();
        codeStylePool->exportCodeStyle(filePath, currentPreferences);
    }
}

void CodeStyleSelectorWidget::slotCodeStyleAdded(ICodeStylePreferences *codeStylePreferences)
{
    if (codeStylePreferences == m_codeStyle
            || codeStylePreferences->id() == m_codeStyle->id())
        return;

    const QVariant data = QVariant::fromValue(codeStylePreferences);
    const QString name = displayName(codeStylePreferences);
    m_ui->delegateComboBox->addItem(name, data);
    m_ui->delegateComboBox->setItemData(m_ui->delegateComboBox->count() - 1, name, Qt::ToolTipRole);
    connect(codeStylePreferences, &ICodeStylePreferences::displayNameChanged,
            this, &CodeStyleSelectorWidget::slotUpdateName);
    if (codeStylePreferences->delegatingPool()) {
        connect(codeStylePreferences, &ICodeStylePreferences::currentPreferencesChanged,
                this, &CodeStyleSelectorWidget::slotUpdateName);
    }
}

void CodeStyleSelectorWidget::slotCodeStyleRemoved(ICodeStylePreferences *codeStylePreferences)
{
    m_ignoreGuiSignals = true;
    m_ui->delegateComboBox->removeItem(m_ui->delegateComboBox->findData(QVariant::fromValue(codeStylePreferences)));
    disconnect(codeStylePreferences, &ICodeStylePreferences::displayNameChanged,
               this, &CodeStyleSelectorWidget::slotUpdateName);
    if (codeStylePreferences->delegatingPool()) {
        disconnect(codeStylePreferences, &ICodeStylePreferences::currentPreferencesChanged,
                   this, &CodeStyleSelectorWidget::slotUpdateName);
    }
    m_ignoreGuiSignals = false;
}

void CodeStyleSelectorWidget::slotUpdateName()
{
    auto changedCodeStyle = qobject_cast<ICodeStylePreferences *>(sender());
    if (!changedCodeStyle)
        return;

    updateName(changedCodeStyle);

    QList<ICodeStylePreferences *> codeStyles = m_codeStyle->delegatingPool()->codeStyles();
    for (int i = 0; i < codeStyles.count(); i++) {
        ICodeStylePreferences *codeStyle = codeStyles.at(i);
        if (codeStyle->currentDelegate() == changedCodeStyle)
            updateName(codeStyle);
    }

    m_ui->delegateComboBox->setToolTip(m_ui->delegateComboBox->currentText());
}

void CodeStyleSelectorWidget::updateName(ICodeStylePreferences *codeStyle)
{
    const int idx = m_ui->delegateComboBox->findData(QVariant::fromValue(codeStyle));
    if (idx < 0)
        return;

    const QString name = displayName(codeStyle);
    m_ui->delegateComboBox->setItemText(idx, name);
    m_ui->delegateComboBox->setItemData(idx, name, Qt::ToolTipRole);
}

QString CodeStyleSelectorWidget::displayName(ICodeStylePreferences *codeStyle) const
{
    QString name = codeStyle->displayName();
    if (codeStyle->currentDelegate())
        name = tr("%1 [proxy: %2]").arg(name).arg(codeStyle->currentDelegate()->displayName());
    if (codeStyle->isReadOnly())
        name = tr("%1 [built-in]").arg(name);
    return name;
}

} // TextEditor

#include "codestyleselectorwidget.moc"
