#pragma once
#include "RowSet.h"
#include "Pointer.h"

#define IF_ENDBG if (debugOutput)

class Logger;

namespace DB
{

extern Logger dbLogger;

class Session;

/**
 * @brief Structure to store a single value to bind to a prepared statement.
 *
 * @tparam T Type of sequence container, must have begin() and end() methods
 * @tparam The value type of the container must be db::Any.
 */
struct BindType
{
    Any value;
    std::string name;
    int idx = -1;
};

/**
 * @brief Structure to store a sequential container
 *        to bind multiple parameters at once.
 *
 * @tparam T Type of sequence container, must have begin() and end() methods
 * @tparam The value type of the container must be db::Any.
 */
template <typename T>
struct BindSequenceType
{
    T values;
    static_assert(std::is_same<typename T::value_type, Any>::value, "Container value type must be db::Any");
};
/**
 * @brief Structure to store a map(relevance) container
 *        to bind multiple parameters at once.
 *
 * @tparam T Type of map container, must have begin() and end() methods
 * @note   The key type of the map must be std::string,
 *         and the value type of the map must be db::Any.
 */
template <typename T>
struct BindMapType
{
    T values;
    static_assert(std::is_same<typename T::key_type, std::string>::value, "Map key type must be std::string");
    static_assert(std::is_same<typename T::mapped_type, Any>::value, "Map value type must be db::Any");
};

template <typename T>
struct IntoType
{
    T& value;
};

class Stmt
{

protected:
#if defined(LLDB_DEBUG_MODE)
    bool debugOutput = true;
#else
    bool debugOutput = false;
#endif
    bool autoExecute = false;      ///< Whether to automatically execute the statement on bind
    std::weak_ptr<Session> parent; ///< Parent session
    std::weak_ptr<Stmt> self;

public:
    Stmt(const std::weak_ptr<Session>& parent, bool autoExecute = false);

    virtual ~Stmt();

    /**
     * @brief Turn on/off debug output.
     *
     * @param enable  Enable or not
     */
    LIAPI void setDebugOutput(bool enable);

    /**
     * @brief Bind a value to a statement parameter.
     *
     * @param value  Value to bind
     * @param index  Parameter index
     * @throws std::runtime_error  If error occurs
     *
     * @par Implementation
     * @see SQLiteStmt::bind
     */
    virtual Stmt& bind(const Any& value, int index) = 0;

    /**
     * @brief Bind a value to a statement parameter.
     *
     * @param value  Value to bind
     * @param name   Parameter name
     * @throws std::runtime_error  If error occurs
     *
     * @par Impletementation
     * @see SQLiteStmt::bind
     */
    virtual Stmt& bind(const Any& value, const std::string& name) = 0;

    /**
     * @brief Bind a value to the next statement parameter.
     *
     * @param value  Value to bind
     * @throws std::runtime_error  If error occurs
     *
     * @par Impletementation
     * @see SQLiteStmt::bind
     */
    virtual Stmt& bind(const Any& value) = 0;

    /**
     * @brief Execute the statement(after binding all the parameters)
     * 
     * @return Stmt&  *this
     * @note   If `this->autoExecute` is true, there is no need to call this method
     */
    virtual Stmt& execute() = 0;

    /**
     * @brief Step to the next row(not fetch).
     *
     * @return bool  True if there is a next row
     *
     * @par Impletementation
     * @see SQLiteStmt::step
     */
    virtual bool step() = 0;

    /**
     * @brief Step to the next row(=step).
     *
     * @return bool  True if there is a next row
     *
     * @par Impletementation
     * @see SQLiteStmt::next
     */
    virtual bool next() = 0;

    /**
     * @brief Get weather all the rows have been fetched.
     *
     * @return bool  True if all the rows have been fetched
     *
     * @par Impletementation
     * @see SQLiteStmt::done
     */
    virtual bool done() = 0;

    /**
     * @brief Fetch the current row.
     *
     * @tparam T  The type of the value to return
     * @return T  The current row(converted)
     * @throws std::runtime_error  If there is no row to fetch
     *
     * @par Example
     * @code
     * auto stmt = sess->prepare("SELECT * FROM table");
     * while (stmt->step()) {
     *     auto row = stmt->fetch();
     *     // Do something with the row
     * }
     * stmt->close();
     * @endcode
     */
    template <typename T = Row>
    inline T fetch()
    {
        return row_to<T>(_Fetch());
    }

    /**
     * @brief Fetch the current row.
     * 
     * @param[out] row    The current row
     * @return     Stmt&  *this
     */
    template <typename T = Row>
    inline Stmt& fetch(T& row)
    {
        row = row_to<T>(_Fetch());
        return *this;
    }

    /**
     * @brief Fetch each of the result rows.
     *
     * @param  cb     Callback function to handle the result rows
     * @return Stmt&  *this
     * @note   Return false in callback to stop fetching
     *
     * @par Example
     * @code
     * sess->prepare("SELECT * FROM table")
     *     ->fetchEach([](const Row& row) {
     *         // Do something with the row
     *         return true;
     *       })
     *     ->close();
     * @endcode
     */
    inline Stmt& fetchEach(std::function<bool(const Row&)> cb)
    {
        do {
            auto res = _Fetch();
            if (res.size() == 0) {
                continue;
            }
            if (!cb(res)) {
                break;
            }
        } while (step());
        return *this;
    }

    /**
     * @brief Fetch each of the result rows(For compatibility).
     * 
     * @param  cb     Callback function to handle the result rows
     * @return Stmt&  *this
     * @note   Return false in callback to stop fetching
     * @see Stmt::fetchEach
     */
    inline Stmt& fetchAll(std::function<bool(const Row&)> cb) 
    {
        return fetchEach(cb);
    }
    //virtual Stmt& fetchAll(std::function<bool(const Row&)> cb);

    /**
     * @brief Fetch all the result rows.
     *
     * @tparam T   The value type of vector
     * @param[out] rows   The result set
     * @return     Stmt&  *this
     */
    template <typename T>
    inline Stmt& fetchAll(std::vector<T>& rows) {
        return fetchEach([&](const Row& row) {
            rows.push_back(row_to<T>(row));
            return true;
        });
        return *this;
    }

    /**
     * @brief Fetch all the result rows.
     *
     * @tparam T  The value type of vector
     * @return std::vector<T>  The result rows
     */
    template <typename T>
    inline std::vector<T> fetchAll() {
        std::vector<T> result;
        fetchAll(result);
        return result;
    }
    //virtual ResultSet fetchAll() = 0;
    //virtual Stmt& fetchAll(ResultSet& rows);

    inline ResultSet fetchAll()
    {
        ResultSet set;
        fetchAll(set);
        return set;
    }

    inline Stmt& fetchAll(ResultSet& rows)
    {
        return fetchEach([&rows](const Row& row) {
            rows.push_back(row);
            return true;
        });
    }

    /**
     * @brief Reset the statement from executing state to perpared state
     * 
     * @return Stmt& *this
     * 
     * @par Note
     * Different between `reset()`, `reexec` and `clear()`:
     * - `reset()` : Reset the statement to the prepared state
     * - `reexec()`: Reset the statement to the prepared state and execute it
     * - `clear()` : Reset the statement to the prepared state and clear the parameters, but not execute it
     */
    virtual Stmt& reset() = 0;

    /**
     * @brief Re-execute the statement(keep the currently bound value to re-excute).
     *
     * @return Stmt&  *this
     * @note   If you want to clear the bound value, use clear() instead.
     * @see    Stmt::reset
     * 
     * @par Impletementation
     * @see SQLiteStmt::reexec
     */
    virtual Stmt& reexec() = 0;

    /**
     * @brief Clear all the bound values.
     *
     * @return Stmt&  *this
     * @see    Stmt::reset
     *
     * @par Impletementation
     * @see SQLiteStmt::clear
     */
    virtual Stmt& clear() = 0;

    /**
     * @brief Close the statement.
     *
     *
     * @par Impletementation
     * @see SQLiteStmt::close
     */
    virtual void close() = 0;

    /**
     * @brief Get the number of rows affected by the statement.
     *
     * @return int  The number of rows affected
     * @note   It will return -1(ULLONG_MAX - 1) if the row count is not available
     *
     * @par Impletementation
     * @see SQLiteStmt::getAffectedRows
     */
    virtual uint64_t getAffectedRows() const = 0;

    /**
     * @brief Get the insert id of the statement
     *
     * @return uint64_t  The insert id
     * @throws std::runtime_error  If error occurs
     * @note   It will return -1(ULLONG_MAX - 1) if the insert id is not available
     *
     * @par Implementation
     * @see SQLiteStmt::getInsertId
     */
    virtual uint64_t getInsertId() const = 0;

    /**
     * @brief Get the number of the unbound parameters.
     *
     * @return int  The number of the unbound parameters
     *
     * @par Impletementation
     * @see SQLiteStmt::getUnboundParams
     */
    virtual int getUnboundParams() const = 0;

    /**
     * @brief Get the number of the bound parameters.
     *
     * @return int  The number of the bound parameters
     *
     * @par Impletementation
     * @see SQLiteStmt::getBoundParams
     */
    virtual int getBoundParams() const = 0;

    /**
     * @brief Get the number of parameters.
     *
     * @return int  The number of parameters
     *
     * @par Impletementation
     * @see SQLiteStmt::getParamsCount
     */
    virtual int getParamsCount() const = 0;

    /**
     * @brief Get the session.
     *
     * @return std::weak_ptr<Session>  The session ptr
     */
    virtual std::weak_ptr<Session> getParent() const;

    /**
     * @brief Get the shared pointer point to this
     * 
     * @return SharedPointer<Stmt>  The ptr
     */
    virtual SharedPointer<Stmt> getSharedPointer() const;

    /**
     * @brief Get the session type
     *
     * @return db::DBType  The database type
     *
     * @par Impletementation
     * @see SQLiteStmt::getType
     */
    virtual DBType getType() const = 0;


    /**
     * @brief Fetch the current row(internal).
     * 
     * @return Row  The current row
     */
    virtual Row _Fetch() = 0;


    /**
     * @brief Operator<< to bind values.
     *
     * @param  v  The value
     * @return SharedPointer<Stmt>  this
     */
    inline SharedPointer<Stmt> operator<<(const Any& v)
    {
        bind(v);
        return getSharedPointer();
    }

    /**
     * @brief Operator>> to store the result.
     * 
     * @tparam T  The value type
     * @param  v  Where to store
     * @return SharedPointer<Stmt>  this
     */
    template <typename T>
    inline SharedPointer<Stmt> operator>>(T& v)
    {
        fetch(v);
        return getSharedPointer();
    }
    template <>
    inline SharedPointer<Stmt> operator>>(ResultSet& v)
    {
        fetchAll(v);
        return getSharedPointer();     
    }
    template <typename T>
    inline SharedPointer<Stmt> operator>>(std::vector<T>& v)
    {
        fetchAll(v);
        return getSharedPointer();
    }

    /**
     * @brief Operator, to bind single values.
     *
     * @param  b      The return value of db::use
     * @return SharedPointer<Stmt>  this
     */
    virtual SharedPointer<Stmt> operator,(const BindType& b);
    /**
     * @brief Operator, to bind a sequence container.
     *
     * @param  b      The return value of db::use
     * @return SharedPointer<Stmt>  this
     */
    template <typename T>
    inline SharedPointer<Stmt> operator,(const BindSequenceType<T>& b)
    {
        for (auto& v : b.values)
        {
            bind(v);
        }
        return getSharedPointer();
    }
    /**
     * @brief Operator, to bind a row.
     *
     * @param  b      The return value of db::use
     * @return SharedPointer<Stmt>  this
     */
    template <>
    inline SharedPointer<Stmt> operator,(const BindSequenceType<Row>& b)
    {
        if (b.values.header && b.values.header->size())
        {
            b.values.forEach([&](const std::string& name, const Any& value) {
                bind(value, name);
                return true;
            });
        }
        else
        {
            for (auto& v : b.values)
            {
                bind(v);
            }
        }
        return getSharedPointer();
    }
    /**
     * @brief Operator, to bind a map container.
     *
     * @param  b      The return value of db::bind
     * @return SharedPointer<Stmt>  this
     */
    template <typename T>
    inline SharedPointer<Stmt> operator,(const BindMapType<T>& b)
    {
        for (auto& v : b.values)
        {
            bind(v.second, v.first);
        }
        return getSharedPointer();
    }
    /**
     * @brief Operator, to store a row of results.
     *
     * @param  i      The return value of db::into
     * @return SharedPointer<Stmt>  this
     */
    template <typename T>
    inline SharedPointer<Stmt> operator,(IntoType<T>& i)
    {
        if (!done()) fetch<T>(i.value);
        return getSharedPointer();
    }
    /**
     * @brief Operator, to store a set of results.
     *
     * @param  i      The return value of db::into
     * @return SharedPointer<Stmt>  this
     */
    template <typename T>
    inline SharedPointer<Stmt> operator,(IntoType<std::vector<T>>& i)
    {
        fetchAll<std::vector<T>>(i.value);
        return getSharedPointer();
    }
    /**
     * @brief Operator, to store a set of results.
     *
     * @param  i      The return value of db::into
     * @return SharedPointer<Stmt>  this
     */
    template <>
    inline SharedPointer<Stmt> operator,(IntoType<ResultSet>& i)
    {
        fetchAll(i.value);
        return getSharedPointer();
    }
    /**
     * @brief Operator, to store a row of results.
     *
     * @param  i      The return value of db::into
     * @return SharedPointer<Stmt>  this
     */
    template <>
    inline SharedPointer<Stmt> operator,(IntoType<Row>& i)
    {
        fetch(i.value);
        return getSharedPointer();
    }

    /**
     * @brief Operator-> to implement better API.
     * 
     * @return Stmt*  this
     */
    inline Stmt* operator->()
    {
        return this;
    }
};

inline BindType use(const Any& value, int idx = -1)
{
    return BindType{value, std::string(), idx};
}
inline BindType use(const Any& value, const std::string& name)
{
    return BindType{value, name};
}
inline BindSequenceType<Row> use(const Row& values)
{
    return BindSequenceType<Row>{values};
}


template <typename T>
inline BindSequenceType<std::vector<T>> use(const std::vector<T>& values)
{
    return BindSequenceType<std::vector<Any>>{to_any_container(values)};
}
template <typename T>
inline BindSequenceType<std::set<T>> use(const std::set<T>& values)
{
    return BindSequenceType<std::set<T>>{to_any_container(values)};
}
template <typename T>
inline BindSequenceType<std::list<T>> use(const std::list<T>& values)
{
    return BindSequenceType<std::list<T>>{to_any_container(values)};
}
template <typename T>
inline BindSequenceType<std::vector<T>> use(const std::initializer_list<T>& values)
{
    return BindSequenceType<std::vector<T>>{to_any_container(std::vector<T>(values))};
}
template <>
inline BindSequenceType<std::vector<Any>> use(const std::vector<Any>& values)
{
    return BindSequenceType<std::vector<Any>>{values};
}
template <>
inline BindSequenceType<std::set<Any>> use(const std::set<Any>& values)
{
    return BindSequenceType<std::set<Any>>{values};
}
template <>
inline BindSequenceType<std::list<Any>> use(const std::list<Any>& values)
{
    return BindSequenceType<std::list<Any>>{values};
}
template <>
inline BindSequenceType<std::vector<Any>> use(const std::initializer_list<Any>& values)
{
    return BindSequenceType<std::vector<Any>>{std::vector<Any>(values)};
}


// Map
template <typename T>
inline BindMapType<std::map<std::string, T>> use(const std::map<std::string, T>& values)
{
    return BindMapType<std::map<std::string, T>>{values};
}
template <typename T>
inline BindMapType<std::unordered_map<std::string, T>> use(const std::unordered_map<std::string, T>& values)
{
    return BindMapType<std::unordered_map<std::string, T>>{values};
}
template <>
inline BindMapType<std::map<std::string, Any>> use(const std::map<std::string, Any>& values)
{
    return BindMapType<std::map<std::string, Any>>{values};
}
template <>
inline BindMapType<std::unordered_map<std::string, Any>> use(const std::unordered_map<std::string, Any>& values)
{
    return BindMapType<std::unordered_map<std::string, Any>>{values};
}
inline BindMapType<std::map<std::string, Any>> use(const std::initializer_list<std::pair<std::string, Any>>& values)
{
    std::map<std::string, Any> result;
    for (auto& pair : values)
    {
        result.insert(std::make_pair(pair.first, pair.second));
    }
    return BindMapType<std::map<std::string, Any>>{result};
}

template <typename T>
inline IntoType<T> into(T& out)
{
    return IntoType<T>{out};
}

} // namespace db