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

#include <sqlite3.h> 

template <size_t I>
struct visit_impl
{
    template <typename T, typename V>
    static void visit(T& tup, size_t idx, V value)
    {
        if (idx == I - 1) 
        {
            if (std::is_same<std::get<I - 1>(tup), value>)
            {
                std::get<I - 1>(tup) = value;
            }
            else
            {
                assert(false);
            }
        }
        else 
        {
            visit_impl<I - 1>::visit(tup, idx, value);
        }
    }
};
 
template <>
struct visit_impl<0>
{
    template <typename T, typename V>
    static void visit(T& tup, size_t idx, V fun) { assert(false); }
};
 
//~ template <typename V, typename... Ts>
//~ void visit_at(std::tuple<Ts...> const& tup, size_t idx, V value)
//~ {
    //~ visit_impl<sizeof...(Ts)>::visit(tup, idx, value);
//~ }
 
template <typename V, typename... Ts>
void visit_at(std::tuple<Ts...>& tup, size_t idx, V value)
{
    visit_impl<sizeof...(Ts)>::visit(tup, idx, value);
}

class DBManager
{
    public:
        
        // Constructor
        DBManager(const std::string& db_path);
        
        // Destructor
        ~DBManager();
        
        // execute query
        template<typename T>
        std::vector<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<T*>();
            
            int step_return = 0;
            
            // Iterate until we've collected all the output
            do 
            {
                // Gran the
                step_return = sqlite3_step(sqlite_statement);
                
                // Create a new result row to be populated below
                auto result_row = new T();
                
                // Iterate over the columns in the row
                int num_cols = sqlite3_data_count(sqlite_statement);
                for (int col_idx = 0; col_idx < num_cols; col_idx++)
                {
                    switch(sqlite3_column_type(sqlite_statement, col_idx))
                    {
                        case SQLITE_INTEGER:
                            visit_at(*result_row, col_idx, 
                                     sqlite3_column_int64(sqlite_statement, col_idx));
                        
                            //~ std::get<col_idx>(result_row) = 
                                //~ sqlite3_column_int64(sqlite_statement, col_idx);
                            break;
                            
                        case SQLITE_FLOAT:
                            visit_at(*result_row, col_idx, sqlite3_column_double(sqlite_statement, col_idx));
                            //~ std::get<col_idx>(result_row) = 
                                //~ sqlite3_column_double(sqlite_statement, col_idx);
                            break;
                            
                        case SQLITE_TEXT:
                            visit_at(*result_row, col_idx, 
                                     std::string(reinterpret_cast<const char*>(sqlite3_column_text(sqlite_statement, col_idx))));
                         
                            //~ std::get<col_idx>(result_row) = 
                                //~ std::string(sqlite3_column_text(sqlite_statement, col_idx));
                            break;
                            
                        case SQLITE_BLOB:
                            break;
                            
                        case SQLITE_NULL:
                            break;
                    }
                }
                
                
            }
            while (step_return == SQLITE_ROW);
            
            return output_vector;
        }
        
        void test();
 
        // templatized member function!
        template<typename T>
        std::vector<T> func(T& foo)
        {
            std::cout << "Does this work??? " << std::get<2>(foo) << std::endl;
            
            std::vector<T> bar;
            bar.push_back(foo);
            return bar;
        }
    
    private:
    
        sqlite3 *db;
        uint32_t numba;
};
