#include "assets/brush_package.h"
#include "test_harness.h"

#include <array>
#include <cstddef>
#include <string>
#include <string_view>

namespace {

std::array<std::byte, pp::assets::ppbr_header_size> ppbr_header(std::uint16_t major, std::uint16_t minor)
{
    return {
        std::byte { 'P' },
        std::byte { 'P' },
        std::byte { 'B' },
        std::byte { 'R' },
        static_cast<std::byte>(major & 0xffU),
        static_cast<std::byte>((major >> 8U) & 0xffU),
        static_cast<std::byte>(minor & 0xffU),
        static_cast<std::byte>((minor >> 8U) & 0xffU),
    };
}

void parses_ppbr_header_and_legacy_version_tolerance(pp::tests::Harness& harness)
{
    struct Case {
        std::uint16_t major;
        std::uint16_t minor;
        bool accepted;
    };

    const std::array<Case, 6> cases {
        Case { 0U, 0U, true },
        Case { 0U, 1U, true },
        Case { 0U, 2U, true },
        Case { 1U, 1U, true },
        Case { 2U, 1U, true },
        Case { 1U, 2U, false },
    };

    for (const auto& item : cases) {
        const auto result = pp::assets::parse_ppbr_header(ppbr_header(item.major, item.minor));
        if (item.accepted) {
            PP_EXPECT(harness, result);
            PP_EXPECT(harness, result.value().major == item.major);
            PP_EXPECT(harness, result.value().minor == item.minor);
        } else {
            PP_EXPECT(harness, !result);
            PP_EXPECT(harness, result.status().code == pp::foundation::StatusCode::invalid_argument);
        }
    }
}

void rejects_truncated_and_bad_magic_headers(pp::tests::Harness& harness)
{
    const std::array<std::byte, 4> truncated {
        std::byte { 'P' },
        std::byte { 'P' },
        std::byte { 'B' },
        std::byte { 'R' },
    };
    auto bad_magic = ppbr_header(0, 1);
    bad_magic[2] = std::byte { 'X' };

    const auto truncated_result = pp::assets::parse_ppbr_header(truncated);
    const auto magic_result = pp::assets::parse_ppbr_header(bad_magic);

    PP_EXPECT(harness, !truncated_result);
    PP_EXPECT(harness, truncated_result.status().code == pp::foundation::StatusCode::out_of_range);
    PP_EXPECT(harness, !magic_result);
    PP_EXPECT(harness, magic_result.status().code == pp::foundation::StatusCode::invalid_argument);
}

void plans_export_package_and_data_paths(pp::tests::Harness& harness)
{
    const auto regular = pp::assets::plan_ppbr_export_paths(
        "D:/Paint/clouds",
        "",
        true,
        pp::assets::PpbrDataDirectoryPolicy::next_to_package);
    PP_EXPECT(harness, regular);
    if (regular) {
        PP_EXPECT(harness, regular.value().package_path == "D:/Paint/clouds.ppbr");
        PP_EXPECT(harness, regular.value().directory == "D:/Paint");
        PP_EXPECT(harness, regular.value().stem == "clouds");
        PP_EXPECT(harness, regular.value().extension == "ppbr");
        PP_EXPECT(harness, regular.value().data_directory == "D:/Paint/clouds_data");
        PP_EXPECT(harness, regular.value().data_directory_enabled);
    }

    const auto override = pp::assets::plan_ppbr_export_paths(
        "/brushes/clouds.ppbr",
        "/Users/artist/Exports",
        true,
        pp::assets::PpbrDataDirectoryPolicy::override_directory);
    PP_EXPECT(harness, override);
    if (override) {
        PP_EXPECT(harness, override.value().data_directory == "/Users/artist/Exports/clouds_data");
        PP_EXPECT(harness, override.value().data_directory_enabled);
    }

    const auto no_data = pp::assets::plan_ppbr_export_paths(
        "D:/Paint/clouds.ppbr",
        "",
        false,
        pp::assets::PpbrDataDirectoryPolicy::next_to_package);
    PP_EXPECT(harness, no_data);
    if (no_data) {
        PP_EXPECT(harness, !no_data.value().data_directory_enabled);
    }
}

void preserves_legacy_extension_containment_rule(pp::tests::Harness& harness)
{
    const auto path = pp::assets::normalize_ppbr_export_path("D:/Paint/clouds.ppbr.tmp");

    PP_EXPECT(harness, path);
    PP_EXPECT(harness, path.value() == "D:/Paint/clouds.ppbr.tmp");
}

void rejects_export_paths_that_legacy_regex_could_not_match(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        !pp::assets::plan_ppbr_export_paths(
            "",
            "",
            true,
            pp::assets::PpbrDataDirectoryPolicy::next_to_package));
    PP_EXPECT(
        harness,
        !pp::assets::plan_ppbr_export_paths(
            "clouds",
            "",
            true,
            pp::assets::PpbrDataDirectoryPolicy::next_to_package));
    PP_EXPECT(
        harness,
        !pp::assets::plan_ppbr_export_paths(
            "D:/Paint/.ppbr",
            "",
            true,
            pp::assets::PpbrDataDirectoryPolicy::next_to_package));
    PP_EXPECT(
        harness,
        !pp::assets::plan_ppbr_export_paths(
            "D:/Paint/clouds.ppbr!",
            "",
            true,
            pp::assets::PpbrDataDirectoryPolicy::next_to_package));
}

void plans_imported_brush_image_targets(pp::tests::Harness& harness)
{
    const auto brush = pp::assets::plan_brush_package_image_target_paths(
        "D:/Paint/data",
        pp::assets::BrushPackageImageKind::brush_tip,
        "cloud",
        "png");
    PP_EXPECT(harness, brush);
    if (brush) {
        PP_EXPECT(harness, brush.value().image_path == "D:/Paint/data/brushes/cloud.png");
        PP_EXPECT(harness, brush.value().thumbnail_path == "D:/Paint/data/brushes/thumbs/cloud.png");
    }

    const auto pattern = pp::assets::plan_brush_package_image_target_paths(
        "D:/Paint/data",
        pp::assets::BrushPackageImageKind::pattern,
        "paper",
        "jpg");
    PP_EXPECT(harness, pattern);
    if (pattern) {
        PP_EXPECT(harness, pattern.value().image_path == "D:/Paint/data/patterns/paper.jpg");
        PP_EXPECT(harness, pattern.value().thumbnail_path == "D:/Paint/data/patterns/thumbs/paper.jpg");
    }
}

void rejects_invalid_imported_brush_image_targets(pp::tests::Harness& harness)
{
    PP_EXPECT(
        harness,
        !pp::assets::plan_brush_package_image_target_paths(
            "",
            pp::assets::BrushPackageImageKind::brush_tip,
            "cloud",
            "png"));
    PP_EXPECT(
        harness,
        !pp::assets::plan_brush_package_image_target_paths(
            "D:/Paint/data",
            pp::assets::BrushPackageImageKind::brush_tip,
            "",
            "png"));
    PP_EXPECT(
        harness,
        !pp::assets::plan_brush_package_image_target_paths(
            "D:/Paint/data",
            pp::assets::BrushPackageImageKind::pattern,
            "paper",
            "png!"));
}

} // namespace

int main()
{
    pp::tests::Harness harness;
    harness.run("parses PPBR header and legacy version tolerance", parses_ppbr_header_and_legacy_version_tolerance);
    harness.run("rejects truncated and bad magic headers", rejects_truncated_and_bad_magic_headers);
    harness.run("plans export package and data paths", plans_export_package_and_data_paths);
    harness.run("preserves legacy extension containment rule", preserves_legacy_extension_containment_rule);
    harness.run("rejects export paths that legacy regex could not match", rejects_export_paths_that_legacy_regex_could_not_match);
    harness.run("plans imported brush image targets", plans_imported_brush_image_targets);
    harness.run("rejects invalid imported brush image targets", rejects_invalid_imported_brush_image_targets);
    return harness.finish();
}
