#ifndef _LIBRARY_SQLITE_STATEMENT_HPP_
#define _LIBRARY_SQLITE_STATEMENT_HPP_

// ------------------------------------------------------------------------------------------------
#include "Library/SQLite/Shared.hpp"

// ------------------------------------------------------------------------------------------------
#include <unordered_map>

// ------------------------------------------------------------------------------------------------
namespace SqMod {
namespace SQLite {

/* ------------------------------------------------------------------------------------------------
 * Class used to manage a connection to an SQLite statement.
*/
class Statement
{
public:

    /* --------------------------------------------------------------------------------------------
     * Default constructor (invalid).
    */
    Statement();

    /* --------------------------------------------------------------------------------------------
     * Create a statement on the specified connection and use the specified query.
    */
    Statement(const Connection & db, const SQChar * query);

    /* --------------------------------------------------------------------------------------------
     * Copy constructor.
    */
    Statement(const Statement &) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move constructor.
    */
    Statement(Statement &&) = delete;

    /* --------------------------------------------------------------------------------------------
     * Destructor.
    */
    ~Statement();

    /* --------------------------------------------------------------------------------------------
     * Copy assignment operator.
    */
    Statement & operator = (const Statement &) = delete;

    /* --------------------------------------------------------------------------------------------
     * Move assignment operator.
    */
    Statement & operator = (Statement &&) = delete;

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to a raw SQLite statement handle.
    */
    operator sqlite3_stmt * () const
    {
        return m_Handle;
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to a raw SQLite connection handle.
    */
    operator sqlite3 * () const
    {
        return static_cast< sqlite3 * >(m_Connection);
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to boolean.
    */
    operator bool () const
    {
        return (m_Handle != NULL);
    }

    /* --------------------------------------------------------------------------------------------
     * Negation operator.
    */
    operator ! ()  const
    {
        return (m_Handle == NULL);
    }

    /* --------------------------------------------------------------------------------------------
     * Equality operator.
    */
    bool operator == (const Statement & o) const
    {
        return (m_Handle == o.m_Handle);
    }

    /* --------------------------------------------------------------------------------------------
     * Inequality operator.
    */
    bool operator != (const Statement & o) const
    {
        return (m_Handle != o.m_Handle);
    }

    /* --------------------------------------------------------------------------------------------
     * Used by the script to compare two instances of this type.
    */
    SQInt32 Cmp(const Statement & o) const;

    /* --------------------------------------------------------------------------------------------
     * Convert this type to a string.
    */
    const SQChar * ToString() const;

    /* --------------------------------------------------------------------------------------------
     * Retrieve the local tag.
    */
    const SQChar * GetLocalTag() const;

    /* --------------------------------------------------------------------------------------------
     * Change the local tag.
    */
    void SetLocalTag(const SQChar * tag);

    /* --------------------------------------------------------------------------------------------
     * Retrieve the local data.
    */
    SqObj & GetLocalData();

    /* --------------------------------------------------------------------------------------------
     * Change the local data.
    */
    void SetLocalData(SqObj & data);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    bool IsValid()
    {
        return (m_Handle != NULL);
    }

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void Reset();

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void Clear();

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SQInt32 GetStatus() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SQInt32 GetColumns() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    const SQChar * GetQuery() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    const SQChar * GetErrStr() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    const SQChar * GetErrMsg() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    Connection GetConnection() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    bool GetGood() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    bool GetDone() const;

    /* --------------------------------------------------------------------------------------------
     * Return the numeric result code for the most recent failed API call (if any).
    */
    SQInt32 GetErrorCode() const;

    /* --------------------------------------------------------------------------------------------
     * Return the extended numeric result code for the most recent failed API call (if any).
    */
    SQInt32 GetExtendedErrorCode() const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SQInt32 Exec();

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    bool Step();

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindA(const Array & arr);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindT(const Table & tbl);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindI(SQInt32 idx, SQInt32 value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindL(SQInt32 idx, const SLongInt & value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindV(SQInt32 idx, SQInteger value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindF(SQInt32 idx, SQFloat value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindS(SQInt32 idx, const SQChar * value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindB(SQInt32 idx, bool value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBindN(SQInt32 idx);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindT(const Table & tbl);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindI(const SQChar * name, SQInt32 value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindL(const SQChar * name, const SLongInt & value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindV(const SQChar * name, SQInteger value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindF(const SQChar * name, SQFloat value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindS(const SQChar * name, const SQChar * value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindB(const SQChar * name, bool value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBindN(const SQChar * name);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void IndexBind(SQInt32 idx, SqObj & value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    void NameBind(const SQChar * name, SqObj & value);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SqObj FetchColumnIndex(SQInt32 idx);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SqObj FetchColumnName(const SQChar * name);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    Array FetchArray();

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    Table FetchTable();

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    bool CheckIndex(SQInt32 idx) const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    bool IsColumnNull(SQInt32 idx) const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SQInt32 GetColumnIndex(const SQChar * name);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    const SQChar * GetColumnName(SQInt32 idx);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    const SQChar * GetColumnOriginName(SQInt32 idx);

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SQInteger GetColumnType(SQInt32 idx) const;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    SQInteger GetColumnBytes(SQInt32 idx) const;

protected:

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    typedef std::unordered_map< String, SQInt32 > ColumnIndex;

    /* --------------------------------------------------------------------------------------------
     * ...
    */
    template < typename... Args > void Status(const SQChar * msg, Args&&... args) const
    {
        if (m_Status != SQLITE_OK)
        {
            LogErr(msg, std::forward< Args >(args)...);
        }
    }

private:

    /* --------------------------------------------------------------------------------------------
     * The handle to the managed SQLite statement resource.
    */
    sqlite3_stmt*       m_Handle;

    /* --------------------------------------------------------------------------------------------
     * The SQLite connection associated with this statement.
    */
    ConnectionHandle    m_Connection;

    /* --------------------------------------------------------------------------------------------
     * The query string used to create this statement.
    */
    String              m_Query;

    /* --------------------------------------------------------------------------------------------
     * The last status code of this connection handle.
    */
    SQInt32             m_Status;

    /* --------------------------------------------------------------------------------------------
     * The amount of columns available in this statement.
    */
    SQInt32             m_Columns;

    /* --------------------------------------------------------------------------------------------
     * True when a row has been fetched with step.
    */
    ColumnIndex         m_ColIdx;

    /* --------------------------------------------------------------------------------------------
     * True when a row has been fetched with step.
    */
    bool                m_Good;

    /* --------------------------------------------------------------------------------------------
     * True when the last step had no more rows to fetch.
    */
    bool                m_Done;

    /* --------------------------------------------------------------------------------------------
     * The local tag associated with this instance.
    */
    SqTag               m_Tag;

    /* --------------------------------------------------------------------------------------------
     * The local data associated with this instance.
    */
    SqObj               m_Data;
};

} // Namespace:: SQLite
} // Namespace:: SqMod

#endif // _LIBRARY_SQLITE_STATEMENT_HPP_