#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/rive_lua_libs.hpp"
#endif
#include "rive/component_dirt.hpp"
#include "rive/layout_component.hpp"
#include "rive/scripted/scripted_layout.hpp"

using namespace rive;

#ifdef WITH_RIVE_SCRIPTING
bool ScriptedLayout::scriptInit(LuaState* state)
{
    ScriptedDrawable::scriptInit(state);
    if (parent() != nullptr && parent()->is<LayoutComponent>())
    {
        parent()->as<LayoutComponent>()->markLayoutNodeDirty(true);
    }
    return true;
}

void ScriptedLayout::callScriptedResize(Vec2D size)
{
    if (m_state == nullptr)
    {
        return;
    }
    auto state = m_state->state;
    rive_lua_pushRef(state, m_self);
    if (static_cast<lua_Type>(lua_getfield(state, -1, "resize")) !=
        LUA_TFUNCTION)
    {
        fprintf(stderr, "expected resize to be a function\n");
        rive_lua_pop(state, 1);
    }
    else
    {
        lua_pushvalue(state, -2);
        lua_pushvec2d(state, size);
        if (static_cast<lua_Status>(rive_lua_pcall(state, 2, 0)) != LUA_OK)
        {
            fprintf(stderr, "resize failed\n");
            rive_lua_pop(state, 1);
        }
    }
    rive_lua_pop(state, 1);
}

Vec2D ScriptedLayout::measureLayout(float width,
                                    LayoutMeasureMode widthMode,
                                    float height,
                                    LayoutMeasureMode heightMode)
{
    if (!measures() || m_state == nullptr)
    {
        return Vec2D(0, 0);
    }
    auto measuredWidth = std::numeric_limits<float>::max();
    auto measuredHeight = std::numeric_limits<float>::max();
    auto state = m_state->state;
    rive_lua_pushRef(state, m_self);
    if (static_cast<lua_Type>(lua_getfield(state, -1, "measure")) !=
        LUA_TFUNCTION)
    {
        fprintf(stderr, "expected measure to be a function\n");
        rive_lua_pop(state, 1);
    }
    else
    {
        lua_pushvalue(state, -2);
        if (static_cast<lua_Status>(rive_lua_pcall(state, 1, 1)) != LUA_OK)
        {
            fprintf(stderr, "measure failed\n");
        }
        else
        {
            if (static_cast<lua_Type>(lua_type(state, -1)) != LUA_TVECTOR)
            {
                fprintf(stderr, "expected measure to return a Vec2D\n");
            }
            else
            {
                auto size = lua_tovec2d(state, -1);
                measuredWidth = size->x;
                measuredHeight = size->y;
            }
        }
        rive_lua_pop(state, 1);
    }
    rive_lua_pop(state, 1);

    return Vec2D(std::min((widthMode == LayoutMeasureMode::undefined
                               ? std::numeric_limits<float>::max()
                               : width),
                          measuredWidth),
                 std::min((heightMode == LayoutMeasureMode::undefined
                               ? std::numeric_limits<float>::max()
                               : height),
                          measuredHeight));
}

void ScriptedLayout::controlSize(Vec2D size,
                                 LayoutScaleType widthScaleType,
                                 LayoutScaleType heightScaleType,
                                 LayoutDirection direction)
{
    m_size = size;
    callScriptedResize(size);
}
#else
Vec2D ScriptedLayout::measureLayout(float width,
                                    LayoutMeasureMode widthMode,
                                    float height,
                                    LayoutMeasureMode heightMode)
{
    return Vec2D((widthMode == LayoutMeasureMode::undefined
                      ? std::numeric_limits<float>::max()
                      : width),
                 (heightMode == LayoutMeasureMode::undefined
                      ? std::numeric_limits<float>::max()
                      : height));
}

void ScriptedLayout::controlSize(Vec2D size,
                                 LayoutScaleType widthScaleType,
                                 LayoutScaleType heightScaleType,
                                 LayoutDirection direction)
{
    m_size = size;
}
#endif

void ScriptedLayout::addProperty(CustomProperty* prop)
{
    auto scriptInput = ScriptInput::from(prop);
    if (scriptInput != nullptr)
    {
        scriptInput->scriptedObject(this);
    }
    CustomPropertyContainer::addProperty(prop);
}

Core* ScriptedLayout::clone() const
{
    ScriptedLayout* twin = ScriptedLayoutBase::clone()->as<ScriptedLayout>();
    if (m_fileAsset != nullptr)
    {
        twin->setAsset(m_fileAsset);
    }
    return twin;
}