#include "rive/viewmodel/runtime/viewmodel_instance_runtime.hpp"
#include "rive/viewmodel/viewmodel.hpp"
#include "rive/viewmodel/viewmodel_instance_number.hpp"
#include "rive/viewmodel/viewmodel_instance_viewmodel.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_value_runtime.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_number_runtime.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_string_runtime.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_boolean_runtime.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_color_runtime.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_enum_runtime.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_trigger_runtime.hpp"
#include "rive/viewmodel/runtime/viewmodel_instance_list_runtime.hpp"
#include "rive/viewmodel/viewmodel_property_string.hpp"
#include "rive/viewmodel/viewmodel_property_number.hpp"
#include "rive/viewmodel/viewmodel_property_boolean.hpp"
#include "rive/viewmodel/viewmodel_property_color.hpp"
#include "rive/viewmodel/viewmodel_property_list.hpp"
#include "rive/viewmodel/viewmodel_property_enum.hpp"
#include "rive/viewmodel/viewmodel_property_enum_custom.hpp"
#include "rive/viewmodel/viewmodel_property_enum_system.hpp"
#include "rive/viewmodel/viewmodel_property_trigger.hpp"
#include "rive/viewmodel/viewmodel_property_viewmodel.hpp"
#include "rive/viewmodel/runtime/viewmodel_runtime.hpp"

// Default namespace for Rive Cpp code
using namespace rive;

ViewModelInstanceRuntime::ViewModelInstanceRuntime(
    rcp<ViewModelInstance> instance) :
    m_viewModelInstance(instance)
{}

ViewModelInstanceRuntime::~ViewModelInstanceRuntime()
{
    for (auto& it : m_properties)
    {
        delete it.second;
    }
}

const std::string& ViewModelInstanceRuntime::name() const
{
    return m_viewModelInstance->name();
}

size_t ViewModelInstanceRuntime::propertyCount() const
{
    return m_viewModelInstance->propertyValues().size();
}

ViewModelInstanceRuntime* ViewModelInstanceRuntime::viewModelInstanceAtPath(
    const std::string& path) const
{
    std::string delimiter = "/";
    size_t firstDelim = path.find(delimiter);
    std::string viewModelInstanceName =
        firstDelim == std::string::npos ? path : path.substr(0, firstDelim);
    std::string restOfPath = firstDelim == std::string::npos
                                 ? ""
                                 : path.substr(firstDelim + 1, path.size());

    if (!viewModelInstanceName.empty())
    {
        auto instance = instanceRuntime(viewModelInstanceName);
        if (instance != nullptr)
        {
            if (restOfPath.empty())
            {
                return instance.get();
            }
            else
            {
                return instance->viewModelInstanceAtPath(restOfPath);
            }
        }
    }
    return nullptr;
}

ViewModelInstanceValueRuntime* ViewModelInstanceRuntime::property(
    const std::string& path) const
{
    if (path.empty())
    {
        return nullptr;
    }
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstanceRuntime = viewModelInstanceFromFullPath(path);
    if (viewModelInstanceRuntime != nullptr)
    {
        auto properties = viewModelInstanceRuntime->properties();
        for (auto p : properties)
        {
            if (p.name == propertyName)
            {
                switch (p.type)
                {
                    case DataType::string:
                        return viewModelInstanceRuntime->propertyString(
                            propertyName);
                    case DataType::number:
                        return viewModelInstanceRuntime->propertyNumber(
                            propertyName);
                    case DataType::boolean:
                        return viewModelInstanceRuntime->propertyBoolean(
                            propertyName);
                    case DataType::color:
                        return viewModelInstanceRuntime->propertyColor(
                            propertyName);
                    case DataType::assetImage:
                        return viewModelInstanceRuntime->propertyImage(
                            propertyName);
                    case DataType::artboard:
                        return viewModelInstanceRuntime->propertyArtboard(
                            propertyName);
                    case DataType::list:
                        return viewModelInstanceRuntime->propertyList(
                            propertyName);
                    case DataType::enumType:
                        return viewModelInstanceRuntime->propertyEnum(
                            propertyName);
                    case DataType::trigger:
                        return viewModelInstanceRuntime->propertyTrigger(
                            propertyName);
                    default:
                        break;
                }
            }
        }
    }
    return nullptr;
}

std::string ViewModelInstanceRuntime::getPropertyNameFromPath(
    const std::string& path) const
{
    if (!path.empty())
    {
        std::string delimiter = "/";
        auto propertyNameDelimiter = path.rfind(delimiter);
        return propertyNameDelimiter == std::string::npos
                   ? path
                   : path.substr(propertyNameDelimiter + 1);
    }
    return "";
}

const ViewModelInstanceRuntime* ViewModelInstanceRuntime::
    viewModelInstanceFromFullPath(const std::string& path) const
{
    std::string delimiter = "/";
    auto propertyNameDelimiter = path.rfind(delimiter);
    if (propertyNameDelimiter != std::string::npos)
    {
        return viewModelInstanceAtPath(path.substr(0, propertyNameDelimiter));
    }
    return this;
}

ViewModelInstanceNumberRuntime* ViewModelInstanceRuntime::propertyNumber(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceNumber,
                                  ViewModelInstanceNumberRuntime>(propertyName);
    }
    return nullptr;
}

ViewModelInstanceBooleanRuntime* ViewModelInstanceRuntime::propertyBoolean(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceBoolean,
                                  ViewModelInstanceBooleanRuntime>(
                propertyName);
    }
    return nullptr;
}

ViewModelInstanceStringRuntime* ViewModelInstanceRuntime::propertyString(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceString,
                                  ViewModelInstanceStringRuntime>(propertyName);
    }
    return nullptr;
}

ViewModelInstanceColorRuntime* ViewModelInstanceRuntime::propertyColor(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceColor,
                                  ViewModelInstanceColorRuntime>(propertyName);
    }
    return nullptr;
}

ViewModelInstanceTriggerRuntime* ViewModelInstanceRuntime::propertyTrigger(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceTrigger,
                                  ViewModelInstanceTriggerRuntime>(
                propertyName);
    }
    return nullptr;
}

ViewModelInstanceEnumRuntime* ViewModelInstanceRuntime::propertyEnum(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceEnum,
                                  ViewModelInstanceEnumRuntime>(propertyName);
    }
    return nullptr;
}

ViewModelInstanceListRuntime* ViewModelInstanceRuntime::propertyList(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceList,
                                  ViewModelInstanceListRuntime>(propertyName);
    }
    return nullptr;
}

rcp<ViewModelInstance> ViewModelInstanceRuntime::viewModelInstanceProperty(
    const std::string& name) const
{
    auto viewModelInstanceValue = m_viewModelInstance->propertyValue(name);
    if (viewModelInstanceValue != nullptr &&
        viewModelInstanceValue->is<ViewModelInstanceViewModel>())
    {
        return viewModelInstanceValue->as<ViewModelInstanceViewModel>()
            ->referenceViewModelInstance();
    }
    return nullptr;
}

rcp<ViewModelInstanceRuntime> ViewModelInstanceRuntime::instanceRuntime(
    const std::string& name) const
{
    auto itr = m_viewModelInstances.find(name);
    if (itr != m_viewModelInstances.end())
    {
        return itr->second;
    }
    auto viewModelInstance = viewModelInstanceProperty(name);
    if (viewModelInstance != nullptr)
    {
        auto viewModelInstanceRef =
            make_rcp<ViewModelInstanceRuntime>(viewModelInstance);
        m_viewModelInstances[name] = viewModelInstanceRef;
        return viewModelInstanceRef;
    }
    return nullptr;
}

rcp<ViewModelInstanceRuntime> ViewModelInstanceRuntime::propertyViewModel(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {
        return viewModelInstance->instanceRuntime(propertyName);
    }
    return nullptr;
}

ViewModelInstanceAssetImageRuntime* ViewModelInstanceRuntime::propertyImage(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceAssetImage,
                                  ViewModelInstanceAssetImageRuntime>(
                propertyName);
    }
    return nullptr;
}

ViewModelInstanceArtboardRuntime* ViewModelInstanceRuntime::propertyArtboard(
    const std::string& path) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {

        return viewModelInstance
            ->getPropertyInstance<ViewModelInstanceArtboard,
                                  ViewModelInstanceArtboardRuntime>(
                propertyName);
    }
    return nullptr;
}

bool ViewModelInstanceRuntime::replaceViewModel(
    const std::string& path,
    ViewModelInstanceRuntime* value) const
{
    const auto propertyName = getPropertyNameFromPath(path);
    auto viewModelInstance = viewModelInstanceFromFullPath(path);
    if (viewModelInstance != nullptr)
    {
        return viewModelInstance->replaceViewModelByName(propertyName, value);
    }
    return false;
}

bool ViewModelInstanceRuntime::replaceViewModelByName(
    const std::string& name,
    ViewModelInstanceRuntime* value) const
{
    if (m_viewModelInstance->replaceViewModelByName(name, value->instance()))
    {
        bool isStored = false;
        for (const auto& rcpInstance : m_viewModelInstances)
        {
            if (rcpInstance.second.get() == value)
            {
                isStored = true;
                break;
            }
        }
        if (!isStored)
        {
            m_viewModelInstances[name] = ref_rcp(value);
        }
        return true;
    }
    return false;
}

std::vector<PropertyData> ViewModelInstanceRuntime::properties() const
{
    std::vector<PropertyData> props;
    auto properties = m_viewModelInstance->viewModel()->properties();
    return ViewModelRuntime::buildPropertiesData(properties);
}