// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017 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 <mobius/core/application.h>
#include <mobius/database/database.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
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief application data structure
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static struct
{
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // constants
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  const std::string id = "mobiusft";
  const std::string name = "Mobius Forensic Toolkit";
  const std::string version = "0.5.27";
  const std::string title = "Mobius v0.5.27";
  const std::string copyright = "Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016 Eduardo Aguiar";
  const std::string data_folder = "/home/aguiar/tmp/mobius.install/share/" + id;
  
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // variables
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  bool is_loaded = false;
  std::string config_folder;
  std::string cache_folder;
  mobius::database::database config_db;
} data_;

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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 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 create application object
//! \see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
application::application ()
{
  if (!data_.is_loaded)
    {
      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);
      
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // set loaded
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      data_.is_loaded = true;
    }
}

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

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

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

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

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

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

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

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

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create config database
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const mobius::database::database
application::get_config_database () const
{
  if (!data_.config_db)
    {
      data_.config_db = mobius::database::database (get_config_path (data_.id + ".sqlite"));

      data_.config_db.exec (
          "CREATE TABLE IF NOT EXISTS config"
                "(var TEXT PRIMARY KEY,"
                 "value TEXT NULL);"
         );
    }

  return data_.config_db;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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_config_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_config_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_config_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
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const std::string
application::get_config_string (const std::string& var) const
{
  auto db = get_config_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_config_database ();
  auto statement = db.new_statement ("INSERT OR REPLACE INTO config VALUES (?, ?)");
  statement.bind (1, var);
  statement.bind (2, value);
  statement.exec ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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_config_database ();
  auto statement = db.new_statement ("INSERT OR REPLACE INTO config VALUES (?, ?)");
  statement.bind (1, var);
  statement.bind (2, value);
  statement.exec ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \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_config_database ();
  auto statement = db.new_statement ("INSERT OR REPLACE INTO config VALUES (?, ?)");
  statement.bind (1, var);
  statement.bind (2, value);
  statement.exec ();
}

} // namespace core
} // namespace mobius
