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

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

namespace {

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

    UiOverlayLifetime overlays(tree, root.value());
    const auto popup = overlays.open_popup();
    PP_EXPECT(h, popup);
    if (!popup) {
        return;
    }

    const auto child = overlays.open_child_popup(popup.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    PP_EXPECT(h, overlays.overlay_count() == 2U);
    const auto captured_child = tree.captured_node(UiCaptureKind::pointer);
    PP_EXPECT(h, captured_child);
    if (captured_child) {
        PP_EXPECT(h, captured_child.value() == child.value());
    }

    PP_EXPECT(h, overlays.close(child.value()).ok());
    PP_EXPECT(h, overlays.overlay_count() == 1U);
    PP_EXPECT(h, !tree.contains(child.value()));
    PP_EXPECT(h, tree.contains(popup.value()));

    const auto captured_parent = tree.captured_node(UiCaptureKind::pointer);
    PP_EXPECT(h, captured_parent);
    if (captured_parent) {
        PP_EXPECT(h, captured_parent.value() == popup.value());
    }
}

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

    UiOverlayLifetime overlays(tree, root.value());
    const auto popup = overlays.open_popup();
    PP_EXPECT(h, popup);
    if (!popup) {
        return;
    }

    const auto child = overlays.open_child_popup(popup.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    PP_EXPECT(h, overlays.close(popup.value()).ok());
    PP_EXPECT(h, overlays.overlay_count() == 0U);
    PP_EXPECT(h, !tree.contains(popup.value()));
    PP_EXPECT(h, !tree.contains(child.value()));
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
}

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

    UiOverlayLifetime overlays(tree, root.value());
    const auto popup = overlays.open_popup();
    PP_EXPECT(h, popup);
    if (!popup) {
        return;
    }

    const auto dialog = overlays.open_dialog(true);
    PP_EXPECT(h, dialog);
    if (!dialog) {
        return;
    }

    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() == dialog.value());
    }
    if (keyboard) {
        PP_EXPECT(h, keyboard.value() == dialog.value());
    }

    PP_EXPECT(h, overlays.close(dialog.value()).ok());
    const auto restored_pointer = tree.captured_node(UiCaptureKind::pointer);
    PP_EXPECT(h, restored_pointer);
    if (restored_pointer) {
        PP_EXPECT(h, restored_pointer.value() == popup.value());
    }
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
}

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

    UiOverlayLifetime overlays(tree, root.value());
    const auto popup = overlays.open_popup();
    PP_EXPECT(h, popup);
    if (!popup) {
        return;
    }

    const auto dialog = overlays.open_dialog(false);
    PP_EXPECT(h, dialog);
    if (!dialog) {
        return;
    }

    const auto pointer = tree.captured_node(UiCaptureKind::pointer);
    PP_EXPECT(h, pointer);
    if (pointer) {
        PP_EXPECT(h, pointer.value() == popup.value());
    }
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
    PP_EXPECT(h, overlays.top_overlay());
    if (const auto top = overlays.top_overlay()) {
        PP_EXPECT(h, top.value() == dialog.value());
    }
}

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

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

    UiOverlayLifetime overlays(tree, root.value());
    const auto close_raw = overlays.close(raw_child.value());
    const auto child_popup = overlays.open_child_popup(raw_child.value());
    PP_EXPECT(h, !close_raw.ok());
    PP_EXPECT(h, close_raw.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !child_popup.ok());
    PP_EXPECT(h, child_popup.status().code == StatusCode::invalid_argument);
}

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

    UiOverlayLifetime overlays(tree, root.value());
    const auto popup = overlays.open_popup();
    PP_EXPECT(h, popup);
    if (!popup) {
        return;
    }

    const auto dialog = overlays.open_dialog(true);
    PP_EXPECT(h, dialog);
    if (!dialog) {
        return;
    }

    overlays.clear_for_layout_reload();
    PP_EXPECT(h, overlays.overlay_count() == 0U);
    PP_EXPECT(h, !overlays.top_overlay().ok());
    PP_EXPECT(h, !tree.contains(root.value()));
    PP_EXPECT(h, !tree.contains(popup.value()));
    PP_EXPECT(h, !tree.contains(dialog.value()));
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
    PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
}

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

    UiOverlayLifetime overlays(tree, root.value());
    const auto popup = overlays.open_popup();
    PP_EXPECT(h, popup);
    if (!popup) {
        return;
    }

    const auto child = overlays.open_child_popup(popup.value());
    PP_EXPECT(h, child);
    if (!child) {
        return;
    }

    PP_EXPECT(h, overlays.close(child.value()).ok());
    PP_EXPECT(h, !overlays.close(child.value()).ok());
    PP_EXPECT(h, overlays.close(popup.value()).ok());
    PP_EXPECT(h, !overlays.close(popup.value()).ok());
    PP_EXPECT(h, overlays.overlay_count() == 0U);
    PP_EXPECT(h, !overlays.tracks(child.value()));
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run(
        "opens_root_and_child_popups_with_capture_restore",
        opens_root_and_child_popups_with_capture_restore);
    harness.run("closing_parent_popup_closes_child_branch", closing_parent_popup_closes_child_branch);
    harness.run("modal_dialog_captures_pointer_and_keyboard", modal_dialog_captures_pointer_and_keyboard);
    harness.run("modeless_dialog_does_not_steal_capture", modeless_dialog_does_not_steal_capture);
    harness.run("rejects_untracked_or_dead_overlay_closes", rejects_untracked_or_dead_overlay_closes);
    harness.run("double_close_overlay_returns_invalid_argument", double_close_overlay_returns_invalid_argument);
    harness.run("clear_for_layout_reload_invalidates_overlays", clear_for_layout_reload_invalidates_overlays);
    return harness.finish();
}
