#include "ServiceScriptGenericFunction.h"
#include "Deer/Log.h"

#include "DeerStudio/EditorEngine/ErrorHandle.h"
#include "angelscript.h"

namespace Deer {
	bool copyParameter(asIScriptContext* ctx, int paramIndex, int typeId,
	                   asDWORD flags, asIScriptGeneric* gen);
	bool copyReturnValue(asIScriptGeneric* gen, asIScriptContext* ctx,
	                     asIScriptFunction* func);

	void EditorEngine::apiFunction(asIScriptGeneric* func) {
		ServiceScriptFunction* serviceScriptFunction =
		    (ServiceScriptFunction*)func->GetAuxiliary();

		asIScriptContext* context = serviceScriptFunction->scriptContext;
		asIScriptFunction* apiFunction = serviceScriptFunction->apiFunction;
		DEER_CORE_ASSERT(context, "Nullptr service script context calling {0}",
		                 func->GetFunction()->GetDeclaration());

		AS_CHECK(context->Prepare(apiFunction));
		AS_CHECK(context->SetObject(serviceScriptFunction->scriptObject));

		// Extract atributes
		asUINT paramCount = apiFunction->GetParamCount();
		for (asUINT i = 0; i < paramCount; i++) {
			int type;
			asDWORD flags;

			apiFunction->GetParam(i, &type, &flags, nullptr);
			copyParameter(context, i, type, flags, func);
		}

		AS_CHECK(context->Execute());

		copyReturnValue(func, context, apiFunction);

		AS_CHECK(context->Unprepare());
	}

	bool copyParameter(asIScriptContext* ctx, int paramIndex, int typeId,
	                   asDWORD flags, asIScriptGeneric* gen) {
		if (flags & asTM_OUTREF) {
			// Skip out refs for now
			// Unsupported type
			DEER_EDITOR_ENGINE_WARN(
			    "Skipping our ref type {0} when calling {1}",
			    ctx->GetEngine()->GetTypeDeclaration(typeId, true),
			    gen->GetFunction()->GetDeclaration());
			return false;
		}

		if (typeId == asTYPEID_INT32) {
			ctx->SetArgDWord(paramIndex, gen->GetArgDWord(paramIndex));
		} else if (typeId == asTYPEID_FLOAT) {
			ctx->SetArgFloat(paramIndex, gen->GetArgFloat(paramIndex));
		} else if (typeId == asTYPEID_DOUBLE) {
			ctx->SetArgDouble(paramIndex, gen->GetArgDouble(paramIndex));
		} else if (typeId == asTYPEID_INT64) {
			ctx->SetArgQWord(paramIndex, gen->GetArgQWord(paramIndex));
		} else if (typeId & asTYPEID_OBJHANDLE) {
			void* obj =
			    gen->GetArgAddress(paramIndex); // Use GetArgAddress instead of
			                                    // GetArgObject for handles
			ctx->SetArgObject(paramIndex, obj);
		} else if (typeId & asTYPEID_MASK_OBJECT) {
			void* obj = gen->GetArgObject(paramIndex);
			ctx->SetArgObject(paramIndex, obj);
		} else {
			// Unsupported type
			DEER_EDITOR_ENGINE_WARN(
			    "Unsuported type {0} when calling {1}",
			    ctx->GetEngine()->GetTypeDeclaration(typeId, true),
			    gen->GetFunction()->GetDeclaration());
			return false;
		}

		return true;
	}

	bool copyReturnValue(asIScriptGeneric* gen, asIScriptContext* ctx,
	                     asIScriptFunction* func) {
		int returnTypeId = func->GetReturnTypeId();
		asITypeInfo* typeInfo = ctx->GetEngine()->GetTypeInfoById(returnTypeId);

		if (returnTypeId == asTYPEID_VOID) {
			// Nothing to return
			return true;
		}

		if (returnTypeId == asTYPEID_INT32) {
			gen->SetReturnDWord(ctx->GetReturnDWord());
		} else if (returnTypeId == asTYPEID_FLOAT) {
			gen->SetReturnFloat(ctx->GetReturnFloat());
		} else if (returnTypeId == asTYPEID_DOUBLE) {
			gen->SetReturnDouble(ctx->GetReturnDouble());
		} else if (returnTypeId == asTYPEID_INT64) {
			gen->SetReturnQWord(ctx->GetReturnQWord());
		} else if (returnTypeId & asTYPEID_OBJHANDLE) {
			void* obj = ctx->GetReturnAddress(); // For handles, this returns
			                                     // the pointer
			gen->SetReturnAddress(obj);
		} else if (returnTypeId & asTYPEID_MASK_OBJECT) {
			bool isRef = (typeInfo->GetFlags() & asOBJ_REF) != 0;

			if (isRef) {
				void* obj = ctx->GetReturnObject();
				gen->SetReturnObject(obj);
			} else {
				void* src = ctx->GetReturnObject();
				void* dst = gen->GetAddressOfReturnLocation();

				memcpy(dst, src, typeInfo->GetSize());
			}
		} else {
			DEER_EDITOR_ENGINE_WARN(
			    "Unsupported return type {0} in call to {1}",
			    ctx->GetEngine()->GetTypeDeclaration(returnTypeId, true),
			    func->GetDeclaration());
			return false;
		}

		return true;
	}

} // namespace Deer