#pragma once
#include "Core/TObject.h"
#include "Core/EnginePtr.h"
#include "Output.h"
#include "Operand.h"

namespace code {
	STORM_PKG(core.asm);

	class Listing;
	class Binary;
	class RegSet;
	class TypeDesc;
	class Instr;

	/**
	 * An arena represents a collection of compiled code and external references for some architecture.
	 *
	 * Abstract class, there is one instantiation for each supported platform.
	 */
	class Arena : public ObjectOn<Compiler> {
		STORM_ABSTRACT_CLASS;
	public:
		// Create an arena.
		Arena();

		// Create external references.
		Ref external(const wchar *name, const void *ptr) const;
		RefSource *externalSource(const wchar *name, const void *ptr) const;

		/**
		 * Transform and translate code into machine code.
		 */

		// Detailed info from the transform operation.
		class TransformInfo {
			STORM_VALUE;
		public:
			// The final listing.
			Listing *listing;

			// The layout of all local variables.
			Array<Offset> *varLayout;

			// Create.
			STORM_CTOR TransformInfo(Listing *listing, Array<Offset> *layout)
				: listing(listing), varLayout(layout) {}
		};

		// Transform the code in preparation for this backend's code generation. This is
		// backend-specific. 'owner' is the binary object that will be called to handle exceptions.
		virtual TransformInfo STORM_FN transformInfo(Listing *src) const ABSTRACT;
		virtual Listing *STORM_FN transform(Listing *src) const;

		// Translate a previously transformed listing into machine code for this arena.
		virtual void STORM_FN output(Listing *src, Output *to) const ABSTRACT;

		/**
		 * Create output objects for this backend.
		 */

		// Create an offset-computing output.
		virtual LabelOutput *STORM_FN labelOutput() const ABSTRACT;

		// Create a code-generating output based on sizes computed by a LabelOutput.
		virtual CodeOutput *STORM_FN codeOutput(Binary *owner, LabelOutput *size) const ABSTRACT;

		// Remove all registers not preserved during a function call on this platform. This
		// implementation removes ptrA, ptrB and ptrC, but other Arena implementations may want to
		// remove others as well.
		virtual void STORM_FN removeFnRegs(RegSet *from) const;

		// Get a list of registers that may contain the return value from a function.
		virtual RegSet *STORM_FN fnResultRegs() const ABSTRACT;
		virtual Instr *STORM_FN saveFnResultReg(Reg reg, Operand to) const;
		virtual Instr *STORM_FN restoreFnResultReg(Reg reg, Operand from) const;


		/**
		 * Other backend-specific things.
		 */

		// Create a function that calls another function (optionally with a pointer sized parameter)
		// to figure out which function to actually call. Useful when implementing lazy compilation.
		//
		// Calls 'fn' with 'param' (always pointer-sized or empty) to compute the
		// actual function to call. The actual function (as well as the 'function' implemented by
		// the redirect) takes params as defined by 'params' and returns 'result'.
		//
		// These redirect objects are *not* platform independent!
		virtual Listing *STORM_FN redirect(Bool member, TypeDesc *result, Array<TypeDesc *> *params, Ref fn, Operand param) ABSTRACT;

		// Create a function that calls another (pre-determined) function and appends an 'EnginePtr'
		// object as the first parameter to the other function. Calling member functions in this
		// manner is not supported.
		virtual Listing *STORM_FN engineRedirect(TypeDesc *result, Array<TypeDesc *> *params, Ref fn, Operand engine) ABSTRACT;


		/**
		 * Get the location of the first parameter for a function call. Assumes that a member function is called.
		 *
		 * The location is acquired in two steps: first, an implementation asks the ID of the
		 * parameter location by calling the 'firstParamId(TypeDesc *)' function. This returns one
		 * out of several possible integers describing the parameter location. The number of
		 * possible values can be acquired by calling 'firstParamId(null)'.
		 *
		 * The ID can then be passed to 'firstParamLoc' to get an Operand describing the location.
		 *
		 * This scheme is used so that classes like VTableCalls can detect when two functions with
		 * different return values have the same vtable stub. This allows it to re-use the stubs.
		 */

		// Get the ID of the location of the first param.
		virtual Nat STORM_FN firstParamId(MAYBE(TypeDesc *) desc) ABSTRACT;

		// Access the location of the first parameter in a function size. The returned Operand is
		// always pointer-sized.
		virtual Operand STORM_FN firstParamLoc(Nat id) ABSTRACT;

		// Get a parameter that can safely be used to implement function dispatches.
		virtual Reg STORM_FN functionDispatchReg() ABSTRACT;


		/**
		 * Machine-specific parts of the ability to replace active functions.
		 */

		// Information about a created skeleton.
		class Skeleton : public Object {
			STORM_CLASS;
		public:
			// Saved non-volatile registers.
			Array<Operand> *savedRegs;

			// Location of the non-volatile registers, relative to the frame pointer.
			Array<Operand> *savedLocs;

			// Variable offsets we know about. Some may be empty.
			Array<Operand> *varOffsets;

			// Listing containing variables to make the stack frame compatible the one in the Binary.
			Listing *listing;

			// Array of extra offsets that need to be restored during an update.
			Array<Offset> *extraMetadata;

			// Current block and activation.
			Nat currentBlock;
			Nat currentActivation;

			// How to access variables from a different stack frame of a larger size:
			// < 0: use same offsets as from the other stack frame.
			// >=0: offsets are relative to 'n' bytes after the end of this frame.
			Int accessMode;

			// Create.
			STORM_CTOR Skeleton(Listing *listing);
		};

		// Create a skeleton Listing that has a variable layout that is binary compatible with the
		// code in a given Binary object. The function may also initialize the listing with dummy
		// writes to callee saved registers in order to get the right layout.
		virtual Skeleton *STORM_FN compatibleFrameSkeleton(Binary *binary, Nat offset) ABSTRACT;

		// Update any exception information when replacing an active function.
		virtual void updateEhInfo(const void *function, size_t offset, void *framePointer);

		// Generate code to resize the stack frame of the current function to be as large as
		// required by 'newSize'.
		virtual void STORM_FN resizeStackFrame(Listing *out, Reg tmpReg, Binary *newSize) ABSTRACT;

	protected:

		// Helper function for the first step of 'compatibleFrameSkeleton':
		// Creates a listing, and populates it from the binary.
		Skeleton *frameSkeletonHead(Binary *binary);

		// Helper function for the last step of 'compatibleFrameSkeleton':
		// Assumes that 'preservedRegs' and 'preservedLocs' have been filled in after the first step.
		// Also, expects 'extraWords' indicating the number of words with "overhead" that is allocated
		// on the stack by the backend. Also expects that 'currentBlock' and 'currentActivation' are
		// populated. 'minAlign' is the minimum alignment imposed by the stack layout.
		void frameSkeletonTail(Binary *binary, Skeleton *skeleton, Nat extraWords, Nat minAlign, Bool is64);

		// Version of 'frameSkeletonTail', but for architectures where the frame pointer is placed
		// after the variables rather than before them (e.g. ARM).
		void frameSkeletonTailBelow(Binary *binary, Skeleton *skeleton, Nat extraBelow, Nat extraAbove, Nat minAlign, Bool is64);
	};

	// Create an arena for this platform.
	Arena *STORM_FN arena(EnginePtr e);

	// Extract the Binary associated with a function. This is only valid for code generated with the current backend.
	// 'fn' is expected to be a pointer to the start of a code allocation.
	Binary *codeBinary(const void *fn);
	Binary *codeBinaryImpl(GcCode *refs);

}
