#include "introspection/name.hpp"
#include "introspection/type.hpp"
#include "std.hpp"

#include "tap.hpp"
#include <iostream>


///////////////////////////////////////////////////////////////////////////////
// simple test struct of scalars
struct foo
{
    int a;
    float b;
};


//-----------------------------------------------------------------------------
namespace bar {
    struct qux {
        short c;
    };

    template <typename>
    struct templated_qux { };
}


///////////////////////////////////////////////////////////////////////////////
template <typename ...ValueT>
struct templated_with_type { };


template <int ValueV>
struct templated_with_value { };


enum enumeration_t { FOO };

template <enumeration_t EnumV>
struct templated_with_enum { };


///////////////////////////////////////////////////////////////////////////////
// define introspection data
namespace cruft::introspection
{
    template <>
    struct type<foo>
    {
        typedef std::tuple<
            field<foo,int,&foo::a>,
            field<foo,float,&foo::b>
        > fields;
    };

    template <> const std::string field<foo,int,&foo::a>::name = "a";
    template <> const std::string field<foo,float,&foo::b>::name = "b";
}


//-----------------------------------------------------------------------------
template <typename A, typename B>
static
bool
equal (A &&a, B &&b)
{
    return std::equal (
        std::begin (a), std::end (a),
        std::begin (b), std::end (b)
    );
}


///----------------------------------------------------------------------------
/// Returns true if the two strings are equal except for any embedded
/// whitespace.
static bool
whitespace_equal (std::string_view a, std::string_view b)
{
    auto const a_size = std::ssize (a);
    auto const b_size = std::ssize (b);

    int a_cursor = 0;
    int b_cursor = 0;

    while (a_cursor < a_size && b_cursor < b_size) {
        if (a[a_cursor] == ' ') {
            ++a_cursor;
            continue;
        }

        if (b[b_cursor] == ' ') {
            ++b_cursor;
            continue;
        }

        if (a[a_cursor] != b[b_cursor])
            return false;

        ++a_cursor;
        ++b_cursor;
    }

    return a_cursor == a_size && b_cursor == b_size;
}


//-----------------------------------------------------------------------------
int main ()
{
    cruft::TAP::logger tap;

    // Ensure tuples are mapped to themselves with type_tuple::type
    {
        using src_t = std::tuple<int>;
        using dst_t = typename cruft::introspection::type_tuple<src_t>::type;

        tap.expect (std::is_same<src_t, dst_t>::value, "static identity type_tuple");
    }

    // Check member extraction from a simple POD structure.
    {
        foo d_foo { 7, 42.0 };
        auto f_tuple = cruft::introspection::as_tuple (d_foo);

        tap.expect (cruft::equal (d_foo.a, std::get<0> (f_tuple)) &&
                    cruft::equal (d_foo.b, std::get<1> (f_tuple)),
                    "dynamic member access after conversion to tuple");
    }

    tap.expect_eq (cruft::view (cruft::introspection::name::bare<i08> ()), "i08", "i08 type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<i16> ()), "i16", "i16 type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<i32> ()), "i32", "i32 type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<i64> ()), "i64", "i64 type_name");

    tap.expect_eq (cruft::view (cruft::introspection::name::bare<u08> ()), "u08", "u08 type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<u16> ()), "u16", "u16 type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<u32> ()), "u32", "u32 type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<u64> ()), "u64", "u64 type_name");

    tap.expect_eq (cruft::view (cruft::introspection::name::bare<f32> ()), "f32", "f32 type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<f64> ()), "f64", "f64 type_name");

    tap.expect_eq (cruft::view (cruft::introspection::name::bare<foo> ()), "foo", "struct type_name");
    tap.expect_eq (cruft::view (cruft::introspection::name::bare<bar::qux> ()), "qux", "de-namespaced struct type_name");

    tap.expect (
        equal (
            cruft::introspection::name::full<bar::qux> (),
            std::string_view ("bar::qux")
        ),
        "namespaced struct"
    );

    tap.expect (
        equal (
            cruft::introspection::name::full<bar::templated_qux<int>> (),
            std::string_view ("bar::templated_qux<int>")
        ),
        "namespaced templated struct"
    );

    tap.expect_eq (
        cruft::introspection::name::full<templated_with_type<int>> (),
        std::string_view {"templated_with_type<int>"},
        "templated_with_type"
    );

    tap.expect_eq (
        cruft::introspection::name::full<templated_with_type<int, int>> (),
        std::string_view {"templated_with_type<int, int>"},
        "templated_with_type"
    );

    // The trailing '>' characters are separated by whitespace in GCC, and
    // Clang<11, but aren't meaningfully different in user interpretation.
    tap.expect (
        whitespace_equal (
            cruft::introspection::name::full<templated_with_type<templated_with_type<int>>> (),
            std::string_view {"templated_with_type<templated_with_type<int>>"}
        ),
        "templated_with_type"
    );

    tap.expect_eq (
        cruft::introspection::name::full<templated_with_value<-1>> (),
        std::string_view {"templated_with_value<-1>"},
        "templated_with_value"
    );

    tap.expect_eq (
        cruft::introspection::name::full<templated_with_enum<FOO>> (),
        std::string_view {"templated_with_enum<FOO>"},
        "templated_with_enum"
    );

    return tap.status ();
}