/*
 * SQLiteConnection.h, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */
#pragma once

using sqlite3 = struct sqlite3;
using sqlite3_stmt = struct sqlite3_stmt;

class SQLiteInstance;
class SQLiteStatement;

using SQLiteInstancePtr = std::unique_ptr<SQLiteInstance>;
using SQLiteStatementPtr = std::unique_ptr<SQLiteStatement>;

class SQLiteStatement : boost::noncopyable
{
public:
	friend class SQLiteInstance;

	bool execute();
	void reset();
	void clear();

	~SQLiteStatement();

	template<typename... Args>
	void executeOnce(const Args &... args)
	{
		setBinds(args...);
		execute();
		reset();
	}

	template<typename... Args>
	void setBinds(const Args &... args)
	{
		setBindSingle(1, args...); // The leftmost SQL parameter has an index of 1
	}

	template<typename... Args>
	void getColumns(Args &... args)
	{
		getColumnSingle(0, args...); // The leftmost column of the result set has the index 0
	}

private:
	void setBindSingle(size_t index, const double & value);
	void setBindSingle(size_t index, const bool & value);
	void setBindSingle(size_t index, const uint8_t & value);
	void setBindSingle(size_t index, const uint16_t & value);
	void setBindSingle(size_t index, const uint32_t & value);
	void setBindSingle(size_t index, const int32_t & value);
	void setBindSingle(size_t index, const int64_t & value);
	void setBindSingle(size_t index, const std::string & value);
	void setBindSingle(size_t index, const char * value);

	void getColumnSingle(size_t index, double & value);
	void getColumnSingle(size_t index, bool & value);
	void getColumnSingle(size_t index, uint8_t & value);
	void getColumnSingle(size_t index, uint16_t & value);
	void getColumnSingle(size_t index, uint32_t & value);
	void getColumnSingle(size_t index, int32_t & value);
	void getColumnSingle(size_t index, int64_t & value);
	void getColumnSingle(size_t index, std::string & value);

	template < typename T, typename std::enable_if_t < std::is_enum_v<T>, int  > = 0 >
	void getColumnSingle(size_t index, T & value)
	{
		using Integer = std::underlying_type_t<T>;
		Integer result;
		getColumnSingle(index, result);

		value = static_cast<T>(result);
	}

	template<typename Rep, typename Period>
	void getColumnSingle(size_t index, std::chrono::duration<Rep, Period> & value)
	{
		int64_t durationValue = 0;
		getColumnSingle(index, durationValue);
		value = std::chrono::duration<Rep, Period>(durationValue);
	}

	SQLiteStatement(SQLiteInstance & instance, sqlite3_stmt * statement);

	template<typename T, typename... Args>
	void setBindSingle(size_t index, T const & arg, const Args &... args)
	{
		setBindSingle(index, arg);
		setBindSingle(index + 1, args...);
	}

	template<typename T, typename... Args>
	void getColumnSingle(size_t index, T & arg, Args &... args)
	{
		getColumnSingle(index, arg);
		getColumnSingle(index + 1, args...);
	}

	SQLiteInstance & m_instance;
	sqlite3_stmt * m_statement;
};

class SQLiteInstance : boost::noncopyable
{
public:
	friend class SQLiteStatement;

	static SQLiteInstancePtr open(const boost::filesystem::path & db_path, bool allow_write);

	~SQLiteInstance();

	SQLiteStatementPtr prepare(const std::string & statement);

private:
	explicit SQLiteInstance(sqlite3 * connection);

	sqlite3 * m_connection;
};