#pragma once
#include "Core/Thread.h"
#include "Core/Fn.h"
#include "Code/Reference.h"
#include "Code/DelegatedRef.h"
#include "Code/Listing.h"
#include "Code/Binary.h"
#include "Value.h"
#include "CodeGen.h"
#include "InlineParams.h"

namespace storm {
	STORM_PKG(core.lang);

	class Function;

	/**
	 * Code is a block of machine code belonging to a function. This code takes parameters as
	 * described by the Function it belongs to. The reason we're not using code::Binary directly is
	 * to easier management of lazy compilation, which code::Binary does not support in an easy way.
	 */
	class Code : public ObjectOn<Compiler> {
		STORM_CLASS;
	public:
		// Create.
		STORM_CTOR Code();

		// Attach to a specific function. Done by the function itself before 'update' is called.
		void STORM_FN attach(Function *owner);

		// Detach from the old owner.
		void STORM_FN detach();

		// Set up the RefSource. This 'ref' shall be kept updated until the destruction of this
		// object or when 'update' is called with another object.
		void STORM_FN update(code::RefSource *ref);

		// Force compilation (if needed).
		virtual void STORM_FN compile();

	protected:
		// A new reference has been found.
		virtual void STORM_FN newRef();

		// The reference to update.
		MAYBE(code::RefSource *) toUpdate;

		// Owning function.
		MAYBE(Function *) owner;
	};


	/**
	 * Statically allocated chunks of code.
	 */
	class StaticCode : public Code {
		STORM_CLASS;
	public:
		// Code to be executed.
		StaticCode(const void *ptr);

	protected:
		virtual void STORM_FN newRef();

	private:
		const void *ptr;
	};


	/**
	 * Delegate to another piece of code (no runtime overhead).
	 */
	class DelegatedCode : public Code {
		STORM_CLASS;
	public:
		STORM_CTOR DelegatedCode(code::Ref ref);

		// Get what we refer to.
		code::Ref STORM_FN to() const;

	protected:
		virtual void STORM_FN newRef();

	private:
		// Our content.
		code::DelegatedContent *content;
	};


	/**
	 * Subclass for statically allocated chunks of code. Automatically insert a 'EnginePtr' as the
	 * first parameter to the function.
	 */
	class StaticEngineCode : public Code {
		STORM_CLASS;
	public:
		StaticEngineCode(const void *code);

	protected:
		virtual void STORM_FN newRef();

	private:
		// Code to be executed.
		code::Binary *code;

		// Reference to the original code.
		code::RefSource *original;

		// Generate code which does the redirection. Assumes 'owner' is set.
		code::Listing *redirectCode(code::Ref ref);
	};


	/**
	 * Code generated by the system. The original intermediate code is cached until the object is
	 * told to discard it. Normally, the Package loading the code will tell all functions to discard
	 * their contents after the loading is complete to save memory, but this behavior can be turned
	 * off if desired.
	 */
	class GeneratedCode : public Code {
		STORM_CLASS;
	public:
		// Get the dynamically generated code, if it is still present. Otherwise, returns null.
		virtual MAYBE(code::Listing *) STORM_FN source();

		// Tell the implementation to discard any cached code.
		virtual void STORM_FN discardSource();
	};


	/**
	 * Dynamically generated code (eagerly compiled).
	 */
	class DynamicCode : public GeneratedCode {
		STORM_CLASS;
	public:
		STORM_CTOR DynamicCode(code::Listing *code);

		virtual MAYBE(code::Listing *) STORM_FN source();
		virtual void STORM_FN discardSource();

	protected:
		virtual void STORM_FN newRef();

	private:
		code::Listing *code;
		code::Binary *binary;
	};


	/**
	 * Lazily generated code.
	 */
	class LazyCode : public GeneratedCode {
		STORM_CLASS;
	public:
		// The 'generate' function will be called when code needs to be generated.
		STORM_CTOR LazyCode(Fn<CodeGen *> *generate);

		// Compile.
		virtual void STORM_FN compile();

		// Get source.
		virtual MAYBE(code::Listing *) STORM_FN source();

		// Discard source.
		virtual void STORM_FN discardSource();

		// Called to update code.
		static const void *CODECALL updateCode(LazyCode *c);

	protected:
		// Update reference.
		virtual void STORM_FN newRef();

	private:
		// Currently used code.
		code::Binary *binary;

		// Shared memory between the 'generate' function and the source listing.
		UNKNOWN(PTR_GC) void *sourceData;

		// Generate code using this function.
		inline Fn<CodeGen *> *generate() const { return (Fn<CodeGen *> *)sourceData; }

		// State enum.
		enum {
			// Nothing is loaded, 'sourceData' contains 'generate'.
			sUnloaded = 0x00,

			// Code is currently loading. 'sourceData' still contains 'generate'.
			sLoading = 0x01,

			// Code is loaded, 'sourceData' contains either the source listing or nothing.
			sLoaded = 0x02,

			// Mask for the sequential states above.
			sMask = 0x0F,

			// Is the source listing to be discarded? (OR:ed with others)
			sDiscardSource = 0x10
		};

		// Current state.
		Nat state;

		// Called to update code from the Compiler thread.
		static const void *CODECALL updateCodeLocal(LazyCode *c);

		// Create a redirect chunk of code.
		void createRedirect();

		// Set the code in here.
		void setCode(code::Listing *src);
	};


	/**
	 * Subclass for inlined code. Lazily provides a non-inlined variant as well.
	 */
	class InlineCode : public LazyCode {
		STORM_CLASS;
	public:
		STORM_CTOR InlineCode(Fn<void, InlineParams> *create);

		// Generate inlined code.
		virtual void STORM_FN code(CodeGen *state, Array<code::Operand> *params, CodeResult *result);

	private:
		// Generate function.
		Fn<void, InlineParams> *create;

		// Generate a non-inline version as well.
		CodeGen *CODECALL generatePtr();
	};


	// Create code that throws an AbstractFnCalled exception, suitable to use for abstract functions.
	Code *STORM_FN abstractThrowCode(Value result, Array<Value> *params, Str *name);

}
