// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// 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 <mobius/database/database.h>
#include <mobius/database/exception.inc>
#include <thread>
#include <chrono>

namespace mobius
{
namespace database
{
static constexpr int SLEEP_TIME = 20;        // microseconds

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief implementation struct
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
struct database_impl
{
  sqlite3 *db = nullptr;
  int transaction_level = 0;
  bool is_null = true;
  
  ~database_impl ();
};

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief destroy shared implementation
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
database_impl::~database_impl ()
{
  if (db)
    sqlite3_close (db);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create null database object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
database::database ()
{
  impl_ = std::make_shared <database_impl>();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create database object
//! \param path database file path
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
database::database (const std::string& path)
{
  impl_ = std::make_shared <database_impl> ();

  // define flags, including multi-thread support
  int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
  
  if (sqlite3_threadsafe ())
    flags |= SQLITE_OPEN_NOMUTEX;

  // open database
  int rc = sqlite3_open_v2 (path.c_str (), &(impl_->db), flags, nullptr);

  if (rc != SQLITE_OK)
    throw std::runtime_error (MOBIUS_EXCEPTION_SQLITE);

  impl_->is_null = false;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief check whether database is not null
//! \return true/false
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
database::operator bool () const
{
  return !impl_->is_null;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief execute SQL command
//! \param sql SQL statement
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
database::exec (const std::string& sql)
{
  int rc = SQLITE_BUSY;

  while (rc == SQLITE_BUSY)
    {
      rc = sqlite3_exec (impl_->db, sql.c_str (), 0, 0, 0);
    
      if (rc == SQLITE_BUSY)
        std::this_thread::sleep_for (std::chrono::microseconds (SLEEP_TIME));
    }

  if (rc != SQLITE_OK)
    throw std::runtime_error (MOBIUS_EXCEPTION_SQLITE);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new transaction object
//! \return transaction object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const transaction
database::new_transaction ()
{
  bool is_master = impl_->transaction_level++ == 0;

  return transaction (*this, is_master);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief end current transaction
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
database::end_transaction ()
{
  impl_->transaction_level--;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief create new statement object
//! \param sql SQL command
//! \return statement object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const statement
database::new_statement (const std::string& sql)
{
  sqlite3_stmt *stmt = nullptr;
  int rc = SQLITE_BUSY;
  
  while (rc == SQLITE_BUSY)
    {
      rc = sqlite3_prepare_v2 (impl_->db, sql.c_str (), -1, &stmt, 0);
      
      if (rc == SQLITE_BUSY)
        std::this_thread::sleep_for (std::chrono::microseconds (SLEEP_TIME));
    }

  if (rc != SQLITE_OK)
    throw std::runtime_error (MOBIUS_EXCEPTION_SQLITE);
  
  return statement (*this, stmt);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief get last error message
//! \return error message
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
const std::string
database::get_error_message () const
{
  return sqlite3_errmsg (impl_->db);
}


} // namespace database
} // namespace mobius
