// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

#include "browser.h"
#include "qsqlconnectiondialog.h"
#include <ui_browserwidget.h>

#include <QAction>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QSqlDriver>
#include <QSqlError>
#include <QSqlField>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QTextEdit>
#include <QTimer>

Browser::Browser(QWidget *parent)
    : QWidget(parent)
    , m_ui(new Ui::Browser)
{
    m_ui->setupUi(this);

    m_ui->table->addAction(m_ui->insertRowAction);
    m_ui->table->addAction(m_ui->deleteRowAction);
    m_ui->table->addAction(m_ui->fieldStrategyAction);
    m_ui->table->addAction(m_ui->rowStrategyAction);
    m_ui->table->addAction(m_ui->manualStrategyAction);
    m_ui->table->addAction(m_ui->submitAction);
    m_ui->table->addAction(m_ui->revertAction);
    m_ui->table->addAction(m_ui->selectAction);

    connect(m_ui->insertRowAction, &QAction::triggered, this, &Browser::insertRow);
    connect(m_ui->deleteRowAction, &QAction::triggered, this, &Browser::deleteRow);
    connect(m_ui->fieldStrategyAction, &QAction::triggered, this, &Browser::onFieldStrategyAction);
    connect(m_ui->rowStrategyAction, &QAction::triggered, this, &Browser::onRowStrategyAction);
    connect(m_ui->sqlEdit, &QTextEdit::textChanged, this, &Browser::updateActions);

    connect(m_ui->connectionWidget, &ConnectionWidget::tableActivated,
            this, &Browser::showTable);
    connect(m_ui->connectionWidget, &ConnectionWidget::metaDataRequested,
            this, &Browser::showMetaData);

    connect(m_ui->submitButton, &QPushButton::clicked,
            this, &Browser::onSubmitButton);
    connect(m_ui->clearButton, &QPushButton::clicked,
            this, &Browser::onClearButton);

    if (QSqlDatabase::drivers().isEmpty())
        QMessageBox::information(this, tr("No database drivers found"),
                                 tr("This demo requires at least one Qt database driver. "
                                    "Please check the documentation how to build the "
                                    "Qt SQL plugins."));

    QTimer::singleShot(0, this, [this]() {
        updateActions();
        emit statusMessage(tr("Ready."));
    });
}

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

void Browser::exec()
{
    QSqlQueryModel *model = new QSqlQueryModel(m_ui->table);
    model->setQuery(QSqlQuery(m_ui->sqlEdit->toPlainText(), m_ui->connectionWidget->currentDatabase()));
    m_ui->table->setModel(model);

    if (model->lastError().type() != QSqlError::NoError)
        emit statusMessage(model->lastError().text());
    else if (model->query().isSelect())
        emit statusMessage(tr("Query OK."));
    else
        emit statusMessage(tr("Query OK, number of affected rows: %1").arg(
                           model->query().numRowsAffected()));

    updateActions();
}

QSqlError Browser::addConnection(const QString &driver, const QString &dbName, const QString &host,
                                 const QString &user, const QString &passwd, int port)
{
    static int cCount = 0;

    QSqlError err;
    QSqlDatabase db = QSqlDatabase::addDatabase(driver, QString("Browser%1").arg(++cCount));
    db.setDatabaseName(dbName);
    db.setHostName(host);
    db.setPort(port);
    if (!db.open(user, passwd)) {
        err = db.lastError();
        db = QSqlDatabase();
        QSqlDatabase::removeDatabase(QString("Browser%1").arg(cCount));
    }
    m_ui->connectionWidget->refresh();

    return err;
}

void Browser::openNewConnectionDialog()
{
    QSqlConnectionDialog dialog(this);
    if (dialog.exec() != QDialog::Accepted)
        return;

    if (dialog.useInMemoryDatabase()) {
        QSqlDatabase::database("in_mem_db", false).close();
        QSqlDatabase::removeDatabase("in_mem_db");
        QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "in_mem_db");
        db.setDatabaseName(":memory:");
        if (!db.open()) {
            QMessageBox::warning(this, tr("Unable to open database"),
                                 tr("An error occurred while "
                                    "opening the connection: %1") .arg(db.lastError().text()));
            return;
        }
        db.transaction();
        QSqlQuery q(db);
        q.exec("drop table Movies");
        q.exec("drop table Names");
        q.exec("create table Movies (id integer primary key, Title varchar, Director varchar, Rating number)");
        q.exec("insert into Movies values (0, 'Metropolis', 'Fritz Lang', '8.4')");
        q.exec("insert into Movies values (1, 'Nosferatu, eine Symphonie des Grauens', 'F.W. Murnau', '8.1')");
        q.exec("insert into Movies values (2, 'Bis ans Ende der Welt', 'Wim Wenders', '6.5')");
        q.exec("insert into Movies values (3, 'Hardware', 'Richard Stanley', '5.2')");
        q.exec("insert into Movies values (4, 'Mitchell', 'Andrew V. McLaglen', '2.1')");
        q.exec("create table Names (id integer primary key, FirstName varchar, LastName varchar, City varchar)");
        q.exec("insert into Names values (0, 'Sala', 'Palmer', 'Morristown')");
        q.exec("insert into Names values (1, 'Christopher', 'Walker', 'Morristown')");
        q.exec("insert into Names values (2, 'Donald', 'Duck', 'Andeby')");
        q.exec("insert into Names values (3, 'Buck', 'Rogers', 'Paris')");
        q.exec("insert into Names values (4, 'Sherlock', 'Holmes', 'London')");
        db.commit();
        m_ui->connectionWidget->refresh();
    } else {
        QSqlError err = addConnection(dialog.driverName(), dialog.databaseName(), dialog.hostName(),
                                      dialog.userName(), dialog.password(), dialog.port());
        if (err.type() != QSqlError::NoError)
            QMessageBox::warning(this, tr("Unable to open database"),
                                 tr("An error occurred while "
                                    "opening the connection: %1").arg(err.text()));
    }
}

void Browser::showTable(const QString &t)
{
    QSqlTableModel *model = new CustomModel(m_ui->table, m_ui->connectionWidget->currentDatabase());
    model->setEditStrategy(QSqlTableModel::OnRowChange);
    model->setTable(m_ui->connectionWidget->currentDatabase().driver()->escapeIdentifier(t, QSqlDriver::TableName));
    model->select();
    if (model->lastError().type() != QSqlError::NoError)
        emit statusMessage(model->lastError().text());

    m_ui->table->setModel(model);
    m_ui->table->setEditTriggers(QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed);
    connect(m_ui->table->selectionModel(), &QItemSelectionModel::currentRowChanged,
            this, &Browser::updateActions);

    connect(m_ui->submitAction, &QAction::triggered, model, &QSqlTableModel::submitAll);
    connect(m_ui->revertAction, &QAction::triggered, model, &QSqlTableModel::revertAll);
    connect(m_ui->selectAction, &QAction::triggered, model, &QSqlTableModel::select);

    updateActions();
}

void Browser::showMetaData(const QString &t)
{
    QSqlRecord rec = m_ui->connectionWidget->currentDatabase().record(t);
    QStandardItemModel *model = new QStandardItemModel(m_ui->table);

    model->insertRows(0, rec.count());
    model->insertColumns(0, 7);

    model->setHeaderData(0, Qt::Horizontal, "Fieldname");
    model->setHeaderData(1, Qt::Horizontal, "Type");
    model->setHeaderData(2, Qt::Horizontal, "Length");
    model->setHeaderData(3, Qt::Horizontal, "Precision");
    model->setHeaderData(4, Qt::Horizontal, "Required");
    model->setHeaderData(5, Qt::Horizontal, "AutoValue");
    model->setHeaderData(6, Qt::Horizontal, "DefaultValue");

    for (int i = 0; i < rec.count(); ++i) {
        QSqlField fld = rec.field(i);
        model->setData(model->index(i, 0), fld.name());
        model->setData(model->index(i, 1), fld.typeID() == -1
                ? QString(fld.metaType().name())
                : QString("%1 (%2)").arg(fld.metaType().name()).arg(fld.typeID()));
        model->setData(model->index(i, 2), fld.length());
        model->setData(model->index(i, 3), fld.precision());
        model->setData(model->index(i, 4), fld.requiredStatus() == -1 ? QVariant("?")
                : QVariant(bool(fld.requiredStatus())));
        model->setData(model->index(i, 5), fld.isAutoValue());
        model->setData(model->index(i, 6), fld.defaultValue());
    }

    m_ui->table->setModel(model);
    m_ui->table->setEditTriggers(QAbstractItemView::NoEditTriggers);

    updateActions();
}

void Browser::insertRow()
{
    QSqlTableModel *model = qobject_cast<QSqlTableModel *>(m_ui->table->model());
    if (!model)
        return;

    QModelIndex insertIndex = m_ui->table->currentIndex();
    int row = insertIndex.row() == -1 ? 0 : insertIndex.row();
    model->insertRow(row);
    insertIndex = model->index(row, 0);
    m_ui->table->setCurrentIndex(insertIndex);
    m_ui->table->edit(insertIndex);
}

void Browser::deleteRow()
{
    QSqlTableModel *model = qobject_cast<QSqlTableModel *>(m_ui->table->model());
    if (!model)
        return;

    const QModelIndexList currentSelection = m_ui->table->selectionModel()->selectedIndexes();
    for (const auto &idx : currentSelection) {
        if (idx.column() != 0)
            continue;
        model->removeRow(idx.row());
    }

    updateActions();
}

void Browser::updateActions()
{
    QSqlTableModel *tm = qobject_cast<QSqlTableModel *>(m_ui->table->model());
    bool enableIns = tm != nullptr;
    bool enableDel = enableIns && m_ui->table->currentIndex().isValid();

    m_ui->insertRowAction->setEnabled(enableIns);
    m_ui->deleteRowAction->setEnabled(enableDel);

    m_ui->submitAction->setEnabled(tm);
    m_ui->revertAction->setEnabled(tm);
    m_ui->selectAction->setEnabled(tm);

    const bool isEmpty = m_ui->sqlEdit->toPlainText().isEmpty();
    m_ui->submitButton->setEnabled(m_ui->connectionWidget->currentDatabase().isOpen() && !isEmpty);
    m_ui->clearButton->setEnabled(!isEmpty);

    if (tm) {
        QSqlTableModel::EditStrategy es = tm->editStrategy();
        m_ui->fieldStrategyAction->setChecked(es == QSqlTableModel::OnFieldChange);
        m_ui->rowStrategyAction->setChecked(es == QSqlTableModel::OnRowChange);
        m_ui->manualStrategyAction->setChecked(es == QSqlTableModel::OnManualSubmit);
    } else {
        m_ui->fieldStrategyAction->setEnabled(false);
        m_ui->rowStrategyAction->setEnabled(false);
        m_ui->manualStrategyAction->setEnabled(false);
    }
}

void Browser::about()
{
    QMessageBox::about(this, tr("About"),
                       tr("The SQL Browser demonstration shows how a data browser "
                          "can be used to visualize the results of SQL "
                          "statements on a live database"));
}

void Browser::onFieldStrategyAction()
{
    QSqlTableModel *tm = qobject_cast<QSqlTableModel *>(m_ui->table->model());
    if (tm)
        tm->setEditStrategy(QSqlTableModel::OnFieldChange);
}

void Browser::onRowStrategyAction()
{
    QSqlTableModel *tm = qobject_cast<QSqlTableModel *>(m_ui->table->model());
    if (tm)
        tm->setEditStrategy(QSqlTableModel::OnRowChange);
}

void Browser::onManualStrategyAction()
{
    QSqlTableModel *tm = qobject_cast<QSqlTableModel *>(m_ui->table->model());
    if (tm)
        tm->setEditStrategy(QSqlTableModel::OnManualSubmit);
}

void Browser::onSubmitButton()
{
    exec();
    m_ui->sqlEdit->setFocus();
}

void Browser::onClearButton()
{
    m_ui->sqlEdit->clear();
    m_ui->sqlEdit->setFocus();
}
