#include "stdafx.h"
#include "SequentialConnection.h"

namespace sql {

	/**
	 * Connection.
	 */

	SequentialConnection::SequentialConnection()
		: toFinalize(null), currentFetching(null) {}

	void SequentialConnection::finalizeAll() {
		while (toFinalize) {
			finalizeStmt(toFinalize);
			toFinalize = toFinalize->toFinalize;
		}
	}

	void SequentialConnection::onFinalize(SequentialStatement *stmt) {
		if (currentFetching == stmt || currentFetching == null) {
			// Finalize immediately, nothing is going on.
			finalizeStmt(stmt);
			currentFetching = null;
		} else {
			// Delay finalization until no fetch is going on.
			stmt->toFinalize = toFinalize;
			toFinalize = stmt;
		}
	}

	void SequentialConnection::startFetch(SequentialStatement *stmt) {
		if (currentFetching != stmt)
			clearFetch();
		currentFetching = stmt;
	}

	void SequentialConnection::doneFetch(SequentialStatement *stmt) {
		if (currentFetching == stmt)
			currentFetching = null;

		finalizeAll();
	}

	void SequentialConnection::clearFetch() {
		if (currentFetching) {
			currentFetching->fetchAll();
			// Should be cleared by 'fetchAll'.
			STORM_DBG_ASSERT(currentFetching == null);
			currentFetching = null;
		}
	}

	void SequentialConnection::clearFetch(SequentialStatement *stmt) {
		if (currentFetching == stmt)
			return;
		clearFetch();
	}

	Bool SequentialConnection::isFetching(SequentialStatement *stmt) {
		return currentFetching == stmt;
	}


	/**
	 * Statement.
	 */

	SequentialStatement::SequentialStatement(SequentialConnection *owner)
		: Statement(owner), owner(owner), atEnd(true) {}

	SequentialStatement::~SequentialStatement() {
		finalize();
	}

	void SequentialStatement::finalize() {
		// Invalidate any existing result instances.
		invalidateResult();

		if (owner) {
			owner->onFinalize(this);
			deregister(owner);
		}
		// To avoid multiple finalization, for example when the parent class is closed or finalized before us.
		owner = null;
	}

	Statement::Result SequentialStatement::execute() {
		invalidateResult();
		disposeResult();
		owner->startFetch(this);

		try {
			if (executeSeq()) {
				atEnd = false;
			} else {
				// No result, reset everything.
				disposeResult();
			}
			return Result(this);
		} catch (...) {
			// Error during execute, make sure to indicate that we don't have any result.
			disposeResult();
			throw;
		}
	}

	Maybe<Row> SequentialStatement::nextRow() {
		Maybe<Row> out; // null by default

		if (buffer) {
			if (buffer->any()) {
				out = Maybe<Row>(buffer->last());
				buffer->pop();
			} else {
				buffer = null;
				atEnd = true;
			}
		} else if (!atEnd) {
			out = nextRowSeq();
			if (out.empty())
				fetchDone();
		}

		return out;
	}

	void SequentialStatement::disposeResult() {
		// This may if the statement is explicitly finalized, but result instances linger.
		if (!owner)
			return;

		// Note: 'dispose' may be called just about anytime, so we need to make sure to only call
		// the Seq version when it is actually our turn. Otherwise we fail to uphold the Seq
		// guarantees.
		if (owner->isFetching(this)) {
			disposeResultSeq();
			owner->doneFetch(this);
		}
	}

	void SequentialStatement::fetchDone() {
		// Call dispose to help the client to clean up. We will inhibit future calls as soon as we
		// call 'owner->doneFetch'.
		disposeResultSeq();

		atEnd = true;
		owner->doneFetch(this);
	}

	void SequentialStatement::fetchAll() {
		buffer = new (this) Array<Row>();
		while (!atEnd) {
			Maybe<Row> r = nextRowSeq();
			if (r.any())
				buffer->push(r.value());
			else
				fetchDone();
		}
		buffer->reverse();
	}

}
