

#include "core/object.h"

#include "primitives/classobject.h"
#include "primitives/methodobject.h"
#include "primitives/nullobject.h"

#include "compiler/function.h"
#include "interpreter/interpreter.h"

namespace obj {

	Object* ObjectMemAllocate(const ObjectType* type);
	void ObjectMemDeallocate(Object* in);

	objects_api* NDO = NULL;

	void hierarchy_copy(Object* self, const Object* in, const ObjectType* type);
	void hierarchy_construct(Object* self, const ObjectType* type);

	objects_api::objects_api() {
		tp::memSetVal(sl_callbacks, SAVE_LOAD_MAX_CALLBACK_SLOTS * sizeof(save_load_callbacks*), 0);
		interp = new Interpreter();
	}

	objects_api::~objects_api() { delete interp; }

	void objects_api::define(ObjectType* type) {
		MODULE_SANITY_CHECK(gModuleObjects);

		DEBUG_ASSERT(NDO && "using uninitialize objects api");
		DEBUG_ASSERT(!types.presents(type->name) && "Type Redefinition");
		types.put(type->name, type);

		type->type_methods.init();
	}

	Object* objects_api::create(const tp::String& name) {
		MODULE_SANITY_CHECK(gModuleObjects);

		const ObjectType* type = types.get(name);

		Object* obj_instance = ObjectMemAllocate(type);

		if (!obj_instance) {
			return NULL;
		}

		hierarchy_construct(obj_instance, obj_instance->type);

		setrefc(obj_instance, 0);

		NDO_CASTV(ClassObject, obj_instance, classobj);
		if (classobj) {
			auto idx = classobj->members->presents("__init__");
			DEBUG_ASSERT(idx);
			if (idx) {
				auto constructor_obj = classobj->members->getSlotVal(idx);
				NDO_CASTV(MethodObject, constructor_obj, constructor_method);
				if (constructor_method) {
					interp->execAll(constructor_method, classobj);
				}
			}
		}

		return obj_instance;
	}

	Object* objects_api::copy(Object* self, const Object* in) {
		if (self->type != in->type) {
			return NULL;
		}

		hierarchy_copy(self, in, self->type);

		return self;
	}

	bool objects_api::compare(Object* first, Object* second) {
		if (first->type == second->type) {
			if (first->type->comparison) {
				return first->type->comparison(first, second);
			}

			// raw data comparison
			return tp::memCompare(first, second, first->type->size) == 0;
		}

		return false;
	}

	Object* objects_api::instatiate(Object* in) {
		obj::Object* obj = NDO->create(in->type->name);
		NDO->copy(obj, in);
		return obj;
	}

	void objects_api::set(Object* self, tp::alni val) {
		if (self->type->convesions && self->type->convesions->from_int) {
			self->type->convesions->from_int(self, val);
			return;
		}
	}

	void objects_api::set(Object* self, tp::alnf val) {
		if (self->type->convesions && self->type->convesions->from_float) {
			self->type->convesions->from_float(self, val);
			return;
		}
	}

	void objects_api::set(Object* self, tp::String val) {
		if (self->type->convesions && self->type->convesions->from_string) {
			self->type->convesions->from_string(self, val);
			return;
		}
	}

	tp::alni objects_api::toInt(Object* self) {
		DEBUG_ASSERT(self->type->convesions && self->type->convesions->to_int);
		return self->type->convesions->to_int(self);
	}

	tp::alnf objects_api::toFloat(Object* self) {
		DEBUG_ASSERT(self->type->convesions && self->type->convesions->to_float);
		return self->type->convesions->to_float(self);
	}

	bool objects_api::toBool(Object* self) {
		if (self->type->convesions && self->type->convesions->to_int) {
			return (bool) self->type->convesions->to_int(self);
		}
		return true;
	}

	tp::String objects_api::toString(Object* self) {
		DEBUG_ASSERT(self->type->convesions && self->type->convesions->to_string);
		return self->type->convesions->to_string(self);
	}

	void objects_api::destroy(Object* in) {

		if (!in) {
			return;
		}

#ifdef OBJECT_REF_COUNT
		ObjectMemHead* mh = NDO_MEMH_FROM_NDO(in);
		if (mh->refc > 1) {
			mh->refc--;
			return;
		}
#endif

		NDO_CASTV(ClassObject, in, classobj);
		if (classobj) {
			auto idx = classobj->members->presents("__del__");
			DEBUG_ASSERT(idx);
			if (idx) {
				auto constructor_obj = classobj->members->getSlotVal(idx);
				NDO_CASTV(MethodObject, constructor_obj, constructor_method);
				if (constructor_method) {
					interp->execAll(constructor_method, classobj);
				}
			}
		}

		for (const ObjectType* iter = in->type; iter; iter = iter->base) {
			if (iter->destructor) {
				iter->destructor(in);
			}
		}

		ObjectMemDeallocate(in);
	}

#ifdef OBJECT_REF_COUNT
	tp::halni objects_api::getrefc(Object* in) {
		ObjectMemHead* mh = NDO_MEMH_FROM_NDO(in);
		return (tp::halni) mh->refc;
	}

	void objects_api::refinc(Object* in) {
		ObjectMemHead* mh = NDO_MEMH_FROM_NDO(in);
		mh->refc++;
	}

	void objects_api::setrefc(Object* in, tp::halni refc) {
		ObjectMemHead* mh = NDO_MEMH_FROM_NDO(in);
		mh->refc = refc;
	}
#endif

	void hierarchy_copy(Object* self, const Object* in, const ObjectType* type) {
		if (type->base) {
			hierarchy_copy(self, in, type->base);
		}

		if (type->copy) {
			type->copy(self, in);
		}
	}

	void hierarchy_construct(Object* self, const ObjectType* type) {
		if (type->base) {
			hierarchy_construct(self, type->base);
		}

		if (type->constructor) {
			type->constructor(self);
		}
	}

	Object* ndo_cast(const Object* in, const ObjectType* to_type) {
		const ObjectType* typeiter = in->type;
		while (typeiter) {
			if (typeiter == to_type) {
				return (Object*) in;
			}
			typeiter = typeiter->base;
		}
		return NULL;
	}
};
