// Copyright (c) Wojciech Figat. All rights reserved.

#include "UIControl.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Scripting/ManagedCLR/MException.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Profiler/ProfilerMemory.h"

#if COMPILE_WITHOUT_CSHARP
#define UICONTROL_INVOKE(event)
#else
// Cached methods (FlaxEngine.CSharp.dll is loaded only once)
MMethod* UIControl_Serialize = nullptr;
MMethod* UIControl_Deserialize = nullptr;
MMethod* UIControl_ParentChanged = nullptr;
MMethod* UIControl_TransformChanged = nullptr;
MMethod* UIControl_OrderInParentChanged = nullptr;
MMethod* UIControl_ActiveChanged = nullptr;
MMethod* UIControl_BeginPlay = nullptr;
MMethod* UIControl_EndPlay = nullptr;

#define UICONTROL_INVOKE(event) \
    auto* managed = GetManagedInstance(); \
    if (managed) \
	{ \
        PROFILE_MEM(UI); \
	    MObject* exception = nullptr; \
	    UIControl_##event->Invoke(managed, nullptr, &exception); \
	    if (exception) \
	    { \
		    MException ex(exception); \
		    ex.Log(LogType::Error, TEXT("UICanvas::" #event)); \
	    } \
	}
#endif

UIControl::UIControl(const SpawnParams& params)
    : Actor(params)
{
#if !COMPILE_WITHOUT_CSHARP
    Platform::MemoryBarrier();
    if (UIControl_Serialize == nullptr)
    {
        MClass* mclass = GetClass();
        UIControl_Deserialize = mclass->GetMethod("Deserialize", 2);
        UIControl_ParentChanged = mclass->GetMethod("ParentChanged");
        UIControl_TransformChanged = mclass->GetMethod("TransformChanged");
        UIControl_OrderInParentChanged = mclass->GetMethod("OrderInParentChanged");
        UIControl_ActiveChanged = mclass->GetMethod("ActiveChanged");
        UIControl_BeginPlay = mclass->GetMethod("BeginPlay");
        UIControl_EndPlay = mclass->GetMethod("EndPlay");
        UIControl_Serialize = mclass->GetMethod("Serialize", 2);
        Platform::MemoryBarrier();
    }
#endif
}

#if USE_EDITOR

BoundingBox UIControl::GetEditorBox() const
{
    const Vector3 size(50);
    return BoundingBox(_transform.Translation - size, _transform.Translation + size);
}

#endif

void UIControl::Serialize(SerializeStream& stream, const void* otherObj)
{
    // Base
    Actor::Serialize(stream, otherObj);

    SERIALIZE_GET_OTHER_OBJ(UIControl);
    SERIALIZE_MEMBER(NavTargetUp, _navTargetUp);
    SERIALIZE_MEMBER(NavTargetDown, _navTargetDown);
    SERIALIZE_MEMBER(NavTargetLeft, _navTargetLeft);
    SERIALIZE_MEMBER(NavTargetRight, _navTargetRight);

#if !COMPILE_WITHOUT_CSHARP
    PROFILE_MEM(UI);
    void* params[2];
    MString* controlType = nullptr;
    params[0] = &controlType;
    params[1] = other ? other->GetOrCreateManagedInstance() : nullptr;
    MObject* exception = nullptr;
    const auto invokeResultStr = (MString*)UIControl_Serialize->Invoke(GetOrCreateManagedInstance(), params, &exception);
    if (exception)
    {
        MException ex(exception);
        ex.Log(LogType::Error, TEXT("UIControl::Serialize"));
        return;
    }

    // No control
    if (!controlType)
    {
        if (!other)
        {
            stream.JKEY("Control");
            stream.String("", 0);

            stream.JKEY("Data");
            stream.RawValue("{}", 2);
        }
        return;
    }

    const StringView controlTypeChars = MCore::String::GetChars(controlType);
    if (controlTypeChars.Length() != 0)
    {
        stream.JKEY("Control");
        stream.String(controlTypeChars);
    }

    const StringView invokeResultStrChars = MCore::String::GetChars(invokeResultStr);
    stream.JKEY("Data");
    stream.RawValue(invokeResultStrChars);
#endif
}

void UIControl::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
    // Base
    Actor::Deserialize(stream, modifier);

    DESERIALIZE_MEMBER(NavTargetUp, _navTargetUp);
    DESERIALIZE_MEMBER(NavTargetDown, _navTargetDown);
    DESERIALIZE_MEMBER(NavTargetLeft, _navTargetLeft);
    DESERIALIZE_MEMBER(NavTargetRight, _navTargetRight);

#if !COMPILE_WITHOUT_CSHARP
    PROFILE_MEM(UI);
    MTypeObject* typeObj = nullptr;
    const auto controlMember = stream.FindMember("Control");
    if (controlMember != stream.MemberEnd())
    {
        const StringAnsiView controlType(controlMember->value.GetStringAnsiView());
        const MClass* type = Scripting::FindClass(controlType);
        if (type != nullptr)
        {
            typeObj = INTERNAL_TYPE_GET_OBJECT(type->GetType());
        }
        else
        {
            LOG(Warning, "Unknown UIControl type: {0}", String(controlType));
        }
    }

    const auto dataMember = stream.FindMember("Data");
    if (dataMember != stream.MemberEnd())
    {
        rapidjson_flax::StringBuffer buffer;
        rapidjson_flax::Writer<rapidjson_flax::StringBuffer> writer(buffer);
        dataMember->value.Accept(writer);
        void* args[2];
        args[0] = MCore::String::New(buffer.GetString(), (int32)buffer.GetSize());
        args[1] = typeObj;
        MObject* exception = nullptr;
        UIControl_Deserialize->Invoke(GetOrCreateManagedInstance(), args, &exception);
        if (exception)
        {
            MException ex(exception);
            ex.Log(LogType::Error, TEXT("UIControl::Deserialize"));
        }
    }
#endif
}

void UIControl::OnParentChanged()
{
    // Base
    Actor::OnParentChanged();

    UICONTROL_INVOKE(ParentChanged);
}

void UIControl::OnTransformChanged()
{
    // Base
    Actor::OnTransformChanged();

    _box = BoundingBox(_transform.Translation);
    _sphere = BoundingSphere(_transform.Translation, 0.0f);

    UICONTROL_INVOKE(TransformChanged);
}

void UIControl::OnBeginPlay()
{
    UICONTROL_INVOKE(BeginPlay);

    // Base
    Actor::OnBeginPlay();
}

void UIControl::OnEndPlay()
{
    UICONTROL_INVOKE(EndPlay);

    // Base
    Actor::OnEndPlay();
}

void UIControl::OnOrderInParentChanged()
{
    // Base
    Actor::OnOrderInParentChanged();

    UICONTROL_INVOKE(OrderInParentChanged);
}

void UIControl::OnActiveChanged()
{
    UICONTROL_INVOKE(ActiveChanged);

    // Base
    Actor::OnActiveChanged();
}

#if !COMPILE_WITHOUT_CSHARP

void UIControl::GetNavTargets(UIControl*& up, UIControl*& down, UIControl*& left, UIControl*& right) const
{
    up = _navTargetUp;
    down = _navTargetDown;
    left = _navTargetLeft;
    right = _navTargetRight;
}

void UIControl::SetNavTargets(UIControl* up, UIControl* down, UIControl* left, UIControl* right)
{
    _navTargetUp = up;
    _navTargetDown = down;
    _navTargetLeft = left;
    _navTargetRight = right;
}

#endif
