#include <rive/file.hpp>
#include <rive/node.hpp>
#include <rive/shapes/rectangle.hpp>
#include <rive/shapes/shape.hpp>
#include <utils/no_op_renderer.hpp>
#include <rive/shapes/paint/solid_color.hpp>
#include <rive/shapes/paint/color.hpp>
#include <rive/shapes/paint/fill.hpp>
#include <rive/shapes/paint/solid_color.hpp>
#include <rive/text/text_value_run.hpp>
#include <rive/custom_property_boolean.hpp>
#include <rive/custom_property_number.hpp>
#include <rive/custom_property_string.hpp>
#include <rive/custom_property_trigger.hpp>
#include <rive/constraints/follow_path_constraint.hpp>
#include <rive/viewmodel/runtime/viewmodel_runtime.hpp>
#include <rive/viewmodel/runtime/viewmodel_instance_runtime.hpp>
#include <rive/viewmodel/runtime/viewmodel_instance_value_runtime.hpp>
#include <rive/data_bind/data_values/data_type.hpp>
#include <rive/viewmodel/viewmodel_instance_number.hpp>
#include <rive/viewmodel/viewmodel_instance_color.hpp>
#include <rive/viewmodel/viewmodel_instance_string.hpp>
#include <rive/viewmodel/viewmodel_instance_boolean.hpp>
#include <rive/viewmodel/viewmodel_instance_enum.hpp>
#include <rive/viewmodel/viewmodel_instance_trigger.hpp>
#include <rive/viewmodel/viewmodel_instance_list.hpp>
#include <rive/viewmodel/viewmodel_instance_list_item.hpp>
#include "rive/animation/state_machine_instance.hpp"
#include "rive/nested_artboard.hpp"
#include "utils/serializing_factory.hpp"
#include "rive_file_reader.hpp"
#include <catch.hpp>
#include <cstdio>

using namespace rive;

TEST_CASE("artboard with bound properties", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test.riv");

    auto artboard = file->artboard("artboard-1")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    artboard->bindViewModelInstance(viewModelInstance);
    artboard->advance(0.0f);
    REQUIRE(artboard->find<rive::Rectangle>("bound_rect") != nullptr);
    auto rectMapped = artboard->find<rive::Rectangle>("bound_rect");
    REQUIRE(rectMapped->width() == 100.0f);
    REQUIRE(artboard->find<rive::Shape>("bound_rect_shape") != nullptr);
    auto shapeMapped = artboard->find<rive::Shape>("bound_rect_shape");
    // Rotation has a system converter applied to it
    REQUIRE(shapeMapped->rotation() == Approx(1.5708f));

    REQUIRE(shapeMapped->children()[1]->is<rive::Fill>());
    rive::Fill* rectMappadFill = shapeMapped->children()[1]->as<rive::Fill>();
    REQUIRE(rectMappadFill->paint()->is<rive::SolidColor>());
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 255, 0, 0));

    REQUIRE(artboard->find<rive::TextValueRun>("bound_text_run") != nullptr);
    auto textRunMapped = artboard->find<rive::TextValueRun>("bound_text_run");
    REQUIRE(textRunMapped->text() == "bound text");

    REQUIRE(artboard->find<rive::FollowPathConstraint>("") != nullptr);
    auto followPathConstraint = artboard->find<rive::FollowPathConstraint>("");
    REQUIRE(followPathConstraint->orient() == false);

    // View model properties
    // Number
    auto widthProperty = viewModelInstance->propertyValue("width");
    REQUIRE(widthProperty != nullptr);
    REQUIRE(widthProperty->is<rive::ViewModelInstanceNumber>());
    // Number with comverter
    auto rotationProperty = viewModelInstance->propertyValue("rotation");
    REQUIRE(rotationProperty != nullptr);
    REQUIRE(rotationProperty->is<rive::ViewModelInstanceNumber>());
    // Color
    auto colorProperty = viewModelInstance->propertyValue("color");
    REQUIRE(colorProperty != nullptr);
    REQUIRE(colorProperty->is<rive::ViewModelInstanceColor>());
    // String
    auto textProperty = viewModelInstance->propertyValue("text");
    REQUIRE(textProperty != nullptr);
    REQUIRE(textProperty->is<rive::ViewModelInstanceString>());
    // Boolean
    auto orientProperty = viewModelInstance->propertyValue("orient");
    REQUIRE(orientProperty != nullptr);
    REQUIRE(orientProperty->is<rive::ViewModelInstanceBoolean>());
    // Update view model values
    widthProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(200.0f);
    rotationProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(
        180.0f);
    colorProperty->as<rive::ViewModelInstanceColor>()->propertyValue(
        rive::colorARGB(255, 0, 255, 0));
    textProperty->as<rive::ViewModelInstanceString>()->propertyValue(
        "New text");
    orientProperty->as<rive::ViewModelInstanceBoolean>()->propertyValue(true);
    // Advance artboard so data binds apply
    artboard->advance(0.0f);
    // Validate new properties
    REQUIRE(rectMapped->width() == 200.0f);
    REQUIRE(shapeMapped->rotation() == Approx(3.14159f));
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 0, 255, 0));
    REQUIRE(textRunMapped->text() == "New text");
    REQUIRE(followPathConstraint->orient() == true);
}

TEST_CASE("state machine led by enums and triggers", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test.riv");

    auto artboard = file->artboard("artboard-2")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    //

    REQUIRE(artboard->find<rive::Shape>("color_rectangle") != nullptr);
    auto shapeMapped = artboard->find<rive::Shape>("color_rectangle");

    REQUIRE(shapeMapped->children()[1]->is<rive::Fill>());
    REQUIRE(shapeMapped->x() == 250);
    REQUIRE(shapeMapped->y() == 250);
    rive::Fill* rectMappadFill = shapeMapped->children()[1]->as<rive::Fill>();
    REQUIRE(rectMappadFill->paint()->is<rive::SolidColor>());
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 116, 116, 116));

    // View model properties
    // Enum
    auto stateProperty = viewModelInstance->propertyValue("state");
    REQUIRE(stateProperty != nullptr);
    REQUIRE(stateProperty->is<rive::ViewModelInstanceEnum>());
    // Trigger
    auto triggerProperty = viewModelInstance->propertyValue("trigger-prop");
    REQUIRE(triggerProperty != nullptr);
    REQUIRE(triggerProperty->is<rive::ViewModelInstanceTrigger>());
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 255, 0, 0));
    // Update view model properties
    // Update enum by index
    stateProperty->as<rive::ViewModelInstanceEnum>()->value(1);

    // Advance state machine
    machine->advanceAndApply(0.0f);
    // Validate values have updated
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 0, 255, 0));
    REQUIRE(shapeMapped->x() == 150);
    REQUIRE(shapeMapped->y() == 250);
    // Update view model properties
    // Update enum by name
    stateProperty->as<rive::ViewModelInstanceEnum>()->value("state-blue");
    // Update trigger
    triggerProperty->as<rive::ViewModelInstanceTrigger>()->propertyValue(1);

    // Advance state machine
    machine->advanceAndApply(0.0f);
    // Validate values have updated
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 0, 0, 255));
    REQUIRE(shapeMapped->x() == 350);
    REQUIRE(shapeMapped->y() == 250);
    // Update trigger
    triggerProperty->as<rive::ViewModelInstanceTrigger>()->propertyValue(1);

    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(shapeMapped->x() == 350);
    REQUIRE(shapeMapped->y() == 350);
}

TEST_CASE("calculate and to string converters with numbers", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test.riv");

    auto artboard = file->artboard("artboard-3")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Bound property with Calculate Converter (multiply by 2)
    REQUIRE(artboard->find<rive::CustomPropertyNumber>("num_prop") != nullptr);
    auto customPropertyNumber =
        artboard->find<rive::CustomPropertyNumber>("num_prop");
    REQUIRE(customPropertyNumber->propertyValue() == 0.0f);
    // Bound property with Group Converter with:
    // - Calculate (divide by 3)
    // - Convert to string (round decimals and remove trailing zeros)
    REQUIRE(artboard->find<rive::TextValueRun>("text_run_bound") != nullptr);
    auto textRunBound = artboard->find<rive::TextValueRun>("text_run_bound");
    // View model properties
    // Number with initial value set to 17
    auto numProperty = viewModelInstance->propertyValue("num1");
    REQUIRE(numProperty != nullptr);
    REQUIRE(numProperty->is<rive::ViewModelInstanceNumber>());

    REQUIRE(textRunBound->text() == "text");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    // Test Calculate Converter (multiply by 2)
    REQUIRE(customPropertyNumber->propertyValue() == 34.0f);
    REQUIRE(textRunBound->text() == "6");

    // Update value to -10.0f
    numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(-10.0f);
    // Advance state machine
    machine->advanceAndApply(0.0f);
    // Test Calculate Converter (multiply by 2)
    REQUIRE(customPropertyNumber->propertyValue() == -20.0f);
    REQUIRE(textRunBound->text() == "-3");
}

TEST_CASE("trim string converter", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test.riv");

    auto artboard = file->artboard("artboard-3")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);

    REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_trim_both") !=
            nullptr);
    auto trimmedBothTextRunBound =
        artboard->find<rive::TextValueRun>("second_text_run_trim_both");

    REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_trim_start") !=
            nullptr);
    auto trimmedStartTextRunBound =
        artboard->find<rive::TextValueRun>("second_text_run_trim_start");

    REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_trim_end") !=
            nullptr);
    auto trimmedEndTextRunBound =
        artboard->find<rive::TextValueRun>("second_text_run_trim_end");

    REQUIRE(artboard->find<rive::TextValueRun>("second_text_run_no_trim") !=
            nullptr);
    auto notTrimmedTextRunBound =
        artboard->find<rive::TextValueRun>("second_text_run_no_trim");
    // View model properties
    // String with initial value "     abc    "
    auto stringProperty = viewModelInstance->propertyValue("text");
    REQUIRE(stringProperty != nullptr);
    REQUIRE(stringProperty->is<rive::ViewModelInstanceString>());

    REQUIRE(notTrimmedTextRunBound->text() == "text");
    REQUIRE(trimmedBothTextRunBound->text() == "text");
    REQUIRE(trimmedStartTextRunBound->text() == "text");
    REQUIRE(trimmedEndTextRunBound->text() == "text");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(trimmedBothTextRunBound->text() == "abc");
    REQUIRE(notTrimmedTextRunBound->text() == "     abc    ");
    REQUIRE(trimmedStartTextRunBound->text() == "abc    ");
    REQUIRE(trimmedEndTextRunBound->text() == "     abc");

    // Update value to "a b c "
    stringProperty->as<rive::ViewModelInstanceString>()->propertyValue(
        "a b c ");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(notTrimmedTextRunBound->text() == "a b c ");
    REQUIRE(trimmedBothTextRunBound->text() == "a b c");
    REQUIRE(trimmedStartTextRunBound->text() == "a b c ");
    REQUIRE(trimmedEndTextRunBound->text() == "a b c");
}

TEST_CASE("To string converter with color formatters", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test.riv");

    auto artboard = file->artboard("artboard-4")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);

    REQUIRE(artboard->find<rive::TextValueRun>("RGBA_formatted_color_run") !=
            nullptr);
    auto RGBATextRunBound =
        artboard->find<rive::TextValueRun>("RGBA_formatted_color_run");

    REQUIRE(artboard->find<rive::TextValueRun>("rgba_formatted_color_run") !=
            nullptr);
    auto rgbaTextRunBound =
        artboard->find<rive::TextValueRun>("rgba_formatted_color_run");

    REQUIRE(artboard->find<rive::TextValueRun>("hls_formatted_color_run") !=
            nullptr);
    auto hlsTextRunBound =
        artboard->find<rive::TextValueRun>("hls_formatted_color_run");

    REQUIRE(artboard->find<rive::TextValueRun>("escaped_characters_run") !=
            nullptr);
    auto escapedTextRunBound =
        artboard->find<rive::TextValueRun>("escaped_characters_run");

    // View model properties
    // color with initial value "red 30, green 90, blue 200, alpha 255"
    auto colorProperty = viewModelInstance->propertyValue("col");
    REQUIRE(colorProperty != nullptr);
    REQUIRE(colorProperty->is<rive::ViewModelInstanceColor>());

    REQUIRE(RGBATextRunBound->text() == "text");
    REQUIRE(rgbaTextRunBound->text() == "text");
    REQUIRE(hlsTextRunBound->text() == "text");
    REQUIRE(escapedTextRunBound->text() == "text");
    // // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(RGBATextRunBound->text() ==
            "color: {red: 1E, green: 5A, blue: C8, alpha: FF}");
    REQUIRE(rgbaTextRunBound->text() ==
            "color: {red: 30, green: 90, blue: 200, alpha: 255}");
    REQUIRE(hlsTextRunBound->text() ==
            "color: {hue: 219, luminance: 45, saturation: 74}");
    REQUIRE(escapedTextRunBound->text() == "%r %g %b %a \\a");

    // // Update value to "red 200, green 100, blue 50, alpha 100"
    colorProperty->as<rive::ViewModelInstanceColor>()->propertyValue(
        rive::colorARGB(100, 200, 100, 50));
    // // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(RGBATextRunBound->text() ==
            "color: {red: C8, green: 64, blue: 32, alpha: 64}");
    REQUIRE(rgbaTextRunBound->text() ==
            "color: {red: 200, green: 100, blue: 50, alpha: 100}");
    REQUIRE(hlsTextRunBound->text() ==
            "color: {hue: 20, luminance: 49, saturation: 60}");
    REQUIRE(escapedTextRunBound->text() == "%r %g %b %a \\a");

    // // Update value to "red 0, green 10, blue 16, alpha 100"
    colorProperty->as<rive::ViewModelInstanceColor>()->propertyValue(
        rive::colorARGB(100, 0, 10, 15));
    // // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(RGBATextRunBound->text() ==
            "color: {red: 00, green: 0A, blue: 0F, alpha: 64}");
    REQUIRE(rgbaTextRunBound->text() ==
            "color: {red: 0, green: 10, blue: 15, alpha: 100}");
    REQUIRE(hlsTextRunBound->text() ==
            "color: {hue: 200, luminance: 3, saturation: 100}");
    REQUIRE(escapedTextRunBound->text() == "%r %g %b %a \\a");
}

struct FormulaResult
{
    std::string runName;
    std::string result;

    // Constructor
    FormulaResult(const std::string& name, const std::string& res) :
        runName(name), result(res)
    {}
};

// TEST_CASE("Formula converter", "[data binding]")
// {
//     auto file = ReadRiveFile("assets/data_binding_test.riv");

//     auto artboard = file->artboard("artboard-5")->instance();
//     REQUIRE(artboard != nullptr);
//     auto viewModelInstance =
//         file->createDefaultViewModelInstance(artboard.get());
//     REQUIRE(viewModelInstance != nullptr);
//     auto machine = artboard->defaultStateMachine();
//     machine->bindViewModelInstance(viewModelInstance);
//     REQUIRE(machine != nullptr);
//     // Advance state machine
//     machine->advanceAndApply(0.0f);
//     // Initial input is 1.1
//     std::vector<FormulaResult> results = {
//         {"result-run-1", "11.1"},      // {Input} + 10.0
//         {"result-run-2", "0.11"},      // {Input} / 10.0
//         {"result-run-3", "-8.9"},      // {Input} - 10.0
//         {"result-run-4", "11"},        // {Input} * 10.0
//         {"result-run-5", "0.2"},       // {Input} % 0.9
//         {"result-run-6", "0.891207"},  // sin({Input})
//         {"result-run-7", "0.453596"},  // cos({Input})
//         {"result-run-8", "1.96476"},   // tan({Input})
//         {"result-run-9", "1.460573"},  // acos({Input}/10.0)
//         {"result-run-10", "0.110223"}, // asin({Input}/10.0)
//         {"result-run-11", "0.10956"},  // atan({Input}/10.0)
//         {"result-run-12", "0.03665"},  // atan2({Input}/10.0,3.0)
//         {"result-run-13", "1"},        // max(-1.0,{Input}/10.0,1.0)
//         {"result-run-14", "-1.1"},     // min(-1.0,-1.0*{Input},1.0)
//         {"result-run-15", "1"},        // round({Input})
//         {"result-run-16", "2"},        // ceil({Input})
//         {"result-run-17", "1"},        // floor({Input})
//         {"result-run-18", "1.048809"}, // sqrt({Input})
//         {"result-run-19", "1.21"},     // pow({Input},2.0)
//         {"result-run-20", "3.004166"}, // exp({Input})
//         {"result-run-21", "0.09531"},  // log({Input})
//         {"result-run-22",
//          "1.546404"}, // max(-1.0*cos({Input})+min(2.0,5.0),-3.0)
//         {"result-run-23",
//          "1"}, // max(min(max(floor({Input}),-10.0),10.0),-10.0)
//         {"result-run-24",
//          "-9565.368164"},          //
//          cos(sin(tan({Input})))+exp(10.0)/log(0.1)
//         {"result-run-25", "1.11"}, // {Input}*{Input}+{Input}/{Input}-{Input}
//         {"result-run-26",
//          "1.557408"}, // (sin(1.0)/cos(1.0))*(((exp(log(1.0)))))
//         {"result-run-27", "0.910958"},  // pow(sin(1.0),cos(1.0))
//         {"result-run-28", "0.1"},       // {Input}%round({Input})
//         {"result-run-29", "14.993111"},
//         // 4.0/{Input}*sqrt(17.0+max(-2.0,0.0))
//         {"result-run-30", "1"}, //
//         ceil(max({Input}*2.0/10.0,{Input}/10.0*2.0))

//     };
//     for (auto& formulaResult : results)
//     {
//         REQUIRE(artboard->find<rive::TextValueRun>(formulaResult.runName) !=
//                 nullptr);
//         auto formulaTextRun =
//             artboard->find<rive::TextValueRun>(formulaResult.runName);
//         REQUIRE(formulaTextRun->text() == formulaResult.result);
//     }
// }

// TODO: revisit this test when data binding in design mode is merged
// TEST_CASE("Time based interpolator", "[data binding]")
// {
//     auto file = ReadRiveFile("assets/data_binding_test_2.riv");

//     auto artboard = file->artboard("artboard-1")->instance();
//     REQUIRE(artboard != nullptr);
//     auto viewModelInstance =
//         file->createDefaultViewModelInstance(artboard.get());
//     REQUIRE(viewModelInstance != nullptr);
//     auto machine = artboard->defaultStateMachine();
//     machine->bindViewModelInstance(viewModelInstance);
//     REQUIRE(machine != nullptr);
//     // Advance state machine
//     // artboard->advance(0);
//     machine->advanceAndApply(0.0f);

//     REQUIRE(artboard->find<rive::Shape>("rect") != nullptr);
//     auto shapeRect = artboard->find<rive::Shape>("rect");
//     REQUIRE(shapeRect->x() == 0);
//     // View model properties
//     // Number
//     auto numProperty = viewModelInstance->propertyValue("num");
//     REQUIRE(numProperty != nullptr);
//     REQUIRE(numProperty->is<rive::ViewModelInstanceNumber>());
//     numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(100.0f);
//     fprintf(stderr, "==> machine advance\n");
//     machine->advanceAndApply(0.0f);
//     // Advance state machine
//     fprintf(stderr, "==> machine advance\n");
//     machine->advanceAndApply(0.25f);
//     fprintf(stderr, "==> machine advance\n");
//     machine->advanceAndApply(0.25f);
//     fprintf(stderr, "shapeRect->x(): %f\n", shapeRect->x());
//     REQUIRE(shapeRect->x() == 25.0f);
//     machine->advanceAndApply(0.25f);
//     fprintf(stderr, "shapeRect->x(): %f\n", shapeRect->x());
//     REQUIRE(shapeRect->x() == 50.0f);
// }

TEST_CASE("Range Mapper", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test_2.riv");

    auto artboard = file->artboard("artboard-2")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    // Range mapper [2 - 3]
    REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-1") !=
            nullptr);
    auto customPropertyNumber1 =
        artboard->find<rive::CustomPropertyNumber>("mapped-range-1");
    REQUIRE(customPropertyNumber1->propertyValue() == 6.0f);

    // Range mapper [2 - 3] - lower clamp - upper clamp
    REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-2") !=
            nullptr);
    auto customPropertyNumber2 =
        artboard->find<rive::CustomPropertyNumber>("mapped-range-2");
    REQUIRE(customPropertyNumber2->propertyValue() == 3.0f);

    // Range mapper [2 - 3] - modulo
    REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-3") !=
            nullptr);
    auto customPropertyNumber3 =
        artboard->find<rive::CustomPropertyNumber>("mapped-range-3");
    REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);

    // Range mapper [2 - 3] - lower clamp - upper clamp - reversed
    REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-4") !=
            nullptr);
    auto customPropertyNumber4 =
        artboard->find<rive::CustomPropertyNumber>("mapped-range-4");
    REQUIRE(customPropertyNumber4->propertyValue() == 2.0f);

    // Range mapper [2 - 2]
    REQUIRE(artboard->find<rive::CustomPropertyNumber>("mapped-range-5") !=
            nullptr);
    auto customPropertyNumber5 =
        artboard->find<rive::CustomPropertyNumber>("mapped-range-5");
    REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);

    // View model properties
    // Number starts at 4.0f
    auto numProperty = viewModelInstance->propertyValue("map-range-num");
    REQUIRE(numProperty != nullptr);
    REQUIRE(numProperty->is<rive::ViewModelInstanceNumber>());
    REQUIRE(numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue() ==
            4.0f);

    // Change value to -1.0f
    numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(-1.0f);
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyNumber1->propertyValue() == 1.0f);
    REQUIRE(customPropertyNumber2->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber4->propertyValue() == 3.0f);
    REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);

    // Change value to 0.0f
    numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(0.0f);
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyNumber1->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber2->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber4->propertyValue() == 3.0f);
    REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);

    // Change value to 0.25f
    numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(0.25f);
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyNumber1->propertyValue() == Approx(2.12916f));
    REQUIRE(customPropertyNumber2->propertyValue() == Approx(2.12916f));
    REQUIRE(customPropertyNumber3->propertyValue() == Approx(2.12916f));
    REQUIRE(customPropertyNumber4->propertyValue() == Approx(2.87084f));
    REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);

    // Change value to 2.0f
    numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(2.0f);
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyNumber1->propertyValue() == 4.0f);
    REQUIRE(customPropertyNumber2->propertyValue() == 3.0f);
    REQUIRE(customPropertyNumber3->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber4->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);

    // Change value to 2.25f
    numProperty->as<rive::ViewModelInstanceNumber>()->propertyValue(2.25f);
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyNumber1->propertyValue() == 4.25f);
    REQUIRE(customPropertyNumber2->propertyValue() == 3.0f);
    REQUIRE(customPropertyNumber3->propertyValue() == Approx(2.12916f));
    REQUIRE(customPropertyNumber4->propertyValue() == 2.0f);
    REQUIRE(customPropertyNumber5->propertyValue() == 2.0f);
}

TEST_CASE("Pad String", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test_2.riv");

    auto artboard = file->artboard("artboard-3")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    // Pad string "abc" - length: 11 - Start
    REQUIRE(artboard->find<rive::CustomPropertyString>("pad-string-1") !=
            nullptr);
    auto customPropertyString1 =
        artboard->find<rive::CustomPropertyString>("pad-string-1");
    REQUIRE(customPropertyString1->propertyValue() == "abcabcatext");

    // Pad string "abc" - length: 12 - End
    REQUIRE(artboard->find<rive::CustomPropertyString>("pad-string-2") !=
            nullptr);
    auto customPropertyString2 =
        artboard->find<rive::CustomPropertyString>("pad-string-2");
    REQUIRE(customPropertyString2->propertyValue() == "textabcabcab");

    // Pad string "abc" - length: 12 - End - Worng type of input
    REQUIRE(artboard->find<rive::CustomPropertyString>("pad-string-3") !=
            nullptr);
    auto customPropertyString3 =
        artboard->find<rive::CustomPropertyString>("pad-string-3");
    REQUIRE(customPropertyString3->propertyValue() == "");

    // View model properties
    // String starts with "text"
    auto stringProperty = viewModelInstance->propertyValue("pad-string");
    REQUIRE(stringProperty != nullptr);
    REQUIRE(stringProperty->is<rive::ViewModelInstanceString>());
    REQUIRE(
        stringProperty->as<rive::ViewModelInstanceString>()->propertyValue() ==
        "text");

    // Change value to "text-text-text", longer than length
    stringProperty->as<rive::ViewModelInstanceString>()->propertyValue(
        "text-text-text");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyString1->propertyValue() == "text-text-text");
    REQUIRE(customPropertyString2->propertyValue() == "text-text-text");
    REQUIRE(customPropertyString3->propertyValue() == "");

    // Change value to "", empty string
    stringProperty->as<rive::ViewModelInstanceString>()->propertyValue("");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyString1->propertyValue() == "abcabcabcab");
    REQUIRE(customPropertyString2->propertyValue() == "abcabcabcabc");
    REQUIRE(customPropertyString3->propertyValue() == "");
}

TEST_CASE("Boolean Toggle", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test_2.riv");

    auto artboard = file->artboard("artboard-3")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    // Boolean property
    REQUIRE(artboard->find<rive::CustomPropertyBoolean>("negate-bool-1") !=
            nullptr);
    auto customPropertyBoolean1 =
        artboard->find<rive::CustomPropertyBoolean>("negate-bool-1");
    REQUIRE(customPropertyBoolean1->propertyValue() == true);

    // View model properties
    // bool property starts as false
    auto boolProperty = viewModelInstance->propertyValue("bool-prop");
    REQUIRE(boolProperty != nullptr);
    REQUIRE(boolProperty->is<rive::ViewModelInstanceBoolean>());
    REQUIRE(
        boolProperty->as<rive::ViewModelInstanceBoolean>()->propertyValue() ==
        false);

    // Change value to true
    boolProperty->as<rive::ViewModelInstanceBoolean>()->propertyValue(true);
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(customPropertyBoolean1->propertyValue() == false);
}

TEST_CASE("Instance is shared when the same one is attached to two properties",
          "[data binding]")
{
    auto file = ReadRiveFile("assets/shared_viewmodel_instance.riv");

    auto artboard = file->artboard("main")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    // View model properties
    auto childInstanceViewModel1 = viewModelInstance->propertyValue("child1");
    REQUIRE(childInstanceViewModel1 != nullptr);
    REQUIRE(childInstanceViewModel1->is<rive::ViewModelInstanceViewModel>());
    auto referencedViewModel =
        childInstanceViewModel1->as<rive::ViewModelInstanceViewModel>()
            ->referenceViewModelInstance();
    REQUIRE(referencedViewModel != nullptr);
    auto labelProperty = referencedViewModel->propertyValue("label");
    REQUIRE(labelProperty != nullptr);
    REQUIRE(labelProperty->is<rive::ViewModelInstanceString>());

    // Elements bound to different view model properties that share same view
    // model instance
    REQUIRE(artboard->find<rive::NestedArtboard>("child1") != nullptr);
    auto nestedArtboardChild1 = artboard->find<rive::NestedArtboard>("child1");

    auto nestedArtboardArtboardChild1 =
        nestedArtboardChild1->artboardInstance();
    REQUIRE(nestedArtboardArtboardChild1 != nullptr);
    auto textRunChild1 =
        nestedArtboardArtboardChild1->find<rive::TextValueRun>("text_run");
    REQUIRE(textRunChild1 != nullptr);
    REQUIRE(textRunChild1->text() == "label-vmi-1");

    REQUIRE(artboard->find<rive::NestedArtboard>("child2") != nullptr);
    auto nestedArtboardChild2 = artboard->find<rive::NestedArtboard>("child2");

    auto nestedArtboardArtboardChild2 =
        nestedArtboardChild2->artboardInstance();
    REQUIRE(nestedArtboardArtboardChild2 != nullptr);
    auto textRunChild2 =
        nestedArtboardArtboardChild2->find<rive::TextValueRun>("text_run");
    REQUIRE(textRunChild2 != nullptr);
    REQUIRE(textRunChild2->text() == "label-vmi-1");

    // Changing the value on a single instance should affect both text although
    // they are linked to different view model properties
    labelProperty->as<rive::ViewModelInstanceString>()->propertyValue(
        "label-update");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(textRunChild1->text() == "label-update");
    REQUIRE(textRunChild2->text() == "label-update");
}

TEST_CASE("Instance is not shared when the different ones are attached to two "
          "properties",
          "[data binding]")
{
    auto file = ReadRiveFile("assets/shared_viewmodel_instance.riv");

    auto artboard = file->artboard("main_2")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    // View model properties
    auto childInstanceViewModel1 =
        viewModelInstance->propertyValue("vm_2_child1");
    REQUIRE(childInstanceViewModel1 != nullptr);
    REQUIRE(childInstanceViewModel1->is<rive::ViewModelInstanceViewModel>());
    auto referencedViewModel =
        childInstanceViewModel1->as<rive::ViewModelInstanceViewModel>()
            ->referenceViewModelInstance();
    REQUIRE(referencedViewModel != nullptr);
    auto labelProperty = referencedViewModel->propertyValue("label");
    REQUIRE(labelProperty != nullptr);
    REQUIRE(labelProperty->is<rive::ViewModelInstanceString>());

    // Elements bound to different view model properties that do not share same
    // view model instance
    REQUIRE(artboard->find<rive::NestedArtboard>("child1") != nullptr);
    auto nestedArtboardChild1 = artboard->find<rive::NestedArtboard>("child1");

    auto nestedArtboardArtboardChild1 =
        nestedArtboardChild1->artboardInstance();
    REQUIRE(nestedArtboardArtboardChild1 != nullptr);
    auto textRunChild1 =
        nestedArtboardArtboardChild1->find<rive::TextValueRun>("text_run");
    REQUIRE(textRunChild1 != nullptr);
    REQUIRE(textRunChild1->text() == "label-vmi-1");

    REQUIRE(artboard->find<rive::NestedArtboard>("child2") != nullptr);
    auto nestedArtboardChild2 = artboard->find<rive::NestedArtboard>("child2");

    auto nestedArtboardArtboardChild2 =
        nestedArtboardChild2->artboardInstance();
    REQUIRE(nestedArtboardArtboardChild2 != nullptr);
    auto textRunChild2 =
        nestedArtboardArtboardChild2->find<rive::TextValueRun>("text_run");
    REQUIRE(textRunChild2 != nullptr);
    REQUIRE(textRunChild2->text() == "label-vmi-2");

    // Changing the value on a single instance should not affect the other
    // instance because they are not pointing to the same instance
    labelProperty->as<rive::ViewModelInstanceString>()->propertyValue(
        "label-update");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(textRunChild1->text() == "label-update");
    REQUIRE(textRunChild2->text() == "label-vmi-2");
}

TEST_CASE("Instances are not shared when a new view model instance is created",
          "[data binding]")
{
    auto file = ReadRiveFile("assets/shared_viewmodel_instance.riv");

    auto artboard = file->artboard("main_2")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance = file->createViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    // View model properties
    auto childInstanceViewModel1 =
        viewModelInstance->propertyValue("vm_2_child1");
    REQUIRE(childInstanceViewModel1 != nullptr);
    REQUIRE(childInstanceViewModel1->is<rive::ViewModelInstanceViewModel>());
    auto referencedViewModel =
        childInstanceViewModel1->as<rive::ViewModelInstanceViewModel>()
            ->referenceViewModelInstance();
    REQUIRE(referencedViewModel != nullptr);
    auto labelProperty = referencedViewModel->propertyValue("label");
    REQUIRE(labelProperty != nullptr);
    REQUIRE(labelProperty->is<rive::ViewModelInstanceString>());

    // Elements bound to different view model properties that do not share same
    // view model instance
    REQUIRE(artboard->find<rive::NestedArtboard>("child1") != nullptr);
    auto nestedArtboardChild1 = artboard->find<rive::NestedArtboard>("child1");

    auto nestedArtboardArtboardChild1 =
        nestedArtboardChild1->artboardInstance();
    REQUIRE(nestedArtboardArtboardChild1 != nullptr);
    auto textRunChild1 =
        nestedArtboardArtboardChild1->find<rive::TextValueRun>("text_run");
    REQUIRE(textRunChild1 != nullptr);
    REQUIRE(textRunChild1->text() == "");

    REQUIRE(artboard->find<rive::NestedArtboard>("child2") != nullptr);
    auto nestedArtboardChild2 = artboard->find<rive::NestedArtboard>("child2");

    auto nestedArtboardArtboardChild2 =
        nestedArtboardChild2->artboardInstance();
    REQUIRE(nestedArtboardArtboardChild2 != nullptr);
    auto textRunChild2 =
        nestedArtboardArtboardChild2->find<rive::TextValueRun>("text_run");
    REQUIRE(textRunChild2 != nullptr);
    REQUIRE(textRunChild2->text() == "");

    // Changing the value on a single instance should not affect the other
    // instance because they are not pointing to the same instance
    labelProperty->as<rive::ViewModelInstanceString>()->propertyValue(
        "label-update");
    // Advance state machine
    machine->advanceAndApply(0.0f);
    REQUIRE(textRunChild1->text() == "label-update");
    REQUIRE(textRunChild2->text() == "");
}

TEST_CASE("Triggers updated by events correctly update state", "[data binding]")
{
    auto file = ReadRiveFile("assets/data_binding_test_triggers.riv");

    auto artboard = file->artboard("root")->instance();
    REQUIRE(artboard != nullptr);
    auto viewModelInstance = file->createViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    REQUIRE(artboard->find<rive::Shape>("main_rect") != nullptr);
    auto rect = artboard->find<rive::Shape>("main_rect");

    REQUIRE(rect->children()[1]->is<rive::Fill>());
    rive::Fill* rectMappadFill = rect->children()[1]->as<rive::Fill>();
    REQUIRE(rectMappadFill->paint()->is<rive::SolidColor>());
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 255, 0, 0));

    // Advance state machine so the child reports the event
    machine->advanceAndApply(0.7f);
    // Advance state machine so the parent consumes the event
    machine->advanceAndApply(0.1f);
    REQUIRE(rectMappadFill->paint()->as<rive::SolidColor>()->colorValue() ==
            rive::colorARGB(255, 0, 255, 0));
}

TEST_CASE("Transition self conditions", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file =
        ReadRiveFile("assets/transition_self_comparator_test.riv", &silver);

    auto artboard = file->artboardDefault();

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.1f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());
    auto numProp =
        vmi->propertyValue("num")->as<rive::ViewModelInstanceNumber>();
    auto triggerProp =
        vmi->propertyValue("tri")->as<rive::ViewModelInstanceTrigger>();
    auto colorProp =
        vmi->propertyValue("col")->as<rive::ViewModelInstanceColor>();
    auto bolProp =
        vmi->propertyValue("bol")->as<rive::ViewModelInstanceBoolean>();
    auto stringProp =
        vmi->propertyValue("str")->as<rive::ViewModelInstanceString>();
    auto lisProp = vmi->propertyValue("lis")->as<rive::ViewModelInstanceList>();

    // Number value changes once triggering a state transition
    silver.addFrame();
    numProp->propertyValue(20);
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Setting number property to same value doesn't trigger state transition
    silver.addFrame();
    numProp->propertyValue(20);
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating number property twice triggers single state transition
    silver.addFrame();
    numProp->propertyValue(10);
    numProp->propertyValue(20);
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating two properties triggers transition in different layers
    silver.addFrame();
    numProp->propertyValue(10);
    triggerProp->trigger();
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating color property twice triggers state transition
    silver.addFrame();
    colorProp->propertyValue(rive::colorARGB(100, 0, 10, 15));
    colorProp->propertyValue(rive::colorARGB(101, 0, 10, 15));
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating color property once more triggers state transition
    silver.addFrame();
    colorProp->propertyValue(rive::colorARGB(102, 0, 10, 15));
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating boolean property twice triggers state transition
    silver.addFrame();
    bolProp->propertyValue(true);
    bolProp->propertyValue(false);
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating boolean property once more triggers state transition
    silver.addFrame();
    bolProp->propertyValue(true);
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating string property twice triggers state transition
    silver.addFrame();
    stringProp->propertyValue("a");
    stringProp->propertyValue("b");
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating string property once more triggers state transition
    silver.addFrame();
    stringProp->propertyValue("c");
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());

    // Updating list property by adding two items to the list triggers a single
    // state transition
    {
        silver.addFrame();
        auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
        lisProp->addItem(itemList);
        auto itemList2 = rive::make_rcp<rive::ViewModelInstanceListItem>();
        lisProp->addItem(itemList2);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    // Updating list property by adding one item to the list triggers a single
    // state transition
    {
        silver.addFrame();
        auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
        lisProp->addItem(itemList);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    // Updating list property by adding one item at a specific position triggers
    // a state transition
    {
        silver.addFrame();
        auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
        lisProp->addItemAt(itemList, 0);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    // Updating list property by adding one item at a at an invalid position
    // does not trigger a state transition
    {
        silver.addFrame();
        auto itemList = rive::make_rcp<rive::ViewModelInstanceListItem>();
        lisProp->addItemAt(itemList, 10);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    // Updating list property swapping items triggers a state transition
    {
        silver.addFrame();
        lisProp->swap(0, 1);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    // Removing item from list by index triggers a state transition
    {
        silver.addFrame();
        lisProp->removeItem(0);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    // Removing item from list by index outside of range doesn't trigger a state
    // transition
    {
        silver.addFrame();
        lisProp->removeItem(10);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("transition_self_comparator_test"));
}

TEST_CASE("Computed root transform nested artboard", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/computed_root_transform.riv", &silver);

    auto artboard = file->artboardNamed("nested-artboard-main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    int frames = (int)(1.0f / 0.016f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.016f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("computed_root_transform-nested_artboard"));
}

TEST_CASE("Computed root transform list", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/computed_root_transform.riv", &silver);

    auto artboard = file->artboardNamed("list-main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    int frames = (int)(1.0f / 0.016f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.016f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("computed_root_transform-list"));
}

TEST_CASE("Trigger based listeners", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/trigger_based_listeners.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());
    stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);
    stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);

    silver.addFrame();
    artboard->draw(renderer.get());

    CHECK(silver.matches("trigger_based_listeners"));
}

TEST_CASE("Custom Property Trigger Binding", "[data binding]")
{
    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/custom_property_trigger.riv", &silver);

    auto artboard = file->artboard("Main")->instance();
    REQUIRE(artboard != nullptr);
    silver.frameSize(artboard->width(), artboard->height());

    auto viewModelInstance =
        file->createDefaultViewModelInstance(artboard.get());
    REQUIRE(viewModelInstance != nullptr);
    auto machine = artboard->defaultStateMachine();
    machine->bindViewModelInstance(viewModelInstance);
    REQUIRE(machine != nullptr);
    // Advance state machine
    machine->advanceAndApply(0.0f);

    auto circle = artboard->find<rive::Shape>("MainCircle");
    REQUIRE(circle != nullptr);
    REQUIRE(circle->scaleX() == 1.0f);
    REQUIRE(circle->scaleY() == 1.0f);

    auto trig = artboard->find<rive::CustomPropertyTrigger>("Trig");
    REQUIRE(trig != nullptr);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    int frames = (int)(1.0f / 0.16f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        machine->advanceAndApply(0.16f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("custom_property_trigger_bind"));
}

TEST_CASE("Data binding solos", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/data_bind_solo.riv", &silver);

    auto artboard = file->artboardNamed("values-to-solos");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    int frames = (int)(1.0f / 0.016f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.016f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("data_bind_solo-values-to-solos"));
}

TEST_CASE("Data binding solos - target to source", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/data_bind_solo.riv", &silver);

    auto artboard = file->artboardNamed("solos-to-values");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    int frames = (int)(1.0f / 0.016f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.016f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("data_bind_solo-solos-to-values"));
}

TEST_CASE("State machine fire triggers", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file =
        ReadRiveFile("assets/state_transition_fire_trigger.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);

    silver.addFrame();
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);
    artboard->draw(renderer.get());

    silver.addFrame();
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);
    artboard->draw(renderer.get());

    CHECK(silver.matches("state_transition_fire_trigger"));
}

TEST_CASE("Custom enum properties", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/custom_property_enum.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    int frames = (int)(3.0f / 0.048f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.048f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("custom_property_enum"));
}

TEST_CASE("View model runtime properties", "[data binding]")
{

    auto file = ReadRiveFile("assets/viewmodel_runtime_file.riv");

    auto vm = file->viewModelByName("vm");
    REQUIRE(vm != nullptr);
    auto instance = vm->createDefaultInstance();
    REQUIRE(instance != nullptr);
    // Number
    auto num = instance->propertyNumber("num");
    REQUIRE(num != nullptr);
    REQUIRE(num->dataType() == rive::DataType::number);
    // string
    auto str = instance->propertyString("str");
    REQUIRE(str != nullptr);
    REQUIRE(str->dataType() == rive::DataType::string);
    // Requesting a property with the same name from cache doesn't return the
    // wrong object
    auto strWrong = instance->propertyNumber("str");
    REQUIRE(strWrong == nullptr);
    // Boolean
    auto boo = instance->propertyBoolean("boo");
    REQUIRE(boo != nullptr);
    REQUIRE(boo->dataType() == rive::DataType::boolean);
    // Color
    auto col = instance->propertyColor("col");
    REQUIRE(col != nullptr);
    REQUIRE(col->dataType() == rive::DataType::color);
    // Trigger
    auto tri = instance->propertyTrigger("tri");
    REQUIRE(tri != nullptr);
    REQUIRE(tri->dataType() == rive::DataType::trigger);
    // Enum
    auto enu = instance->propertyEnum("enu");
    REQUIRE(enu != nullptr);
    REQUIRE(enu->dataType() == rive::DataType::enumType);
    // Image
    auto ima = instance->propertyImage("ima");
    REQUIRE(ima != nullptr);
    REQUIRE(ima->dataType() == rive::DataType::assetImage);
    // Artboard
    auto art = instance->propertyArtboard("art");
    REQUIRE(art != nullptr);
    REQUIRE(art->dataType() == rive::DataType::artboard);
    // List
    auto lis = instance->propertyList("lis");
    REQUIRE(lis != nullptr);
    REQUIRE(lis->dataType() == rive::DataType::list);
    // number in nested view model: chi > num
    auto numChi = instance->propertyNumber("chi/chi-num");
    REQUIRE(numChi != nullptr);
    REQUIRE(numChi->dataType() == rive::DataType::number);
}

TEST_CASE("Trigger fires single change on listener", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/trigger_fires_single_change.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->pointerDown(rive::Vec2D(225.0f, 275.0f));
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);
    stateMachine->pointerUp(rive::Vec2D(225.0f, 275.0f));
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);
    silver.addFrame();
    artboard->draw(renderer.get());

    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->pointerDown(rive::Vec2D(225.0f, 275.0f));
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);
    stateMachine->pointerUp(rive::Vec2D(225.0f, 275.0f));
    stateMachine->advanceAndApply(0.1f);
    stateMachine->advanceAndApply(1.0f);
    silver.addFrame();
    artboard->draw(renderer.get());

    CHECK(silver.matches("trigger_fires_single_change"));
}

TEST_CASE("Convert to number", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/data_converter_to_number.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    int frames = (int)(1.2f / 0.016f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.016f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("data_converter_to_number"));
}

TEST_CASE("List to path", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/list_to_path.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);
    auto listProp =
        vmi->propertyValue("lis")->as<rive::ViewModelInstanceList>();

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    silver.addFrame();
    // Create a square with 4 XY vertices

    auto vertex1 = file->createViewModelInstance("vertex-x-y");
    auto vertexInstanceListItem1 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItem1->viewModelInstance(vertex1);
    listProp->addItem(vertexInstanceListItem1);

    auto vertex2 = file->createViewModelInstance("vertex-x-y");
    vertex2->propertyValue("x")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(100);
    auto vertexInstanceListItem2 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItem2->viewModelInstance(vertex2);
    listProp->addItem(vertexInstanceListItem2);

    auto vertex3 = file->createViewModelInstance("vertex-x-y");
    vertex3->propertyValue("x")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(100);
    vertex3->propertyValue("y")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(100);
    auto vertexInstanceListItem3 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItem3->viewModelInstance(vertex3);
    listProp->addItem(vertexInstanceListItem3);

    auto vertex4 = file->createViewModelInstance("vertex-x-y");
    vertex4->propertyValue("y")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(100);
    auto vertexInstanceListItem4 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItem4->viewModelInstance(vertex4);
    listProp->addItem(vertexInstanceListItem4);

    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    silver.addFrame();
    // Insert a mirrored vertex at index 2
    auto vertexRD1 = file->createViewModelInstance("vertex-rotation-distance");
    vertexRD1->propertyValue("x")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(200);
    vertexRD1->propertyValue("rotation")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(1.5f);
    vertexRD1->propertyValue("distance")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(20);
    auto vertexInstanceListItemRD1 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItemRD1->viewModelInstance(vertexRD1);
    listProp->addItemAt(vertexInstanceListItemRD1, 2);

    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    silver.addFrame();
    // Insert a detached vertex at index 3
    auto vertexD1 = file->createViewModelInstance("vertex-detached");
    vertexD1->propertyValue("x")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(200);
    vertexD1->propertyValue("y")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(100);
    vertexD1->propertyValue("inRotation")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(1);
    vertexD1->propertyValue("outRotation")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(2);
    vertexD1->propertyValue("inDistance")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(10);
    vertexD1->propertyValue("outDistance")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(30);
    auto vertexInstanceListItemD1 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItemD1->viewModelInstance(vertexD1);
    listProp->addItemAt(vertexInstanceListItemD1, 3);

    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    silver.addFrame();
    // Insert a cubic in out vertex at index 4
    auto vertexIO1 = file->createViewModelInstance("vertex-in-out");
    vertexIO1->propertyValue("x")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(100);
    vertexIO1->propertyValue("y")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(200);
    vertexIO1->propertyValue("inX")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(40);
    vertexIO1->propertyValue("inY")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(20);
    vertexIO1->propertyValue("outX")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(10);
    vertexIO1->propertyValue("outY")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(30);
    auto vertexInstanceListItemIO1 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItemIO1->viewModelInstance(vertexIO1);
    listProp->addItemAt(vertexInstanceListItemIO1, 4);

    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    silver.addFrame();
    // Insert a non valid vertex at index 4
    auto vertexN1 = file->createViewModelInstance("non-vertex");
    auto vertexInstanceListItemN1 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItemN1->viewModelInstance(vertexN1);
    listProp->addItemAt(vertexInstanceListItemN1, 5);

    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    silver.addFrame();
    // Insert a vertex with some paired values undefined
    auto vertexI1 = file->createViewModelInstance("vertex-incomplete");

    vertexI1->propertyValue("x")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(100);
    vertexI1->propertyValue("y")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(300);
    vertexI1->propertyValue("inDistance")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(60);
    vertexI1->propertyValue("inRotation")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(-1);
    vertexI1->propertyValue("outX")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(30);
    vertexI1->propertyValue("inX")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(-30);
    auto vertexInstanceListItemI1 =
        rive::make_rcp<rive::ViewModelInstanceListItem>();
    vertexInstanceListItemI1->viewModelInstance(vertexI1);
    listProp->addItemAt(vertexInstanceListItemI1, 4);

    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    silver.addFrame();
    // Update some values to trigger dirt updates
    vertexI1->propertyValue("inX")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(-30);

    vertex1->propertyValue("x")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(50);

    vertexRD1->propertyValue("rotation")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(1.0f);

    vertexD1->propertyValue("inDistance")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(30);

    vertexIO1->propertyValue("outY")
        ->as<rive::ViewModelInstanceNumber>()
        ->propertyValue(40);

    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    for (int i = 0; i < 60; i++)
    {
        silver.addFrame();
        vertexI1->propertyValue("inRotation")
            ->as<rive::ViewModelInstanceNumber>()
            ->propertyValue((float)i * 6);
        vertexRD1->propertyValue("rotation")
            ->as<rive::ViewModelInstanceNumber>()
            ->propertyValue((float)i * 6);
        stateMachine->advanceAndApply(0.01f);
        stateMachine->advanceAndApply(0.0f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("list_to_path"));
}

TEST_CASE("Format text with commas", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/format_number_with_commas.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    CHECK(silver.matches("format_number_with_commas"));
}

TEST_CASE("Interpolate color and number", "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/time_based_interpolation.riv", &silver);

    auto artboard = file->artboardNamed("main");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.016f);
    stateMachine->advanceAndApply(0.016f);
    stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.016f);
    stateMachine->advanceAndApply(0.016f);

    silver.addFrame();
    artboard->draw(renderer.get());

    int frames = (int)(1.0f / 0.032f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.032f);
        artboard->draw(renderer.get());
    }

    stateMachine->pointerDown(rive::Vec2D(425.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.016f);
    stateMachine->advanceAndApply(0.016f);
    stateMachine->pointerUp(rive::Vec2D(425.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.016f);
    stateMachine->advanceAndApply(0.016f);

    silver.addFrame();
    artboard->draw(renderer.get());

    for (int i = 0; i < 10; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.032f);
        artboard->draw(renderer.get());
    }

    // Interrupt interpolation mid way
    stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.016f);
    stateMachine->advanceAndApply(0.016f);
    stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
    // Advance and apply twice to take the transition and apply the next state.
    stateMachine->advanceAndApply(0.016f);
    stateMachine->advanceAndApply(0.016f);

    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.032f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("time_based_interpolation"));
}

TEST_CASE("Bidirectional data binding with source to target precedence",
          "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/bidirectional_precedence.riv", &silver);

    auto artboard = file->artboardNamed("source_first");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    auto xProp = vmi->propertyValue("x")->as<rive::ViewModelInstanceNumber>();
    auto yProp = vmi->propertyValue("y")->as<rive::ViewModelInstanceNumber>();

    // On source first these values will overwrite the target values
    // that are [250,250]
    xProp->propertyValue(100);
    yProp->propertyValue(100);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    CHECK(silver.matches("bidirectional_precedence-source_first"));
}

TEST_CASE("Bidirectional data binding with target to source precedence",
          "[data binding]")
{

    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/bidirectional_precedence.riv", &silver);

    auto artboard = file->artboardNamed("target_first");

    silver.frameSize(artboard->width(), artboard->height());

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);

    auto xProp = vmi->propertyValue("x")->as<rive::ViewModelInstanceNumber>();
    auto yProp = vmi->propertyValue("y")->as<rive::ViewModelInstanceNumber>();

    // On target first these values will be overwritten by the target values
    // that are [250,250]
    xProp->propertyValue(100);
    yProp->propertyValue(100);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.0f);
    stateMachine->advanceAndApply(0.016f);

    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    CHECK(silver.matches("bidirectional_precedence-target_first"));
}

TEST_CASE("Artboards as conditions", "[silver]")
{
    SerializingFactory silver;
    auto file = ReadRiveFile("assets/databind_artboard.riv", &silver);

    auto artboard = file->artboardDefault();
    REQUIRE(artboard != nullptr);

    silver.frameSize(artboard->width(), artboard->height());

    auto stateMachine = artboard->stateMachineAt(0);

    auto vmi =
        file->createViewModelInstance((int)artboard.get()->viewModelId(), 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.1f);
    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    silver.addFrame();

    stateMachine->pointerDown(rive::Vec2D(247, 332));
    stateMachine->pointerUp(rive::Vec2D(247, 332));
    stateMachine->advanceAndApply(0.1f);

    artboard->draw(renderer.get());

    silver.addFrame();

    stateMachine->advanceAndApply(0.1f);

    artboard->draw(renderer.get());

    CHECK(silver.matches("databind_artboard"));
}

TEST_CASE("Relative data binding", "[silver]")
{
    SerializingFactory silver;
    auto file = ReadRiveFile("assets/relative_data_binding.riv", &silver);

    auto artboard = file->artboardDefault();
    REQUIRE(artboard != nullptr);

    silver.frameSize(artboard->width(), artboard->height());

    auto stateMachine = artboard->stateMachineAt(0);

    auto vmi =
        file->createViewModelInstance((int)artboard.get()->viewModelId(), 0);

    stateMachine->bindViewModelInstance(vmi);
    stateMachine->advanceAndApply(0.1f);
    auto renderer = silver.makeRenderer();
    artboard->draw(renderer.get());

    CHECK(silver.matches("relative_data_binding"));
}
TEST_CASE("Relative data binding view model path", "[silver]")
{
    SerializingFactory silver;
    auto file = ReadRiveFile("assets/relative_data_bind_path.riv", &silver);

    auto artboard = file->artboardDefault();
    REQUIRE(artboard != nullptr);

    silver.frameSize(artboard->width(), artboard->height());

    auto stateMachine = artboard->stateMachineAt(0);
    auto renderer = silver.makeRenderer();
    // First use the default view model instance that is attached to the
    // artboard
    {
        auto vmi =
            file->createViewModelInstance((int)artboard.get()->viewModelId(),
                                          0);

        stateMachine->bindViewModelInstance(vmi);
        stateMachine->advanceAndApply(0.1f);
        artboard->draw(renderer.get());
        silver.addFrame();
    }
    // Next bind it to a different view model type that matches the expected
    // view model shape
    {
        auto vm = file->viewModel("ViewModel1");
        auto vmi = file->createDefaultViewModelInstance(vm);
        stateMachine->bindViewModelInstance(vmi);
        stateMachine->advanceAndApply(0.1f);
        artboard->draw(renderer.get());
        silver.addFrame();
    }
    // Next bind it to a different view model with the wrong shape
    {
        auto vm = file->viewModel("ViewModel2");
        auto vmi = file->createDefaultViewModelInstance(vm);
        stateMachine->bindViewModelInstance(vmi);
        stateMachine->advanceAndApply(0.1f);
        artboard->draw(renderer.get());
    }

    CHECK(silver.matches("relative_data_bind_path"));
}

TEST_CASE("Listen to view model value changes in state machines", "[silver]")
{
    SerializingFactory silver;
    auto file = ReadRiveFile("assets/listener_view_model.riv", &silver);

    auto artboard = file->artboardDefault();
    silver.frameSize(artboard->width(), artboard->height());

    auto stateMachine = artboard->stateMachineAt(0);
    int viewModelId = artboard.get()->viewModelId();

    auto vmi = viewModelId == -1
                   ? file->createViewModelInstance(artboard.get())
                   : file->createViewModelInstance(viewModelId, 0);
    auto colorProp = vmi->propertyValue("col")->as<ViewModelInstanceColor>();
    auto triggerProp =
        vmi->propertyValue("tri")->as<ViewModelInstanceTrigger>();
    auto numProp = vmi->propertyValue("num1")->as<ViewModelInstanceNumber>();

    stateMachine->bindViewModelInstance(vmi);
    auto renderer = silver.makeRenderer();
    stateMachine->advanceAndApply(0.0f);
    artboard->draw(renderer.get());
    silver.addFrame();
    colorProp->propertyValue(rive::colorARGB(100, 0, 10, 15));
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());
    silver.addFrame();
    triggerProp->trigger();
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());
    silver.addFrame();
    numProp->propertyValue(55);
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    CHECK(silver.matches("listener_view_model"));
}