defmodule RDF.ListTest do
  use RDF.Test.Case

  doctest RDF.List

  import RDF.Sigils

  alias RDF.{IRI, BlankNode, Literal, Graph}

  use RDF.Vocabulary.Namespace

  setup do
    {:ok,
     empty: RDF.List.new(RDF.nil(), Graph.new()),
     one: RDF.List.from([EX.element()], head: ~B<one>),
     abc: RDF.List.from(~w[a b c], head: ~B<abc>),
     ten: RDF.List.from(Enum.to_list(1..10), head: ~B<ten>),
     nested: RDF.List.from(["foo", [1, 2], "bar"], head: ~B<nested>)}
  end

  describe "new/2" do
    #######################################################################
    # success cases

    test "valid head list node" do
      graph =
        Graph.new(
          ~B<Foo>
          |> RDF.first(1)
          |> RDF.rest(~B<Bar>)
        )
        |> Graph.add(
          ~B<Bar>
          |> RDF.first(2)
          |> RDF.rest(RDF.nil())
        )

      assert %RDF.List{} = list = RDF.List.new(~B<Foo>, graph)
      assert list.head == ~B<Foo>
      assert list.graph == graph
    end

    test "with non-blank list nodes" do
      graph =
        Graph.new(
          EX.Foo
          |> RDF.first(1)
          |> RDF.rest(RDF.nil())
        )

      assert %RDF.List{} = list = RDF.List.new(EX.Foo, graph)
      assert list.head == RDF.iri(EX.Foo)
    end

    test "with other properties on its nodes" do
      assert RDF.List.new(
               ~B<Foo>,
               Graph.new(
                 ~B<Foo>
                 |> EX.other(EX.Property)
                 |> RDF.first(1)
                 |> RDF.rest(~B<Bar>)
               )
               |> Graph.add(
                 ~B<Bar>
                 |> EX.other(EX.Property2)
                 |> RDF.first(2)
                 |> RDF.rest(RDF.nil())
               )
             )
             |> RDF.List.valid?() == true
    end

    #######################################################################
    # failure cases

    test "when given list node doesn't exist in the given graph" do
      assert RDF.List.new(RDF.bnode(), RDF.Graph.new()) == nil
    end

    test "When the given head node is not a list" do
      assert RDF.List.new(42, RDF.Graph.new()) == nil
      assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar(), EX.Baz})) == nil
      assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first(), EX.Baz})) == nil
    end

    test "when list nodes are incomplete" do
      assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first(), EX.Baz})) == nil
      assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest(), RDF.nil()})) == nil
    end

    test "when head node has multiple rdf:first objects" do
      assert RDF.List.new(
               ~B<Foo>,
               Graph.new(
                 ~B<Foo>
                 |> RDF.first([1, 2])
                 |> RDF.rest(RDF.nil())
               )
             ) == nil
    end

    test "when later list nodes have multiple rdf:first objects" do
      assert RDF.List.new(
               ~B<Foo>,
               Graph.new(
                 ~B<Foo>
                 |> RDF.first(1)
                 |> RDF.rest(~B<Bar>)
               )
               |> Graph.add(
                 ~B<Bar>
                 |> RDF.first([2, 3])
                 |> RDF.rest(RDF.nil())
               )
             ) == nil
    end

    test "when list nodes have multiple rdf:rest objects" do
      assert RDF.List.new(
               ~B<Foo>,
               Graph.new(
                 ~B<Foo>
                 |> RDF.first(1)
                 |> RDF.rest([~B<Bar>, ~B<Baz>])
               )
               |> Graph.add(
                 ~B<Bar>
                 |> RDF.first(2)
                 |> RDF.rest(RDF.nil())
               )
               |> Graph.add(
                 ~B<Baz>
                 |> RDF.first(3)
                 |> RDF.rest(RDF.nil())
               )
             ) == nil

      assert RDF.List.new(
               ~B<Foo>,
               Graph.new(
                 ~B<Foo>
                 |> RDF.first(1)
                 |> RDF.rest(~B<Bar>)
               )
               |> Graph.add(
                 ~B<Bar>
                 |> RDF.first(2)
                 |> RDF.rest([RDF.nil(), ~B<Baz>])
               )
               |> Graph.add(
                 ~B<Baz>
                 |> RDF.first(3)
                 |> RDF.rest(RDF.nil())
               )
             ) == nil
    end

    test "when the list is cyclic" do
      assert RDF.List.new(
               ~B<Foo>,
               Graph.new(
                 ~B<Foo>
                 |> RDF.first(1)
                 |> RDF.rest(~B<Bar>)
               )
               |> Graph.add(
                 ~B<Bar>
                 |> RDF.first(2)
                 |> RDF.rest(~B<Foo>)
               )
             ) == nil
    end
  end

  describe "from/1" do
    test "an empty list", %{empty: empty} do
      assert RDF.List.from([]) == empty
    end

    test "an empty list with named head node", %{empty: empty} do
      assert RDF.List.from([], name: ~B<foo>) == empty
    end

    %{
      "IRI" => RDF.iri(EX.Foo),
      "blank node" => ~B<Foo>,
      "literal" => ~L<Foo>,
      "string" => "Foo",
      "integer" => 42,
      "float" => 3.14,
      "true" => true,
      "false" => false,
      "unresolved namespace-qualified name" => EX.Foo
    }
    |> Enum.each(fn {type, element} ->
      @tag element: element
      test "list with #{type} element", %{element: element} do
        {bnode, graph_with_list} = one_element_list(element)

        assert RDF.List.from([element], head: bnode) ==
                 RDF.List.new(bnode, graph_with_list)
      end
    end)

    test "nested list" do
      assert %RDF.List{head: bnode, graph: graph_with_list} = RDF.List.from([[1]])
      assert [nested] = get_in(graph_with_list, [bnode, RDF.first()])
      assert get_in(graph_with_list, [bnode, RDF.rest()]) == [RDF.nil()]
      assert get_in(graph_with_list, [nested, RDF.first()]) == [XSD.integer(1)]
      assert get_in(graph_with_list, [nested, RDF.rest()]) == [RDF.nil()]

      assert %RDF.List{head: bnode, graph: graph_with_list} =
               RDF.List.from(["foo", [1, 2], "bar"])

      assert get_in(graph_with_list, [bnode, RDF.first()]) == [~L"foo"]
      assert [second] = get_in(graph_with_list, [bnode, RDF.rest()])
      assert [nested] = get_in(graph_with_list, [second, RDF.first()])
      assert get_in(graph_with_list, [nested, RDF.first()]) == [XSD.integer(1)]
      assert [nested_second] = get_in(graph_with_list, [nested, RDF.rest()])
      assert get_in(graph_with_list, [nested_second, RDF.first()]) == [XSD.integer(2)]
      assert get_in(graph_with_list, [nested_second, RDF.rest()]) == [RDF.nil()]
      assert [third] = get_in(graph_with_list, [second, RDF.rest()])
      assert get_in(graph_with_list, [third, RDF.first()]) == [~L"bar"]
      assert get_in(graph_with_list, [third, RDF.rest()]) == [RDF.nil()]
    end

    %{
      "preserve order" => [3, 2, 1],
      "different types" => [1, "foo", true, false, 3.14, EX.foo(), EX.Foo, ~B<Foo>]
    }
    |> Enum.each(fn {desc, list} ->
      @tag list: list
      test "list with multiple elements: #{desc}", %{list: list} do
        assert %RDF.List{head: bnode, graph: graph_with_list} = RDF.List.from(list)

        assert RDF.nil() ==
                 Enum.reduce(list, bnode, fn element, list_node ->
                   case element do
                     %IRI{} ->
                       assert get_in(graph_with_list, [list_node, RDF.first()]) == [element]

                     %BlankNode{} ->
                       assert get_in(graph_with_list, [list_node, RDF.first()]) == [element]

                     %Literal{} ->
                       assert get_in(graph_with_list, [list_node, RDF.first()]) == [element]

                     element when is_boolean(element) ->
                       assert get_in(graph_with_list, [list_node, RDF.first()]) == [
                                RDF.Literal.new(element)
                              ]

                     element when is_atom(element) ->
                       assert get_in(graph_with_list, [list_node, RDF.first()]) == [
                                RDF.iri(element)
                              ]

                     _ ->
                       assert get_in(graph_with_list, [list_node, RDF.first()]) == [
                                RDF.Literal.new(element)
                              ]
                   end

                   [next] = get_in(graph_with_list, [list_node, RDF.rest()])

                   unless next == RDF.nil() do
                     assert %BlankNode{} = next
                   end

                   next
                 end)
      end
    end)

    test "an enumerable" do
      assert RDF.List.from(MapSet.new([42]), head: ~B<foo>) ==
               RDF.List.from([42], head: ~B<foo>)
    end

    test "head option with unresolved namespace-qualified name" do
      assert RDF.List.from([42], head: EX.Foo).head == RDF.iri(EX.Foo)
    end
  end

  describe "values/1" do
    test "the empty list", %{empty: empty} do
      assert RDF.List.values(empty) == []
    end

    test "list with one element", %{one: one} do
      assert RDF.List.values(one) == [EX.element()]
    end

    test "list with multiple elements", %{abc: abc, ten: ten} do
      assert RDF.List.values(abc) == ~w[a b c] |> Enum.map(&Literal.new/1)
      assert RDF.List.values(ten) == 1..10 |> Enum.to_list() |> Enum.map(&Literal.new/1)
    end

    test "list with non-blank list nodes" do
      assert RDF.List.from([EX.element()], head: EX.Foo)
             |> RDF.List.values() == [EX.element()]
    end

    test "nested list", %{nested: nested} do
      assert RDF.List.values(nested) ==
               [~L"foo", [XSD.integer(1), XSD.integer(2)], ~L"bar"]

      assert RDF.list(["foo", [1, 2]]) |> RDF.List.values() ==
               [~L"foo", [XSD.integer(1), XSD.integer(2)]]

      assert RDF.list([[1, 2], "foo"]) |> RDF.List.values() ==
               [[XSD.integer(1), XSD.integer(2)], ~L"foo"]

      inner_list = RDF.list([1, 2], head: ~B<inner>)

      assert RDF.list(["foo", ~B<inner>], graph: inner_list.graph)
             |> RDF.List.values() == [~L"foo", [XSD.integer(1), XSD.integer(2)]]
    end
  end

  describe "nodes/1" do
    test "the empty list", %{empty: empty} do
      assert RDF.List.nodes(empty) == []
    end

    test "non-empty list", %{one: one} do
      assert RDF.List.nodes(one) == [~B<one>]
    end

    test "nested list", %{nested: nested} do
      assert RDF.list([[1, 2, 3]], head: ~B<outer>)
             |> RDF.List.nodes() == [~B<outer>]

      assert [~B<nested>, _, _] = RDF.List.nodes(nested)
    end
  end

  describe "valid?/2" do
    test "the empty list", %{empty: empty} do
      assert RDF.List.valid?(empty)
    end

    test "valid list with one element", %{one: one} do
      assert RDF.List.valid?(one) == true
    end

    test "valid list with multiple elements", %{abc: abc, ten: ten} do
      assert RDF.List.valid?(abc) == true
      assert RDF.List.valid?(ten) == true
    end

    test "valid nested list", %{nested: nested} do
      assert RDF.List.valid?(nested) == true
    end

    test "a non-blank list node is not valid" do
      assert RDF.list([EX.element()], head: EX.Foo) |> RDF.List.valid?() == false
    end

    test "a non-blank list node on later nodes makes the whole list invalid" do
      assert RDF.List.new(
               ~B<Foo>,
               Graph.new(
                 ~B<Foo>
                 |> RDF.first(1)
                 |> RDF.rest(EX.Foo)
               )
               |> Graph.add(
                 EX.Foo
                 |> RDF.first(2)
                 |> RDF.rest(RDF.nil())
               )
             )
             |> RDF.List.valid?() == false
    end
  end

  describe "node?" do
    test "the empty list", %{empty: empty} do
      assert RDF.List.node?(empty.head, empty.graph) == true
    end

    test "list with one element", %{one: one} do
      assert RDF.List.node?(one.head, one.graph) == true
    end

    test "list with multiple elements", %{abc: abc, ten: ten} do
      assert RDF.List.node?(abc.head, abc.graph) == true
      assert RDF.List.node?(ten.head, ten.graph) == true
    end

    test "nested list", %{nested: nested} do
      assert RDF.List.node?(nested.head, nested.graph) == true
    end

    test "unresolved namespace-qualified name" do
      assert RDF.List.node?(
               EX.Foo,
               RDF.List.from([EX.element()], head: EX.Foo).graph
             ) == true
    end

    test "when given list node doesn't exist in the given graph" do
      assert RDF.List.node?(RDF.bnode(), RDF.Graph.new()) == false
    end

    test "literal" do
      assert RDF.List.node?(~L"Foo", RDF.Graph.new()) == false
      assert RDF.List.node?(42, RDF.Graph.new()) == false
      assert RDF.List.node?(true, RDF.Graph.new()) == false
      assert RDF.List.node?(false, RDF.Graph.new()) == false
      assert RDF.List.node?(nil, RDF.Graph.new()) == false
    end

    test "non-list node" do
      assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar(), EX.Baz})) == false
    end

    test "incomplete list nodes" do
      assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first(), EX.Baz})) == false
      assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest(), RDF.nil()})) == false
    end
  end

  describe "prepend/3" do
    test "prepending to an empty list", %{empty: empty} do
      prepended = RDF.List.prepend(empty, "new")
      assert RDF.List.values(prepended) == [~L"new"]
      assert prepended.head != empty.head
      assert RDF.bnode?(prepended.head)
    end

    test "prepending to a non-empty list", %{one: one} do
      prepended = RDF.List.prepend(one, "new")
      assert RDF.List.values(prepended) == [~L"new", EX.element()]
      assert RDF.bnode?(prepended.head)
      assert prepended.head != one.head
    end

    test "with custom head node" do
      list = RDF.List.from(["a", "b"], head: ~B<abc>)
      prepended = RDF.List.prepend(list, "new", head: ~B<custom>)
      assert RDF.List.values(prepended) == [~L"new", ~L"a", ~L"b"]
      assert prepended.head == ~B<custom>
    end
  end

  test "the same list in two different graphs are equal", %{abc: abc} do
    assert abc ==
             RDF.List.new(abc.head, Graph.new(abc.graph) |> Graph.add(EX.S |> EX.p(abc.head)))
  end

  describe "Enumerable.reduce" do
    test "the empty list", %{empty: empty} do
      assert Enum.reduce(empty, [], fn description, acc -> [description | acc] end) == []
    end

    test "a valid list", %{one: one} do
      assert [one.graph[one.head]] ==
               Enum.reduce(one, [], fn description, acc -> [description | acc] end)
    end
  end

  defp one_element_list(element),
    do: one_element_list(element, RDF.bnode())

  defp one_element_list(element, bnode) do
    {bnode,
     Graph.new(
       bnode
       |> RDF.first(element)
       |> RDF.rest(RDF.nil())
     )}
  end
end
