// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022Eduardo 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 "application.h"
#include "extension_set.h"
#include "log.h"
#include <mobius/database/connection_pool.h>
#include <mobius/exception.inc>
#include <mobius/io/path.h>
#include <mobius/io/folder.h>
#include <atomic>
#include <cstdlib>
#include <stdexcept>
#include <sys/stat.h>
#include <sys/types.h>

namespace mobius
{
namespace core
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Application data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief application ID
static const std::string id_ = "mobiusft";

//! \brief application name
static const std::string name_ = "Mobius Forensic Toolkit";

//! \brief application version
static const std::string version_ = "1.26";

//! \brief application title
static const std::string title_ = "Mobius v1.26";

//! \brief application copyright
static const std::string copyright_ = "Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022 Eduardo Aguiar";

//! \brief application OS name  
static const std::string os_name_ = "x86_64-pc-linux-gnu";

//! \brief data folder path
static const std::string data_folder_ = "/home/aguiar/tmp/mobius.install/share/" + id_;

//! \brief configuration folder path
std::string config_folder_;

//! \brief cache folder path
std::string cache_folder_;

//! \brief module's connection pool
mobius::database::connection_pool connection_pool_;

//! \brief C++ extensions
static extension_set extensions_;

//! \brief Is application data loaded
std::atomic_bool is_loaded_ (false);

//! \brief Is application running
std::atomic_bool is_running_ (false);

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief join a relative path to a absolute path
//! \param abs_path absolute path
//! \param rel_path relative path
//! \return absolute path
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static std::string
join_path (const std::string& abs_path, const std::string& rel_path)
{
  auto path = mobius::io::path (abs_path);
  return to_string (path.get_child_by_path (rel_path));
}

} // namespace

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

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Constructor
//! \see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
application::application ()
{
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Check if it is already loaded
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  bool expected_value = false;

  if (!is_loaded_.compare_exchange_strong (expected_value, true))
    return;

  const char *home = getenv ("HOME");
      
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Config folder
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const char *xdg_config_home = getenv ("XDG_CONFIG_HOME");

  if (xdg_config_home)
    config_folder_ = xdg_config_home;
  else
    config_folder_ = home + std::string ("/.config");

  config_folder_ += std::string ("/") + id_;

  auto config_folder = mobius::io::new_folder_by_path (config_folder_);
  config_folder.create ();

  set_logfile_path (config_folder_ + "/mobius.log");
      
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Cache folder
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const char *xdg_cache_home = getenv ("XDG_CACHE_HOME");
      
  if (xdg_cache_home)
    cache_folder_ = xdg_cache_home;

  else
    cache_folder_ = home + std::string ("/.cache");

  cache_folder_ += std::string ("/") + id_;

  auto cache_folder = mobius::io::new_folder_by_path (cache_folder_);
  cache_folder.create ();
      
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Database
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const std::string db_path = join_path (config_folder_, id_ + ".sqlite");
  connection_pool_.set_path (db_path);

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

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

  db.execute (
      "CREATE TABLE IF NOT EXISTS config"
                            "(var TEXT PRIMARY KEY,"
                           "value TEXT NULL);"
         );
  
  transaction.commit ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Start application
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
application::start ()
{
  mobius::core::log log (__FILE__, __FUNCTION__);
  log.info (__LINE__, "starting application");

  // check if application is already running
  if (is_running_)
    throw std::runtime_error (MOBIUS_EXCEPTION_MSG ("application is already running"));

  // search installed C++ extensions
  log.info (__LINE__, "loading C++ extensions");
  auto extensions_path = get_config_path ("extensions");
  auto folder = mobius::io::new_folder_by_path (extensions_path);
  
  for (auto& c : folder.get_children ())
    {
      if (c.is_file () && c.get_extension () == "so")
        extensions_.load (c.get_path ());
    }

  // start extensions
  for (auto& e : extensions_.get_extensions ())
    e.start ();

  // set application running
  log.info (__LINE__, "application started");
  is_running_ = true;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Stop application
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
application::stop ()
{
  mobius::core::log log (__FILE__, __FUNCTION__);

  // check if application is running
  if (!is_running_)
    throw std::runtime_error (MOBIUS_EXCEPTION_MSG ("application is not running"));

  // stop extensions
  log.info (__LINE__, "unloading C++ extensions");

  for (auto& e : extensions_.get_extensions ())
    e.stop ();

  // remove extensions
  extensions_.clear ();

  // set running false  
  log.info (__LINE__, "application stopped");
  is_running_ = false;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get application ID
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_id () const
{
  return id_;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get application name
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_name () const
{
  return name_;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get application version
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_version () const
{
  return version_;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get application title
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_title () const
{
  return title_;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get application copyright
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_copyright () const
{
  return copyright_;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get application host OS
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_os_name () const
{
  return os_name_;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get data path
//! \param path relative path
//! \return absolute path to data_folder sub-path
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_data_path (const std::string& path) const
{
  return join_path (data_folder_, path);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get config path
//! \param path relative path
//! \return absolute path to config_folder sub-path
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_config_path (const std::string& path) const
{
  return join_path (config_folder_, path);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get cache path
//! \param path relative path
//! \return absolute path to cache_folder sub-path
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_cache_path (const std::string& path) const
{
  return join_path (cache_folder_, path);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new connection to application database
//! \return new connection object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
mobius::database::connection
application::new_connection ()
{
  return connection_pool_.acquire ();
}

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

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief check if config var exists
//! \param var var name
//! \return true/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
application::has_config (const std::string& var) const
{
  auto db = get_database ();
  auto statement = db.new_statement ("SELECT value FROM config WHERE var = ?");
  statement.bind (1, var);

  return statement.fetch_row ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get config var
//! \param var var name
//! \return int var
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
int
application::get_config_int (const std::string& var) const
{
  auto db = get_database ();
  auto statement = db.new_statement ("SELECT value FROM config WHERE var = ?");
  statement.bind (1, var);
  
  int value = 0;

  if (statement.fetch_row ())
    value = statement.get_column_int (0);
  
  return value;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get config var
//! \param var var name
//! \return double var
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
double
application::get_config_double (const std::string& var) const
{
  auto db = get_database ();
  auto statement = db.new_statement ("SELECT value FROM config WHERE var = ?");
  statement.bind (1, var);
  
  double value = 0.0;

  if (statement.fetch_row ())
    value = statement.get_column_double (0);
  
  return value;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get config var
//! \param var var name
//! \return string var
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_config_string (const std::string& var) const
{
  auto db = get_database ();
  auto statement = db.new_statement ("SELECT value FROM config WHERE var = ?");
  statement.bind (1, var);
  
  std::string value;

  if (statement.fetch_row ())
    value = statement.get_column_string (0);
  
  return value;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief set config int var
//! \param var var name
//! \param v int value
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
application::set_config (const std::string& var, int value) const
{
  auto db = get_database ();
  auto statement = db.new_statement ("INSERT OR REPLACE INTO config VALUES (?, ?)");
  statement.bind (1, var);
  statement.bind (2, value);
  statement.execute ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief set config double var
//! \param var var name
//! \param v double value
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
application::set_config (const std::string& var, double value) const
{
  auto db = get_database ();
  auto statement = db.new_statement ("INSERT OR REPLACE INTO config VALUES (?, ?)");
  statement.bind (1, var);
  statement.bind (2, value);
  statement.execute ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief set config string var
//! \param var var name
//! \param v string value
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
application::set_config (const std::string& var, const std::string& value) const
{
  auto db = get_database ();
  auto statement = db.new_statement ("INSERT OR REPLACE INTO config VALUES (?, ?)");
  statement.bind (1, var);
  statement.bind (2, value);
  statement.execute ();
}

} // namespace core
} // namespace mobius
