# frozen_string_literal: true

require_relative 'spec_helper'
require 'rdf/spec/writer'

describe JSON::LD::API do
  let(:logger) { RDF::Spec.logger }

  describe ".fromRdf" do
    context "simple tests" do
      it "One subject IRI object" do
        input = %(<http://a/b> <http://a/c> <http://a/d> .)
        expect(serialize(input)).to produce_jsonld([
                                                     {
                                                       '@id' => "http://a/b",
                                                       "http://a/c" => [{ "@id" => "http://a/d" }]
                                                     }
                                                   ], logger)
      end

      it "generates object list" do
        input = %(@prefix : <http://example.com/> . :b :c :d, :e .)
        expect(serialize(input))
          .to produce_jsonld([{
            '@id' => "http://example.com/b",
            "http://example.com/c" => [
              { "@id" => "http://example.com/d" },
              { "@id" => "http://example.com/e" }
            ]
          }], logger)
      end

      it "generates property list" do
        input = %(@prefix : <http://example.com/> . :b :c :d; :e :f .)
        expect(serialize(input))
          .to produce_jsonld([{
            '@id' => "http://example.com/b",
            "http://example.com/c" => [{ "@id" => "http://example.com/d" }],
            "http://example.com/e" => [{ "@id" => "http://example.com/f" }]
          }], logger)
      end

      it "serializes multiple subjects" do
        input = '
          @prefix : <http://www.w3.org/2006/03/test-description#> .
          @prefix dc: <http://purl.org/dc/elements/1.1/> .
          <test-cases/0001> a :TestCase .
          <test-cases/0002> a :TestCase .
        '
        expect(serialize(input))
          .to produce_jsonld([
                               { '@id' => "test-cases/0001",
                                 '@type' => ["http://www.w3.org/2006/03/test-description#TestCase"] },
                               { '@id' => "test-cases/0002", '@type' => ["http://www.w3.org/2006/03/test-description#TestCase"] }
                             ], logger)
      end
    end

    context "literals" do
      context "coercion" do
        it "typed literal" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b "foo"^^ex:d .)
          expect(serialize(input)).to produce_jsonld([
                                                       {
                                                         '@id' => "http://example.com/a",
                                                         "http://example.com/b" => [{ "@value" => "foo",
                                                                                      "@type" => "http://example.com/d" }]
                                                       }
                                                     ], logger)
        end

        it "integer" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b 1 .)
          expect(serialize(input, useNativeTypes: true)).to produce_jsonld([{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@value" => 1 }]
          }], logger)
        end

        it "integer (non-native)" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b 1 .)
          expect(serialize(input, useNativeTypes: false)).to produce_jsonld([{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@value" => "1", "@type" => "http://www.w3.org/2001/XMLSchema#integer" }]
          }], logger)
        end

        it "boolean" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b true .)
          expect(serialize(input, useNativeTypes: true)).to produce_jsonld([{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@value" => true }]
          }], logger)
        end

        it "boolean (non-native)" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b true .)
          expect(serialize(input, useNativeTypes: false)).to produce_jsonld([{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@value" => "true", "@type" => "http://www.w3.org/2001/XMLSchema#boolean" }]
          }], logger)
        end

        it "decmal" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b 1.0 .)
          expect(serialize(input, useNativeTypes: true)).to produce_jsonld([{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@value" => "1.0", "@type" => "http://www.w3.org/2001/XMLSchema#decimal" }]
          }], logger)
        end

        it "double" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b 1.0e0 .)
          expect(serialize(input, useNativeTypes: true)).to produce_jsonld([{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@value" => 1.0E0 }]
          }], logger)
        end

        it "double (non-native)" do
          input = %(@prefix ex: <http://example.com/> . ex:a ex:b 1.0e0 .)
          expect(serialize(input, useNativeTypes: false)).to produce_jsonld([{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@value" => "1.0E0", "@type" => "http://www.w3.org/2001/XMLSchema#double" }]
          }], logger)
        end
      end

      context "datatyped (non-native)" do
        {
          integer: 1,
          unsignedInteger: 1,
          nonNegativeInteger: 1,
          float: 1,
          nonPositiveInteger: -1,
          negativeInteger: -1
        }.each do |t, v|
          it t.to_s do
            input = %(
              @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
              @prefix ex: <http://example.com/> .
              ex:a ex:b "#{v}"^^xsd:#{t} .
            )
            expect(serialize(input, useNativeTypes: false)).to produce_jsonld([{
              '@id' => "http://example.com/a",
              "http://example.com/b" => [{ "@value" => v.to_s, "@type" => "http://www.w3.org/2001/XMLSchema##{t}" }]
            }], logger)
          end
        end
      end

      it "encodes language literal" do
        input = %(@prefix ex: <http://example.com/> . ex:a ex:b "foo"@en-us .)
        expect(serialize(input)).to produce_jsonld([{
          '@id' => "http://example.com/a",
          "http://example.com/b" => [{ "@value" => "foo", "@language" => "en-us" }]
        }], logger)
      end

      context "with @type: @json" do
        {
          true => {
            output: %([{
               "@id": "http://example.org/vocab#id",
               "http://example.org/vocab#bool": [{"@value": true, "@type": "@json"}]
             }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:bool "true"^^rdf:JSON .
            )
          },
          false => {
            output: %([{
               "@id": "http://example.org/vocab#id",
               "http://example.org/vocab#bool": [{"@value": false, "@type": "@json"}]
             }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:bool "false"^^rdf:JSON .
            )
          },
          double: {
            output: %([{
               "@id": "http://example.org/vocab#id",
               "http://example.org/vocab#double": [{"@value": 1.23E0, "@type": "@json"}]
             }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:double "1.23E0"^^rdf:JSON .
            )
          },
          'double-zero': {
            output: %([{
               "@id": "http://example.org/vocab#id",
               "http://example.org/vocab#double": [{"@value": 0, "@type": "@json"}]
             }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:double "0.0E0"^^rdf:JSON .
            )
          },
          integer: {
            output: %([{
               "@id": "http://example.org/vocab#id",
               "http://example.org/vocab#integer": [{"@value": 123, "@type": "@json"}]
             }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:integer "123"^^rdf:JSON .
            )
          },
          string: {
            output: %([{
              "@id": "http://example.org/vocab#id",
              "http://example.org/vocab#string": [{
                "@value": "string",
                "@type": "@json"
              }]
            }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:string "\\"string\\""^^rdf:JSON .
            )
          },
          null: {
            output: %([{
              "@id": "http://example.org/vocab#id",
              "http://example.org/vocab#null": [{
                "@value": null,
                "@type": "@json"
              }]
            }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:null "null"^^rdf:JSON .
            )
          },
          object: {
            output: %([{
               "@id": "http://example.org/vocab#id",
               "http://example.org/vocab#object": [{"@value": {"foo": "bar"}, "@type": "@json"}]
             }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:object """{"foo":"bar"}"""^^rdf:JSON .
            )
          },
          array: {
            output: %([{
               "@id": "http://example.org/vocab#id",
               "http://example.org/vocab#array": [{"@value": [{"foo": "bar"}], "@type": "@json"}]
             }]),
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:array """[{"foo":"bar"}]"""^^rdf:JSON .
            )
          }
        }.each do |title, params|
          params[:input] = RDF::Graph.new << RDF::Turtle::Reader.new(params[:input])
          it(title) { do_fromRdf(processingMode: "json-ld-1.1", **params) }
        end
      end

      context "extendedRepresentation: true" do
        {
          true => {
            output: [{
              "@id" => "http://example.org/vocab#id",
              "http://example.org/vocab#bool" => [{ "@value" => RDF::Literal(true) }]
            }],
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:bool true .
            )
          },
          false => {
            output: [{
              "@id" => "http://example.org/vocab#id",
              "http://example.org/vocab#bool" => [{ "@value" => RDF::Literal(false) }]
            }],
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:bool false .
            )
          },
          double: {
            output: [{
              "@id" => "http://example.org/vocab#id",
              "http://example.org/vocab#double" => [{ "@value" => RDF::Literal(1.23E0) }]
            }],
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:double 1.23E0 .
            )
          },
          'double-zero': {
            output: [{
              "@id" => "http://example.org/vocab#id",
              "http://example.org/vocab#double" => [{ "@value" => RDF::Literal(0, datatype: RDF::XSD.double) }]
            }],
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:double 0.0E0 .
            )
          },
          integer: {
            output: [{
              "@id" => "http://example.org/vocab#id",
              "http://example.org/vocab#integer" => [{ "@value" => RDF::Literal(123) }]
            }],
            input: %(
              @prefix ex: <http://example.org/vocab#> .
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              ex:id ex:integer 123 .
            )
          }
        }.each do |title, params|
          params[:input] = RDF::Graph.new << RDF::Turtle::Reader.new(params[:input])
          it(title) {
            do_fromRdf(processingMode: "json-ld-1.1",
              useNativeTypes: true,
              extendedRepresentation: true,
            **params)
          }
        end
      end
    end

    context "anons" do
      it "generates bare anon" do
        input = %(@prefix : <http://example.com/> . _:a :a :b .)
        expect(serialize(input)).to produce_jsonld([
                                                     {
                                                       "@id" => "_:a",
                                                       "http://example.com/a" => [{ "@id" => "http://example.com/b" }]
                                                     }
                                                   ], logger)
      end

      it "generates anon as object" do
        input = %(@prefix : <http://example.com/> . :a :b _:a . _:a :c :d .)
        expect(serialize(input)).to produce_jsonld([
                                                     {
                                                       "@id" => "_:a",
                                                       "http://example.com/c" => [{ "@id" => "http://example.com/d" }]
                                                     },
                                                     {
                                                       "@id" => "http://example.com/a",
                                                       "http://example.com/b" => [{ "@id" => "_:a" }]
                                                     }
                                                   ], logger)
      end
    end

    context "lists" do
      {
        "literal list" => {
          input: '
            @prefix : <http://example.com/> .
            @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
            :a :b ("apple" "banana")  .
          ',
          output: [{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{
              "@list" => [
                { "@value" => "apple" },
                { "@value" => "banana" }
              ]
            }]
          }]
        },
        "iri list" => {
          input: '@prefix : <http://example.com/> . :a :b (:c) .',
          output: [{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{
              "@list" => [
                { "@id" => "http://example.com/c" }
              ]
            }]
          }]
        },
        "empty list" => {
          input: '@prefix : <http://example.com/> . :a :b () .',
          output: [{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@list" => [] }]
          }]
        },
        "single element list" => {
          input: '@prefix : <http://example.com/> . :a :b ( "apple" ) .',
          output: [{
            '@id' => "http://example.com/a",
            "http://example.com/b" => [{ "@list" => [{ "@value" => "apple" }] }]
          }]
        },
        "single element list without @type" => {
          input: '@prefix : <http://example.com/> . :a :b ( _:a ) . _:a :b "foo" .',
          output: [
            {
              '@id' => "_:a",
              "http://example.com/b" => [{ "@value" => "foo" }]
            },
            {
              '@id' => "http://example.com/a",
              "http://example.com/b" => [{ "@list" => [{ "@id" => "_:a" }] }]
            }
          ]
        },
        "multiple graphs with shared BNode" => {
          input: '
            <http://www.example.com/z> <http://www.example.com/q> _:z0 <http://www.example.com/G> .
            _:z0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "cell-A" <http://www.example.com/G> .
            _:z0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:z1 <http://www.example.com/G> .
            _:z1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "cell-B" <http://www.example.com/G> .
            _:z1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://www.example.com/G> .
            <http://www.example.com/x> <http://www.example.com/p> _:z1 <http://www.example.com/G1> .
          ',
          output: [{
            "@id" => "http://www.example.com/G",
            "@graph" => [{
              "@id" => "_:z0",
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [{ "@value" => "cell-A" }],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [{ "@id" => "_:z1" }]
            }, {
              "@id" => "_:z1",
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [{ "@value" => "cell-B" }],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [{ "@list" => [] }]
            }, {
              "@id" => "http://www.example.com/z",
              "http://www.example.com/q" => [{ "@id" => "_:z0" }]
            }]
          },
                   {
                     "@id" => "http://www.example.com/G1",
                     "@graph" => [{
                       "@id" => "http://www.example.com/x",
                       "http://www.example.com/p" => [{ "@id" => "_:z1" }]
                     }]
                   }],
          reader: RDF::NQuads::Reader
        },
        "multiple graphs with shared BNode (at head)" => {
          input: '
            <http://www.example.com/z> <http://www.example.com/q> _:z0 <http://www.example.com/G> .
            _:z0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "cell-A" <http://www.example.com/G> .
            _:z0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:z1 <http://www.example.com/G> .
            _:z1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "cell-B" <http://www.example.com/G> .
            _:z1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://www.example.com/G> .
            <http://www.example.com/z> <http://www.example.com/q> _:z0 <http://www.example.com/G1> .
          ',
          output: [{
            "@id" => "http://www.example.com/G",
            "@graph" => [{
              "@id" => "_:z0",
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [{ "@value" => "cell-A" }],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [{ "@list" => [{ "@value" => "cell-B" }] }]
            }, {
              "@id" => "http://www.example.com/z",
              "http://www.example.com/q" => [{ "@id" => "_:z0" }]
            }]
          },
                   {
                     "@id" => "http://www.example.com/G1",
                     "@graph" => [{
                       "@id" => "http://www.example.com/z",
                       "http://www.example.com/q" => [{ "@id" => "_:z0" }]
                     }]
                   }],
          reader: RDF::NQuads::Reader
        },
        "@list containing empty @list" => {
          input: %(
            <http://example.com/a> <http://example.com/property> (()) .
          ),
          output: %([{
            "@id": "http://example.com/a",
            "http://example.com/property": [{"@list": [{"@list": []}]}]
          }]),
          reader: RDF::Turtle::Reader
        },
        "@list containing multiple lists" => {
          input: %(
            <http://example.com/a> <http://example.com/property> (("a") ("b")) .
          ),
          output: %([{
            "@id": "http://example.com/a",
            "http://example.com/property": [{"@list": [
              {"@list": [{"@value": "a"}]},
              {"@list": [{"@value": "b"}]}
            ]}]
          }]),
          reader: RDF::Turtle::Reader
        },
        "0008a" => {
          input: %(
          <http://example.com> <http://example.com/property> _:outerlist .
          _:outerlist <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> _:lista .
          _:outerlist <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b0 .

          _:lista <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "a1" .
          _:lista <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:a2 .
          _:a2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "a2" .
          _:a2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:a3 .
          _:a3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "a3" .
          _:a3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .

          _:c0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> _:c1 .
          _:c0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
          _:c1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "c1" .
          _:c1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:c2 .
          _:c2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "c2" .
          _:c2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:c3 .
          _:c3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "c3" .
          _:c3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .

          _:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> _:b1 .
          _:b0 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:c0 .
          _:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "b1" .
          _:b1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b2 .
          _:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "b2" .
          _:b2 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> _:b3 .
          _:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> "b3" .
          _:b3 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
          ),
          output: JSON.parse(%([
            {
              "@id": "http://example.com",
              "http://example.com/property": [
                {
                  "@list": [
                    {"@list": [{"@value": "a1"}, {"@value": "a2"}, {"@value": "a3"}]},
                    {"@list": [{"@value": "b1"}, {"@value": "b2"}, {"@value": "b3"}]},
                    {"@list": [{"@value": "c1"}, {"@value": "c2"}, {"@value": "c3"}]}
                  ]
                }
              ]
            }
          ])),
          reader: RDF::NQuads::Reader
        }
      }.each do |name, params|
        it name.to_s do
          do_fromRdf(params)
        end
      end
    end

    context "quads" do
      {
        "simple named graph" => {
          input: %(
            <http://example.com/a> <http://example.com/b> <http://example.com/c> <http://example.com/U> .
          ),
          output: [
            {
              "@id" => "http://example.com/U",
              "@graph" => [{
                "@id" => "http://example.com/a",
                "http://example.com/b" => [{ "@id" => "http://example.com/c" }]
              }]
            }
          ]
        },
        "with properties" => {
          input: %(
            <http://example.com/a> <http://example.com/b> <http://example.com/c> <http://example.com/U> .
            <http://example.com/U> <http://example.com/d> <http://example.com/e> .
          ),
          output: [
            {
              "@id" => "http://example.com/U",
              "@graph" => [{
                "@id" => "http://example.com/a",
                "http://example.com/b" => [{ "@id" => "http://example.com/c" }]
              }],
              "http://example.com/d" => [{ "@id" => "http://example.com/e" }]
            }
          ]
        },
        "with lists" => {
          input: %(
            <http://example.com/a> <http://example.com/b> _:a <http://example.com/U> .
            _:a <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.com/c> <http://example.com/U> .
            _:a <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.com/U> .
            <http://example.com/U> <http://example.com/d> _:b .
            _:b <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.com/e> .
            _:b <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
          ),
          output: [
            {
              "@id" => "http://example.com/U",
              "@graph" => [{
                "@id" => "http://example.com/a",
                "http://example.com/b" => [{ "@list" => [{ "@id" => "http://example.com/c" }] }]
              }],
              "http://example.com/d" => [{ "@list" => [{ "@id" => "http://example.com/e" }] }]
            }
          ]
        },
        "Two Graphs with same subject and lists" => {
          input: %(
            <http://example.com/a> <http://example.com/b> _:a <http://example.com/U> .
            _:a <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.com/c> <http://example.com/U> .
            _:a <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.com/U> .
            <http://example.com/a> <http://example.com/b> _:b <http://example.com/V> .
            _:b <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> <http://example.com/e> <http://example.com/V> .
            _:b <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> <http://example.com/V> .
          ),
          output: [
            {
              "@id" => "http://example.com/U",
              "@graph" => [
                {
                  "@id" => "http://example.com/a",
                  "http://example.com/b" => [{
                    "@list" => [{ "@id" => "http://example.com/c" }]
                  }]
                }
              ]
            },
            {
              "@id" => "http://example.com/V",
              "@graph" => [
                {
                  "@id" => "http://example.com/a",
                  "http://example.com/b" => [{
                    "@list" => [{ "@id" => "http://example.com/e" }]
                  }]
                }
              ]
            }
          ]
        }
      }.each_pair do |name, params|
        it name.to_s do
          do_fromRdf(params.merge(reader: RDF::NQuads::Reader))
        end
      end
    end

    context "@direction" do
      context "rdfDirection: null" do
        {
          'no language rtl datatype': {
            input: '
              <http://example.com/a> <http://example.org/label> "no language"^^<https://www.w3.org/ns/i18n#_rtl> .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "no language", "@type": "https://www.w3.org/ns/i18n#_rtl"}]
            }]'
          },
          'no language rtl compound-literal': {
            input: '
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              <http://example.com/a> <http://example.org/label> _:cl1 .

              _:cl1 rdf:value "no language";
                rdf:direction "rtl" .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@id": "_:cl1"}]
            }, {
              "@id": "_:cl1",
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": [{"@value": "no language"}],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#direction": [{"@value": "rtl"}]
            }]'
          },
          'en-US rtl datatype': {
            input: '
              <http://example.com/a> <http://example.org/label> "en-US"^^<https://www.w3.org/ns/i18n#en-us_rtl> .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "en-US", "@type": "https://www.w3.org/ns/i18n#en-us_rtl"}]
            }]'
          },
          'en-US rtl compound-literal': {
            input: '
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              <http://example.com/a> <http://example.org/label> _:cl1 .

              _:cl1 rdf:value "en-US";
                rdf:language "en-us";
                rdf:direction "rtl" .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@id": "_:cl1"}]
            }, {
              "@id": "_:cl1",
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": [{"@value": "en-US"}],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#language": [{"@value": "en-us"}],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#direction": [{"@value": "rtl"}]
            }]'
          }
        }.each_pair do |name, params|
          it name do
            do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: nil))
          end
        end
      end

      context "rdfDirection: i18n-datatype" do
        {
          'no language rtl datatype': {
            input: '
              <http://example.com/a> <http://example.org/label> "no language"^^<https://www.w3.org/ns/i18n#_rtl> .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "no language", "@direction": "rtl"}]
            }]'
          },
          'no language rtl compound-literal': {
            input: '
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              <http://example.com/a> <http://example.org/label> _:cl1 .

              _:cl1 rdf:value "no language";
                rdf:direction "rtl" .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@id": "_:cl1"}]
            }, {
              "@id": "_:cl1",
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": [{"@value": "no language"}],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#direction": [{"@value": "rtl"}]
            }]'
          },
          'en-US rtl datatype': {
            input: '
              <http://example.com/a> <http://example.org/label> "en-US"^^<https://www.w3.org/ns/i18n#en-US_rtl> .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "en-US", "@language": "en-US", "@direction": "rtl"}]
            }]'
          },
          'en-US rtl compound-literal': {
            input: '
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              <http://example.com/a> <http://example.org/label> _:cl1 .

              _:cl1 rdf:value "en-US";
                rdf:language "en-US";
                rdf:direction "rtl" .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@id": "_:cl1"}]
            }, {
              "@id": "_:cl1",
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#value": [{"@value": "en-US"}],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#language": [{"@value": "en-US"}],
              "http://www.w3.org/1999/02/22-rdf-syntax-ns#direction": [{"@value": "rtl"}]
            }]'
          }
        }.each_pair do |name, params|
          it name do
            do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'i18n-datatype',
              processingMode: 'json-ld-1.1'))
          end
        end
      end

      context "rdfDirection: compound-literal" do
        {
          'no language rtl datatype': {
            input: '
              <http://example.com/a> <http://example.org/label> "no language"^^<https://www.w3.org/ns/i18n#_rtl> .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "no language", "@type": "https://www.w3.org/ns/i18n#_rtl"}]
            }]'
          },
          'no language rtl compound-literal': {
            input: '
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              <http://example.com/a> <http://example.org/label> _:cl1 .

              _:cl1 rdf:value "no language";
                rdf:direction "rtl" .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "no language", "@direction": "rtl"}]
            }]'
          },
          'en-US rtl datatype': {
            input: '
              <http://example.com/a> <http://example.org/label> "en-US"^^<https://www.w3.org/ns/i18n#en-us_rtl> .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "en-US", "@type": "https://www.w3.org/ns/i18n#en-us_rtl"}]
            }]'
          },
          'en-US rtl compound-literal': {
            input: '
              @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
              <http://example.com/a> <http://example.org/label> _:cl1 .

              _:cl1 rdf:value "en-US";
                rdf:language "en-us";
                rdf:direction "rtl" .
            ',
            output: '[{
              "@id": "http://example.com/a",
              "http://example.org/label": [{"@value": "en-US", "@language": "en-us", "@direction": "rtl"}]
            }]'
          }
        }.each_pair do |name, params|
          it name do
            do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'compound-literal',
              processingMode: 'json-ld-1.1'))
          end
        end
      end
    end

    context "RDF-star" do
      {
        'subject-iii': {
          input: RDF::Statement(
            RDF::Statement(
              RDF::URI('http://example/s1'),
              RDF::URI('http://example/p1'),
              RDF::URI('http://example/o1')
            ),
            RDF::URI('http://example/p'),
            RDF::URI('http://example/o')
          ),
          output: %([{
            "@id": {
              "@id": "http://example/s1",
              "http://example/p1": [{"@id": "http://example/o1"}]
            },
            "http://example/p": [{"@id": "http://example/o"}]
          }])
        },
        'subject-iib': {
          input: RDF::Statement(
            RDF::Statement(
              RDF::URI('http://example/s1'),
              RDF::URI('http://example/p1'),
              RDF::Node.new('o1')
            ),
            RDF::URI('http://example/p'),
            RDF::URI('http://example/o')
          ),
          output: %([{
            "@id": {
              "@id": "http://example/s1",
              "http://example/p1": [{"@id": "_:o1"}]
            },
            "http://example/p": [{"@id": "http://example/o"}]
          }])
        },
        'subject-iil': {
          input: RDF::Statement(
            RDF::Statement(
              RDF::URI('http://example/s1'),
              RDF::URI('http://example/p1'),
              RDF::Literal('o1')
            ),
            RDF::URI('http://example/p'),
            RDF::URI('http://example/o')
          ),
          output: %([{
            "@id": {
              "@id": "http://example/s1",
              "http://example/p1": [{"@value": "o1"}]
            },
            "http://example/p": [{"@id": "http://example/o"}]
          }])
        },
        'subject-bii': {
          input: RDF::Statement(
            RDF::Statement(
              RDF::Node('s1'),
              RDF::URI('http://example/p1'),
              RDF::URI('http://example/o1')
            ),
            RDF::URI('http://example/p'),
            RDF::URI('http://example/o')
          ),
          output: %([{
            "@id": {
              "@id": "_:s1",
              "http://example/p1": [{"@id": "http://example/o1"}]
            },
            "http://example/p": [{"@id": "http://example/o"}]
          }])
        },
        'subject-bib': {
          input: RDF::Statement(
            RDF::Statement(
              RDF::Node('s1'),
              RDF::URI('http://example/p1'),
              RDF::Node.new('o1')
            ),
            RDF::URI('http://example/p'), RDF::URI('http://example/o')
          ),
          output: %([{
            "@id": {
              "@id": "_:s1",
              "http://example/p1": [{"@id": "_:o1"}]
            },
            "http://example/p": [{"@id": "http://example/o"}]
          }])
        },
        'subject-bil': {
          input: RDF::Statement(
            RDF::Statement(
              RDF::Node('s1'),
              RDF::URI('http://example/p1'),
              RDF::Literal('o1')
            ),
            RDF::URI('http://example/p'),
            RDF::URI('http://example/o')
          ),
          output: %([{
            "@id": {
              "@id": "_:s1",
              "http://example/p1": [{"@value": "o1"}]
            },
            "http://example/p": [{"@id": "http://example/o"}]
          }])
        },
        'object-iii': {
          input: RDF::Statement(
            RDF::URI('http://example/s'),
            RDF::URI('http://example/p'),
            RDF::Statement(
              RDF::URI('http://example/s1'),
              RDF::URI('http://example/p1'),
              RDF::URI('http://example/o1')
            )
          ),
          output: %([{
            "@id": "http://example/s",
            "http://example/p": [{
              "@id": {
                "@id": "http://example/s1",
                "http://example/p1": [{"@id": "http://example/o1"}]
              }
            }]
          }])
        },
        'object-iib': {
          input: RDF::Statement(
            RDF::URI('http://example/s'),
            RDF::URI('http://example/p'),
            RDF::Statement(
              RDF::URI('http://example/s1'),
              RDF::URI('http://example/p1'),
              RDF::Node.new('o1')
            )
          ),
          output: %([{
            "@id": "http://example/s",
            "http://example/p": [{
              "@id": {
                "@id": "http://example/s1",
                "http://example/p1": [{"@id": "_:o1"}]
              }
            }]
          }])
        },
        'object-iil': {
          input: RDF::Statement(
            RDF::URI('http://example/s'),
            RDF::URI('http://example/p'),
            RDF::Statement(
              RDF::URI('http://example/s1'),
              RDF::URI('http://example/p1'),
              RDF::Literal('o1')
            )
          ),
          output: %([{
            "@id": "http://example/s",
            "http://example/p": [{
              "@id": {
                "@id": "http://example/s1",
                "http://example/p1": [{"@value": "o1"}]
              }
            }]
          }])
        },
        'recursive-subject': {
          input: RDF::Statement(
            RDF::Statement(
              RDF::Statement(
                RDF::URI('http://example/s2'),
                RDF::URI('http://example/p2'),
                RDF::URI('http://example/o2')
              ),
              RDF::URI('http://example/p1'),
              RDF::URI('http://example/o1')
            ),
            RDF::URI('http://example/p'),
            RDF::URI('http://example/o')
          ),
          output: %([{
            "@id": {
              "@id": {
                "@id": "http://example/s2",
                "http://example/p2": [{"@id": "http://example/o2"}]
              },
              "http://example/p1": [{"@id": "http://example/o1"}]
            },
            "http://example/p": [{"@id": "http://example/o"}]
          }])
        }
      }.each do |name, params|
        it name do
          graph = RDF::Graph.new { |g| g << params[:input] }
          do_fromRdf(params.merge(input: graph, prefixes: { ex: 'http://example/' }))
        end
      end
    end

    context "problems" do
      {
        "xsd:boolean as value" => {
          input: %(
            @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
            @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

            <http://data.wikia.com/terms#playable> rdfs:range xsd:boolean .
          ),
          output: [{
            "@id" => "http://data.wikia.com/terms#playable",
            "http://www.w3.org/2000/01/rdf-schema#range" => [
              { "@id" => "http://www.w3.org/2001/XMLSchema#boolean" }
            ]
          }]
        }
      }.each do |t, params|
        it t.to_s do
          do_fromRdf(params)
        end
      end
    end
  end

  def parse(input, **options)
    reader = options[:reader] || RDF::TriG::Reader
    reader.new(input, **options, &:each_statement).to_a.extend(RDF::Enumerable)
  end

  # Serialize ntstr to a string and compare against regexps
  def serialize(ntstr, **options)
    logger.info ntstr if ntstr.is_a?(String)
    g = ntstr.is_a?(String) ? parse(ntstr, **options) : ntstr
    logger.info g.dump(:trig)
    statements = g.each_statement.to_a
    JSON::LD::API.fromRdf(statements, logger: logger, **options)
  end

  def do_fromRdf(params)
    input = params[:input]
    output = params[:output]
    output = JSON.parse(output) if output.is_a?(String)
    jld = nil
    if params[:write]
      expect { jld = serialize(input, **params) }.to write(params[:write]).to(:error)
    else
      expect { jld = serialize(input, **params) }.not_to write.to(:error)
    end
    expect(jld).to produce_jsonld(output, logger)
  rescue JSON::LD::JsonLdError => e
    raise("#{e.class}: #{e.message}\n" \
          "#{logger}\n" \
          "Backtrace:\n#{e.backtrace.join("\n")}")
  end
end
