const std = @import("std");
const mem = std.mem;
const testing = std.testing;
const parse = @import("../parse.zig");

const Node = parse.Node;
const Tree = parse.Tree;

test "explicit doc" {
    const source =
        \\--- !tapi-tbd
        \\tbd-version: 4
        \\abc-version: 5
        \\...
    ;

    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);

    const directive = tree.tokens[doc.directive.?];
    try testing.expectEqual(directive.id, .Literal);
    try testing.expect(mem.eql(u8, "tapi-tbd", tree.source[directive.start..directive.end]));

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .map);

    const map = doc.value.?.cast(Node.Map).?;
    try testing.expectEqual(map.start.?, 5);
    try testing.expectEqual(map.end.?, 14);
    try testing.expectEqual(map.values.items.len, 2);

    {
        const entry = map.values.items[0];

        const key = tree.tokens[entry.key];
        try testing.expectEqual(key.id, .Literal);
        try testing.expect(mem.eql(u8, "tbd-version", tree.source[key.start..key.end]));

        const value = entry.value.cast(Node.Value).?;
        const value_tok = tree.tokens[value.start.?];
        try testing.expectEqual(value_tok.id, .Literal);
        try testing.expect(mem.eql(u8, "4", tree.source[value_tok.start..value_tok.end]));
    }

    {
        const entry = map.values.items[1];

        const key = tree.tokens[entry.key];
        try testing.expectEqual(key.id, .Literal);
        try testing.expect(mem.eql(u8, "abc-version", tree.source[key.start..key.end]));

        const value = entry.value.cast(Node.Value).?;
        const value_tok = tree.tokens[value.start.?];
        try testing.expectEqual(value_tok.id, .Literal);
        try testing.expect(mem.eql(u8, "5", tree.source[value_tok.start..value_tok.end]));
    }
}

test "leaf in quotes" {
    const source =
        \\key1: no quotes
        \\key2: 'single quoted'
        \\key3: "double quoted"
    ;

    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
    try testing.expect(doc.directive == null);

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .map);

    const map = doc.value.?.cast(Node.Map).?;
    try testing.expectEqual(map.start.?, 0);
    try testing.expectEqual(map.end.?, tree.tokens.len - 2);
    try testing.expectEqual(map.values.items.len, 3);

    {
        const entry = map.values.items[0];

        const key = tree.tokens[entry.key];
        try testing.expectEqual(key.id, .Literal);
        try testing.expect(mem.eql(
            u8,
            "key1",
            tree.source[key.start..key.end],
        ));

        const value = entry.value.cast(Node.Value).?;
        const start = tree.tokens[value.start.?];
        const end = tree.tokens[value.end.?];
        try testing.expectEqual(start.id, .Literal);
        try testing.expectEqual(end.id, .Literal);
        try testing.expect(mem.eql(
            u8,
            "no quotes",
            tree.source[start.start..end.end],
        ));
    }
}

test "nested maps" {
    const source =
        \\key1:
        \\  key1_1 : value1_1
        \\  key1_2 : value1_2
        \\key2   : value2
    ;

    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);
    try testing.expect(doc.directive == null);

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .map);

    const map = doc.value.?.cast(Node.Map).?;
    try testing.expectEqual(map.start.?, 0);
    try testing.expectEqual(map.end.?, tree.tokens.len - 2);
    try testing.expectEqual(map.values.items.len, 2);

    {
        const entry = map.values.items[0];

        const key = tree.tokens[entry.key];
        try testing.expectEqual(key.id, .Literal);
        try testing.expect(mem.eql(u8, "key1", tree.source[key.start..key.end]));

        const nested_map = entry.value.cast(Node.Map).?;
        try testing.expectEqual(nested_map.start.?, 4);
        try testing.expectEqual(nested_map.end.?, 16);
        try testing.expectEqual(nested_map.values.items.len, 2);

        {
            const nested_entry = nested_map.values.items[0];

            const nested_key = tree.tokens[nested_entry.key];
            try testing.expectEqual(nested_key.id, .Literal);
            try testing.expect(mem.eql(
                u8,
                "key1_1",
                tree.source[nested_key.start..nested_key.end],
            ));

            const nested_value = nested_entry.value.cast(Node.Value).?;
            const nested_value_tok = tree.tokens[nested_value.start.?];
            try testing.expectEqual(nested_value_tok.id, .Literal);
            try testing.expect(mem.eql(
                u8,
                "value1_1",
                tree.source[nested_value_tok.start..nested_value_tok.end],
            ));
        }

        {
            const nested_entry = nested_map.values.items[1];

            const nested_key = tree.tokens[nested_entry.key];
            try testing.expectEqual(nested_key.id, .Literal);
            try testing.expect(mem.eql(
                u8,
                "key1_2",
                tree.source[nested_key.start..nested_key.end],
            ));

            const nested_value = nested_entry.value.cast(Node.Value).?;
            const nested_value_tok = tree.tokens[nested_value.start.?];
            try testing.expectEqual(nested_value_tok.id, .Literal);
            try testing.expect(mem.eql(
                u8,
                "value1_2",
                tree.source[nested_value_tok.start..nested_value_tok.end],
            ));
        }
    }

    {
        const entry = map.values.items[1];

        const key = tree.tokens[entry.key];
        try testing.expectEqual(key.id, .Literal);
        try testing.expect(mem.eql(u8, "key2", tree.source[key.start..key.end]));

        const value = entry.value.cast(Node.Value).?;
        const value_tok = tree.tokens[value.start.?];
        try testing.expectEqual(value_tok.id, .Literal);
        try testing.expect(mem.eql(
            u8,
            "value2",
            tree.source[value_tok.start..value_tok.end],
        ));
    }
}

test "map of list of values" {
    const source =
        \\ints:
        \\  - 0
        \\  - 1
        \\  - 2
    ;
    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .map);

    const map = doc.value.?.cast(Node.Map).?;
    try testing.expectEqual(map.start.?, 0);
    try testing.expectEqual(map.end.?, tree.tokens.len - 2);
    try testing.expectEqual(map.values.items.len, 1);

    const entry = map.values.items[0];
    const key = tree.tokens[entry.key];
    try testing.expectEqual(key.id, .Literal);
    try testing.expect(mem.eql(u8, "ints", tree.source[key.start..key.end]));

    const value = entry.value.cast(Node.List).?;
    try testing.expectEqual(value.start.?, 4);
    try testing.expectEqual(value.end.?, tree.tokens.len - 2);
    try testing.expectEqual(value.values.items.len, 3);

    {
        const elem = value.values.items[0].cast(Node.Value).?;
        const leaf = tree.tokens[elem.start.?];
        try testing.expectEqual(leaf.id, .Literal);
        try testing.expect(mem.eql(u8, "0", tree.source[leaf.start..leaf.end]));
    }

    {
        const elem = value.values.items[1].cast(Node.Value).?;
        const leaf = tree.tokens[elem.start.?];
        try testing.expectEqual(leaf.id, .Literal);
        try testing.expect(mem.eql(u8, "1", tree.source[leaf.start..leaf.end]));
    }

    {
        const elem = value.values.items[2].cast(Node.Value).?;
        const leaf = tree.tokens[elem.start.?];
        try testing.expectEqual(leaf.id, .Literal);
        try testing.expect(mem.eql(u8, "2", tree.source[leaf.start..leaf.end]));
    }
}

test "map of list of maps" {
    const source =
        \\key1:
        \\- key2 : value2
        \\- key3 : value3
        \\- key4 : value4
    ;

    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .map);

    const map = doc.value.?.cast(Node.Map).?;
    try testing.expectEqual(map.start.?, 0);
    try testing.expectEqual(map.end.?, tree.tokens.len - 2);
    try testing.expectEqual(map.values.items.len, 1);

    const entry = map.values.items[0];
    const key = tree.tokens[entry.key];
    try testing.expectEqual(key.id, .Literal);
    try testing.expect(mem.eql(u8, "key1", tree.source[key.start..key.end]));

    const value = entry.value.cast(Node.List).?;
    try testing.expectEqual(value.start.?, 3);
    try testing.expectEqual(value.end.?, tree.tokens.len - 2);
    try testing.expectEqual(value.values.items.len, 3);

    {
        const elem = value.values.items[0].cast(Node.Map).?;
        const nested = elem.values.items[0];
        const nested_key = tree.tokens[nested.key];
        try testing.expectEqual(nested_key.id, .Literal);
        try testing.expect(mem.eql(u8, "key2", tree.source[nested_key.start..nested_key.end]));

        const nested_v = nested.value.cast(Node.Value).?;
        const leaf = tree.tokens[nested_v.start.?];
        try testing.expectEqual(leaf.id, .Literal);
        try testing.expect(mem.eql(u8, "value2", tree.source[leaf.start..leaf.end]));
    }

    {
        const elem = value.values.items[1].cast(Node.Map).?;
        const nested = elem.values.items[0];
        const nested_key = tree.tokens[nested.key];
        try testing.expectEqual(nested_key.id, .Literal);
        try testing.expect(mem.eql(u8, "key3", tree.source[nested_key.start..nested_key.end]));

        const nested_v = nested.value.cast(Node.Value).?;
        const leaf = tree.tokens[nested_v.start.?];
        try testing.expectEqual(leaf.id, .Literal);
        try testing.expect(mem.eql(u8, "value3", tree.source[leaf.start..leaf.end]));
    }

    {
        const elem = value.values.items[2].cast(Node.Map).?;
        const nested = elem.values.items[0];
        const nested_key = tree.tokens[nested.key];
        try testing.expectEqual(nested_key.id, .Literal);
        try testing.expect(mem.eql(u8, "key4", tree.source[nested_key.start..nested_key.end]));

        const nested_v = nested.value.cast(Node.Value).?;
        const leaf = tree.tokens[nested_v.start.?];
        try testing.expectEqual(leaf.id, .Literal);
        try testing.expect(mem.eql(u8, "value4", tree.source[leaf.start..leaf.end]));
    }
}

test "list of lists" {
    const source =
        \\- [name        , hr, avg  ]
        \\- [Mark McGwire , 65, 0.278]
        \\- [Sammy Sosa   , 63, 0.288]
    ;

    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .list);

    const list = doc.value.?.cast(Node.List).?;
    try testing.expectEqual(list.start.?, 0);
    try testing.expectEqual(list.end.?, tree.tokens.len - 2);
    try testing.expectEqual(list.values.items.len, 3);

    {
        try testing.expectEqual(list.values.items[0].tag, .list);
        const nested = list.values.items[0].cast(Node.List).?;
        try testing.expectEqual(nested.values.items.len, 3);

        {
            try testing.expectEqual(nested.values.items[0].tag, .value);
            const value = nested.values.items[0].cast(Node.Value).?;
            const leaf = tree.tokens[value.start.?];
            try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
        }

        {
            try testing.expectEqual(nested.values.items[1].tag, .value);
            const value = nested.values.items[1].cast(Node.Value).?;
            const leaf = tree.tokens[value.start.?];
            try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
        }

        {
            try testing.expectEqual(nested.values.items[2].tag, .value);
            const value = nested.values.items[2].cast(Node.Value).?;
            const leaf = tree.tokens[value.start.?];
            try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
        }
    }

    {
        try testing.expectEqual(list.values.items[1].tag, .list);
        const nested = list.values.items[1].cast(Node.List).?;
        try testing.expectEqual(nested.values.items.len, 3);

        {
            try testing.expectEqual(nested.values.items[0].tag, .value);
            const value = nested.values.items[0].cast(Node.Value).?;
            const start = tree.tokens[value.start.?];
            const end = tree.tokens[value.end.?];
            try testing.expect(mem.eql(u8, "Mark McGwire", tree.source[start.start..end.end]));
        }

        {
            try testing.expectEqual(nested.values.items[1].tag, .value);
            const value = nested.values.items[1].cast(Node.Value).?;
            const leaf = tree.tokens[value.start.?];
            try testing.expect(mem.eql(u8, "65", tree.source[leaf.start..leaf.end]));
        }

        {
            try testing.expectEqual(nested.values.items[2].tag, .value);
            const value = nested.values.items[2].cast(Node.Value).?;
            const leaf = tree.tokens[value.start.?];
            try testing.expect(mem.eql(u8, "0.278", tree.source[leaf.start..leaf.end]));
        }
    }

    {
        try testing.expectEqual(list.values.items[2].tag, .list);
        const nested = list.values.items[2].cast(Node.List).?;
        try testing.expectEqual(nested.values.items.len, 3);

        {
            try testing.expectEqual(nested.values.items[0].tag, .value);
            const value = nested.values.items[0].cast(Node.Value).?;
            const start = tree.tokens[value.start.?];
            const end = tree.tokens[value.end.?];
            try testing.expect(mem.eql(u8, "Sammy Sosa", tree.source[start.start..end.end]));
        }

        {
            try testing.expectEqual(nested.values.items[1].tag, .value);
            const value = nested.values.items[1].cast(Node.Value).?;
            const leaf = tree.tokens[value.start.?];
            try testing.expect(mem.eql(u8, "63", tree.source[leaf.start..leaf.end]));
        }

        {
            try testing.expectEqual(nested.values.items[2].tag, .value);
            const value = nested.values.items[2].cast(Node.Value).?;
            const leaf = tree.tokens[value.start.?];
            try testing.expect(mem.eql(u8, "0.288", tree.source[leaf.start..leaf.end]));
        }
    }
}

test "inline list" {
    const source =
        \\[name        , hr, avg  ]
    ;

    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .list);

    const list = doc.value.?.cast(Node.List).?;
    try testing.expectEqual(list.start.?, 0);
    try testing.expectEqual(list.end.?, tree.tokens.len - 2);
    try testing.expectEqual(list.values.items.len, 3);

    {
        try testing.expectEqual(list.values.items[0].tag, .value);
        const value = list.values.items[0].cast(Node.Value).?;
        const leaf = tree.tokens[value.start.?];
        try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
    }

    {
        try testing.expectEqual(list.values.items[1].tag, .value);
        const value = list.values.items[1].cast(Node.Value).?;
        const leaf = tree.tokens[value.start.?];
        try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
    }

    {
        try testing.expectEqual(list.values.items[2].tag, .value);
        const value = list.values.items[2].cast(Node.Value).?;
        const leaf = tree.tokens[value.start.?];
        try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
    }
}

test "inline list as mapping value" {
    const source =
        \\key : [
        \\        name        ,
        \\        hr, avg  ]
    ;

    var tree = Tree.init(testing.allocator);
    defer tree.deinit();
    try tree.parse(source);

    try testing.expectEqual(tree.docs.items.len, 1);

    const doc = tree.docs.items[0].cast(Node.Doc).?;
    try testing.expectEqual(doc.start.?, 0);
    try testing.expectEqual(doc.end.?, tree.tokens.len - 2);

    try testing.expect(doc.value != null);
    try testing.expectEqual(doc.value.?.tag, .map);

    const map = doc.value.?.cast(Node.Map).?;
    try testing.expectEqual(map.start.?, 0);
    try testing.expectEqual(map.end.?, tree.tokens.len - 2);
    try testing.expectEqual(map.values.items.len, 1);

    const entry = map.values.items[0];
    const key = tree.tokens[entry.key];
    try testing.expectEqual(key.id, .Literal);
    try testing.expect(mem.eql(u8, "key", tree.source[key.start..key.end]));

    const list = entry.value.cast(Node.List).?;
    try testing.expectEqual(list.start.?, 4);
    try testing.expectEqual(list.end.?, tree.tokens.len - 2);
    try testing.expectEqual(list.values.items.len, 3);

    {
        try testing.expectEqual(list.values.items[0].tag, .value);
        const value = list.values.items[0].cast(Node.Value).?;
        const leaf = tree.tokens[value.start.?];
        try testing.expect(mem.eql(u8, "name", tree.source[leaf.start..leaf.end]));
    }

    {
        try testing.expectEqual(list.values.items[1].tag, .value);
        const value = list.values.items[1].cast(Node.Value).?;
        const leaf = tree.tokens[value.start.?];
        try testing.expect(mem.eql(u8, "hr", tree.source[leaf.start..leaf.end]));
    }

    {
        try testing.expectEqual(list.values.items[2].tag, .value);
        const value = list.values.items[2].cast(Node.Value).?;
        const leaf = tree.tokens[value.start.?];
        try testing.expect(mem.eql(u8, "avg", tree.source[leaf.start..leaf.end]));
    }
}
