// Database wrapper library
// Based on "sdsqlite" which ships the following copyright notice:

///
/// Author: Matt Giger
/// http://www.superdeadly.com
/// Copyright (c) 2009 Super Deadly Software, LLC.
///
/// License:
///  Permission is hereby granted, free of charge, to any person obtaining a
///  copy of this software and associated documentation files (the
///  "Software"), to deal in the Software without restriction, including
///  without limitation the rights to use, copy, modify, merge, publish, dis-
///  tribute, sublicense, and/or sell copies of the Software, and to permit
///  persons to whom the Software is furnished to do so, subject to the fol-
///  lowing conditions:
///
///  The above copyright notice and this permission notice shall be included
///  in all copies or substantial portions of the Software.
///
///  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
///  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
///  ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
///  SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
///  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
///  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
///  IN THE SOFTWARE.
///

#ifndef _TN_DB_H_
#define _TN_DB_H_

#include <string>
#include <istream>
#include <ostream>
#include <stdexcept>
#include <iterator>
#include <vector>
#include "foundation/SharedArray.h"
#include "foundation/TnTypes.h"

struct sqlite3;
struct sqlite3_stmt;

//////////////////////////////////////////////////////////////////////
///
/// @class sqlite
///
/// Sqlite database instance.
///
//////////////////////////////////////////////////////////////////////
class sqlite
{
public:
    ///
    /// @param path         Path to sqlite database file
    /// @param busyTimeout  Timeout (in ms) after which calls to step() fail (default = no timeout)
    ///
    /// Construct a database instance to the specified file.
    sqlite(std::string const & path, int busyTimeout=0);
    
    ///
    /// Destructor closes current database connection.
    ~sqlite();
    
    ///
    /// @return         Integer value of the last "ROWID" for the previous insert statement
    ///
    /// Sometimes it is convenient to have the internal sqlite ROWID for the previous insert
    /// statement so you can perform other operations on the just inserted row.
    int LastRow() const;
    
    ///
    /// @return         True on success
    ///
    /// Begin a database transaction.
    bool Begin();
    
    ///
    /// @return         True on success
    ///
    /// Commit the current database transaction.
    bool Commit();
    
    ///
    /// @return         True on success
    ///
    /// Rollback the current transction on the database.
    bool Rollback();
    
    ///
    /// @param name     Table name to check for
    /// @return         True if table name exists
    ///
    /// Return whether a table with the specified name exists in the current database.
    bool Exists(const std::string& name);
    
    ///
    /// @param stmt     Sql statement
    /// @return         True on success
    ///
    /// Execute a sql statement.
    bool Execute(const std::string& stmt);
    
    ///
    /// @return         Sqlite handle to database
    ///
    /// Return the internal sqlite handle to the database.
    sqlite3* Handle() const;
    
private:
    // Sqlite database handle
    sqlite3* m_DB;
    
    // Reference count to handle nested transactions
    int m_NestedTransactions;
};

//////////////////////////////////////////////////////////////////////
///
/// @class sql
///
/// Sqlite prepared statement.
///
//////////////////////////////////////////////////////////////////////
class sql
{
public:
    // Utility class to mark the completion of a statement.
    struct end {};
    
    // Utility class to mark a null value.
    struct null {};
    
    ///
    /// @param db       Sqlite database
    /// @param stmt     Statement to prepare
    ///
    /// Construct a statement to be executed on the specified database.
    sql(sqlite& db);
    
    ///
    /// Destructor. Completes any pending uncompleted statement.
    ~sql();
    
    ///
    /// @param stmt     An sql statement
    /// @return         True on success
    ///
    /// Prepare the specified sql statement to be executed on the database.
    bool Prepare(const std::string& stmt);
    
    ///
    /// @return         Number of input parameters required by statement.
    int Inputs() const;
    
    ///
    /// @return         Number of output parameters supplied by statement.
    int Outputs() const;
    
    ///
    /// @param idx      Zero based index into select statement results.
    /// @return         Type of result expected.
    ///
    ///  SQLITE_INTEGER
    ///  SQLITE_FLOAT
    ///  SQLITE_TEXT
    ///  SQLITE_BLOB
    ///  SQLITE_NULL
    //
    /// Return the type of result expected for the specified column.
    int ColumnType(int idx) const;
    
    ///
    /// @return         True if there are more results pending from a select.
    ///
    /// Step the prepared statement. This will make the values of a select available
    /// or execute an insert or create or other general database statement.
    bool Step();
    
    ///
    /// @return         Reference to the statement
    ///
    /// This terminates the statement by calling step(), which is most useful for
    /// insert statements as a useful shorthand.
    sql& operator<<(const end&);
    
    ///
    /// @return         Reference to the statement
    ///
    /// Bind a null value to the current input.
    sql& operator<<(const null&);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(bool val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(char val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(unsigned char val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(short val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(unsigned short val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(int val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(unsigned int val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(long val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(unsigned long val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(long long val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(unsigned long long val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(float val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a value to the current input.
    sql& operator<<(double val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a string value to the current input.
    sql& operator<<(const char* val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a string value to the current input.
    sql& operator<<(const std::string& val);
    
    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind an input stream value to the current input.
    sql& operator<<(std::istream& val);

    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a byte vector to the current input.
    sql& operator<<(std::vector<uint8_t> const & val);

    ///
    /// @param val      Value to bind
    /// @return         Reference to the statement
    ///
    /// Bind a byte array to the current input.
    sql& operator<<(std::pair<uint8_t const *,size_t> const & val);

    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(bool& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(char& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(unsigned char& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(short& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(unsigned short& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(int& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(unsigned int& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(long& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(unsigned long& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(long long& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(unsigned long long& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(float& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(double& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a value from the current result row.
    sql& operator>>(std::string& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a stream from the current result row.
    sql& operator>>(std::ostream& val);
    
    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a stream from the current result row.
    sql& operator>>(std::vector<uint8_t> & val);

    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract a stream from the current result row.
    sql& operator>>(std::pair<Tn::Foundation::shared_array<uint8_t>,size_t> & val);

    ///
    /// @param val      Reference to variable to have value returned in
    /// @return         Reference to the statement
    ///
    /// Extract from the current result row into a container that supports back insertion
    template <typename T>
    sql& operator>>(std::back_insert_iterator<T>& val)
    {
        typename T::value_type index;
        while(Step())
        {
            *this >> index;
            *val++ = index;
        }
        
        return *this;
    }
    
private:
    ///
    /// Private copy constructor to protect from statement duplication.
    sql(const sql&);
    
    ///
    /// Private copy operator to protect from statement duplication.
    sql& operator=(const sql&);
    
    ///
    /// Delete the prepared statement.
    void Finalize();
    
    ///
    /// Reset the statement to be empty.
    void Reset();
    
private:
    // Reference to the database connection
    sqlite & m_Sqlite;
    
    // Sqlite statement handle
    sqlite3_stmt* m_Stmt;
    
    // Current insert input position
    int m_Ipos;
    
    // Current select output position
    int m_Opos;
    
    // Number of insert columns expected
    int m_Icol;
    
    // Number of select columns expected
    int m_Ocol;
};

//////////////////////////////////////////////////////////////////////
///
/// @class db_exception
///
/// Database exception class.
///
//////////////////////////////////////////////////////////////////////
class db_exception : public std::runtime_error
{
public:
    db_exception(const std::string& what_arg) : std::runtime_error(what_arg) {}
};

#endif
