#pragma once

#include <string>
#include <stdexcept>

#include <sqlite3.h>

namespace sqlite
{

class sqlite_exception : public std::runtime_error
{
  public:
	sqlite_exception(const char *msg, std::string sql, int code = -1) : runtime_error(msg), code(code), sql(sql)
	{
	}
	sqlite_exception(int code, std::string sql) : runtime_error(sqlite3_errstr(code)), code(code), sql(sql)
	{
	}
	int get_code() const
	{
		return code & 0xFF;
	}
	int get_extended_code() const
	{
		return code;
	}
	std::string get_sql() const
	{
		return sql;
	}

  private:
	int code;
	std::string sql;
};

namespace errors
{
// One more or less trivial derived error class for each SQLITE error.
// Note the following are not errors so have no classes:
// SQLITE_OK, SQLITE_NOTICE, SQLITE_WARNING, SQLITE_ROW, SQLITE_DONE
//
// Note these names are exact matches to the names of the SQLITE error codes.
#define SQLITE_MODERN_CPP_ERROR_CODE(NAME, name, derived)                                                              \
	class name : public sqlite_exception                                                                               \
	{                                                                                                                  \
		using sqlite_exception::sqlite_exception;                                                                      \
	};                                                                                                                 \
	derived
#define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE, SUB, base, sub)                                                    \
	class base##_##sub : public base                                                                                   \
	{                                                                                                                  \
		using base::base;                                                                                              \
	};
#include "lists/error_codes.h"
#undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED
#undef SQLITE_MODERN_CPP_ERROR_CODE

// Some additional errors are here for the C++ interface
class more_rows : public sqlite_exception
{
	using sqlite_exception::sqlite_exception;
};
class no_rows : public sqlite_exception
{
	using sqlite_exception::sqlite_exception;
};
class more_statements : public sqlite_exception
{
	using sqlite_exception::sqlite_exception;
}; // Prepared statements can only contain one statement
class invalid_utf16 : public sqlite_exception
{
	using sqlite_exception::sqlite_exception;
};

static void throw_sqlite_error(const int &error_code, const std::string &sql = "")
{
	switch(error_code & 0xFF)
	{
#define SQLITE_MODERN_CPP_ERROR_CODE(NAME, name, derived)                                                              \
	case SQLITE_##NAME:                                                                                                \
		switch(error_code)                                                                                             \
		{                                                                                                              \
			derived default : throw name(error_code, sql);                                                             \
		}
#define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE, SUB, base, sub)                                                    \
	case SQLITE_##BASE##_##SUB:                                                                                        \
		throw base##_##sub(error_code, sql);
#include "lists/error_codes.h"
#undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED
#undef SQLITE_MODERN_CPP_ERROR_CODE
	default:
		throw sqlite_exception(error_code, sql);
	}
}
} // namespace errors
namespace exceptions = errors;
} // namespace sqlite