#include "stdafx.h"
#include "Binary.h"
#include "Exception.h"
#include "Core/StrBuf.h"

namespace code {

	Binary::Binary(Arena *arena, Listing *listing) {
		compile(arena, listing, false);
	}

	Binary::Binary(Arena *arena, Listing *listing, Bool debug) {
		compile(arena, listing, debug);
	}

	void Binary::compile(Arena *arena, Listing *listing, Bool debug) {
		Listing *tfm = arena->transform(listing);
		if (debug)
			PVAR(tfm);

		fillBlocks(tfm);

		LabelOutput *labels = arena->labelOutput();
		arena->output(tfm, labels);

		fillTryBlocks(tfm, labels);

		if (tfm->meta().id < labels->offsets->count()) {
			metaOffset = labels->offsets->at(tfm->meta().id);
		} else {
			metaOffset = 0;
			WARNING(L"No metadata seems to have been generated by the backend.");
			WARNING(L"Exception cleanup will not work!");
		}

		CodeOutput *output = arena->codeOutput(this, labels);
		arena->output(tfm, output);

		runtime::codeUpdatePtrs(output->codePtr());
		set(output->codePtr(), output->tell());

#ifdef DEBUG
		// All backends should output the pointer to the binary like this.
		assert(codeBinary(output->codePtr()) == this);
#endif
	}

	void Binary::toS(StrBuf *to) const {
		*to << S("Binary object:");

		const nat columns = 16;
		const byte *code = (const byte *)address();
		if (!code) {
			*to << S(" <null>");
			return;
		}

		size_t size = runtime::codeSize(code);
		for (size_t i = 0; i < size; i++) {
			if (i % columns == 0) {
				*to << S("\n") << hex(i) << S(" -");
			}

			*to << S(" ") << hex(code[i]);
		}
	}

	const GcType Binary::blockArrayType = {
		GcType::tArray,
		null,
		null,
		sizeof(void *),
		1,
		{ 0 },
	};

	const GcType Binary::blockType = {
		GcType::tArray,
		null,
		null,
		sizeof(Variable),
		0,
		{},
	};

	const GcType Binary::tryInfoArrayType = {
		GcType::tArray,
		null,
		null,
		sizeof(TryInfo),
		1,
		{ OFFSET_OF(TryInfo, type) },
	};

	void Binary::fillBlocks(Listing *src) {
		Array<code::Block> *srcBlocks = src->allBlocks();

		blocks = runtime::allocArray<Block *>(engine(), &blockArrayType, srcBlocks->count());

		for (Nat i = 0; i < srcBlocks->count(); i++) {
			code::Block block = srcBlocks->at(i);
			Array<Var> *vars = src->allVars(block);

			// Count variables that need finalization. Those are the only ones we need.
			size_t count = 0;
			for (Nat j = 0; j < vars->count(); j++)
				if (src->freeOpt(vars->at(j)) & freeOnException)
					count++;

			Block *b = (Block *)runtime::allocArray(engine(), &blockType, count);
			blocks->v[i] = b;
			b->parent = src->parent(block).key();

			size_t at = 0;
			for (Nat j = 0; j < vars->count(); j++) {
				const Var &v = vars->at(j);
				Nat flags = src->freeOpt(v);

				// We only include the ones we actually need.
				if ((flags & freeOnException) == 0)
					continue;

				if (flags & freePtr) {
					// No additional flags needed, but we set sPtr for good measure.
					flags |= Variable::sPtr;
				} else if (v.size() == Size::sPtr) {
					flags |= Variable::sPtr;
				} else if (v.size() == Size::sByte) {
					flags |= Variable::sByte;
				} else if (v.size() == Size::sInt) {
					flags |= Variable::sInt;
				} else if (v.size() == Size::sLong) {
					flags |= Variable::sLong;
				} else {
					throw new (this) InvalidValue(S("Can only use bytes, integers, longs and pointers for ")
												S("variable cleanup. Specify 'freePtr' to get a pointer to ")
												S("the value instead!"));
				}

				b->vars[at].id = v.key();
				b->vars[at].flags = flags;
				at++;
			}
		}
	}

	void Binary::fillTryBlocks(Listing *src, LabelOutput *labels) {
		Nat count = 0;

		Array<code::Block> *blocks = src->allBlocks();
		for (Nat i = 0; i < blocks->count(); i++) {
			if (Array<Listing::CatchInfo> *info = src->catchInfo(blocks->at(i)))
				count += info->count();
		}

		if (count == 0) {
			tryBlocks = null;
			return;
		}

		tryBlocks = runtime::allocArray<TryInfo>(engine(), &tryInfoArrayType, count);
		Nat at = 0;
		for (Nat i = 0; i < blocks->count(); i++) {
			code::Block b = blocks->at(i);
			if (Array<Listing::CatchInfo> *info = src->catchInfo(b)) {
				for (Nat j = 0; j < info->count(); j++) {
					tryBlocks->v[at].blockId = code::Block(b).key();
					tryBlocks->v[at].resumeOffset = labels->offsets->at(info->at(j).resume.id);
					tryBlocks->v[at].type = info->at(j).type;
					at++;
				}
			}
		}
	}

	void Binary::cleanup(StackFrame &frame) {
		for (size_t i = frame.block; i != code::Block().key(); i = blocks->v[i]->parent) {
			Block *b = blocks->v[i];

			// Reverse order is common.
			for (size_t j = b->count; j > 0; j--) {
				cleanup(frame, b->vars[j - 1]);
			}
		}
	}

	Nat Binary::cleanup(StackFrame &frame, Nat until) {
		for (size_t i = frame.block; i != code::Block().key(); i = blocks->v[i]->parent) {
			Block *b = blocks->v[i];

			// Reverse order is common.
			for (size_t j = b->count; j > 0; j--) {
				cleanup(frame, b->vars[j - 1]);
			}

			// Done?
			if (i == until)
				return Nat(blocks->v[i]->parent);
		}

		// Outside of all blocks.
		return code::Block().key();
	}

	void Binary::cleanup(StackFrame &frame, Variable &v) {
		if (v.flags & freeOnException) {
			// Element #0 is the total size, then VarCleanup-instances start.
			byte *data = (byte *)address() + metaOffset + sizeof(void *);
			VarCleanup *vars = (VarCleanup *)data;

			VarCleanup &now = vars[v.id];
			void *freeFn = now.function;
			int offset = now.offset;
			nat activeAfter = now.activeAfter;

			// If not active, we don't destroy it.
			if (frame.activation < activeAfter)
				return;

			void *ptr = frame.toPtr(offset);

			if (v.flags & freeIndirection)
				ptr = *(void **)ptr;

			typedef void (*FPtr)(void *v);
			typedef void (*FByte)(Byte v);
			typedef void (*FInt)(Int v);
			typedef void (*FLong)(Long v);

			if (v.flags & freePtr) {
				FPtr p = (FPtr)freeFn;
				(*p)(ptr);
			} else {
				switch (v.flags & Variable::sMask) {
				case Variable::sPtr: {
					FPtr p = (FPtr)freeFn;
					(*p)(*(void **)ptr);
					break;
				}
				case Variable::sByte: {
					FByte p = (FByte)freeFn;
					(*p)(*(Byte *)ptr);
					break;
				}
				case Variable::sInt: {
					FInt p = (FInt)freeFn;
					(*p)(*(Int *)ptr);
					break;
				}
				case Variable::sLong: {
					FLong p = (FLong)freeFn;
					(*p)(*(Long *)ptr);
					break;
				}
				}
			}
		}
	}

	bool Binary::hasCatch(Nat active, RootObject *exception, Resume &resume) {
		struct Compare {
			inline bool operator() (const TryInfo &l, Nat r) const {
				return l.blockId < r;
			}
		};

		if (!tryBlocks)
			return false;

		for (size_t block = active; block != code::Block().key(); block = blocks->v[block]->parent) {
			TryInfo *end = tryBlocks->v + tryBlocks->count;
			TryInfo *found = std::lower_bound(tryBlocks->v, end, Nat(block), Compare());

			// Check all possible matches.
			for (; found != end && found->blockId == block; found++) {
				if (runtime::isA(exception, found->type)) {
					// Find where to resume.
					byte *data = (byte *)address();
					resume.ip = data + found->resumeOffset;

					// The first entry is the total stack depth.
					ptrdiff_t *table = (ptrdiff_t *)(data + metaOffset);
					resume.stackOffset = table[0];

					// Remember how far to clean.
					resume.cleanUntil = block;

					return true;
				}
			}
		}

		return false;
	}

	ptrdiff_t Binary::stackOffset() const {
		byte *data = (byte *)address();
		// First entry in the metadata table is the stack offset.
		ptrdiff_t *table = (ptrdiff_t *)(data + metaOffset);
		return table[0];
	}

}
