#include "asl/testing/testing.hpp"
#include "asl/hashing/hash.hpp"
#include "asl/strings/string_view.hpp"
#include "asl/strings/string.hpp"
#include "asl/containers/buffer.hpp"
#include "asl/types/box.hpp"
#include "asl/types/option.hpp"
#include "asl/types/status.hpp"
#include "asl/types/status_or.hpp"

static_assert(!asl::hashable<int*>);
static_assert(!asl::hashable<int[]>);
static_assert(!asl::hashable<int[9]>);

static_assert(asl::hashable<uint8_t>);
static_assert(asl::hashable<uint16_t>);
static_assert(asl::hashable<uint32_t>);
static_assert(asl::hashable<uint64_t>);
static_assert(asl::hashable<uint128_t>);

static_assert(asl::hashable<int8_t>);
static_assert(asl::hashable<int16_t>);
static_assert(asl::hashable<int32_t>);
static_assert(asl::hashable<int64_t>);

ASL_TEST(integers)
{
    uint64_t a = asl::hash_value<uint16_t>(45);
    uint64_t b = asl::hash_value<uint16_t>(45);
    uint64_t c = asl::hash_value<uint16_t>(46);
    uint64_t d = asl::hash_value<uint32_t>(45);

    ASL_TEST_EXPECT(a == b);
    ASL_TEST_EXPECT(a != c);
    ASL_TEST_EXPECT(a != d);
}

static_assert(asl::hashable<bool>);

ASL_TEST(bool)
{
    ASL_TEST_EXPECT(asl::hash_value(true) == asl::hash_value(true));
    ASL_TEST_EXPECT(asl::hash_value(false) == asl::hash_value(false));
    ASL_TEST_EXPECT(asl::hash_value(true) != asl::hash_value(false));
}

static_assert(asl::hashable<asl::string_view>);
static_assert(asl::hashable<asl::string<>>);

ASL_TEST(strings)
{
    ASL_TEST_EXPECT(asl::hash_value("hello"_sv) == asl::hash_value("hello"_sv));
    ASL_TEST_EXPECT(asl::hash_value("hello"_sv) != asl::hash_value("hello "_sv));
    ASL_TEST_EXPECT(asl::hash_value("hello"_sv) != asl::hash_value("HELLO"_sv));

    ASL_TEST_EXPECT(asl::hash_value(asl::string("hello"_sv)) == asl::hash_value(asl::string("hello"_sv)));
    ASL_TEST_EXPECT(asl::hash_value(asl::string("hello"_sv)) != asl::hash_value(asl::string("hello "_sv)));
    ASL_TEST_EXPECT(asl::hash_value(asl::string("hello"_sv)) != asl::hash_value(asl::string("HELLO"_sv)));

    ASL_TEST_EXPECT(asl::hash_value("hello"_sv) == asl::hash_value(asl::string("hello"_sv)));
}

static_assert(asl::hashable<asl::span<const int>>);
static_assert(!asl::hashable<asl::span<const int*>>);
static_assert(asl::hashable<asl::span<asl::string_view>>);

ASL_TEST(span)
{
    int ints1[] = {1, 2, 3};
    int ints2[] = {1, 2, 3};
    int ints3[] = {1, 2};
    int ints4[] = {3, 2, 1};

    ASL_TEST_EXPECT(asl::hash_value(asl::span<int>(ints1)) == asl::hash_value(asl::span<int>(ints2)));
    ASL_TEST_EXPECT(asl::hash_value(asl::span<int>(ints1)) != asl::hash_value(asl::span<int>(ints3)));
    ASL_TEST_EXPECT(asl::hash_value(asl::span<int>(ints1)) != asl::hash_value(asl::span<int>(ints4)));

    asl::string_view strs1[] = {"a", "abc", "hello"};
    asl::string_view strs2[] = {"a", "abc", "hello"};
    asl::string_view strs3[] = {"a", "abc"};
    asl::string_view strs4[] = {"a", "abc", "hello", "what"};

    ASL_TEST_EXPECT(asl::hash_value(asl::span<asl::string_view>(strs1)) == asl::hash_value(asl::span<asl::string_view>(strs2)));
    ASL_TEST_EXPECT(asl::hash_value(asl::span<asl::string_view>(strs1)) != asl::hash_value(asl::span<asl::string_view>(strs3)));
    ASL_TEST_EXPECT(asl::hash_value(asl::span<asl::string_view>(strs1)) != asl::hash_value(asl::span<asl::string_view>(strs4)));
}

static_assert(asl::hashable<asl::buffer<int>>);
static_assert(!asl::hashable<asl::buffer<int*>>);

ASL_TEST(buffer)
{
    asl::buffer<int> ints1;
    ints1.push(1);
    ints1.push(2);
    ints1.push(3);

    asl::buffer<int> ints2;
    ints2.push(1);
    ints2.push(2);
    ints2.push(3);

    asl::buffer<int> ints3;
    ints3.push(1);
    ints3.push(2);

    asl::buffer<int> ints4;
    ints4.push(1);
    ints4.push(2);
    ints4.push(4);

    ASL_TEST_EXPECT(asl::hash_value(ints1) == asl::hash_value(ints2));
    ASL_TEST_EXPECT(asl::hash_value(ints1) != asl::hash_value(ints3));
    ASL_TEST_EXPECT(asl::hash_value(ints1) != asl::hash_value(ints4));
    ASL_TEST_EXPECT(asl::hash_value(ints1) == asl::hash_value(ints1.as_span()));

    asl::buffer<asl::string_view> strs1;
    strs1.push("Hello");
    strs1.push("World");

    asl::buffer<asl::string_view> strs2;
    strs2.push("Hello");
    strs2.push("World");

    asl::buffer<asl::string_view> strs3;
    strs3.push("Hello");
    strs3.push("world");

    asl::buffer<asl::string_view> strs4;
    strs4.push("Hello");
    strs4.push("World");
    strs4.push("World");

    ASL_TEST_EXPECT(asl::hash_value(strs1) == asl::hash_value(strs2));
    ASL_TEST_EXPECT(asl::hash_value(strs1) != asl::hash_value(strs3));
    ASL_TEST_EXPECT(asl::hash_value(strs1) != asl::hash_value(strs4));
    ASL_TEST_EXPECT(asl::hash_value(strs1) == asl::hash_value(strs1.as_span()));
}

enum Enum1 {};
enum class Enum2 {};

static_assert(asl::hashable<Enum1>);
static_assert(asl::hashable<Enum2>);

static_assert(!asl::hashable<asl::box<int*>>);
static_assert(asl::hashable<asl::box<asl::string_view>>);

ASL_TEST(box)
{
    auto b1 = asl::make_box<asl::string_view>("Hello, world!");
    auto b2 = asl::make_box<asl::string_view>("Hello, world!");
    auto b3 = asl::make_box<asl::string_view>("Hello, world! 2");

    ASL_TEST_EXPECT(asl::hash_value(b1) == asl::hash_value(b2));
    ASL_TEST_EXPECT(asl::hash_value(b1) != asl::hash_value(b3));
    ASL_TEST_EXPECT(asl::hash_value(b1) == asl::hash_value("Hello, world!"_sv));
}

struct NonZero
{
    int value;

    constexpr explicit NonZero(int x) : value(x)
    {
        ASL_ASSERT(x != 0);
    }

    constexpr explicit NonZero(asl::niche_t) : value(0) {}

    constexpr bool operator==(asl::niche_t) const { return value == 0; }
};

namespace asl { template<> struct is_uniquely_represented<NonZero> : true_type {}; }
static_assert(asl::has_niche<NonZero>);
static_assert(asl::uniquely_represented<NonZero>);

static_assert(asl::hashable<asl::option<int>>);
static_assert(!asl::hashable<asl::option<int*>>);
static_assert(asl::hashable<asl::option<asl::string_view>>);
static_assert(asl::hashable<asl::option<NonZero>>);
static_assert(asl::uniquely_represented<asl::option<NonZero>>);

ASL_TEST(option)
{
    asl::option<int> int1 = 0;
    asl::option<int> int2 = 0;
    asl::option<int> int3 = 1;
    asl::option<int> int4 = asl::nullopt;

    ASL_TEST_EXPECT(asl::hash_value(int1) == asl::hash_value(int2));
    ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int3));
    ASL_TEST_EXPECT(asl::hash_value(int1) != asl::hash_value(int4));

    asl::option<NonZero> noz1{8};
    asl::option<NonZero> noz2{8};
    asl::option<NonZero> noz3{9};
    asl::option<NonZero> noz4 = asl::nullopt;

    ASL_TEST_EXPECT(asl::hash_value(noz1) == asl::hash_value(noz2));
    ASL_TEST_EXPECT(asl::hash_value(noz1) != asl::hash_value(noz3));
    ASL_TEST_EXPECT(asl::hash_value(noz1) != asl::hash_value(noz4));
}

static_assert(asl::hashable<asl::status>);

ASL_TEST(status)
{
    asl::status s1 = asl::ok();
    asl::status s2 = asl::ok();
    asl::status s3 = asl::internal_error();
    asl::status s4 = asl::internal_error();
    asl::status s5 = asl::runtime_error();
    asl::status s6 = asl::internal_error("Oh, no!");
    asl::status s7 = asl::internal_error("Oh, no!");
    asl::status s8 = asl::internal_error("Oh, no");
    asl::status s9 = asl::runtime_error("Oh, no!");

    ASL_TEST_EXPECT(asl::hash_value(s1) == asl::hash_value(s2));
    ASL_TEST_EXPECT(asl::hash_value(s3) == asl::hash_value(s4));
    ASL_TEST_EXPECT(asl::hash_value(s6) == asl::hash_value(s7));

    ASL_TEST_EXPECT(asl::hash_value(s1) != asl::hash_value(s3));
    ASL_TEST_EXPECT(asl::hash_value(s1) != asl::hash_value(s5));
    ASL_TEST_EXPECT(asl::hash_value(s1) != asl::hash_value(s6));
    ASL_TEST_EXPECT(asl::hash_value(s1) != asl::hash_value(s9));

    ASL_TEST_EXPECT(asl::hash_value(s3) != asl::hash_value(s5));
    ASL_TEST_EXPECT(asl::hash_value(s3) != asl::hash_value(s6));
    ASL_TEST_EXPECT(asl::hash_value(s3) != asl::hash_value(s8));
    ASL_TEST_EXPECT(asl::hash_value(s3) != asl::hash_value(s9));

    ASL_TEST_EXPECT(asl::hash_value(s6) != asl::hash_value(s8));
    ASL_TEST_EXPECT(asl::hash_value(s6) != asl::hash_value(s9));
}

static_assert(asl::hashable<asl::status_or<int>>);
static_assert(asl::hashable<asl::status_or<asl::string_view>>);
static_assert(!asl::hashable<asl::status_or<int*>>);

ASL_TEST(status_or)
{
    asl::status_or<int> s1 = 42;
    asl::status_or<int> s2 = 42;
    asl::status_or<int> s3 = 43;
    asl::status_or<int> s4 = asl::runtime_error();
    asl::status_or<int> s5 = asl::runtime_error();
    asl::status_or<int> s6 = asl::runtime_error("Hello");
    asl::status_or<int> s7 = asl::runtime_error("Hello");

    ASL_TEST_EXPECT(asl::hash_value(s1) == asl::hash_value(s2));
    ASL_TEST_EXPECT(asl::hash_value(s4) == asl::hash_value(s5));
    ASL_TEST_EXPECT(asl::hash_value(s6) == asl::hash_value(s7));

    ASL_TEST_EXPECT(asl::hash_value(s1) != asl::hash_value(s3));
    ASL_TEST_EXPECT(asl::hash_value(s1) != asl::hash_value(s4));
    ASL_TEST_EXPECT(asl::hash_value(s1) != asl::hash_value(s6));

    ASL_TEST_EXPECT(asl::hash_value(s4) != asl::hash_value(s6));
}