#ifndef JITCORE_H
#define JITCORE_H

#if _WIN32
#define _MIASM_EXPORT __declspec(dllexport)
#else
#define _MIASM_EXPORT
#endif

#define RAISE(errtype, msg) {PyObject* p; p = PyErr_Format( errtype, msg ); return p;}
#define RAISE_ret0(errtype, msg) {PyObject* p; p = PyErr_Format( errtype, msg ); return 0;}


#define PyGetInt(item, value)						\
	if (PyInt_Check(item)){						\
		value = (uint64_t)PyInt_AsLong(item);			\
	}								\
	else if (PyLong_Check(item)){					\
		value = (uint64_t)PyLong_AsUnsignedLongLong(item);	\
	}								\
	else{								\
		RAISE(PyExc_TypeError,"arg must be int");		\
	}								\


#define PyGetInt_retneg(item, value)					\
	if (PyInt_Check(item)){						\
		value = (uint64_t)PyInt_AsLong(item);			\
	}								\
	else if (PyLong_Check(item)){					\
		value = (uint64_t)PyLong_AsUnsignedLongLong(item);	\
	}								\
	else{								\
		PyErr_SetString(PyExc_TypeError, "Arg must be int");	\
		return -1;						\
	}								\


#define getset_reg_bn(regname, size)					\
	static PyObject *JitCpu_get_ ## regname  (JitCpu *self, void *closure) \
	{								\
		bn_t bn;						\
		int j;							\
		PyObject* py_long;					\
		PyObject* py_long_new;					\
		PyObject* py_tmp;					\
		PyObject* cst_32;					\
		uint64_t tmp;						\
		py_long = PyLong_FromLong(0);				\
		cst_32 = PyLong_FromLong(32);				\
		bn = ((vm_cpu_t*)(self->cpu))->  regname;		\
		bn = bignum_mask(bn, (size));				\
		for (j = BN_BYTE_SIZE - 4; j >= 0 ; j -= 4) {		\
			tmp = bignum_to_uint64(bignum_mask(bignum_rshift(bn, 8 * j), 32)); \
			py_tmp = PyLong_FromUnsignedLong(tmp);		\
			py_long_new = PyObject_CallMethod(py_long, "__lshift__", "O", cst_32); \
			Py_DECREF(py_long);				\
			py_long = PyObject_CallMethod(py_long_new, "__add__", "O", py_tmp); \
			Py_DECREF(py_long_new);				\
			Py_DECREF(py_tmp);				\
		}							\
		Py_DECREF(cst_32);					\
		return py_long;						\
	}								\
									\
	static int JitCpu_set_ ## regname  (JitCpu *self, PyObject *value, void *closure) \
	{								\
		bn_t bn;						\
		int j;							\
		PyObject* py_long = value;				\
		PyObject* py_long_new;					\
		PyObject* py_tmp;					\
		PyObject* cst_32;					\
		PyObject* cst_ffffffff;					\
		uint64_t tmp;						\
									\
		/* Ensure py_long is a PyLong */			\
		if (PyInt_Check(py_long)) {				\
			tmp = (uint64_t)PyInt_AsLong(py_long);		\
			py_long = PyLong_FromLong(tmp);			\
		} else if (PyLong_Check(py_long)){			\
			/* Already PyLong */				\
			/* Increment ref as we will decement it next */	\
			Py_INCREF(py_long);				\
		}							\
		else {							\
			PyErr_SetString(PyExc_TypeError, "Arg must be int"); \
			return -1;					\
		}							\
									\
		cst_ffffffff = PyLong_FromLong(0xffffffff);		\
		cst_32 = PyLong_FromLong(32);				\
		bn = bignum_from_int(0);				\
									\
		for (j = 0; j < BN_BYTE_SIZE; j += 4) {			\
			py_tmp = PyObject_CallMethod(py_long, "__and__", "O", cst_ffffffff); \
			py_long_new = PyObject_CallMethod(py_long, "__rshift__", "O", cst_32); \
			Py_DECREF(py_long);				\
			py_long = py_long_new;				\
			tmp = PyLong_AsUnsignedLongMask(py_tmp);	\
			Py_DECREF(py_tmp);				\
			bn = bignum_or(bn, bignum_lshift(bignum_from_uint64(tmp), 8 * j)); \
		}							\
									\
		((vm_cpu_t*)(self->cpu))->  regname   = bignum_mask(bn, (size)); \
		Py_DECREF(py_long);					\
		Py_DECREF(cst_32);					\
		Py_DECREF(cst_ffffffff);				\
		return 0;						\
	}

#define getset_reg_u64(regname)						\
	static PyObject *JitCpu_get_ ## regname  (JitCpu *self, void *closure) \
	{								\
		return PyLong_FromUnsignedLongLong((uint64_t)(((vm_cpu_t*)(self->cpu))->  regname  )); \
	}								\
	static int JitCpu_set_ ## regname  (JitCpu *self, PyObject *value, void *closure) \
	{								\
		uint64_t val;						\
		PyGetInt_retneg(value, val);				\
		((vm_cpu_t*)(self->cpu))->  regname   = val;		\
		return 0;						\
	}

#define getset_reg_u32(regname)						\
	static PyObject *JitCpu_get_ ## regname  (JitCpu *self, void *closure) \
	{								\
		return PyLong_FromUnsignedLongLong((uint32_t)(((vm_cpu_t*)(self->cpu))->  regname  )); \
	}								\
	static int JitCpu_set_ ## regname  (JitCpu *self, PyObject *value, void *closure) \
	{								\
		uint32_t val;						\
		PyGetInt_retneg(value, val);				\
		((vm_cpu_t*)(self->cpu))->  regname   = val;		\
		return 0;						\
	}


#define getset_reg_u16(regname)						\
	static PyObject *JitCpu_get_ ## regname  (JitCpu *self, void *closure) \
	{								\
		return PyLong_FromUnsignedLongLong((uint16_t)(((vm_cpu_t*)(self->cpu))-> regname  )); \
	}								\
	static int JitCpu_set_ ## regname  (JitCpu *self, PyObject *value, void *closure) \
	{								\
		uint16_t val;						\
		PyGetInt_retneg(value, val);				\
		((vm_cpu_t*)(self->cpu))->  regname   = val;		\
		return 0;						\
	}


#define get_reg(reg)  do {						\
		o = PyLong_FromUnsignedLongLong((uint64_t)((vm_cpu_t*)(self->cpu))->reg); \
		PyDict_SetItemString(dict, #reg, o);			\
		Py_DECREF(o);						\
	} while(0);


#define get_reg_bn(reg, size)  do {					\
		bn_t bn;						\
		int j;							\
		PyObject* py_long;					\
		PyObject* py_long_new;					\
		PyObject* py_tmp;					\
		PyObject* cst_32;					\
		uint64_t tmp;						\
		py_long = PyLong_FromLong(0);				\
		cst_32 = PyLong_FromLong(32);				\
		bn = ((vm_cpu_t*)(self->cpu))->  reg;			\
		bn = bignum_mask(bn, size);				\
		for (j = BN_BYTE_SIZE - 4; j >= 0 ; j -= 4) {		\
			tmp = bignum_to_uint64(bignum_mask(bignum_rshift(bn, 8 * j), 32)); \
			py_tmp = PyLong_FromUnsignedLong(tmp);		\
			py_long_new = PyObject_CallMethod(py_long, "__lshift__", "O", cst_32); \
			Py_DECREF(py_long);				\
			py_long = PyObject_CallMethod(py_long_new, "__add__", "O", py_tmp); \
			Py_DECREF(py_long_new);				\
			Py_DECREF(py_tmp);				\
		}							\
		PyDict_SetItemString(dict, #reg, py_long);		\
		Py_DECREF(py_long);					\
		Py_DECREF(cst_32);					\
	} while(0);


#define get_reg_off(reg)  do {						\
		o = PyLong_FromUnsignedLongLong((uint64_t)offsetof(vm_cpu_t, reg)); \
		PyDict_SetItemString(dict, #reg, o);			\
		Py_DECREF(o);						\
	} while(0);




typedef struct {
	uint8_t is_local;
	uint64_t address;
} block_id;

typedef struct {
	PyObject_HEAD
	VmMngr *pyvm;
	PyObject *jitter;
	void* cpu;
} JitCpu;


typedef struct _reg_dict{
    char* name;
    size_t offset;
    size_t size;
} reg_dict;



void JitCpu_dealloc(JitCpu* self);
PyObject * JitCpu_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
PyObject * JitCpu_get_vmmngr(JitCpu *self, void *closure);
PyObject * JitCpu_set_vmmngr(JitCpu *self, PyObject *value, void *closure);
PyObject * JitCpu_get_jitter(JitCpu *self, void *closure);
PyObject * JitCpu_set_jitter(JitCpu *self, PyObject *value, void *closure);
void Resolve_dst(block_id* BlockDst, uint64_t addr, uint64_t is_local);

#define Resolve_dst(b, arg_addr, arg_is_local) do {(b)->address = (arg_addr); (b)->is_local = (arg_is_local);} while(0)



_MIASM_EXPORT uint8_t MEM_LOOKUP_08(JitCpu* jitcpu, uint64_t addr);
_MIASM_EXPORT uint16_t MEM_LOOKUP_16(JitCpu* jitcpu, uint64_t addr);
_MIASM_EXPORT uint32_t MEM_LOOKUP_32(JitCpu* jitcpu, uint64_t addr);
_MIASM_EXPORT uint64_t MEM_LOOKUP_64(JitCpu* jitcpu, uint64_t addr);

_MIASM_EXPORT bn_t MEM_LOOKUP_BN_BN(JitCpu* jitcpu, int size, bn_t addr);
_MIASM_EXPORT bn_t MEM_LOOKUP_INT_BN(JitCpu* jitcpu, int size, uint64_t addr);

_MIASM_EXPORT uint64_t MEM_LOOKUP_BN_INT(JitCpu* jitcpu, int size, bn_t addr);

_MIASM_EXPORT void MEM_WRITE_BN_BN(JitCpu* jitcpu, int size, bn_t addr, bn_t src);
_MIASM_EXPORT void MEM_WRITE_BN_INT(JitCpu* jitcpu, int size, bn_t addr, uint64_t src);
_MIASM_EXPORT void MEM_WRITE_INT_BN(JitCpu* jitcpu, int size, uint64_t addr, bn_t src);


PyObject* vm_get_mem(JitCpu *self, PyObject* args);

_MIASM_EXPORT void MEM_LOOKUP_INT_BN_TO_PTR(JitCpu* jitcpu, int size, uint64_t addr, char* ptr);
_MIASM_EXPORT void MEM_WRITE_INT_BN_FROM_PTR(JitCpu* jitcpu, int size, uint64_t addr, char* ptr);



#define VM_exception_flag (jitcpu->pyvm->vm_mngr.exception_flags)
#define CPU_exception_flag (((vm_cpu_t*)jitcpu->cpu)->exception_flags)
#define CPU_exception_flag_at_instr ((CPU_exception_flag) && ((CPU_exception_flag) > EXCEPT_NUM_UPDT_EIP))
#define JIT_RET_EXCEPTION 1
#define JIT_RET_NO_EXCEPTION 0

#endif
