#include <cassert>
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>

#include <sqlite3.h>

// Helper function prototypes
void get_value(int64_t& val, int idx, sqlite3_stmt* stmt);
void get_value(double& val, int idx, sqlite3_stmt* stmt);
void get_value(std::string& val, int idx, sqlite3_stmt* stmt);
void get_value(bool& val, int idx, sqlite3_stmt* stmt);

template<size_t I>
struct builder_impl
{
    template<typename T>
    static void set_values_helper(T& tup, sqlite3_stmt* stmt)
    {
        // Assign the "current" value
        get_value(std::get<I - 1>(tup), I - 1, stmt);

        // Recurse to the next value in the tuple
        builder_impl<I - 1>::set_values_helper(tup, stmt);
    }
};

template<>
struct builder_impl<0>
{
    template<typename T>
    static void set_values_helper(T& tup, sqlite3_stmt* stmt)
    {
        // Stop recursing
        return;
    }
};

template <typename... Ts>
void set_values(std::tuple<Ts...>& tup, sqlite3_stmt* stmt)
{
    builder_impl<sizeof...(Ts)>::set_values_helper(tup, stmt);
}


class DBManager
{
    public:

        // Constructor
        DBManager(const std::string& db_path);

        // Destructor
        ~DBManager();

        // Execute query template function
        template<typename... T>
        std::vector<std::tuple<T...>*>* execute_query(
            const std::string& sql_statement,
            int& return_code)
        {
            sqlite3_stmt* sqlite_statement;

            // Prepare the sqlite statement
            return_code = sqlite3_prepare_v2(db, sql_statement.c_str(), -1,
                                             &sqlite_statement, nullptr);
            if (return_code != SQLITE_OK)
            {
                return nullptr;
            }

            auto output_vector = new std::vector<std::tuple<T...>*>();

            // Iterate until we've collected all the output
            int step_return = 0;
            while ((step_return = sqlite3_step(sqlite_statement)) != SQLITE_DONE)
            {
                // Create a new result row
                auto result_row = new std::tuple<T...>();

                // Now populate the result row using some neato variadic
                // template function magic NOTE: a small amount of black magic
                // is in use here; animal sacrifices not necessary
                set_values(*result_row, sqlite_statement);

                // Add the result row to our vector of output rows
                output_vector->push_back(result_row);
            }

            sqlite3_finalize(sqlite_statement);

            return output_vector;
        }

    private:

        sqlite3 *db;
};
