// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018 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 "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 ('/');
          if (idx == std::string::npos)
            throw std::runtime_error (MOBIUS_EXCEPTION_MSG ("cannot create directory"));

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

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

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief join a relative path to a absolute path
//! \param abs_path absolute path
//! \param rel_path relative path
//! \return absolute path
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static const std::string
join_path (const std::string& abs_path, const std::string& rel_path)
{
  mobius::uri url;
  url.set_scheme ("file");
  url.set_path (abs_path);
  url.join_path (rel_path);

  std::string fullpath = get_path_from_url (url.get_value ());

  if (fullpath.substr (0, abs_path.length ()) != abs_path)
    throw std::runtime_error (MOBIUS_EXCEPTION_MSG ("invalid path"));

  return fullpath;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief application data structure
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static struct
{
  //! \brief application ID
  const std::string id = "mobiusft";

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

  //! \brief application version
  const std::string version = "1.10";

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

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

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

  //! \brief data folder path
  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 pool;
  
  //! \brief flag object loaded
  bool is_loaded = false;

} data_;

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief initialize data structure
//! \see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static void
load_data ()
{
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // check if it is already loaded
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  if (data_.is_loaded)
    return;

  const char *home = getenv ("HOME");
      
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // config folder
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const char *xdg_config_home = getenv ("XDG_CONFIG_HOME");
      
  if (xdg_config_home)
    data_.config_folder = xdg_config_home;

  else
    data_.config_folder = home + std::string ("/.config");

  data_.config_folder += std::string ("/") + data_.id;

  make_directory (data_.config_folder);
      
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // cache folder
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const char *xdg_cache_home = getenv ("XDG_CACHE_HOME");
      
  if (xdg_cache_home)
    data_.cache_folder = xdg_cache_home;

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

  data_.cache_folder += std::string ("/") + data_.id;

  make_directory (data_.cache_folder);
      
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // database
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const std::string db_path = join_path (data_.config_folder, data_.id + ".sqlite");
  data_.pool.set_path (db_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 config"
                            "(var TEXT PRIMARY KEY,"
                           "value TEXT NULL);"
         );
  
  transaction.commit ();

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

} // namespace

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

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

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

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

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

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

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get application host OS
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
std::string
application::get_os_name () const
{
  return data_.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_.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
{
  load_data ();
  return join_path (data_.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
{
  load_data ();
  return join_path (data_.cache_folder, path);
}

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