// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019 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 <mobius/uri.h>
#include <cstdlib>
#include <stdexcept>
#include <sys/stat.h>
#include <sys/types.h>

namespace mobius
{
namespace core
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create directory recursively
//! \param path directory path
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static void
make_directory (const std::string& path)
{
  if (mkdir (path.c_str (), 0700) == -1)
    {
      if (errno == ENOENT)
        {
          auto idx = path.rfind ('/');
          std::string head;
          std::string tail;

          if (idx == std::string::npos)
            throw std::runtime_error (MOBIUS_EXCEPTION_MSG ("cannot create directory"));

          head = path.substr (0, idx);
          tail = path.substr (idx + 1);
          make_directory (head);
          make_directory (tail);
        }

      else if (errno != EEXIST)
        throw std::runtime_error (MOBIUS_EXCEPTION_POSIX);
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief category_manager data structure
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static struct
{
  //! \brief module's connection pool
  mobius::database::connection_pool pool;

  //! \brief flag object loaded
  bool is_loaded = false;

} data_;

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief initialize data structure
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static void
load_data ()
{
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // if data is already loaded, return
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  if (data_.is_loaded)
    return;

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // database
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  mobius::core::application app;
  const std::string path = app.get_config_path ("category.sqlite");
  data_.pool.set_path (path);

  auto db = data_.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 ();

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // set data loaded
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  data_.is_loaded = true;
}

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get database object for current thread
//! \return database object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::database::database
category_manager::get_database ()
{
  load_data ();
  return data_.pool.get_database ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new connection to category_manager database
//! \return new connection object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::database::connection
category_manager::new_connection ()
{
  return data_.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 core
} // namespace mobius
