#include "uri.hpp"

#include "tap.hpp"

#include <fmt/ostream.h>

#include <ostream>


///////////////////////////////////////////////////////////////////////////////
static void
test_parse (cruft::TAP::logger &tap)
{
    static const struct {
        const char *src;

        const char *scheme;
        const char *hierarchical;
        const char *authority;
        const char *user;
        const char *host;
        const char *port;
        const char *path;
        const char *query;
        const char *fragment;
    } GOOD[] = {
        // examples from rfc3986
        {
            .src = "ftp://ftp.is.co.za/rfc/rfc1808.txt",

            .scheme = "ftp",
            .hierarchical = "ftp.is.co.za/rfc/rfc1808.txt",
            .authority = "ftp.is.co.za",
            .user = "",
            .host = "ftp.is.co.za",
            .port = "",
            .path = "/rfc/rfc1808.txt",
            .query = "",
            .fragment = "" },
        {
            .src = "http://www.ietf.org/rfc/rfc2396.txt",

            .scheme = "http",
            .hierarchical = "www.ietf.org/rfc/rfc2396.txt",
            .authority = "www.ietf.org",
            .user = "",
            .host = "www.ietf.org",
            .port = "",
            .path = "/rfc/rfc2396.txt",
            .query = "",
            .fragment = "" },

        {
            .src = "ldap://[2001:db8::7]/c=GB?objectClass?one",

            .scheme = "ldap",
            .hierarchical = "[2001:db8::7]/c=GB",
            .authority = "[2001:db8::7]",
            .user = "",
            .host = "[2001:db8::7]",
            .port = "",
            .path = "/c=GB",
            .query = "objectClass?one",
            .fragment = "" },

        {
            .src = "mailto:John.Doe@example.com",

            .scheme= "mailto",
            .hierarchical = "John.Doe@example.com",
            .authority= "",
            .user = "",
            .host = "",
            .port = "",
            .path= "John.Doe@example.com",
            .query= "",
            .fragment= "" },

        {
            .src  = "news:comp.infosystems.www.servers.unix",
            .scheme= "news",
            .hierarchical = "comp.infosystems.www.servers.unix",
            .authority= "",
            .user = "",
            .host = "",
            .port = "",
            .path= "comp.infosystems.www.servers.unix",
            .query= "",
            .fragment= "" },

        {
            .src  = "tel:+1-816-555-1212",

            .scheme= "tel",
            .hierarchical = "+1-816-555-1212",
            .authority= "",
            .user = "",
            .host = "",
            .port = "",
            .path= "+1-816-555-1212",
            .query= "",
            .fragment= "" },

        {
            .src  = "telnet://192.0.2.16:80/",

            .scheme= "telnet",
            .hierarchical = "192.0.2.16:80/",
            .authority= "192.0.2.16:80",
            .user = "",
            .host = "192.0.2.16",
            .port = "80",
            .path= "/",
            .query= "",
            .fragment= "" },

        {
            .src  = "urn:oasis:names:specification:docbook:dtd:xml:4.1.2",

            .scheme= "urn",
            .hierarchical = "oasis:names:specification:docbook:dtd:xml:4.1.2",
            .authority= "",
            .user = "",
            .host = "",
            .port = "",
            .path= "oasis:names:specification:docbook:dtd:xml:4.1.2",
            .query= "",
            .fragment= "" },


        // a case with all possible components
        {
            .src = "https://user:password@example.com:80/path/to?foo=bar#fragment",

            .scheme = "https",
            .hierarchical = "user:password@example.com:80/path/to",
            .authority = "user:password@example.com:80",
            .user = "user:password",
            .host = "example.com",
            .port = "80",
            .path = "/path/to",
            .query = "foo=bar",
            .fragment = "fragment" },
    };

    for (auto t: GOOD) {
        tap.expect_nothrow ([t] (void) { cruft::uri foo (t.src); }, "nothrow parsing '{:s}'", t.src);
        cruft::uri u (t.src);

        tap.expect_eq (u.scheme (), t.scheme, "scheme for '{:s}'", t.src);
        tap.expect_eq (u.heirarchical (), t.hierarchical, "hierarchical for '{:s}'", t.src);
        tap.expect_eq (u.authority (), t.authority, "authority for '{:s}'", t.src);
        tap.expect_eq (u.host (), t.host, "host for '{:s}'", t.src);
        tap.expect_eq (u.user (), t.user, "user for '{:s}'", t.src);
        tap.expect_eq (u.port (), t.port, "port for '{:s}'", t.src);
        tap.expect_eq (u.path (), t.path, "path for '{:s}'", t.src);
        tap.expect_eq (u.query (), t.query, "query for '{:s}'", t.src);
        tap.expect_eq (u.fragment (), t.fragment, "fragment for '{:s}'", t.src);
    }

    static const char* BAD[] = {
        // "www.google.com.au",
    };

    for (auto i: BAD)
        tap.expect_throw<cruft::uri::parse_error> (
            [i] (void) { cruft::uri foo (i); }, "throw parsing '{:s}'", i
        );
}


///////////////////////////////////////////////////////////////////////////////
static void
test_normalise (cruft::TAP::logger &tap)
{
    struct {
        char const *init;

        char const *expected;
    } TESTS[] = {
        // {
        //     // RFC 3986 example
        //     "/a/b/c/./../../g",
        //     "/a/g"
        // },
        // {
        //     // RFC 3986 example
        //     "mid/content=5/../6",
        //     "mid/6"
        // },
        {
            "http://example.com/",
            "http://example.com/",
        },
        {
            "http://example.com/./",
            "http://example.com/",
        },
        {
            "http://example.com/../",
            "http://example.com/",
        },
        {
            "http://example.com/a/../b",
            "http://example.com/b",
        },
        {
            "http://example.com/a/../b/",
            "http://example.com/b/",
        },
        {
            "http://example.com/a/./b",
            "http://example.com/a/b",
        },
        {
            "http://example.com/a/./b/",
            "http://example.com/a/b/",
        },
        {
            "http://example.com/a/b/c/./d/e",
            "http://example.com/a/b/c/d/e",
        },
        {
            "http://example.com/a/b/c/../d/e",
            "http://example.com/a/b/d/e",
        },
        {
            "http://example.com/a/b/c/../../d/e",
            "http://example.com/a/d/e",
        },
        {
            "http://example.com/a/b/c/.././../d/e",
            "http://example.com/a/d/e",
        },
    };

    for (auto const [init, expected]: TESTS) {
        cruft::uri init_obj (init);
        cruft::uri expected_obj (expected);
        auto const res = normalise (init_obj);

        if (res != expected_obj)
            fmt::print (stderr, "# '{}' != '{}'\n", res, expected_obj);

        tap.expect_eq (res, expected_obj, "normalise('{}')", init);
    }
}


///////////////////////////////////////////////////////////////////////////////
static void
test_rfc_resolve (cruft::TAP::logger &tap)
{
    static constexpr char const *BASE = "http://a/b/c/d;p?q";

    struct {
        char const *relative;
        char const *resolved;
    } TESTS[] = {
        { "g:h",     "g:h" },
        { "g",       "http://a/b/c/g" },
        { "./g",     "http://a/b/c/g" },
        { "g/",      "http://a/b/c/g/" },
        { "/g",      "http://a/g" },
        { "//g",     "http://g" },
        { "?y",      "http://a/b/c/d;p?y" },
        { "g?y",     "http://a/b/c/g?y" },
        { "#s",      "http://a/b/c/d;p?q#s" },
        { "g#s",     "http://a/b/c/g#s" },
        { "g?y#s",   "http://a/b/c/g?y#s" },
        { ";x",      "http://a/b/c/;x" },
        { "g;x",     "http://a/b/c/g;x" },
        { "g;x?y#s", "http://a/b/c/g;x?y#s" },
        { "",        "http://a/b/c/d;p?q" },
        { ".",       "http://a/b/c/" },
        { "./",      "http://a/b/c/" },
        { "..",      "http://a/b/" },
        { "../",     "http://a/b/" },
        { "../g",    "http://a/b/g" },
        { "../..",   "http://a/" },
        { "../../",  "http://a/" },
        { "../../g", "http://a/g" },
    };

    cruft::uri const base (BASE);

    for (auto const [relative, expected]: TESTS) {
        cruft::uri const relative_obj (relative);
        cruft::uri const expected_obj (expected);
        cruft::uri const resolved_obj = resolve (base, cruft::uri (relative));

        if (resolved_obj != expected_obj)
            fmt::print (stderr, "# '{}' != '{}'\n", expected_obj, resolved_obj);

        tap.expect_eq (
            resolved_obj,
            expected_obj,
            "resolve '{}', '{}'",
            base, relative
        );
    }
}


///////////////////////////////////////////////////////////////////////////////
void
test_resolve (cruft::TAP::logger &tap)
{
    struct {
        char const *base;
        char const *relative;
        char const *expected;
    } TESTS[] = {
        {
            "http://example.com",
            ".",
            "http://example.com/",
        },
        {
            "http://example.com",
            "./",
            "http://example.com/",
        },
    };

    for (auto const [base, relative, expected]: TESTS) {
        cruft::uri base_obj (base);
        cruft::uri relative_obj (relative);
        cruft::uri expected_obj (expected);
        cruft::uri computed_obj = resolve (base_obj, relative_obj);

        tap.expect_eq (
            resolve (base_obj, relative_obj),
            expected_obj,
            "resolve '{}', '{}'",
            base, relative
        );
    }
}


///////////////////////////////////////////////////////////////////////////////
static
void
test_validity (cruft::TAP::logger &tap)
{
    static char const *GOOD[] = {
        "http://example.com/",
        "http://example.com",
        "http://[2001:db8::7]/",
        "http://[2001:db8::7]:80/",
        "http://user@example.com:10/",
        "http://user@example.com",
        "http://example.com:443/",
        "https://example.com:80/",
        "http://example.com/foo?bar=qux#xyz",
        "http://example.com/?bar=qux#xyz",
        "http://example.com/#xyz",
        "http://example.com/???",
        "http://example.com/?#?",
        "http://example.com/%22",

        // Out of spec, but required
        "/topic/emmanuel-jean-michel-frédéric-macron-2wc",
    };

    for (auto const &good: GOOD)
        tap.expect_nothrow ([&] (void) { (void) cruft::uri (good); }, "parse: {}", good);

    static char const *BAD[] = {
        // "http://example.com/?foo=\"bar\"",
    };

    for (auto const &bad: BAD)
        tap.expect_throw<std::exception> ([&] (void) { (void) cruft::uri (bad); }, "parse: {}", bad);
}


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

    test_validity (tap);
    test_parse (tap);
    test_normalise (tap);
    test_rfc_resolve (tap);
    test_resolve (tap);

    return tap.status ();
}