#pragma once
#include "Core/TObject.h"
#include "Core/GcType.h"
#include "Arena.h"
#include "Listing.h"
#include "RefSource.h"
#include "StackFrame.h"

namespace code {
	STORM_PKG(core.asm);

	/**
	 * A Binary represents a Listing that has been translated into machine code, along with any
	 * extra information needed (such as descriptions of exception handlers or similar).
	 *
	 * Note: The Binary class expects backends to create a table of data starting at the meta() label
	 * where each element looks like this:
	 *   size_t freeFn; - a pointer to the function to be executed when this variable is to be freed.
	 *   size_t offset; - a signed offset to the variable relative to the stack frame provided by the backend.
	 * There shall be one entry for each variable in the Listing. Variable 0 shall be at index 0.
	 */
	class Binary : public Content {
		STORM_CLASS;
	public:
		// Translate a listing into machine code.
		STORM_CTOR Binary(Arena *arena, Listing *src);

		// Data structure containing more information about the generated code.
		class Info {
			STORM_VALUE;
		public:
			// The created binary.
			Binary *binary;

			// The transformed listing.
			Listing *transformed;

			// Offsets of all labels.
			Array<Nat> *offsets;

			// The variable layout. Not available when 'compileTransformed' is called.
			Array<Offset> *varLayout;

			// Create.
			STORM_CTOR Info(Binary *binary, Listing *transformed, Array<Nat> *offsets, Array<Offset> *layout)
				: binary(binary), transformed(transformed), offsets(offsets), varLayout(layout) {}
		};

		// Create a binary, returning more information than the constructor.
		static Info STORM_FN compile(Arena *arena, Listing *src);

		// Create a binary, but from a Listing that is already transformed for the current arena.
		static Info STORM_FN compileTransformed(Arena *arena, Listing *transformed);

		// Clean up a stack frame from this function.
		void cleanup(StackFrame &frame);

		// Clean up a stack frame from this function from the current block, up to and including
		// 'until'. Returns the parent of 'until', which is now to be considered the current part.
		Nat cleanup(StackFrame &frame, Nat until);

		// Check if we have any catch-clauses at all (used for pre-screening in exception handlers).
		inline bool hasCatch() const { return tryBlocks != null; }

		// Information on how to resume from a try-clause.
		struct Resume {
			// Next instruction to execute. 'null' if nothing is to be done.
			void *ip;

			// Offset from the frame pointer to the stack pointer.
			ptrdiff_t stackOffset;

			// Part outside the one handling the exception.
			size_t cleanUntil;
		};

		// Check if we have a catch-clause for the active part or any of its parents.
		bool hasCatch(Nat part, RootObject *exception, Resume &resume);

		// Get the stack offset for this binary, i.e. the difference between the stack pointer and
		// the frame pointer for this function (mainly used for cleanup during exceptions).
		ptrdiff_t stackOffset() const;

		// Get the size of the stack frame. On some platforms, this differs from the stack offset,
		// as it depends on where in the stack frame pointer refers to.
		size_t stackSize() const;

		// Exception status:
		inline Bool STORM_FN exceptionCleanup() const { return (flags & ehClean) != 0; }
		inline Bool STORM_FN exceptionCaught() const { return (flags & ehCatch) != 0; }
		inline Bool STORM_FN exceptionAware() const { return (flags & (ehCatch | ehClean)) != 0; }

		// Is this a binary for a member function?
		inline Bool STORM_FN isMember() const { return (flags & isMemberFn) != 0; }
		// Get result from the function.
		MAYBE(TypeDesc *) STORM_FN result() const;
		// Get parameters to the function.
		Array<TypeDesc *> *STORM_FN params() const;

		// Output to string.
		virtual void STORM_FN toS(StrBuf *to) const;

		// Information about a single variable.
		struct Variable {
			Nat id;

			// The low 16 bits contain FreeOpt, the high 16 bits contain the size of the variable.
			Nat flags;

			enum {
				// Parameter size
				sUnknown = 0x0000,
				sByte = 0x1000,
				sInt = 0x2000,
				sLong = 0x3000,
				sPtr = 0x4000,
				sMask = 0xFF000,

				// Mask for freeOpts.
				sFreeOptMask = 0xFFF,

				// Mask for finding the parameter index (+1).
				sParamMask = 0x0FF00000,
				sParamShift = 20,
			};

			// Variable information. Might be null.
			Listing::VarInfo *varInfo;
		};

		// Remember the block hierarchy. These are allocated as GcArray:s to reduce the number of allocations.
		struct Block {
			// Number of elements.
			const size_t count;

			// Parent block.
			size_t parent;

			// Variables in here.
			Variable vars[1];
		};

		// Get access to the block table, intended for 'compatibleSkeleton':
		GcArray<Block *> *blockInfo() { return blocks; }

		// Get information about variable cleanup.
		VarCleanup *cleanupInfo();

		// Find a code updater for a specific offset in the code here.
		MAYBE(Reference *) STORM_FN findReferenceByOffset(Nat offset);

		// Find a code updater for a specific CodeRef slot in the code here.
		MAYBE(Reference *) STORM_FN findReferenceBySlot(Nat refSlot);

	private:
		// For use inside 'compile'.
		Binary();

		// All blocks.
		GcArray<Block *> *blocks;

		// Information for try-blocks.
		struct TryInfo {
			// Current block id.
			Nat blockId;

			// Offset to continue execution from.
			Nat resumeOffset;

			// Type to catch.
			Type *type;
		};

		// Try-block information. 'null' if no exceptions are caught. Indices do not correspond to
		// parts here since the array is sparse. It is necessary to search the array (perhaps using
		// a binary search).
		GcArray<TryInfo> *tryBlocks;

		// Result and parameter TypeDescs. Element 0 is the result typedesc, remaining is parameters.
		GcArray<TypeDesc *> *resultAndParams;

		// Offset of the metadata label.
		Nat metaOffset;

		// Other misc. flags that contains information about the code.
		enum {
			// Mirrors 'exceptionCleanup' and 'exceptionCaught' in the Listing object.
			ehClean = 0x01,
			ehCatch = 0x02,

			// Is this a member function?
			isMemberFn = 0x10,
		};
		Nat flags;

		// Compile the Listing object.
		Info compileI(Arena *arena, Listing *src, Bool skipTransform);

		// Fill the 'blocks' array.
		void fillBlocks(Listing *src);

		// Fill the 'tryBlocks' array if necessary.
		void fillTryBlocks(Listing *src, LabelOutput *labels);

		// Clean a single variable.
		void cleanup(StackFrame &frame, Variable &v);

		// Fill 'resultAndParams'.
		void fillResultAndParams(Listing *src);

		// Get the raw value of 'stackOffset'.
		ptrdiff_t rawStackOffset() const;

		// Type declarations for the GC.
		static const GcType blockType;
		static const GcType tryInfoArrayType;
	};

}
