#include "assets/ppi_header.h"
#include "document/document.h"
#include "document/ppi_export.h"
#include "document/ppi_import.h"
#include "test_harness.h"

#include <cstdint>
#include <span>
#include <vector>

using pp::document::AnimationFrame;
using pp::document::CanvasDocument;
using pp::document::DocumentLayerConfig;
using pp::document::DocumentSnapshotConfig;
using pp::document::LayerFacePixels;
using pp::document::export_ppi_project_document;
using pp::foundation::StatusCode;

namespace {

void exports_document_metadata_and_face_payloads(pp::tests::Harness& h)
{
    std::vector<std::uint8_t> red_pixel { 255, 0, 0, 255 };
    std::vector<std::uint8_t> blue_pixel { 0, 0, 255, 128 };
    const AnimationFrame root_frames[] {
        { .duration_ms = 100, .face_pixels = {} },
        { .duration_ms = 250, .face_pixels = {} },
    };
    const AnimationFrame base_frames[] {
        {
            .duration_ms = 100,
            .face_pixels = {
                LayerFacePixels {
                    .face_index = 0,
                    .x = 2,
                    .y = 3,
                    .width = 1,
                    .height = 1,
                    .rgba8 = red_pixel,
                },
            },
        },
        { .duration_ms = 250, .face_pixels = {} },
    };
    const AnimationFrame paint_frames[] {
        {
            .duration_ms = 333,
            .face_pixels = {
                LayerFacePixels {
                    .face_index = 5,
                    .x = 4,
                    .y = 5,
                    .width = 1,
                    .height = 1,
                    .rgba8 = blue_pixel,
                },
            },
        },
    };
    const DocumentLayerConfig layers[] {
        {
            .name = "Base",
            .visible = true,
            .alpha_locked = false,
            .opacity = 1.0F,
            .blend_mode = pp::paint::BlendMode::normal,
            .frames = std::span<const AnimationFrame>(base_frames, 2),
        },
        {
            .name = "Paint",
            .visible = false,
            .alpha_locked = true,
            .opacity = 0.5F,
            .blend_mode = pp::paint::BlendMode::overlay,
            .frames = std::span<const AnimationFrame>(paint_frames, 1),
        },
    };
    const auto document = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 32,
        .layers = std::span<const DocumentLayerConfig>(layers, 2),
        .frames = std::span<const AnimationFrame>(root_frames, 2),
        .selection_masks = {},
    });
    PP_EXPECT(h, document.ok());

    const auto exported = export_ppi_project_document(document.value());

    PP_EXPECT(h, exported.ok());
    const auto decoded = pp::assets::decode_ppi_project_images(exported.value());
    PP_EXPECT(h, decoded.ok());
    PP_EXPECT(h, decoded.value().project.body.summary.width == 64U);
    PP_EXPECT(h, decoded.value().project.body.summary.height == 32U);
    PP_EXPECT(h, decoded.value().project.body.summary.layer_count == 2U);
    PP_EXPECT(h, decoded.value().project.body.summary.declared_frame_count == 3U);
    PP_EXPECT(h, decoded.value().project.body.summary.dirty_face_count == 2U);
    PP_EXPECT(h, decoded.value().project.body.layers[0].name == "Base");
    PP_EXPECT(h, decoded.value().project.body.layers[0].frames.size() == 2U);
    PP_EXPECT(h, decoded.value().project.body.layers[0].frames[1].duration_ms == 250U);
    PP_EXPECT(h, decoded.value().project.body.layers[1].name == "Paint");
    PP_EXPECT(h, decoded.value().project.body.layers[1].opacity == 0.5F);
    PP_EXPECT(h, decoded.value().project.body.layers[1].blend_mode == 4U);
    PP_EXPECT(h, decoded.value().project.body.layers[1].alpha_locked);
    PP_EXPECT(h, !decoded.value().project.body.layers[1].visible);
    PP_EXPECT(h, decoded.value().faces.size() == 2U);
    PP_EXPECT(h, decoded.value().faces[0].layer_index == 0U);
    PP_EXPECT(h, decoded.value().faces[0].frame_index == 0U);
    PP_EXPECT(h, decoded.value().faces[0].face_index == 0U);
    PP_EXPECT(h, decoded.value().faces[0].descriptor.x0 == 2U);
    PP_EXPECT(h, decoded.value().faces[0].image.pixels[0] == 255U);
    PP_EXPECT(h, decoded.value().faces[1].layer_index == 1U);
    PP_EXPECT(h, decoded.value().faces[1].frame_index == 0U);
    PP_EXPECT(h, decoded.value().faces[1].face_index == 5U);
    PP_EXPECT(h, decoded.value().faces[1].descriptor.y0 == 5U);
    PP_EXPECT(h, decoded.value().faces[1].image.pixels[2] == 255U);
    PP_EXPECT(h, decoded.value().faces[1].image.pixels[3] == 128U);

    const auto imported = pp::document::import_ppi_project_document(decoded.value());
    PP_EXPECT(h, imported.ok());
    PP_EXPECT(h, imported.value().layers().size() == 2U);
    PP_EXPECT(h, imported.value().layers()[1].alpha_locked);
    PP_EXPECT(h, imported.value().face_pixel_payload_count() == 2U);
}

void rejects_snapshot_payload_that_cannot_export(pp::tests::Harness& h)
{
    const AnimationFrame root_frames[] {
        { .duration_ms = 100, .face_pixels = {} },
    };
    const AnimationFrame bad_frames[] {
        {
            .duration_ms = 100,
            .face_pixels = {
                LayerFacePixels {
                    .face_index = 0,
                    .x = 0,
                    .y = 0,
                    .width = 1,
                    .height = 1,
                    .rgba8 = {},
                },
            },
        },
    };
    const DocumentLayerConfig layers[] {
        {
            .name = "Bad",
            .visible = true,
            .alpha_locked = false,
            .opacity = 1.0F,
            .blend_mode = pp::paint::BlendMode::normal,
            .frames = std::span<const AnimationFrame>(bad_frames, 1),
        },
    };

    const auto document = CanvasDocument::create_from_snapshot(DocumentSnapshotConfig {
        .width = 64,
        .height = 32,
        .layers = std::span<const DocumentLayerConfig>(layers, 1),
        .frames = std::span<const AnimationFrame>(root_frames, 1),
        .selection_masks = {},
    });

    PP_EXPECT(h, !document.ok());
    PP_EXPECT(h, document.status().code == StatusCode::invalid_argument);
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("exports_document_metadata_and_face_payloads", exports_document_metadata_and_face_payloads);
    harness.run("rejects_snapshot_payload_that_cannot_export", rejects_snapshot_payload_that_cannot_export);
    return harness.finish();
}
