#include "settings_widget.hh"
#include "ui_settings_widget.h"
#include <QAbstractButton>
#include <QDebug>
#include <QFile>
#include <QMessageBox>
#include <QXmlStreamWriter>
#include <QXmlStreamReader>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlError>

namespace {

namespace XmlElement {
    constexpr char settings[] = "settings";
    constexpr char server[] = "server";
    constexpr char database[] = "database";
}

namespace ServerElement {
    constexpr char name[] = "name";
    constexpr char logger[] = "logger";
    constexpr char systray[] = "systray";
}

namespace DatabaseElement {
    constexpr char logging[] = "logging";
    constexpr char hostname[] = "hostname";
    constexpr char database[] = "database";
    constexpr char username[] = "username";
    constexpr char password[] = "password";
}

void serializeServerSettings(
    QXmlStreamWriter& xml, const ServerSettings& server)
{
    xml.writeStartElement(XmlElement::server);
    xml.writeTextElement(ServerElement::name, server.name);

    xml.writeTextElement(
        ServerElement::systray,
        server.systray ? "true" : "false");

    xml.writeTextElement(
        ServerElement::logger,
        server.logger ? "true" : "false");

    xml.writeEndElement();
}

void serializeDatabaseSettings(
    QXmlStreamWriter& xml, const DatabaseSettings& database)
{
    xml.writeStartElement(XmlElement::database);

    xml.writeTextElement(
        DatabaseElement::logging,
        database.logging ? "true" : "false");

    xml.writeTextElement(DatabaseElement::hostname, database.hostname);
    xml.writeTextElement(DatabaseElement::database, database.database);
    xml.writeTextElement(DatabaseElement::username, database.username);
    xml.writeTextElement(DatabaseElement::password, database.password);

    xml.writeEndElement();
}

QString serializeSettings(
    const ServerSettings& server, DatabaseSettings& database)
{
    QString xmlString;
    QXmlStreamWriter xml(&xmlString);

    xml.setAutoFormatting(true);

    xml.writeStartDocument();
    xml.writeStartElement(XmlElement::settings);

    serializeServerSettings(xml, server);
    serializeDatabaseSettings(xml, database);

    xml.writeEndElement();
    xml.writeEndDocument();

    return xmlString;
}

bool parseBool(const QString& str)
{
    return str == "true" ? true : false;
}

ServerSettings parseServerSettings(QXmlStreamReader& xml)
{
    ServerSettings server;

    while (xml.readNextStartElement()) {
        if (xml.name() == ServerElement::name)
            server.name = xml.readElementText();
        else if (xml.name() == ServerElement::logger)
            server.logger = parseBool(xml.readElementText());
        else if (xml.name() == ServerElement::systray)
            server.systray = parseBool(xml.readElementText());
        else
            xml.skipCurrentElement();
    }

    return server;
}

DatabaseSettings parseDatabaseSettings(QXmlStreamReader& xml)
{
    DatabaseSettings database;

    while (xml.readNextStartElement()) {
        if (xml.name() == DatabaseElement::logging)
            database.logging = parseBool(xml.readElementText());
        else if (xml.name() == DatabaseElement::hostname)
            database.hostname = xml.readElementText();
        else if (xml.name() == DatabaseElement::database)
            database.database = xml.readElementText();
        else if (xml.name() == DatabaseElement::username)
            database.username = xml.readElementText();
        else if (xml.name() == DatabaseElement::password)
            database.password = xml.readElementText();
        else
            xml.skipCurrentElement();
    }

    return database;
}

void parseSettings(
    QXmlStreamReader& xml, ServerSettings& server,
    DatabaseSettings& database)
{
    xml.readNextStartElement();

    if (xml.name() != XmlElement::settings)
        return;

    while (xml.readNextStartElement()) {
        if (xml.name() == XmlElement::server)
            server = parseServerSettings(xml);
        else if (xml.name() == XmlElement::database)
            database = parseDatabaseSettings(xml);
        else
            xml.skipCurrentElement();
    }
}

} //namespace

ServerSettings::ServerSettings() :
    name("AutoTrader"), logger(false), systray(false)
{}

DatabaseSettings::DatabaseSettings() :
    logging(false), hostname("localhost")
{}

SettingsWidget::SettingsWidget(QWidget* parent) :
    QWidget(parent), ui_(new Ui::SettingsWidget)
{
    ui_->setupUi(this);

    ui_->settingsTabGroup->setId(ui_->serverButton, 0);
    ui_->settingsTabGroup->setId(ui_->databaseButton, 1);

    connect(
        ui_->settingsTabGroup,
        static_cast<void (QButtonGroup::*)(QAbstractButton*, bool)>
        (&QButtonGroup::buttonToggled),
        this, &SettingsWidget::onButtonToggled);

    fillSettingsForms();
}

SettingsWidget::~SettingsWidget()
{
    delete ui_;
}

void SettingsWidget::connectServerSettings()
{
    connect(
        ui_->serverNameEdit, &QLineEdit::editingFinished, [this](){
            server_.name = ui_->serverNameEdit->text();
            saveSettings();
        });

    connect(
        ui_->loggerCheckBox, &QCheckBox::stateChanged, [this](int state){
            server_.logger = state;
            saveSettings();
        });

    connect(
        ui_->systrayCheckBox, &QCheckBox::stateChanged, [this](int state){
            server_.systray = state;
            saveSettings();
        });
}

void SettingsWidget::connectDatabaseSettings()
{
    connect(
        ui_->tradeLoggingCheckBox, &QCheckBox::stateChanged,
        [this](int state){
            database_.logging = state;
            saveSettings();
        });

    connect(
        ui_->hostnameEdit, &QLineEdit::editingFinished, [this](){
            database_.hostname = ui_->hostnameEdit->text();
            saveSettings();
        });

    connect(
        ui_->databaseEdit, &QLineEdit::editingFinished, [this](){
            database_.database = ui_->databaseEdit->text();
            saveSettings();
        });

    connect(
        ui_->usernameEdit, &QLineEdit::editingFinished, [this](){
            database_.username = ui_->usernameEdit->text();
            saveSettings();
        });

    connect(
        ui_->passwordEdit, &QLineEdit::editingFinished, [this](){
            database_.password = ui_->passwordEdit->text();
            saveSettings();
        });

    connect(
        ui_->testConnectionButton, &QAbstractButton::pressed,
        this, &SettingsWidget::onTestConnectionPressed);
}

const ServerSettings& SettingsWidget::serverSettings() const
{
    return server_;
}

const DatabaseSettings& SettingsWidget::databaseSettings() const
{
    return database_;
}

void SettingsWidget::onButtonToggled(QAbstractButton* button, bool checked)
{
    if (!checked)
        return;

    int index = ui_->settingsTabGroup->id(button);
    ui_->stackedWidget->setCurrentIndex(index);
}

void SettingsWidget::setSettingsFile(const QString& fileName)
{
    settingsFileName_ = fileName;

    if (!QFile::exists(settingsFileName_)) {
        connectServerSettings();
        connectDatabaseSettings();
        return;
    }

    QFile file(settingsFileName_);

    if (!file.open(QFile::ReadOnly)){
        QMessageBox::critical(
            this, tr("Error opening settings file"), file.errorString());
        return;
    }

    QXmlStreamReader xml(&file);
    parseSettings(xml, server_, database_);

    fillSettingsForms();

    connectServerSettings();
    connectDatabaseSettings();
}

void SettingsWidget::fillSettingsForms()
{
    ui_->serverNameEdit->setText(server_.name);
    ui_->loggerCheckBox->setChecked(server_.logger);
    ui_->systrayCheckBox->setChecked(server_.systray);
    ui_->tradeLoggingCheckBox->setChecked(database_.logging);
    ui_->hostnameEdit->setText(database_.hostname);
    ui_->databaseEdit->setText(database_.database);
    ui_->usernameEdit->setText(database_.username);
    ui_->passwordEdit->setText(database_.password);
}

void SettingsWidget::saveSettings()
{
    QFile file(settingsFileName_);

    if (!file.open(QFile::WriteOnly)){
        QMessageBox::critical(
            this, QMessageBox::tr("Error opening settings file"),
            file.errorString());
        return;
    }

    auto data = serializeSettings(server_, database_).toLatin1();

    if (file.write(data) == -1) {
        QMessageBox::critical(
            this, QMessageBox::tr("Error writing settings file"),
            file.errorString());
    }
}

void SettingsWidget::onTestConnectionPressed()
{
    {
        QSqlDatabase db =
            QSqlDatabase::addDatabase("QMYSQL", "TestConnection");

        db.setHostName(database_.hostname);
        db.setDatabaseName(database_.database);
        db.setUserName(database_.username);
        db.setPassword(database_.password);

        if (db.open()) {
            QMessageBox::information(
                this, tr("Connection test"), tr("Connection successful"));
        } else {
            QMessageBox::information(
                this, tr("Connection test"), tr("Connection error: ")
                + db.lastError().text());
        }

        db.close();
    }

    QSqlDatabase::removeDatabase("TestConnection");
}
