#pragma once
#include "Driver.h"
#include "Host.h"
#include "Value.h"
#include "SequentialConnection.h"

namespace sql {

	/**
	 * Base class for MySQL-derivatives.
	 *
	 * Use the MySQL or MariaDB classes to connect.
	 */
	class MariaDBBase : public SequentialConnection {
		STORM_CLASS;
	public:
		class Stmt;

		// Destructor of an Mariadb database connection. Calls MariaDB:close().
		virtual ~MariaDBBase();

		// Prepares a statement for execution.
		using DBConnection::prepare;
		Statement *STORM_FN prepare(Str *query) override;

		// Closes the connection.
		void STORM_FN close() override;

		// Returns all names of tables in MariaDB connection in an Array of Str.
		Array<Str*> *STORM_FN tables() override;

		// Returns a Schema for MariaDB connection.
		MAYBE(Schema *) STORM_FN schema(Str *table) override;

		// Migrate database.
		void STORM_FN migrate(Migration *migration) override;

		// Get database features.
		virtual features::DBFeatures STORM_FN features() const override;

	protected:
		// Create the connection, called from derived classes.
		STORM_CTOR MariaDBBase(Host c, MAYBE(Str *) user, MAYBE(Str *) password, Str *database);

		// Create a visitor.
		QueryStr::Visitor *STORM_FN visitor() const override;

		// Transaction management.
		void STORM_FN beginTransaction() override;
		void STORM_FN endTransaction(Transaction::End end) override;

		// Throw an error if one is present.
		void throwError();

		/**
		 * Interface from SequentialConnection.
		 */

		// Finalize a statement.
		virtual void STORM_FN finalizeStmt(SequentialStatement *stmt) override;

	private:
		// Handle to the database.
		UNKNOWN(PTR_NOGC) MYSQL *handle;

		// Pointer to the API block of the handle for easy access.
		UNKNOWN(PTR_NOGC) st_mariadb_api *api;

		// Cached query for last row id.
		Stmt *lastRowIdQuery;

		// Find the last row id.
		Int queryLastRowId();

		// Helper to migrate a single table.
		void migrateTable(Migration::Table *m);

		// Helper to perform one-off queries.
		void query(const char *query);
		void query(Str *query);
		void query(QueryStr *query);

	public:
		/**
		 * Prepared statement.
		 */
		class Stmt : public SequentialStatement {
			STORM_CLASS;
		public:
			// Bind parameters.
			void STORM_FN bind(Nat pos, Str *str) override;
			void STORM_FN bind(Nat pos, Bool b) override;
			void STORM_FN bind(Nat pos, Int i) override;
			void STORM_FN bind(Nat pos, Long l) override;
			void STORM_FN bind(Nat pos, Float f) override;
			void STORM_FN bind(Nat pos, Double d) override;
			void STORM_FN bindNull(Nat pos) override;

		protected:
			// Get last row id and changes.
			Int STORM_FN lastRowId() override { return lastId; }
			Nat STORM_FN changes() override { return lastChanges; }

			// Execute the query.
			Bool STORM_FN executeSeq() override;

			// Get next row.
			Maybe<Row> STORM_FN nextRowSeq() override;

			// Dispose result.
			void STORM_FN disposeResultSeq() override;

		private:
			friend class MariaDBBase;

			// Create.
			Stmt(MariaDBBase *owner, Str *query);

			// The prepared statement itself.
			UNKNOWN(PTR_NOGC) MYSQL_STMT *stmt;

			// Saved row id.
			Int lastId;

			// Saved number of changes.
			Nat lastChanges;


			/**
			 * Bound buffers for the parameters:
			 *
			 * Arrays are allocated with malloc since the library has pointers into them (at least
			 * "values", but maybe also to "resultBind").
			 */

			Nat paramCount;
			UNKNOWN(PTR_NOGC) MYSQL_BIND *paramBinds;
			UNKNOWN(PTR_NOGC) Value *paramValues;


			/**
			 * Bound buffers for the result:
			 *
			 * Arrays are allocated with malloc since the library has pointers into them (at least
			 * "values", but maybe also to "resultBind").
			 */

			Nat resultCount;
			UNKNOWN(PTR_NOGC) MYSQL_BIND *resultBinds;
			UNKNOWN(PTR_NOGC) Value *resultValues;

			// Get the owner.
			MariaDBBase *owner() const {
				return reinterpret_cast<MariaDBBase *>(connection());
			}

			// Throw error.
			void throwError();
		};


		/**
		 * Visitor to transform query strings.
		 */
		class Visitor : public QueryStr::Visitor {
			STORM_CLASS;
		public:
			STORM_CTOR Visitor();
			void STORM_FN name(StrBuf *to, Str *name) override;
			void STORM_FN type(StrBuf *to, QueryType type) override;
			void STORM_FN autoIncrement(StrBuf *to) override;
		};
	};


	/**
	 * Connection to MySQL databases.
	 *
	 * Note: The distinction between MySQL and MariaDB is mostly for future-proofing any differences
	 * that might arise eventually. They are currently compatible enough for this difference to be
	 * irrelevant.
	 */
	class MySQL : public MariaDBBase {
		STORM_CLASS;
	public:
		// Connect.
		STORM_CTOR MySQL(Host c, MAYBE(Str *) user, MAYBE(Str *) password, Str *database);
	};


	/**
	 * Connection to MariaDB databases.
	 *
	 * Note: The distinction between MySQL and MariaDB is mostly for future-proofing any differences
	 * that might arise eventually. They are currently compatible enough for this difference to be
	 * irrelevant.
	 */
	class MariaDB : public MariaDBBase {
		STORM_CLASS;
	public:
		// Connect.
		STORM_CTOR MariaDB(Host c, MAYBE(Str *) user, MAYBE(Str *) password, Str *database);

		// Get database features.
		virtual features::DBFeatures STORM_FN features() const override;
	};

}
