#include "stdafx.h"
#include "FnState.h"
#include "Code/Block.h"
#include "Code/Exception.h"

namespace code {

	Nat encodeFnState(Nat block, Nat activation) {
		if (block != Block().key() && block > 0xFFFE)
			throw new (runtime::someEngine()) InvalidValue(S("The X86 backend does not support more than 65535 blocks."));
		if (activation > 0xFFFF)
			throw new (runtime::someEngine()) InvalidValue(S("The X86 backend does not support more than 65536 activations."));

		if (block == Block().key())
			block = 0xFFFF;

		return (block << 16) | activation;
	}

	void decodeFnState(Nat original, Nat &block, Nat &activation) {
		block = (original >> 16) & 0xFFFF;
		activation = original & 0xFFFF;

		if (block == 0xFFFF)
			block = Block().key();
	}


	/**
	 * Description of the data at the end of each function.
	 */
	struct FnData {
		// Number of entries in the block table.
		size_t blockCount;
	};

	/**
	 * Description of a single entry in the block table.
	 */
	struct FnBlock {
		// At which offset do we start?
		Nat offset;

		// Which block?
		Nat block;
	};

	// Get the block data from a function.
	static const FnBlock *getBlocks(const FnData *data) {
		const FnBlock *end = (const FnBlock *)data;
		return end - data->blockCount;
	}

	// Compare object.
	struct BlockCompare {
		bool operator()(Nat offset, const FnBlock &block) const {
			return offset < block.offset;
		}
	};

	Nat findFunctionState(const void *function, size_t offset) {
		size_t size = runtime::codeSize(function);
		return findFunctionStateFromEnd((const byte *)function + size, offset);
	}

	Nat findFunctionStateFromEnd(const void *end, size_t offset) {
		const FnData *data = (const FnData *)end - 1;
		const FnBlock *blocks = getBlocks(data);

		Nat invalid = Block().key();
		// The entries are sorted by their 'offset' member. We can perform a binary search!
		// Note: if there are multiple elements that are equal, we need to pick the last one.
		const FnBlock *found = std::upper_bound(blocks, blocks + data->blockCount, Nat(offset), BlockCompare());
		if (found == blocks) {
			// Before any block, nothing to do!
			return invalid;
		}
		found--;

		return found->block;
	}

}
