#include "document/document.h"
#include "test_harness.h"

#include <cmath>
#include <string_view>

using pp::paint::BlendMode;
using pp::document::CanvasDocument;
using pp::document::DocumentHistory;
using pp::document::DocumentConfig;
using pp::document::DocumentLayerConfig;
using pp::document::DocumentSnapshotConfig;
using pp::document::AnimationFrame;
using pp::document::LayerFacePixels;
using pp::document::SelectionMask;
using pp::document::max_document_history_entries;
using pp::document::max_canvas_dimension;
using pp::document::max_frame_count;
using pp::document::max_layer_count;
using pp::document::max_layer_name_length;
using pp::foundation::StatusCode;

namespace {

void creates_document_with_default_layers(pp::tests::Harness& h)
{
    const auto document = CanvasDocument::create(
        DocumentConfig { .width = 128, .height = 64, .layer_count = 2 });

    PP_EXPECT(h, document.ok());
    PP_EXPECT(h, document.value().width() == 128U);
    PP_EXPECT(h, document.value().height() == 64U);
    PP_EXPECT(h, document.value().layers().size() == 2U);
    PP_EXPECT(h, document.value().layers()[0].name == std::string_view("Layer 1"));
    PP_EXPECT(h, document.value().layers()[1].name == std::string_view("Layer 2"));
    PP_EXPECT(h, document.value().active_layer_index() == 0U);
    PP_EXPECT(h, document.value().frames().size() == 1U);
    PP_EXPECT(h, document.value().frames()[0].duration_ms == 100U);
    PP_EXPECT(h, document.value().layers()[0].frames.size() == 1U);
    PP_EXPECT(h, document.value().layers()[0].frames[0].duration_ms == 100U);
    PP_EXPECT(h, document.value().animation_duration_ms() == 100U);
    PP_EXPECT(h, document.value().active_frame_index() == 0U);
}

void rejects_invalid_document_configs(pp::tests::Harness& h)
{
    const auto zero_width = CanvasDocument::create(
        DocumentConfig { .width = 0, .height = 64, .layer_count = 1 });
    const auto huge_width = CanvasDocument::create(
        DocumentConfig { .width = max_canvas_dimension + 1U, .height = 64, .layer_count = 1 });
    const auto no_layers = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 0 });
    const auto too_many_layers = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = max_layer_count + 1U });

    PP_EXPECT(h, !zero_width.ok());
    PP_EXPECT(h, zero_width.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !huge_width.ok());
    PP_EXPECT(h, huge_width.status().code == StatusCode::out_of_range);
    PP_EXPECT(h, !no_layers.ok());
    PP_EXPECT(h, no_layers.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !too_many_layers.ok());
    PP_EXPECT(h, too_many_layers.status().code == StatusCode::out_of_range);
}

void manages_layer_add_remove_and_active_index(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    const auto added = document.add_layer("Paint");
    PP_EXPECT(h, added.ok());
    PP_EXPECT(h, added.value() == 1U);
    PP_EXPECT(h, document.active_layer_index() == 1U);
    PP_EXPECT(h, document.layers()[1].name == std::string_view("Paint"));

    PP_EXPECT(h, document.remove_layer(0).ok());
    PP_EXPECT(h, document.layers().size() == 1U);
    PP_EXPECT(h, document.active_layer_index() == 0U);

    const auto remove_last = document.remove_layer(0);
    PP_EXPECT(h, !remove_last.ok());
    PP_EXPECT(h, remove_last.code == StatusCode::invalid_argument);
}

void moves_layers_and_preserves_active_layer_identity(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 3 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    PP_EXPECT(h, document.set_active_layer(2).ok());
    PP_EXPECT(h, document.move_layer(2, 0).ok());
    PP_EXPECT(h, document.active_layer_index() == 0U);
    PP_EXPECT(h, document.layers()[0].name == std::string_view("Layer 3"));
    PP_EXPECT(h, document.layers()[1].name == std::string_view("Layer 1"));
    PP_EXPECT(h, document.layers()[2].name == std::string_view("Layer 2"));

    const auto bad_move = document.move_layer(4, 0);
    PP_EXPECT(h, !bad_move.ok());
    PP_EXPECT(h, bad_move.code == StatusCode::out_of_range);
}

void updates_layer_metadata(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 2 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    PP_EXPECT(h, document.rename_layer(1, "Ink").ok());
    PP_EXPECT(h, document.set_layer_visible(1, false).ok());
    PP_EXPECT(h, document.set_layer_alpha_locked(1, true).ok());
    PP_EXPECT(h, document.set_layer_opacity(1, 0.25F).ok());
    PP_EXPECT(h, document.set_layer_blend_mode(1, BlendMode::multiply).ok());

    PP_EXPECT(h, document.layers()[1].name == std::string_view("Ink"));
    PP_EXPECT(h, !document.layers()[1].visible);
    PP_EXPECT(h, document.layers()[1].alpha_locked);
    PP_EXPECT(h, std::fabs(document.layers()[1].opacity - 0.25F) < 0.0001F);
    PP_EXPECT(h, document.layers()[1].blend_mode == BlendMode::multiply);
}

void rejects_invalid_layer_metadata(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    const auto empty_name = document.rename_layer(0, "");
    const auto long_name = document.rename_layer(0, std::string(max_layer_name_length + 1U, 'x'));
    const auto missing_name = document.rename_layer(4, "Missing");
    const auto bad_opacity_low = document.set_layer_opacity(0, -0.1F);
    const auto bad_opacity_high = document.set_layer_opacity(0, 1.1F);
    const auto bad_opacity_nan = document.set_layer_opacity(0, std::nanf(""));
    const auto missing_visible = document.set_layer_visible(2, true);
    const auto missing_alpha_lock = document.set_layer_alpha_locked(2, true);
    const auto missing_blend = document.set_layer_blend_mode(2, BlendMode::normal);
    const auto bad_blend = document.set_layer_blend_mode(0, static_cast<BlendMode>(255));
    const auto bad_add_layer = document.add_layer(std::string(max_layer_name_length + 1U, 'x'));

    PP_EXPECT(h, !empty_name.ok());
    PP_EXPECT(h, empty_name.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !long_name.ok());
    PP_EXPECT(h, long_name.code == StatusCode::out_of_range);
    PP_EXPECT(h, !missing_name.ok());
    PP_EXPECT(h, missing_name.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_opacity_low.ok());
    PP_EXPECT(h, bad_opacity_low.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_opacity_high.ok());
    PP_EXPECT(h, bad_opacity_high.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_opacity_nan.ok());
    PP_EXPECT(h, bad_opacity_nan.code == StatusCode::out_of_range);
    PP_EXPECT(h, !missing_visible.ok());
    PP_EXPECT(h, missing_visible.code == StatusCode::out_of_range);
    PP_EXPECT(h, !missing_alpha_lock.ok());
    PP_EXPECT(h, missing_alpha_lock.code == StatusCode::out_of_range);
    PP_EXPECT(h, !missing_blend.ok());
    PP_EXPECT(h, missing_blend.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_blend.ok());
    PP_EXPECT(h, bad_blend.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !bad_add_layer.ok());
    PP_EXPECT(h, bad_add_layer.status().code == StatusCode::out_of_range);
}

void creates_document_from_snapshot_metadata(pp::tests::Harness& h)
{
    const DocumentLayerConfig layers[] {
        {
            .name = "Ink",
            .visible = false,
            .alpha_locked = true,
            .opacity = 0.5F,
            .blend_mode = BlendMode::screen,
            .frames = {},
        },
        {
            .name = "Glaze",
            .visible = true,
            .alpha_locked = false,
            .opacity = 0.75F,
            .blend_mode = BlendMode::overlay,
            .frames = {},
        },
    };
    const AnimationFrame frames[] {
        { .duration_ms = 100, .face_pixels = {} },
        { .duration_ms = 250, .face_pixels = {} },
    };

    const auto document_result = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 128,
        .height = 64,
        .layers = layers,
        .frames = frames,
        .selection_masks = {},
    });

    PP_EXPECT(h, document_result.ok());
    PP_EXPECT(h, document_result.value().layers().size() == 2U);
    PP_EXPECT(h, document_result.value().layers()[0].name == std::string_view("Ink"));
    PP_EXPECT(h, !document_result.value().layers()[0].visible);
    PP_EXPECT(h, document_result.value().layers()[0].alpha_locked);
    PP_EXPECT(h, document_result.value().layers()[0].blend_mode == BlendMode::screen);
    PP_EXPECT(h, document_result.value().layers()[0].frames.size() == 2U);
    PP_EXPECT(h, document_result.value().layers()[0].frames[1].duration_ms == 250U);
    PP_EXPECT(h, document_result.value().frames().size() == 2U);
    PP_EXPECT(h, document_result.value().animation_duration_ms() == 350U);
}

void preserves_per_layer_snapshot_timelines(pp::tests::Harness& h)
{
    const AnimationFrame project_frames[] {
        { .duration_ms = 100, .face_pixels = {} },
    };
    const AnimationFrame short_layer_frames[] {
        { .duration_ms = 100, .face_pixels = {} },
        { .duration_ms = 150, .face_pixels = {} },
    };
    const AnimationFrame long_layer_frames[] {
        { .duration_ms = 500, .face_pixels = {} },
    };
    const DocumentLayerConfig layers[] {
        {
            .name = "Short",
            .frames = short_layer_frames,
        },
        {
            .name = "Long",
            .frames = long_layer_frames,
        },
    };

    const auto document_result = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 128,
        .height = 64,
        .layers = layers,
        .frames = project_frames,
        .selection_masks = {},
    });

    PP_EXPECT(h, document_result.ok());
    PP_EXPECT(h, document_result.value().frames().size() == 1U);
    PP_EXPECT(h, document_result.value().layers()[0].frames.size() == 2U);
    PP_EXPECT(h, document_result.value().layers()[1].frames.size() == 1U);
    PP_EXPECT(h, document_result.value().layers()[0].frames[1].duration_ms == 150U);
    PP_EXPECT(h, document_result.value().layers()[1].frames[0].duration_ms == 500U);
    PP_EXPECT(h, document_result.value().animation_duration_ms() == 500U);
    const auto layer_duration = document_result.value().layer_animation_duration_ms(0);
    PP_EXPECT(h, layer_duration.ok());
    PP_EXPECT(h, layer_duration.value() == 250U);
}

void rejects_invalid_snapshot_metadata(pp::tests::Harness& h)
{
    const DocumentLayerConfig layers[] { { .name = "Ink", .frames = {} } };
    const AnimationFrame frames[] { { .duration_ms = 100, .face_pixels = {} } };
    const AnimationFrame bad_frames[] { { .duration_ms = 0, .face_pixels = {} } };
    const DocumentLayerConfig bad_layers[] { { .name = "", .frames = {} } };
    const DocumentLayerConfig bad_layer_frames[] { { .name = "Ink", .frames = bad_frames } };

    const auto no_layers = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = {},
        .frames = frames,
        .selection_masks = {},
    });
    const auto no_frames = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = layers,
        .frames = {},
        .selection_masks = {},
    });
    const auto bad_frame = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = layers,
        .frames = bad_frames,
        .selection_masks = {},
    });
    const auto bad_layer = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = bad_layers,
        .frames = frames,
        .selection_masks = {},
    });
    const auto bad_layer_frame = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = bad_layer_frames,
        .frames = frames,
        .selection_masks = {},
    });

    PP_EXPECT(h, !no_layers.ok());
    PP_EXPECT(h, no_layers.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !no_frames.ok());
    PP_EXPECT(h, no_frames.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !bad_frame.ok());
    PP_EXPECT(h, bad_frame.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !bad_layer.ok());
    PP_EXPECT(h, bad_layer.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !bad_layer_frame.ok());
    PP_EXPECT(h, bad_layer_frame.status().code == StatusCode::invalid_argument);
}

void rejects_invalid_snapshot_face_pixels(pp::tests::Harness& h)
{
    const AnimationFrame frames[] { { .duration_ms = 100, .face_pixels = {} } };
    const AnimationFrame bad_byte_count_frames[] {
        {
            .duration_ms = 100,
            .face_pixels = {
                LayerFacePixels {
                    .face_index = 0,
                    .x = 0,
                    .y = 0,
                    .width = 1,
                    .height = 1,
                    .rgba8 = {},
                },
            },
        },
    };
    const AnimationFrame outside_frames[] {
        {
            .duration_ms = 100,
            .face_pixels = {
                LayerFacePixels {
                    .face_index = 0,
                    .x = 63,
                    .y = 0,
                    .width = 2,
                    .height = 1,
                    .rgba8 = { 1, 2, 3, 4, 5, 6, 7, 8 },
                },
            },
        },
    };
    const AnimationFrame duplicate_face_frames[] {
        {
            .duration_ms = 100,
            .face_pixels = {
                LayerFacePixels {
                    .face_index = 0,
                    .x = 0,
                    .y = 0,
                    .width = 1,
                    .height = 1,
                    .rgba8 = { 1, 2, 3, 4 },
                },
                LayerFacePixels {
                    .face_index = 0,
                    .x = 1,
                    .y = 0,
                    .width = 1,
                    .height = 1,
                    .rgba8 = { 5, 6, 7, 8 },
                },
            },
        },
    };
    const DocumentLayerConfig bad_byte_count_layers[] {
        {
            .name = "Ink",
            .frames = bad_byte_count_frames,
        },
    };
    const DocumentLayerConfig outside_layers[] {
        {
            .name = "Ink",
            .frames = outside_frames,
        },
    };
    const DocumentLayerConfig duplicate_face_layers[] {
        {
            .name = "Ink",
            .frames = duplicate_face_frames,
        },
    };
    const DocumentLayerConfig layers[] {
        {
            .name = "Ink",
            .frames = {},
        },
    };

    const auto bad_layer_payload = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = bad_byte_count_layers,
        .frames = frames,
        .selection_masks = {},
    });
    const auto outside_layer_payload = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = outside_layers,
        .frames = frames,
        .selection_masks = {},
    });
    const auto bad_root_payload = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = layers,
        .frames = bad_byte_count_frames,
        .selection_masks = {},
    });
    const auto duplicate_face_payload = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 64,
        .layers = duplicate_face_layers,
        .frames = frames,
        .selection_masks = {},
    });

    PP_EXPECT(h, !bad_layer_payload.ok());
    PP_EXPECT(h, bad_layer_payload.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !outside_layer_payload.ok());
    PP_EXPECT(h, outside_layer_payload.status().code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_root_payload.ok());
    PP_EXPECT(h, bad_root_payload.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !duplicate_face_payload.ok());
    PP_EXPECT(h, duplicate_face_payload.status().code == StatusCode::invalid_argument);
}

void manages_animation_frames_and_duration(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    const auto added = document.add_frame(250);
    PP_EXPECT(h, added.ok());
    PP_EXPECT(h, added.value() == 1U);
    PP_EXPECT(h, document.active_frame_index() == 1U);
    PP_EXPECT(h, document.frames()[1].duration_ms == 250U);
    PP_EXPECT(h, document.layers()[0].frames[1].duration_ms == 250U);

    const auto duplicated = document.duplicate_frame(1);
    PP_EXPECT(h, duplicated.ok());
    PP_EXPECT(h, duplicated.value() == 2U);
    PP_EXPECT(h, document.frames()[2].duration_ms == 250U);
    PP_EXPECT(h, document.set_frame_duration(2, 333).ok());
    PP_EXPECT(h, document.frames()[2].duration_ms == 333U);
    PP_EXPECT(h, document.layers()[0].frames[2].duration_ms == 333U);
    PP_EXPECT(h, document.animation_duration_ms() == 683U);

    PP_EXPECT(h, document.remove_frame(1).ok());
    PP_EXPECT(h, document.frames().size() == 2U);
    PP_EXPECT(h, document.active_frame_index() == 1U);
    PP_EXPECT(h, document.animation_duration_ms() == 433U);
}

void moves_frames_and_preserves_active_frame_identity(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    PP_EXPECT(h, document.set_frame_duration(0, 100).ok());
    PP_EXPECT(h, document.add_frame(200).ok());
    PP_EXPECT(h, document.add_frame(300).ok());
    PP_EXPECT(h, document.add_frame(400).ok());

    PP_EXPECT(h, document.set_active_frame(2).ok());
    PP_EXPECT(h, document.move_frame(2, 0).ok());
    PP_EXPECT(h, document.active_frame_index() == 0U);
    PP_EXPECT(h, document.frames()[0].duration_ms == 300U);
    PP_EXPECT(h, document.frames()[1].duration_ms == 100U);
    PP_EXPECT(h, document.frames()[2].duration_ms == 200U);
    PP_EXPECT(h, document.frames()[3].duration_ms == 400U);

    PP_EXPECT(h, document.move_frame(3, 1).ok());
    PP_EXPECT(h, document.active_frame_index() == 0U);
    PP_EXPECT(h, document.frames()[1].duration_ms == 400U);
    PP_EXPECT(h, document.animation_duration_ms() == 1000U);

    const auto missing_from = document.move_frame(9, 0);
    const auto missing_to = document.move_frame(0, 9);
    PP_EXPECT(h, !missing_from.ok());
    PP_EXPECT(h, missing_from.code == StatusCode::out_of_range);
    PP_EXPECT(h, !missing_to.ok());
    PP_EXPECT(h, missing_to.code == StatusCode::out_of_range);
}

void rejects_invalid_animation_frame_operations(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    const auto zero_duration = document.add_frame(0);
    const auto duplicate_missing = document.duplicate_frame(9);
    const auto remove_missing = document.remove_frame(9);
    const auto remove_only = document.remove_frame(0);
    const auto set_bad_duration = document.set_frame_duration(0, 0);
    const auto set_missing_active = document.set_active_frame(2);

    PP_EXPECT(h, !zero_duration.ok());
    PP_EXPECT(h, zero_duration.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !duplicate_missing.ok());
    PP_EXPECT(h, duplicate_missing.status().code == StatusCode::out_of_range);
    PP_EXPECT(h, !remove_missing.ok());
    PP_EXPECT(h, remove_missing.code == StatusCode::out_of_range);
    PP_EXPECT(h, !remove_only.ok());
    PP_EXPECT(h, remove_only.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !set_bad_duration.ok());
    PP_EXPECT(h, set_bad_duration.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !set_missing_active.ok());
    PP_EXPECT(h, set_missing_active.code == StatusCode::out_of_range);
    PP_EXPECT(h, max_frame_count > document.frames().size());
}

void attaches_layer_frame_face_pixels(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 32, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    const auto status = document.set_layer_frame_face_pixels(
        0,
        0,
        LayerFacePixels {
            .face_index = 2,
            .x = 3,
            .y = 4,
            .width = 1,
            .height = 1,
            .rgba8 = { 10, 20, 30, 40 },
        });

    PP_EXPECT(h, status.ok());
    PP_EXPECT(h, document.face_pixel_payload_count() == 1U);
    PP_EXPECT(h, document.layers()[0].frames[0].face_pixels.size() == 1U);
    PP_EXPECT(h, document.layers()[0].frames[0].face_pixels[0].face_index == 2U);
    PP_EXPECT(h, document.layers()[0].frames[0].face_pixels[0].x == 3U);
    PP_EXPECT(h, document.layers()[0].frames[0].face_pixels[0].rgba8[3] == 40U);
}

void replaces_existing_face_pixel_payload(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 32, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    PP_EXPECT(h, document.set_layer_frame_face_pixels(
        0,
        0,
        LayerFacePixels {
            .face_index = 1,
            .x = 0,
            .y = 0,
            .width = 1,
            .height = 1,
            .rgba8 = { 1, 2, 3, 4 },
        }).ok());
    PP_EXPECT(h, document.set_layer_frame_face_pixels(
        0,
        0,
        LayerFacePixels {
            .face_index = 1,
            .x = 2,
            .y = 3,
            .width = 1,
            .height = 1,
            .rgba8 = { 5, 6, 7, 8 },
        }).ok());

    PP_EXPECT(h, document.face_pixel_payload_count() == 1U);
    PP_EXPECT(h, document.layers()[0].frames[0].face_pixels[0].x == 2U);
    PP_EXPECT(h, document.layers()[0].frames[0].face_pixels[0].rgba8[0] == 5U);
}

void rejects_invalid_face_pixel_payloads(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 32, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    const auto missing_layer = document.set_layer_frame_face_pixels(
        9,
        0,
        LayerFacePixels { .face_index = 0, .x = 0, .y = 0, .width = 1, .height = 1, .rgba8 = { 1, 2, 3, 4 } });
    const auto missing_frame = document.set_layer_frame_face_pixels(
        0,
        9,
        LayerFacePixels { .face_index = 0, .x = 0, .y = 0, .width = 1, .height = 1, .rgba8 = { 1, 2, 3, 4 } });
    const auto bad_face = document.set_layer_frame_face_pixels(
        0,
        0,
        LayerFacePixels { .face_index = 6, .x = 0, .y = 0, .width = 1, .height = 1, .rgba8 = { 1, 2, 3, 4 } });
    const auto zero_width = document.set_layer_frame_face_pixels(
        0,
        0,
        LayerFacePixels { .face_index = 0, .x = 0, .y = 0, .width = 0, .height = 1, .rgba8 = {} });
    const auto outside_bounds = document.set_layer_frame_face_pixels(
        0,
        0,
        LayerFacePixels { .face_index = 0, .x = 63, .y = 0, .width = 2, .height = 1, .rgba8 = { 1, 2, 3, 4, 5, 6, 7, 8 } });
    const auto bad_byte_count = document.set_layer_frame_face_pixels(
        0,
        0,
        LayerFacePixels { .face_index = 0, .x = 0, .y = 0, .width = 1, .height = 1, .rgba8 = { 1, 2, 3 } });

    PP_EXPECT(h, !missing_layer.ok());
    PP_EXPECT(h, missing_layer.code == StatusCode::out_of_range);
    PP_EXPECT(h, !missing_frame.ok());
    PP_EXPECT(h, missing_frame.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_face.ok());
    PP_EXPECT(h, bad_face.code == StatusCode::out_of_range);
    PP_EXPECT(h, !zero_width.ok());
    PP_EXPECT(h, zero_width.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !outside_bounds.ok());
    PP_EXPECT(h, outside_bounds.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_byte_count.ok());
    PP_EXPECT(h, bad_byte_count.code == StatusCode::invalid_argument);
    PP_EXPECT(h, document.face_pixel_payload_count() == 0U);
}

void manages_selection_masks(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 32, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    PP_EXPECT(h, document.set_selection_mask(SelectionMask {
        .face_index = 4,
        .x = 3,
        .y = 5,
        .width = 2,
        .height = 2,
        .alpha8 = { 0, 64, 128, 255 },
    }).ok());

    PP_EXPECT(h, document.selection_mask_payload_count() == 1U);
    PP_EXPECT(h, document.selection_masks().size() == 1U);
    PP_EXPECT(h, document.selection_masks()[0].face_index == 4U);
    PP_EXPECT(h, document.selection_masks()[0].x == 3U);
    PP_EXPECT(h, document.selection_masks()[0].alpha8[3] == 255U);

    PP_EXPECT(h, document.set_selection_mask(SelectionMask {
        .face_index = 4,
        .x = 7,
        .y = 9,
        .width = 1,
        .height = 1,
        .alpha8 = { 200 },
    }).ok());

    PP_EXPECT(h, document.selection_mask_payload_count() == 1U);
    PP_EXPECT(h, document.selection_masks()[0].x == 7U);
    PP_EXPECT(h, document.selection_masks()[0].alpha8[0] == 200U);

    PP_EXPECT(h, document.clear_selection_mask(4).ok());
    PP_EXPECT(h, document.selection_mask_payload_count() == 0U);
}

void rejects_invalid_selection_masks(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 32, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());
    auto document = document_result.value();

    const auto bad_face = document.set_selection_mask(
        SelectionMask { .face_index = 6, .x = 0, .y = 0, .width = 1, .height = 1, .alpha8 = { 1 } });
    const auto zero_width = document.set_selection_mask(
        SelectionMask { .face_index = 0, .x = 0, .y = 0, .width = 0, .height = 1, .alpha8 = {} });
    const auto outside_bounds = document.set_selection_mask(
        SelectionMask { .face_index = 0, .x = 63, .y = 0, .width = 2, .height = 1, .alpha8 = { 1, 2 } });
    const auto bad_byte_count = document.set_selection_mask(
        SelectionMask { .face_index = 0, .x = 0, .y = 0, .width = 2, .height = 1, .alpha8 = { 1 } });
    const auto missing_clear = document.clear_selection_mask(2);
    const auto bad_clear_face = document.clear_selection_mask(6);
    const DocumentLayerConfig layers[] { { .name = "Ink", .frames = {} } };
    const AnimationFrame frames[] { { .duration_ms = 100, .face_pixels = {} } };
    const SelectionMask duplicate_masks[] {
        { .face_index = 2, .x = 0, .y = 0, .width = 1, .height = 1, .alpha8 = { 1 } },
        { .face_index = 2, .x = 1, .y = 0, .width = 1, .height = 1, .alpha8 = { 2 } },
    };
    const auto duplicate_snapshot_masks = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 32,
        .layers = layers,
        .frames = frames,
        .selection_masks = duplicate_masks,
    });

    PP_EXPECT(h, !bad_face.ok());
    PP_EXPECT(h, bad_face.code == StatusCode::out_of_range);
    PP_EXPECT(h, !zero_width.ok());
    PP_EXPECT(h, zero_width.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !outside_bounds.ok());
    PP_EXPECT(h, outside_bounds.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_byte_count.ok());
    PP_EXPECT(h, bad_byte_count.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !missing_clear.ok());
    PP_EXPECT(h, missing_clear.code == StatusCode::out_of_range);
    PP_EXPECT(h, !bad_clear_face.ok());
    PP_EXPECT(h, bad_clear_face.code == StatusCode::out_of_range);
    PP_EXPECT(h, !duplicate_snapshot_masks.ok());
    PP_EXPECT(h, duplicate_snapshot_masks.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, document.selection_mask_payload_count() == 0U);
}

void preserves_selection_masks_in_snapshots_and_history(pp::tests::Harness& h)
{
    const DocumentLayerConfig layers[] { { .name = "Ink", .frames = {} } };
    const AnimationFrame frames[] { { .duration_ms = 100, .face_pixels = {} } };
    const SelectionMask masks[] {
        SelectionMask {
            .face_index = 3,
            .x = 1,
            .y = 2,
            .width = 1,
            .height = 2,
            .alpha8 = { 12, 240 },
        },
    };

    const auto document_result = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 32,
        .layers = layers,
        .frames = frames,
        .selection_masks = masks,
    });
    PP_EXPECT(h, document_result.ok());
    PP_EXPECT(h, document_result.value().selection_mask_payload_count() == 1U);
    PP_EXPECT(h, document_result.value().selection_masks()[0].alpha8[1] == 240U);

    auto history_result = DocumentHistory::create(document_result.value(), 3);
    PP_EXPECT(h, history_result.ok());
    auto history = history_result.value();
    auto next = history.current();
    PP_EXPECT(h, next.clear_selection_mask(3).ok());
    PP_EXPECT(h, history.apply(next).ok());
    PP_EXPECT(h, history.undo().ok());
    PP_EXPECT(h, history.current().selection_mask_payload_count() == 1U);
}

void records_document_history_and_restores_snapshots(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());

    auto history_result = DocumentHistory::create(document_result.value(), 4);
    PP_EXPECT(h, history_result.ok());
    auto history = history_result.value();

    auto with_layer = history.current();
    const auto added_layer = with_layer.add_layer("Paint");
    PP_EXPECT(h, added_layer.ok());
    PP_EXPECT(h, history.apply(with_layer).ok());

    auto with_frame = history.current();
    const auto added_frame = with_frame.add_frame(250);
    PP_EXPECT(h, added_frame.ok());
    PP_EXPECT(h, history.apply(with_frame).ok());

    PP_EXPECT(h, history.size() == 3U);
    PP_EXPECT(h, history.current_index() == 2U);
    PP_EXPECT(h, history.current().layers().size() == 2U);
    PP_EXPECT(h, history.current().frames().size() == 2U);
    PP_EXPECT(h, history.can_undo());
    PP_EXPECT(h, !history.can_redo());

    PP_EXPECT(h, history.undo().ok());
    PP_EXPECT(h, history.current().layers().size() == 2U);
    PP_EXPECT(h, history.current().frames().size() == 1U);
    PP_EXPECT(h, history.can_redo());

    PP_EXPECT(h, history.undo().ok());
    PP_EXPECT(h, history.current().layers().size() == 1U);
    PP_EXPECT(h, history.current().frames().size() == 1U);
    const auto undo_past_start = history.undo();
    PP_EXPECT(h, !undo_past_start.ok());
    PP_EXPECT(h, undo_past_start.code == StatusCode::out_of_range);

    PP_EXPECT(h, history.redo().ok());
    PP_EXPECT(h, history.current().layers().size() == 2U);
}

void applying_after_undo_discards_redo_branch(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());

    auto history_result = DocumentHistory::create(document_result.value(), 5);
    PP_EXPECT(h, history_result.ok());
    auto history = history_result.value();

    auto first_branch = history.current();
    PP_EXPECT(h, first_branch.add_layer("Branch A").ok());
    PP_EXPECT(h, history.apply(first_branch).ok());

    auto second_branch = history.current();
    PP_EXPECT(h, second_branch.add_layer("Branch B").ok());
    PP_EXPECT(h, history.apply(second_branch).ok());

    PP_EXPECT(h, history.undo().ok());
    PP_EXPECT(h, history.can_redo());

    auto replacement_branch = history.current();
    PP_EXPECT(h, replacement_branch.add_layer("Replacement").ok());
    PP_EXPECT(h, history.apply(replacement_branch).ok());

    PP_EXPECT(h, !history.can_redo());
    PP_EXPECT(h, history.current().layers().size() == 3U);
    PP_EXPECT(h, history.current().layers()[2].name == std::string_view("Replacement"));
}

void bounds_document_history_capacity(pp::tests::Harness& h)
{
    auto document_result = CanvasDocument::create(
        DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
    PP_EXPECT(h, document_result.ok());

    auto too_small = DocumentHistory::create(document_result.value(), 1);
    auto too_large = DocumentHistory::create(document_result.value(), max_document_history_entries + 1U);
    PP_EXPECT(h, !too_small.ok());
    PP_EXPECT(h, too_small.status().code == StatusCode::invalid_argument);
    PP_EXPECT(h, !too_large.ok());
    PP_EXPECT(h, too_large.status().code == StatusCode::out_of_range);

    auto history_result = DocumentHistory::create(document_result.value(), 3);
    PP_EXPECT(h, history_result.ok());
    auto history = history_result.value();

    for (std::uint32_t i = 0; i < 5U; ++i) {
        auto next = history.current();
        const auto added = next.add_frame(100U + i);
        PP_EXPECT(h, added.ok());
        PP_EXPECT(h, history.apply(next).ok());
        PP_EXPECT(h, history.size() <= 3U);
    }

    PP_EXPECT(h, history.size() == 3U);
    PP_EXPECT(h, history.current_index() == 2U);
    PP_EXPECT(h, history.current().frames().size() == 6U);

    PP_EXPECT(h, history.undo().ok());
    PP_EXPECT(h, history.current().frames().size() == 5U);
    PP_EXPECT(h, history.undo().ok());
    PP_EXPECT(h, history.current().frames().size() == 4U);
    const auto undo_evicted_entry = history.undo();
    PP_EXPECT(h, !undo_evicted_entry.ok());
    PP_EXPECT(h, undo_evicted_entry.code == StatusCode::out_of_range);
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("creates_document_with_default_layers", creates_document_with_default_layers);
    harness.run("rejects_invalid_document_configs", rejects_invalid_document_configs);
    harness.run("manages_layer_add_remove_and_active_index", manages_layer_add_remove_and_active_index);
    harness.run("moves_layers_and_preserves_active_layer_identity", moves_layers_and_preserves_active_layer_identity);
    harness.run("updates_layer_metadata", updates_layer_metadata);
    harness.run("rejects_invalid_layer_metadata", rejects_invalid_layer_metadata);
    harness.run("creates_document_from_snapshot_metadata", creates_document_from_snapshot_metadata);
    harness.run("preserves_per_layer_snapshot_timelines", preserves_per_layer_snapshot_timelines);
    harness.run("rejects_invalid_snapshot_metadata", rejects_invalid_snapshot_metadata);
    harness.run("rejects_invalid_snapshot_face_pixels", rejects_invalid_snapshot_face_pixels);
    harness.run("manages_animation_frames_and_duration", manages_animation_frames_and_duration);
    harness.run("moves_frames_and_preserves_active_frame_identity", moves_frames_and_preserves_active_frame_identity);
    harness.run("rejects_invalid_animation_frame_operations", rejects_invalid_animation_frame_operations);
    harness.run("attaches_layer_frame_face_pixels", attaches_layer_frame_face_pixels);
    harness.run("replaces_existing_face_pixel_payload", replaces_existing_face_pixel_payload);
    harness.run("rejects_invalid_face_pixel_payloads", rejects_invalid_face_pixel_payloads);
    harness.run("manages_selection_masks", manages_selection_masks);
    harness.run("rejects_invalid_selection_masks", rejects_invalid_selection_masks);
    harness.run("preserves_selection_masks_in_snapshots_and_history", preserves_selection_masks_in_snapshots_and_history);
    harness.run("records_document_history_and_restores_snapshots", records_document_history_and_restores_snapshots);
    harness.run("applying_after_undo_discards_redo_branch", applying_after_undo_discards_redo_branch);
    harness.run("bounds_document_history_capacity", bounds_document_history_capacity);
    return harness.finish();
}
