// For license of this file, see <project-root-folder>/LICENSE.md.

#include "database/databasedriver.h"

#include "definitions/definitions.h"
#include "exceptions/applicationexception.h"
#include "miscellaneous/iofactory.h"

#include <QDir>
#include <QSqlError>
#include <QSqlQuery>
#include <QThread>

DatabaseDriver::DatabaseDriver(QObject* parent) : QObject(parent) {}

QSqlDatabase DatabaseDriver::threadSafeConnection(const QString& connection_name, DesiredStorageType desired_type) {
  qlonglong thread_id = qlonglong(QThread::currentThreadId());
  bool is_main_thread = QThread::currentThread() == qApp->thread();

  QSqlDatabase database =
    connection(is_main_thread ? connection_name : QSL("db_connection_%1").arg(thread_id), desired_type);

  return database;
}

void DatabaseDriver::updateDatabaseSchema(QSqlQuery& query,
                                          int source_db_schema_version,
                                          const QString& database_name) {
  const int current_version = QSL(APP_DB_SCHEMA_VERSION).toInt();

  while (source_db_schema_version != current_version) {
    const QStringList statements = prepareScript(APP_SQL_PATH,
                                                 QSL(APP_DB_UPDATE_FILE_PATTERN)
                                                   .arg(ddlFilePrefix(),
                                                        QString::number(source_db_schema_version),
                                                        QString::number(source_db_schema_version + 1)),
                                                 database_name);

    for (const QString& statement : statements) {
      if (!query.exec(statement) && query.lastError().isValid()) {
        throw ApplicationException(query.lastError().text());
      }
    }

    // Increment the version.
    qDebugNN << LOGSEC_DB << "Updating database schema " << QUOTE_W_SPACE(source_db_schema_version) << "->"
             << QUOTE_W_SPACE_DOT(source_db_schema_version + 1);

    source_db_schema_version++;
  }

  setSchemaVersion(query, current_version, false);
}

void DatabaseDriver::setSchemaVersion(QSqlQuery& query, int new_schema_version, bool empty_table) {
  if (!query.prepare(empty_table
                       ? QSL("INSERT INTO Information VALUES ('schema_version', :schema_version);")
                       : QSL("UPDATE Information SET inf_value = :schema_version WHERE inf_key = 'schema_version';"))) {
    throw ApplicationException(query.lastError().text());
  }

  query.bindValue(QSL(":schema_version"), QString::number(new_schema_version));
  if (!query.exec()) {
    throw ApplicationException(query.lastError().text());
  }
}

QStringList DatabaseDriver::prepareScript(const QString& base_sql_folder,
                                          const QString& sql_file,
                                          const QString& database_name) {
  QStringList statements;
  QString next_file = base_sql_folder + QDir::separator() + sql_file;
  QString sql_script = QString::fromUtf8(IOFactory::readFile(next_file));
  QStringList new_statements = sql_script.split(QSL(APP_DB_COMMENT_SPLIT),
#if QT_VERSION >= 0x050F00 // Qt >= 5.15.0
                                                Qt::SplitBehaviorFlags::SkipEmptyParts);
#else
                                                QString::SplitBehavior::SkipEmptyParts);
#endif

  for (int i = 0; i < new_statements.size(); i++) {
    if (new_statements.at(i).startsWith(QSL(APP_DB_INCLUDE_PLACEHOLDER))) {
      // We include another file.
      QString included_file_name = new_statements.at(i).mid(QSL(APP_DB_INCLUDE_PLACEHOLDER).size() + 1).simplified();

      QString included_file = base_sql_folder + QDir::separator() + included_file_name;
      QString included_sql_script = QString::fromUtf8(IOFactory::readFile(included_file));
      QStringList included_statements = included_sql_script.split(QSL(APP_DB_COMMENT_SPLIT),
#if QT_VERSION >= 0x050F00 // Qt >= 5.15.0
                                                                  Qt::SplitBehaviorFlags::SkipEmptyParts);
#else
                                                                  QString::SplitBehavior::SkipEmptyParts);
#endif

      statements << included_statements;
    }
    else {
      statements << new_statements.at(i);
    }
  }

  statements = statements.replaceInStrings(QSL(APP_DB_NAME_PLACEHOLDER), database_name);
  statements = statements.replaceInStrings(QSL(APP_DB_AUTO_INC_PRIM_KEY_PLACEHOLDER), autoIncrementPrimaryKey());
  statements = statements.replaceInStrings(QSL(APP_DB_BLOB_PLACEHOLDER), blob());

  return statements;
}
