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

namespace sql {

	/**
	 * PostgreSQL connection.
	 */
	class PostgreSQL : public SequentialConnection {
		STORM_CLASS;
	public:
		// Create.
		STORM_CTOR PostgreSQL(Host host, MAYBE(Str *) user, MAYBE(Str *) password, Str *database);

		// Destructor, calls 'close'.
		virtual ~PostgreSQL();

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

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

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

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

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

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

	protected:
		// Visitors.
		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.
		UNKNOWN(PTR_NOGC) PGconn *connection;

		// Cancellation handle, new version.
		UNKNOWN(PTR_NOGC) PGcancelConn *newCancel;

		// Cancellation handle, old version.
		UNKNOWN(PTR_NOGC) PGcancel *oldCancel;

		// PostgreSQL driver (access to the API).
		UNKNOWN(PTR_NOGC) const PGDriver *driver;

		// Counter for names of prepared statements.
		Nat nextNameId;

		// Query for retrieving the last row id.
		Statement *lastRowIdQuery;

		// Get a new name of a prepared statement.
		const char *nextName();

		// Query for the last row ID.
		Int getLastRowId();

		// Free result after checking for errors.
		void checkAndClear(PGresult *result);

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

		// One-off queries.
		void query(const char *query);
		void query(Str *query);
		void query(QueryStr *query);

		// Get a single string from a query. We use it to find constraints.
		MAYBE(Str *) queryStr(Str *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 PostgreSQL;

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

			// Helper to free the stmt.
			void free();

			// Name of this prepared statement.
			const char *name;

			// Saved row id.
			Int lastId;

			// Saved number of changes.
			Nat lastChanges;

			// Are we currently streaming data from the database? (i.e. is it worth trying to cancel?)
			Bool streamingData;

			// Number of prepared parameters and their types. These are allocated from the regular C++ heap.
			Nat paramCount;
			Oid *paramTypes;
			char **paramVals;
			int *paramLengths;
			int *paramFormats;

			// Number of columns in the output. This is to determine if we return any form of result or not.
			Nat fieldCount;

			// Get the owner.
			PostgreSQL *owner() const {
				return reinterpret_cast<PostgreSQL *>(connection());
			}
			// Get the driver.
			const PGDriver &driver() const {
				return *owner()->driver;
			}
			// Get the connection.
			PGconn *pgConnection() const {
				return owner()->connection;
			}

			// Check if an error has occurred, and make sure to clear the result.
			void checkAndClear(PGresult *result);

			// Ensure that a parameter has a specific length.
			void ensureLength(Nat id, Nat length);
		};


		/**
		 * Visitor.
		 */
		class Visitor : public QueryStr::Visitor {
			STORM_CLASS;
		public:
			STORM_CTOR Visitor();

			// Number of placeholders used.
			Nat placeholderCount;

			void STORM_FN name(StrBuf *to, Str *name) override;
			void STORM_FN placeholder(StrBuf *to) override;
			void STORM_FN type(StrBuf *to, QueryType type) override;
			void STORM_FN autoIncrement(StrBuf *to) override;
			void STORM_FN lastRowId(StrBuf *to) override;
		};
	};

}
