#include <rive/solo.hpp>
#include <rive/nested_artboard.hpp>
#include <rive/shapes/rectangle.hpp>
#include <rive/shapes/shape.hpp>
#include <rive/animation/state_machine_instance.hpp>
#include <rive/animation/nested_linear_animation.hpp>
#include <rive/animation/nested_state_machine.hpp>
#include <rive/viewmodel/viewmodel_instance_number.hpp>
#include "rive_file_reader.hpp"
#include "rive_testing.hpp"
#include "utils/serializing_factory.hpp"
#include <catch.hpp>
#include <cstdio>
#include <iostream>

TEST_CASE("collapsed nested artboards do not advance", "[solo]")
{
    auto file = ReadRiveFile("assets/solos_with_nested_artboards.riv");

    auto artboard = file->artboard("main-artboard")->instance();
    artboard->advance(0.0f);
    auto solos = artboard->find<rive::Solo>();
    REQUIRE(solos.size() == 1);
    auto stateMachine = artboard->stateMachineAt(0);
    stateMachine->advanceAndApply(0.0f);
    artboard->advance(0.75f);
    // Testing whether the squares have moved in the artboard is an indirect way
    // if checking whether the time of each artboard has advanced
    // Unfortunately there is no way of accessing the time of the animations
    // directly
    auto redNestedArtboard =
        stateMachine->artboard()->find<rive::NestedArtboard>("red-artboard");
    auto redNestedArtboardArtboard = redNestedArtboard->artboardInstance();
    auto movingShapes = redNestedArtboardArtboard->find<rive::Shape>();
    auto redRect = movingShapes.at(0);
    REQUIRE(redRect->x() > 50);
    auto greenNestedArtboard =
        stateMachine->artboard()->find<rive::NestedArtboard>("green-artboard");
    auto greenNestedArtboardArtboard = greenNestedArtboard->artboardInstance();
    auto greenMovingShapes = greenNestedArtboardArtboard->find<rive::Shape>();
    auto greenRect = greenMovingShapes.at(0);
    REQUIRE(greenRect->x() == 50);
}

TEST_CASE("nested artboards with looping animations will keep main "
          "advanceAndApply advancing",
          "[nested]")
{
    auto file = ReadRiveFile("assets/ball_test.riv");
    auto artboard = file->artboard("Artboard")->instance();
    artboard->advance(0.0f);
    auto stateMachine = artboard->stateMachineAt(0);
    REQUIRE(stateMachine->advanceAndApply(0.0f) == true);
    REQUIRE(stateMachine->advanceAndApply(1.0f) == true);
    REQUIRE(stateMachine->advanceAndApply(1.0f) == true);
}

TEST_CASE("nested artboards with one shot animations will not main "
          "advanceAndApply advancing",
          "[nested]")
{
    auto file = ReadRiveFile("assets/ball_test.riv");
    auto artboard = file->artboard("Artboard 2")->instance();
    artboard->advance(0.0f);
    auto stateMachine = artboard->stateMachineAt(0);
    REQUIRE(stateMachine->advanceAndApply(0.0f) == true);
    REQUIRE(stateMachine->advanceAndApply(0.9f) == true);
    REQUIRE(stateMachine->advanceAndApply(0.1f) == true);
    // nested artboards animation is 1s long but has an event to complete
    REQUIRE(stateMachine->advanceAndApply(0.1f) == true);
    // animation has ended and no events to report
    REQUIRE(stateMachine->advanceAndApply(0.1f) == false);
}

TEST_CASE(
    "nested artboard remapped timelines controlled by a timeline controlled "
    "itself by a joystick correctly apply the time after the joystick ran",
    "[nested]")
{
    auto file = ReadRiveFile("assets/joystick_nested_remap.riv");
    auto artboard = file->artboard("parent")->instance();
    artboard->advance(0.0f);
    auto stateMachine = artboard->stateMachineAt(0);

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

    auto child = artboard->find<rive::NestedArtboard>("child");

    auto nestedArtboardArtboard = child->artboardInstance();
    REQUIRE(nestedArtboardArtboard != nullptr);
    auto rect = nestedArtboardArtboard->find<rive::Shape>("rect");
    REQUIRE(rect != nullptr);
    REQUIRE(rect->x() == 250.0f);
}

TEST_CASE("Nested events don't conflict with parent events.", "[silver]")
{
    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/nested_events.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());
    // Pointer down on left top square, triggers child event 1
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(50.0f, 50.0f));
    // 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());

    // Pointer down on right top square, triggers child event 2
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(150.0f, 50.0f));
    // 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());

    // Pointer down on left bottom square, triggers parent event 1
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(50.0f, 150.0f));
    // 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());

    // Pointer down on right bottom square, triggers parent event 1
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(150.0f, 150.0f));
    // 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("nested_events"));
}

TEST_CASE("All nested artboard modes respect hug", "[silver]")
{
    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/nested_hug.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());
    silver.addFrame();

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

TEST_CASE("Pause and resume nested artboards", "[silver]")
{
    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/pause_nested_artboard.riv", &silver);

    auto artboard = file->artboardDefault();

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

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    auto vmi = file->createViewModelInstance(artboard.get()->viewModelId(), 0);

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

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

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

    // Click toggles rectangle color
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(250.0f, 250.0f));
    stateMachine->pointerUp(rive::Vec2D(250.0f, 250.0f));
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    // Toggle data bind pauses state machine
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
    stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

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

    // Click does not toggle color back
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(250.0f, 250.0f));
    stateMachine->pointerUp(rive::Vec2D(250.0f, 250.0f));
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    // Toggle data bind resumes state machine
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(25.0f, 25.0f));
    stateMachine->pointerUp(rive::Vec2D(25.0f, 25.0f));
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

    // Click toggles color back
    silver.addFrame();
    stateMachine->pointerDown(rive::Vec2D(250.0f, 250.0f));
    stateMachine->pointerUp(rive::Vec2D(250.0f, 250.0f));
    stateMachine->advanceAndApply(0.016f);
    artboard->draw(renderer.get());

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

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

TEST_CASE("Quantize and speed on nested artboards", "[silver]")
{
    rive::SerializingFactory silver;
    auto file =
        ReadRiveFile("assets/nested_artboard_quantize_and_speed.riv", &silver);

    auto artboard = file->artboardDefault();

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

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    auto vmi = file->createViewModelInstance(artboard.get()->viewModelId(), 0);

    auto speedProp =
        vmi->propertyValue("speed")->as<rive::ViewModelInstanceNumber>();
    auto quantizeProp =
        vmi->propertyValue("quant")->as<rive::ViewModelInstanceNumber>();

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

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

    int frames = (int)(1.0f / 0.016f);
    // Multiple state machines in different speed and quantize modes
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.016f);
        artboard->draw(renderer.get());
    }

    // Update speed and quantize values
    speedProp->propertyValue(4);
    quantizeProp->propertyValue(7);
    stateMachine->advanceAndApply(0.016f);
    for (int i = 0; i < frames; i++)
    {
        silver.addFrame();
        stateMachine->advanceAndApply(0.016f);
        artboard->draw(renderer.get());
    }

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

TEST_CASE("Nested artboard needs advance", "[silver]")
{
    rive::SerializingFactory silver;
    auto file = ReadRiveFile("assets/nested_needs_advance.riv", &silver);

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

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

    REQUIRE(artboard != nullptr);
    auto stateMachine = artboard->stateMachineAt(0);
    stateMachine->advanceAndApply(0.1f);

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

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

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