// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024 Eduardo Aguiar
//
// 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, 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, see <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "category_manager.h"
#include "application.h"

#include <mobius/database/connection_pool.h>
#include <mobius/exception.inc>
#include <mobius/exception_posix.inc>
#include <cstdlib>
#include <mutex>
#include <stdexcept>
#include <sys/stat.h>
#include <sys/types.h>

namespace mobius::core
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Module data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief connection pool
static mobius::database::connection_pool pool_;

//! \brief flag object loaded
static std::once_flag is_loaded_;

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Initialize data structure
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static void
_init ()
{
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Database
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  mobius::core::application app;
  const std::string path = app.get_config_path ("category.sqlite");
  pool_.set_path (path);

  auto db = pool_.get_database ();
  auto transaction = db.new_transaction ();

  db.execute ("PRAGMA foreign_keys = ON;");

  db.execute (
    "CREATE TABLE IF NOT EXISTS category"
    "(uid INTEGER PRIMARY KEY AUTOINCREMENT,"
    "id TEXT NOT NULL,"
    "name TEXT NULL,"
    "description TEXT NULL,"
    "icon_data BLOB NULL)"
  );

  db.execute (
    "CREATE UNIQUE INDEX IF NOT EXISTS idx_category "
    "ON category (id)");

  db.execute (
    "CREATE TABLE IF NOT EXISTS attribute"
    "(uid INTEGER PRIMARY KEY AUTOINCREMENT,"
    "category_uid INTEGER NOT NULL,"
    "id TEXT NOT NULL,"
    "name TEXT NULL,"
    "description TEXT NULL,"
    "datatype TEXT NULL,"
    "value_mask TEXT NULL,"
    "idx INTEGER NOT NULL,"
    "FOREIGN KEY (category_uid) REFERENCES category (uid) ON DELETE CASCADE)"
  );

  db.execute (
    "CREATE UNIQUE INDEX IF NOT EXISTS idx_attribute_1 "
    "ON attribute (category_uid, id)");

  transaction.commit ();
}

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Constructor
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
category_manager::category_manager ()
{
  std::call_once (is_loaded_, _init);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get database object for current thread
//! \return database object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::database::database
category_manager::get_database ()
{
  return pool_.get_database ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Create new connection to category_manager database
//! \return new connection object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::database::connection
category_manager::new_connection ()
{
  return pool_.acquire ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Create new transaction to category_manager database
//! \return new database transaction
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::database::transaction
category_manager::new_transaction ()
{
  auto db = get_database ();
  return db.new_transaction ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Clear database
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
category_manager::clear ()
{
  auto db = get_database ();
  auto stmt = db.new_statement ("DELETE FROM category");

  stmt.execute ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get category
//! \param id category ID
//! \return category
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
category
category_manager::get_category (const std::string& id) const
{
  auto db = get_database ();

  auto stmt = db.new_statement (
                "SELECT uid "
                "FROM category "
                "WHERE id = ?");

  stmt.bind (1, id);

  category cat;

  if (stmt.fetch_row ())
    {
      std::int64_t uid = stmt.get_column_int64 (0);
      cat = category (uid);
    }

  return cat;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief New category
//! \param id category ID
//! \return category
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
category
category_manager::new_category (const std::string& id)
{
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // check if category already exists
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  auto cat = get_category (id);
  if (cat)
    return cat;

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // create category
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  auto db = get_database ();

  auto stmt = db.new_statement (
                "INSERT INTO category "
                "VALUES (NULL, ?, NULL, NULL, NULL)");

  stmt.bind (1, id);
  stmt.execute ();

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // return category
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  auto uid = db.get_last_insert_row_id ();
  return category (uid);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Remove category
//! \param id category ID
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
category_manager::remove_category (const std::string& id)
{
  auto db = get_database ();

  auto stmt = db.new_statement (
                "DELETE FROM category "
                "WHERE id = ?");

  stmt.bind (1, id);
  stmt.execute ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Get categories
//! \return categories
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::vector <category>
category_manager::get_categories () const
{
  auto db = get_database ();

  auto stmt = db.new_statement (
                "SELECT uid "
                "FROM category");

  std::vector <category> categories;

  while (stmt.fetch_row ())
    {
      categories.emplace_back (stmt.get_column_int64 (0));
    }

  return categories;
}

} // namespace mobius::core
