#include "ui_core/node_lifetime.h"
#include "test_harness.h"

using pp::foundation::StatusCode;
using pp::ui::NodeLifetimeTree;
using pp::ui::UiCaptureKind;

namespace {

void creates_parent_child_handles(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto child = tree.create_child(root.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    PP_EXPECT(h, tree.contains(root.value()));
    PP_EXPECT(h, tree.contains(child.value()));

    const auto parent = tree.parent_of(child.value());
    PP_EXPECT(h, parent);
    if (parent) {
        PP_EXPECT(h, parent.value() == root.value());
    }

    const auto count = tree.child_count(root.value());
    PP_EXPECT(h, count);
    if (count) {
        PP_EXPECT(h, count.value() == 1U);
    }

    const auto first_child = tree.child_at(root.value(), 0);
    PP_EXPECT(h, first_child);
    if (first_child) {
        PP_EXPECT(h, first_child.value() == child.value());
    }
}

void rejects_invalid_parent_and_child_lookup(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto invalid_child = tree.create_child({});
    PP_EXPECT(h, !invalid_child.ok());
    PP_EXPECT(h, invalid_child.status().code == StatusCode::invalid_argument);

    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto root_parent = tree.parent_of(root.value());
    const auto missing_child = tree.child_at(root.value(), 2);
    PP_EXPECT(h, !root_parent.ok());
    PP_EXPECT(h, root_parent.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_child.ok());
    PP_EXPECT(h, missing_child.status().code == StatusCode::out_of_range);
}

void invalidates_destroyed_subtree_handles(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto child = tree.create_child(root.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    const auto status = tree.destroy_subtree(root.value());
    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, !tree.contains(root.value()));
    PP_EXPECT(h, !tree.contains(child.value()));

    const auto reused = tree.create_root();
    PP_EXPECT(h, reused);
    if (reused) {
        PP_EXPECT(h, reused.value().slot == root.value().slot);
        PP_EXPECT(h, reused.value().generation != root.value().generation);
        PP_EXPECT(h, tree.contains(reused.value()));
        PP_EXPECT(h, !tree.contains(root.value()));
    }
}

void scoped_connections_disconnect_on_reset(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    int call_count = 0;
    auto scoped = tree.scoped_connect(root.value(), [&call_count](pp::ui::NodeHandle) {
        ++call_count;
    });
    PP_EXPECT(h, scoped);
    if (!scoped) {
        return;
    }

    PP_EXPECT(h, scoped.value().connected());
    PP_EXPECT(h, tree.dispatch(root.value()).ok());
    PP_EXPECT(h, call_count == 1);

    scoped.value().reset();
    PP_EXPECT(h, !scoped.value().connected());
    PP_EXPECT(h, tree.dispatch(root.value()).ok());
    PP_EXPECT(h, call_count == 1);
}

void destroying_node_disconnects_callbacks(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    int call_count = 0;
    const auto connection = tree.connect(root.value(), [&call_count](pp::ui::NodeHandle) {
        ++call_count;
    });
    PP_EXPECT(h, connection);
    if (!connection) {
        return;
    }

    const auto live_count = tree.live_connection_count(root.value());
    PP_EXPECT(h, live_count);
    if (live_count) {
        PP_EXPECT(h, live_count.value() == 1U);
    }

    PP_EXPECT(h, tree.destroy_subtree(root.value()).ok());
    PP_EXPECT(h, !tree.disconnect(connection.value()).ok());
}

void dispatch_survives_destroy_during_callback(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto child = tree.create_child(root.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    int first_count = 0;
    int second_count = 0;
    PP_EXPECT(h, tree.connect(child.value(), [&tree, &first_count](pp::ui::NodeHandle node) {
        ++first_count;
        (void)tree.destroy_subtree(node);
    }));
    PP_EXPECT(h, tree.connect(child.value(), [&second_count](pp::ui::NodeHandle) {
        ++second_count;
    }));

    const auto dispatch = tree.dispatch(child.value());
    PP_EXPECT(h, dispatch.ok());
    PP_EXPECT(h, first_count == 1);
    PP_EXPECT(h, second_count == 0);
    PP_EXPECT(h, !tree.contains(child.value()));
}

void dispatch_uses_stable_connection_snapshot(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    int first_count = 0;
    int second_count = 0;
    PP_EXPECT(h, tree.connect(root.value(), [&tree, &root, &first_count, &second_count](pp::ui::NodeHandle) {
        ++first_count;
        (void)tree.connect(root.value(), [&second_count](pp::ui::NodeHandle) {
            ++second_count;
        });
    }));

    PP_EXPECT(h, tree.dispatch(root.value()).ok());
    PP_EXPECT(h, first_count == 1);
    PP_EXPECT(h, second_count == 0);

    PP_EXPECT(h, tree.dispatch(root.value()).ok());
    PP_EXPECT(h, first_count == 2);
    PP_EXPECT(h, second_count == 1);
}

void destroy_subtree_clears_child_connections(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto child = tree.create_child(root.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    int parent_count = 0;
    int child_count = 0;
    const auto root_connection = tree.connect(root.value(), [&parent_count](pp::ui::NodeHandle) {
        ++parent_count;
    });
    const auto child_connection = tree.connect(child.value(), [&child_count](pp::ui::NodeHandle) {
        ++child_count;
    });
    PP_EXPECT(h, root_connection);
    PP_EXPECT(h, child_connection);
    if (!root_connection || !child_connection) {
        return;
    }

    PP_EXPECT(h, tree.dispatch(child.value()).ok());
    PP_EXPECT(h, parent_count == 0);
    PP_EXPECT(h, child_count == 1);

    PP_EXPECT(h, tree.destroy_subtree(root.value()).ok());
    PP_EXPECT(h, !tree.disconnect(root_connection.value()).ok());
    PP_EXPECT(h, !tree.disconnect(child_connection.value()).ok());
    PP_EXPECT(h, !tree.contains(root.value()));
    PP_EXPECT(h, !tree.contains(child.value()));
}

void captures_and_releases_pointer_and_keyboard_nodes(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto child = tree.create_child(root.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    PP_EXPECT(h, tree.capture(UiCaptureKind::pointer, child.value()).ok());
    PP_EXPECT(h, tree.capture(UiCaptureKind::keyboard, root.value()).ok());

    const auto pointer = tree.captured_node(UiCaptureKind::pointer);
    const auto keyboard = tree.captured_node(UiCaptureKind::keyboard);
    PP_EXPECT(h, pointer);
    PP_EXPECT(h, keyboard);
    if (pointer) {
        PP_EXPECT(h, pointer.value() == child.value());
    }
    if (keyboard) {
        PP_EXPECT(h, keyboard.value() == root.value());
    }

    PP_EXPECT(h, !tree.release_capture(UiCaptureKind::pointer, root.value()).ok());
    PP_EXPECT(h, tree.release_capture(UiCaptureKind::pointer, child.value()).ok());
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
    PP_EXPECT(h, tree.captured_node(UiCaptureKind::keyboard));
}

void destroying_captured_node_releases_capture(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto child = tree.create_child(root.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    PP_EXPECT(h, tree.capture(UiCaptureKind::pointer, child.value()).ok());
    PP_EXPECT(h, tree.capture(UiCaptureKind::keyboard, root.value()).ok());
    PP_EXPECT(h, tree.destroy_subtree(child.value()).ok());
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
    PP_EXPECT(h, tree.captured_node(UiCaptureKind::keyboard));

    PP_EXPECT(h, tree.destroy_subtree(root.value()).ok());
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
}

void clear_models_layout_reload(pp::tests::Harness& h)
{
    NodeLifetimeTree tree;
    const auto root = tree.create_root();
    PP_EXPECT(h, root);
    if (!root) {
        return;
    }

    const auto child = tree.create_child(root.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    int call_count = 0;
    const auto connection = tree.connect(child.value(), [&call_count](pp::ui::NodeHandle) {
        ++call_count;
    });
    PP_EXPECT(h, connection);
    PP_EXPECT(h, tree.capture(UiCaptureKind::pointer, child.value()).ok());

    tree.clear();
    PP_EXPECT(h, !tree.contains(root.value()));
    PP_EXPECT(h, !tree.contains(child.value()));
    PP_EXPECT(h, !tree.dispatch(child.value()).ok());
    PP_EXPECT(h, !tree.disconnect(connection.value()).ok());
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
    PP_EXPECT(h, call_count == 0);

    const auto reloaded_root = tree.create_root();
    PP_EXPECT(h, reloaded_root);
    if (reloaded_root) {
        PP_EXPECT(h, tree.contains(reloaded_root.value()));
        PP_EXPECT(h, reloaded_root.value().generation != root.value().generation);
    }
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("creates_parent_child_handles", creates_parent_child_handles);
    harness.run("rejects_invalid_parent_and_child_lookup", rejects_invalid_parent_and_child_lookup);
    harness.run("invalidates_destroyed_subtree_handles", invalidates_destroyed_subtree_handles);
    harness.run("scoped_connections_disconnect_on_reset", scoped_connections_disconnect_on_reset);
    harness.run("destroying_node_disconnects_callbacks", destroying_node_disconnects_callbacks);
    harness.run("dispatch_survives_destroy_during_callback", dispatch_survives_destroy_during_callback);
    harness.run("dispatch_uses_stable_connection_snapshot", dispatch_uses_stable_connection_snapshot);
    harness.run("destroy_subtree_clears_child_connections", destroy_subtree_clears_child_connections);
    harness.run("captures_and_releases_pointer_and_keyboard_nodes", captures_and_releases_pointer_and_keyboard_nodes);
    harness.run("destroying_captured_node_releases_capture", destroying_captured_node_releases_capture);
    harness.run("clear_models_layout_reload", clear_models_layout_reload);
    return harness.finish();
}
