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

#include <cmath>
#include <cstdint>
#include <string>
#include <string_view>

using pp::assets::SettingsDocument;
using pp::assets::SettingsValue;
using pp::assets::max_settings_entries;
using pp::assets::settings_value_type_name;
using pp::assets::validate_settings_key;
using pp::assets::validate_settings_value;
using pp::foundation::StatusCode;

namespace {

void stores_updates_and_reads_typed_values(pp::tests::Harness& h)
{
    SettingsDocument document;

    PP_EXPECT(h, document.empty());
    PP_EXPECT(h, document.set("ui.theme", std::string("dark")).ok());
    PP_EXPECT(h, document.set("brush.size", std::int64_t { 42 }).ok());
    PP_EXPECT(h, document.set("brush.opacity", 0.75).ok());
    PP_EXPECT(h, document.set("tablet.enabled", true).ok());
    PP_EXPECT(h, document.size() == 4U);
    PP_EXPECT(h, document.has("brush.size"));

    const auto theme = document.get("ui.theme");
    const auto size = document.get("brush.size");
    const auto opacity = document.get("brush.opacity");
    const auto tablet = document.get("tablet.enabled");

    PP_EXPECT(h, theme.ok());
    PP_EXPECT(h, std::get<std::string>(theme.value()) == std::string_view("dark"));
    PP_EXPECT(h, settings_value_type_name(theme.value()) == std::string_view("string"));
    PP_EXPECT(h, size.ok());
    PP_EXPECT(h, std::get<std::int64_t>(size.value()) == 42);
    PP_EXPECT(h, opacity.ok());
    PP_EXPECT(h, std::fabs(std::get<double>(opacity.value()) - 0.75) < 0.0001);
    PP_EXPECT(h, tablet.ok());
    PP_EXPECT(h, std::get<bool>(tablet.value()));

    PP_EXPECT(h, document.set("brush.size", std::int64_t { 64 }).ok());
    PP_EXPECT(h, document.size() == 4U);
    PP_EXPECT(h, std::get<std::int64_t>(document.get("brush.size").value()) == 64);
}

void unsets_and_clears_entries(pp::tests::Harness& h)
{
    SettingsDocument document;

    PP_EXPECT(h, document.set("a", true).ok());
    PP_EXPECT(h, document.set("b", std::int64_t { 2 }).ok());
    PP_EXPECT(h, document.unset("a").ok());
    PP_EXPECT(h, !document.has("a"));
    PP_EXPECT(h, document.size() == 1U);

    const auto missing = document.unset("a");
    PP_EXPECT(h, !missing.ok());
    PP_EXPECT(h, missing.code == StatusCode::out_of_range);

    document.clear();
    PP_EXPECT(h, document.empty());
}

void rejects_bad_keys_and_values(pp::tests::Harness& h)
{
    const auto empty = validate_settings_key("");
    const auto dotted_start = validate_settings_key(".bad");
    const auto dotted_end = validate_settings_key("bad.");
    const auto invalid_char = validate_settings_key("bad/key");
    const auto long_key = validate_settings_key(std::string(129, 'a'));
    const auto non_finite = validate_settings_value(SettingsValue { std::nan("") });
    const auto huge_string = validate_settings_value(SettingsValue { std::string(4097, 'x') });

    PP_EXPECT(h, !empty.ok());
    PP_EXPECT(h, empty.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !dotted_start.ok());
    PP_EXPECT(h, dotted_start.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !dotted_end.ok());
    PP_EXPECT(h, dotted_end.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !invalid_char.ok());
    PP_EXPECT(h, invalid_char.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !long_key.ok());
    PP_EXPECT(h, long_key.code == StatusCode::out_of_range);
    PP_EXPECT(h, !non_finite.ok());
    PP_EXPECT(h, non_finite.code == StatusCode::invalid_argument);
    PP_EXPECT(h, !huge_string.ok());
    PP_EXPECT(h, huge_string.code == StatusCode::out_of_range);
}

void rejects_missing_and_excessive_entries(pp::tests::Harness& h)
{
    SettingsDocument document;
    const auto missing = document.get("missing");
    PP_EXPECT(h, !missing.ok());
    PP_EXPECT(h, missing.status().code == StatusCode::out_of_range);

    for (std::size_t i = 0; i < max_settings_entries; ++i) {
        const auto key = std::string("k") + std::to_string(i);
        PP_EXPECT(h, document.set(key, std::int64_t { 1 }).ok());
    }

    const auto excessive = document.set("one-more", std::int64_t { 1 });
    PP_EXPECT(h, !excessive.ok());
    PP_EXPECT(h, excessive.code == StatusCode::out_of_range);
}

}

int main()
{
    pp::tests::Harness harness;
    harness.run("stores_updates_and_reads_typed_values", stores_updates_and_reads_typed_values);
    harness.run("unsets_and_clears_entries", unsets_and_clears_entries);
    harness.run("rejects_bad_keys_and_values", rejects_bad_keys_and_values);
    harness.run("rejects_missing_and_excessive_entries", rejects_missing_and_excessive_entries);
    return harness.finish();
}
