#include "document/ppi_export.h"

#include "assets/image_pixels.h"
#include "assets/ppi_header.h"

#include <cstdint>
#include <span>
#include <utility>

namespace pp::document {

namespace {

[[nodiscard]] pp::foundation::Result<std::uint32_t> ppi_blend_mode(
    pp::paint::BlendMode blend_mode) noexcept
{
    switch (blend_mode) {
    case pp::paint::BlendMode::normal:
        return pp::foundation::Result<std::uint32_t>::success(0);
    case pp::paint::BlendMode::multiply:
        return pp::foundation::Result<std::uint32_t>::success(1);
    case pp::paint::BlendMode::screen:
        return pp::foundation::Result<std::uint32_t>::success(2);
    case pp::paint::BlendMode::color_dodge:
        return pp::foundation::Result<std::uint32_t>::success(3);
    case pp::paint::BlendMode::overlay:
        return pp::foundation::Result<std::uint32_t>::success(4);
    }

    return pp::foundation::Result<std::uint32_t>::failure(
        pp::foundation::Status::invalid_argument("document layer blend mode cannot be exported to PPI"));
}

}

pp::foundation::Result<std::vector<std::byte>> export_ppi_project_document(
    const CanvasDocument& document)
{
    std::vector<std::vector<pp::assets::PpiFrameConfig>> frame_configs;
    frame_configs.reserve(document.layers().size());
    std::vector<pp::assets::PpiLayerConfig> layer_configs;
    layer_configs.reserve(document.layers().size());
    std::vector<std::vector<std::byte>> payloads;
    payloads.reserve(document.face_pixel_payload_count());
    std::vector<pp::assets::PpiDirtyFacePayloadConfig> dirty_faces;
    dirty_faces.reserve(document.face_pixel_payload_count());

    for (std::size_t layer_index = 0; layer_index < document.layers().size(); ++layer_index) {
        const auto& layer = document.layers()[layer_index];
        const auto blend_mode = ppi_blend_mode(layer.blend_mode);
        if (!blend_mode) {
            return pp::foundation::Result<std::vector<std::byte>>::failure(blend_mode.status());
        }

        auto& frames = frame_configs.emplace_back();
        frames.reserve(layer.frames.size());
        for (std::size_t frame_index = 0; frame_index < layer.frames.size(); ++frame_index) {
            const auto& frame = layer.frames[frame_index];
            frames.push_back(pp::assets::PpiFrameConfig {
                .duration_ms = frame.duration_ms,
            });

            for (const auto& face : frame.face_pixels) {
                const auto encoded = pp::assets::encode_png_rgba8(
                    face.width,
                    face.height,
                    face.rgba8);
                if (!encoded) {
                    return pp::foundation::Result<std::vector<std::byte>>::failure(encoded.status());
                }

                payloads.push_back(encoded.value());
                const auto& payload = payloads.back();
                dirty_faces.push_back(pp::assets::PpiDirtyFacePayloadConfig {
                    .layer_index = static_cast<std::uint32_t>(layer_index),
                    .frame_index = static_cast<std::uint32_t>(frame_index),
                    .face_index = face.face_index,
                    .x = face.x,
                    .y = face.y,
                    .width = face.width,
                    .height = face.height,
                    .png_rgba8 = std::span<const std::byte>(payload.data(), payload.size()),
                });
            }
        }

        layer_configs.push_back(pp::assets::PpiLayerConfig {
            .name = layer.name,
            .metadata = pp::assets::PpiLayerMetadataConfig {
                .opacity = layer.opacity,
                .blend_mode = blend_mode.value(),
                .alpha_locked = layer.alpha_locked,
                .visible = layer.visible,
            },
            .frames = std::span<const pp::assets::PpiFrameConfig>(frames.data(), frames.size()),
        });
    }

    return pp::assets::create_ppi_project(pp::assets::PpiProjectConfig {
        .width = document.width(),
        .height = document.height(),
        .layers = std::span<const pp::assets::PpiLayerConfig>(layer_configs.data(), layer_configs.size()),
        .dirty_faces = std::span<const pp::assets::PpiDirtyFacePayloadConfig>(dirty_faces.data(), dirty_faces.size()),
    });
}

}
