#ifndef PROXYSQL_SQLITE3DB_H
#define PROXYSQL_SQLITE3DB_H
#include <pthread.h>
#include "sqlite3.h"
#undef swap
#undef min
#undef max
#include <cstdint>
#include <memory>
#include <vector>
#include <utility>
#define PROXYSQL_SQLITE3DB_PTHREAD_MUTEX

#ifndef SAFE_SQLITE3_STEP2
#define SAFE_SQLITE3_STEP2(_stmt) do {\
  do {\
    rc=(*proxy_sqlite3_step)(_stmt);\
    if (rc==SQLITE_LOCKED || rc==SQLITE_BUSY) {\
      usleep(100);\
    }\
  } while (rc==SQLITE_LOCKED || rc==SQLITE_BUSY);\
} while (0)
#endif // SAFE_SQLITE3_STEP2

/* Forward-declare core proxy types that appear in function pointer prototypes */
class SQLite3_row;
class SQLite3_result;
class SQLite3DB;


#ifndef MAIN_PROXY_SQLITE3
extern int (*proxy_sqlite3_bind_double)(sqlite3_stmt*, int, double);
extern int (*proxy_sqlite3_bind_int)(sqlite3_stmt*, int, int);
extern int (*proxy_sqlite3_bind_int64)(sqlite3_stmt*, int, sqlite3_int64);
extern int (*proxy_sqlite3_bind_null)(sqlite3_stmt*, int);
extern int (*proxy_sqlite3_bind_text)(sqlite3_stmt*,int,const char*,int,void(*)(void*));
extern int (*proxy_sqlite3_bind_blob)(sqlite3_stmt*, int, const void*, int, void(*)(void*));
extern const char *(*proxy_sqlite3_column_name)(sqlite3_stmt*, int N);
extern const unsigned char *(*proxy_sqlite3_column_text)(sqlite3_stmt*, int iCol);
extern int (*proxy_sqlite3_column_bytes)(sqlite3_stmt*, int iCol);
extern int (*proxy_sqlite3_column_type)(sqlite3_stmt*, int iCol);
extern int (*proxy_sqlite3_column_count)(sqlite3_stmt *pStmt);
extern int (*proxy_sqlite3_column_int)(sqlite3_stmt*, int iCol);
extern sqlite3_int64 (*proxy_sqlite3_column_int64)(sqlite3_stmt*, int iCol);
extern double (*proxy_sqlite3_column_double)(sqlite3_stmt*, int iCol);
extern sqlite3_int64 (*proxy_sqlite3_last_insert_rowid)(sqlite3*);
extern const char *(*proxy_sqlite3_errstr)(int);
extern sqlite3* (*proxy_sqlite3_db_handle)(sqlite3_stmt*);
extern int (*proxy_sqlite3_enable_load_extension)(sqlite3*, int);
extern int (*proxy_sqlite3_auto_extension)(void(*)(void));

extern void (*proxy_sqlite3_global_stats_row_step)(SQLite3DB*, sqlite3_stmt*, const char*, ...);
extern const char *(*proxy_sqlite3_errmsg)(sqlite3*);
extern int (*proxy_sqlite3_finalize)(sqlite3_stmt *pStmt);
extern int (*proxy_sqlite3_reset)(sqlite3_stmt *pStmt);
extern int (*proxy_sqlite3_clear_bindings)(sqlite3_stmt*);
extern int (*proxy_sqlite3_close_v2)(sqlite3*);
extern int (*proxy_sqlite3_get_autocommit)(sqlite3*);
extern void (*proxy_sqlite3_free)(void*);
extern int (*proxy_sqlite3_status)(int op, int *pCurrent, int *pHighwater, int resetFlag);
extern int (*proxy_sqlite3_status64)(int op, long long *pCurrent, long long *pHighwater, int resetFlag);
extern int (*proxy_sqlite3_changes)(sqlite3*);
extern long long (*proxy_sqlite3_total_changes64)(sqlite3*);
extern int (*proxy_sqlite3_step)(sqlite3_stmt*);
extern int (*proxy_sqlite3_config)(int, ...);
extern int (*proxy_sqlite3_shutdown)(void);

extern int (*proxy_sqlite3_prepare_v2)(
  sqlite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);

extern int (*proxy_sqlite3_open_v2)(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb,         /* OUT: SQLite db handle */
  int flags,              /* Flags */
  const char *zVfs        /* Name of VFS module to use */
);

extern int (*proxy_sqlite3_exec)(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);
#else
int (*proxy_sqlite3_bind_double)(sqlite3_stmt*, int, double);
int (*proxy_sqlite3_bind_int)(sqlite3_stmt*, int, int);
int (*proxy_sqlite3_bind_int64)(sqlite3_stmt*, int, sqlite3_int64);
int (*proxy_sqlite3_bind_null)(sqlite3_stmt*, int);
int (*proxy_sqlite3_bind_text)(sqlite3_stmt*,int,const char*,int,void(*)(void*));
int (*proxy_sqlite3_bind_blob)(sqlite3_stmt*, int, const void*, int, void(*)(void*));
sqlite3_int64 (*proxy_sqlite3_column_int64)(sqlite3_stmt*, int iCol);
double (*proxy_sqlite3_column_double)(sqlite3_stmt*, int iCol);
sqlite3_int64 (*proxy_sqlite3_last_insert_rowid)(sqlite3*);
const char *(*proxy_sqlite3_errstr)(int);
sqlite3* (*proxy_sqlite3_db_handle)(sqlite3_stmt*);
const char *(*proxy_sqlite3_column_name)(sqlite3_stmt*, int N);
const unsigned char *(*proxy_sqlite3_column_text)(sqlite3_stmt*, int iCol);
int (*proxy_sqlite3_column_bytes)(sqlite3_stmt*, int iCol);
int (*proxy_sqlite3_column_type)(sqlite3_stmt*, int iCol);
int (*proxy_sqlite3_column_count)(sqlite3_stmt *pStmt);
int (*proxy_sqlite3_column_int)(sqlite3_stmt*, int iCol);
int (*proxy_sqlite3_enable_load_extension)(sqlite3*, int);
int (*proxy_sqlite3_auto_extension)(void(*)(void));
void (*proxy_sqlite3_global_stats_row_step)(SQLite3DB*, sqlite3_stmt*, const char*, ...);
const char *(*proxy_sqlite3_errmsg)(sqlite3*);
int (*proxy_sqlite3_finalize)(sqlite3_stmt *pStmt);
int (*proxy_sqlite3_reset)(sqlite3_stmt *pStmt);
int (*proxy_sqlite3_clear_bindings)(sqlite3_stmt*);
int (*proxy_sqlite3_close_v2)(sqlite3*);
int (*proxy_sqlite3_get_autocommit)(sqlite3*);
void (*proxy_sqlite3_free)(void*);
int (*proxy_sqlite3_status)(int op, int *pCurrent, int *pHighwater, int resetFlag);
int (*proxy_sqlite3_status64)(int op, long long *pCurrent, long long *pHighwater, int resetFlag);

int (*proxy_sqlite3_changes)(sqlite3*);
long long (*proxy_sqlite3_total_changes64)(sqlite3*);
int (*proxy_sqlite3_step)(sqlite3_stmt*);
int (*proxy_sqlite3_config)(int, ...);
int (*proxy_sqlite3_shutdown)(void);

int (*proxy_sqlite3_prepare_v2)(
  sqlite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);

int (*proxy_sqlite3_open_v2)(
  const char *filename,   /* Database filename (UTF-8) */
  sqlite3 **ppDb,         /* OUT: SQLite db handle */
  int flags,              /* Flags */
  const char *zVfs        /* Name of VFS module to use */
);

int (*proxy_sqlite3_exec)(
  sqlite3*,                                  /* An open database */
  const char *sql,                           /* SQL to be evaluated */
  int (*callback)(void*,int,char**,char**),  /* Callback function */
  void *,                                    /* 1st argument to callback */
  char **errmsg                              /* Error msg written here */
);
#endif //MAIN_PROXY_SQLITE3
class SQLite3_row {
	public:
	int cnt;
	int ds;
	int *sizes;
	char **fields;
	char *data;
	SQLite3_row(int c);
	unsigned long long get_size();
	~SQLite3_row();
	void add_fields(sqlite3_stmt *stmt);
	void add_fields(char **_fields);
};

class SQLite3_column {
	public:
	int datatype;
	char *name;
	SQLite3_column(int a, const char *b);
	~SQLite3_column();
};

class SQLite3_result {
	public:
	pthread_mutex_t m;
	bool enabled_mutex;
	int columns;
	int rows_count;
	char *checksum();
	uint64_t raw_checksum();

	std::vector<SQLite3_column *> column_definition;
	std::vector<SQLite3_row *> rows;
	SQLite3_result();
	SQLite3_result(SQLite3_result *);
	unsigned long long get_size();
	void add_column_definition(int a, const char *b);
	int add_row(sqlite3_stmt *stmt, bool skip=false);
	int add_row(char **_fields);
	int add_row(const char **_fields) { return add_row((char **)_fields); }
	int add_row(SQLite3_row *old_row);
	int add_row(const char* _field, ...);
	SQLite3_result(sqlite3_stmt *stmt);
	SQLite3_result(sqlite3_stmt *stmt, int *found_rows, unsigned int offset, unsigned int limit);
	SQLite3_result(int num_columns, bool en_mutex=false);
	~SQLite3_result();
	void dump_to_stderr();
};

/**
 * @brief Helper type for finalizing 'sqlite3_stmt' managed by smart pointers.
 */
struct stmt_deleter_t {
	void operator()(sqlite3_stmt* x) const;
};

/**
 * @brief Safe type for automatically deallocation of 'sqlite3_stmt'.
 */
using stmt_unique_ptr = std::unique_ptr<sqlite3_stmt, stmt_deleter_t>;

class SQLite3DB {
	private:
	char *url;
	sqlite3 *db;
	pthread_rwlock_t rwlock;
	public:
	char *get_url() const { return url; }
	sqlite3 *get_db() const { return db; }
	int assert_on_error;
	SQLite3DB();
	~SQLite3DB();
	int open(char *, int);

	void rdlock();
	void rdunlock();
	void wrlock();
	void wrunlock();

	bool execute(const char *);
	bool execute_statement(const char *, char **, int *, int *, SQLite3_result **);
	SQLite3_result* execute_statement(const char *, char **_error=NULL, int *_cols=NULL, int *_affected_rows=NULL);
	bool execute_statement_raw(const char *, char **, int *, int *, sqlite3_stmt **);
	bool execute_prepared(sqlite3_stmt* statement, char** error, int* cols, int* affected_rows, SQLite3_result** resultset);
	SQLite3_result* execute_prepared(sqlite3_stmt* statement, char** _error, int* cols, int* affected_rows);
	int return_one_int(const char *);
	int check_table_structure(const char *table_name, const char *table_def);
	bool build_table(const char *table_name, const char *table_def, bool dropit);
	bool check_and_build_table(const char *table_name, const char *table_def);
	[[deprecated("Use safer alternative 'prepare_v2(const char *)'")]]
	int prepare_v2(const char *, sqlite3_stmt **);
	/**
	 * @brief Prepares a query as a statement in the SQLite3DB.
	 * @param query The query to be prepared as an 'sqlite3_stmt'.
	 * @return A pair of with shape { err_code, stmt_unique_ptr }.
	 */
	std::pair<int,stmt_unique_ptr> prepare_v2(const char* query);
	static void LoadPlugin(const char *);
};

#endif /* PROXYSQL_SQLITE3DB_H */
