#ifndef _J3_CODE_GEN_H_
#define _J3_CODE_GEN_H_

#include "llvm/IR/IRBuilder.h"
#include "j3/j3codegenvar.h"
#include "j3/j3codegenexception.h"

namespace llvm {
	class Function;
	class Value;
}

namespace vmkit {
	class BumpAllocator;
}

namespace j3 {
	class J3;
	class J3Type;
	class J3Class;
	class J3ArrayClass;	
	class J3ClassLoader;
	class J3Field;
	class J3Method;
	class J3Signature;
	class J3Reader;
	class J3ObjectType;
	class J3Signature;

	class J3OpInfo {
	public:
		llvm::Instruction* insn;
		llvm::BasicBlock*  bb;
		llvm::Type**       metaStack;
		size_t             topStack;
	};

	class J3CodeGen {
	public:
		enum {
			WithMethod = 1,
			WithCaller = 2,
			OnlyTranslate = 4,
			NotUseStub = 8,
			NotNeedGC = 16,
			SupposeClinited = 32,
			NoRuntimeCheck = 64
		};

		bool withMethod() { return mode & WithMethod; }
		bool withCaller() { return mode & WithCaller; }
		bool onlyTranslate() { return mode & OnlyTranslate; }
		bool useStub() { return !(mode & NotUseStub); }
		bool needGC() { return !(mode & NotNeedGC); }
		bool supposeClinited() { return mode & SupposeClinited; }
		bool noRuntimeCheck() { return mode & NoRuntimeCheck; }

	private:
		friend class J3CodeGenVar;
		friend class J3ExceptionTable;
		friend class J3ExceptionNode;
		friend class J3Signature;

		uint32_t               mode;

		vmkit::BumpAllocator*  allocator;
		llvm::Module*          module;
		llvm::IRBuilder<>      builder;
		llvm::Function*        llvmFunction;

		J3*                    vm;
		J3Class*               cl;
		J3ClassLoader*         loader;
		J3Method*              method;
		J3Signature*           signature;
		J3Reader*              codeReader;
		uint32_t               access;

		llvm::BasicBlock*      bbCheckCastFailed;
		llvm::BasicBlock*      bbNullCheckFailed;
		llvm::BasicBlock*      bbRet;

		J3ExceptionTable       exceptions;
		uint32_t               curExceptionNode;

		J3OpInfo*              opInfos;
		uint32_t*              pendingBranchs;
		uint32_t               topPendingBranchs;

		llvm::Type*            uintPtrTy;
		llvm::Value*           nullValue;

		llvm::MDNode*          dbgInfo;
		uint32_t               javaPC;
		uint32_t               bc;

		J3CodeGenVar           locals;
		J3CodeGenVar           stack;
		J3CodeGenVar           ret;

		bool                   closeBB;

		bool                   isWide;

		uint32_t            wideReadU1();
		uint32_t            wideReadS1();

		llvm::Function*     buildFunction(J3Method* method, bool isStub=1);
		llvm::Value*        typeDescriptor(J3ObjectType* objectType, llvm::Type* type);

		llvm::Value*        currentThread();

		llvm::Value*        nullCheck(llvm::Value* obj);

		llvm::BasicBlock*   newBB(const char* name);
		bool                onEndPoint();
		llvm::BasicBlock*   forwardBranch(const char* id, uint32_t pc, bool doAlloc, bool doPush);
		void                condBr(llvm::Value* op);

		llvm::Value*        flatten(llvm::Value* v);
		llvm::Value*        unflatten(llvm::Value* v, llvm::Type* type);

		llvm::Value*        handleToObject(llvm::Value* obj);
		llvm::Value*        javaClass(J3ObjectType* type, bool doPush);
		llvm::Value*        staticObject(J3Class* cl);
		llvm::Value*        vt(J3ObjectType* cl);
		llvm::Value*        vt(llvm::Value* obj);
		void                resolveJ3ObjectType(J3ObjectType* cl);
		void                initialiseJ3ObjectType(J3ObjectType* cl);

		void                monitorEnter(llvm::Value* obj);
		void                monitorExit(llvm::Value* obj);

		llvm::CallInst*     isAssignableTo(llvm::Value* obj, J3ObjectType* type);
		void                instanceof(llvm::Value* obj, J3ObjectType* type);
		void                checkCast(llvm::Value* obj, J3ObjectType* type);

		void                floatToInteger(J3Type* from, J3Type* to);
		void                compareFP(bool isL);
		void                compareLong();

		llvm::Value*        fieldOffset(llvm::Value* obj, J3Field* f);

		void                get(llvm::Value* obj, J3Field* field);
		void                getField(uint32_t idx);
		void                getStatic(uint32_t idx);

		void                put(llvm::Value* obj, llvm::Value* val, J3Field* field);
		void                putField(uint32_t idx);
		void                putStatic(uint32_t idx);

		void                invoke(uint32_t access, J3Method* method, llvm::Value* func);
		void                invokeVirtual(uint32_t idx);
		void                invokeStatic(uint32_t idx);
		void                invokeSpecial(uint32_t idx);
		void                invokeInterface(uint32_t idx);

		llvm::Value*        arrayContent(J3Type* cType, llvm::Value* array, llvm::Value* idx);

		void                arrayBoundCheck(llvm::Value* obj, llvm::Value* idx);
		llvm::Value*        arrayLength(llvm::Value* obj);
		llvm::Value*        arrayLengthPtr(llvm::Value* obj);
		void                arrayStore(J3Type* cType);
		void                arrayLoad(J3Type* cType);

		void                multianewArray();
		void                newArray(uint8_t atype);
		void                newArray(J3ArrayClass* type);
		void                newObject(J3Class* cl);

		void                lookupSwitch();
		void                tableSwitch();

		void                ldc(uint32_t idx);

		void                selectExceptionNode(uint32_t idx);

		void                translate();

		void                initExceptionNode(J3ExceptionNode** pnode, uint32_t pc, J3ExceptionNode* node);
		void                addToExceptionNode(J3ExceptionNode* node, J3ExceptionEntry* entry);
		void                closeExceptionNode(J3ExceptionNode* node);

		void                generateJava();
		void                generateNative();
		llvm::Function*     lookupNative();
		llvm::Type*         doNativeType(llvm::Type* type);

		llvm::Value*        buildString(const char* msg);

		static void         echoDebugEnter(uint32_t isLeave, const char* msg, ...);
		static void         echoDebugExecute(uint32_t level, const char* msg, ...);
		static void         echoElement(uint32_t level, uint32_t type, uintptr_t elmt);
		void                genEchoElement(const char* msg, uint32_t idx, llvm::Value* val);
		void                genDebugOpcode();
		void                genDebugEnterLeave(bool isLeave);

#define _x(name, id, forceInline)								\
		llvm::Function* name;
#include "j3/j3meta.def"
#undef _x
		llvm::GlobalValue* gvTypeInfo;            /* typename void* */
		llvm::Function*    gcRoot;             
		llvm::Function*    stackMap;
		llvm::Function*    patchPoint64;
		llvm::Function*    patchPointVoid;

		J3CodeGen(vmkit::BumpAllocator* _allocator, J3Method* method, uint32_t mode);
		~J3CodeGen();

		void* operator new(size_t n, vmkit::BumpAllocator* _allocator);
		void  operator delete(void* ptr);
	public:
		static void translate(J3Method* method, uint32_t mode);
	};
}

#endif
