<?php

namespace Tests\EasyRdf\Parser;

/*
 * EasyRdf
 *
 * LICENSE
 *
 * Copyright (c) 2021 Konrad Abicht <hi@inspirito.de>
 * Copyright (c) 2009-2020 Nicholas J Humfrey.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author 'Nicholas J Humfrey" may be used to endorse or
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package    EasyRdf
 * @copyright  Copyright (c) 2021 Konrad Abicht <hi@inspirito.de>
 * @copyright  Copyright (c) 2009-2020 Nicholas J Humfrey
 * @license    https://www.opensource.org/licenses/bsd-license.php
 */

use EasyRdf\Format;
use EasyRdf\Graph;
use EasyRdf\Parser\Ntriples;
use EasyRdf\Parser\Turtle;
use Test\TestCase;
use Tests\EasyRdf\Serialiser\NtriplesArray;

Format::register('ntriples-array', 'PHP Array of Triples');
Format::registerSerialiser('ntriples-array', NtriplesArray::class);

class TurtleTest extends TestCase
{
    /** @var Turtle */
    protected $turtleParser;

    /** @var Ntriples */
    protected $ntriplesParser;

    protected $baseUri;

    protected function setUp(): void
    {
        $this->turtleParser = new Turtle();
        $this->ntriplesParser = new Ntriples();
        $this->baseUri = 'http://www.w3.org/2001/sw/DataAccess/df1/tests/';
    }

    public function testParseFoaf()
    {
        $graph = new Graph();
        $count = $this->turtleParser->parse(
            $graph,
            readFixture('foaf.ttl'),
            'turtle',
            $this->baseUri
        );
        $this->assertSame(14, $count);

        $joe = $graph->resource('http://www.example.com/joe#me');
        $this->assertNotNull($joe);
        $this->assertClass('EasyRdf\Resource', $joe);
        $this->assertSame('http://www.example.com/joe#me', $joe->getUri());

        $name = $joe->get('foaf:name');
        $this->assertNotNull($name);
        $this->assertClass('EasyRdf\Literal', $name);
        $this->assertStringEquals('Joe Bloggs', $name);
        $this->assertSame('en', $name->getLang());
        $this->assertNull($name->getDatatype());

        $foaf = $graph->resource('http://www.example.com/joe/foaf.rdf');
        $this->assertNotNull($foaf);
        $this->assertStringEquals("Joe Bloggs' FOAF File", $foaf->label());
    }

    public function testParseCollection()
    {
        $graph = new Graph();
        $count = $this->turtleParser->parse(
            $graph,
            "<http://example.com/s> <http://example.com/p> ('A' 'B' 'C' 'D') .",
            'turtle',
            $this->baseUri
        );
        $this->assertSame(9, $count);

        $array = [];
        $collection = $graph->resource('http://example.com/s')->get('<http://example.com/p>');
        foreach ($collection as $item) {
            $array[] = (string) $item;
        }

        $this->assertEquals(
            ['A', 'B', 'C', 'D'],
            $array
        );
    }

    public function testParseUnsupportedFormat()
    {
        $this->expectException('EasyRdf\Exception');
        $this->expectExceptionMessage(
            'EasyRdf\Parser\Turtle does not support: unsupportedformat'
        );
        $this->turtleParser->parse(
            new Graph(),
            'data',
            'unsupportedformat',
            null
        );
    }

    /* The rest of this script is runs the Turtle Test Suite
       from the files here:
       http://www.w3.org/TeamSubmission/turtle/tests/
     */

    protected function parseTurtle($filename)
    {
        $graph = new Graph();
        $this->turtleParser->parse(
            $graph,
            readFixture($filename),
            'turtle',
            $this->baseUri.basename($filename)
        );

        return $graph->serialise('ntriples-array');
    }

    protected function parseNtriples($filename)
    {
        $graph = new Graph();
        $this->ntriplesParser->parse(
            $graph,
            readFixture($filename),
            'ntriples',
            $this->baseUri.basename($filename)
        );

        return $graph->serialise('ntriples-array');
    }

    protected function turtleTestCase($name)
    {
        $this->assertSame(
            $this->parseNtriples("turtle/$name.out"),
            $this->parseTurtle("turtle/$name.ttl")
        );
    }

    public function testCase00()
    {
        // Blank subject
        $this->turtleTestCase('test-00');
    }

    public function testCase01()
    {
        // @prefix and qnames
        $this->turtleTestCase('test-01');
    }

    public function testCase02()
    {
        // , operator
        $this->turtleTestCase('test-02');
    }

    public function testCase03()
    {
        // ; operator
        $this->turtleTestCase('test-03');
    }

    public function testCase04()
    {
        // empty [] as subject and object
        $this->turtleTestCase('test-04');
    }

    public function testCase05()
    {
        // non-empty [] as subject and object
        $this->turtleTestCase('test-05');
    }

    public function testCase06()
    {
        // 'a' as predicate
        $this->turtleTestCase('test-06');
    }

    public function testCase07()
    {
        // simple collection
        $this->turtleTestCase('test-07');
    }

    public function testCase08()
    {
        // empty collection
        $this->turtleTestCase('test-08');
    }

    public function testCase09()
    {
        // integer datatyped literal
        $this->turtleTestCase('test-09');
    }

    public function testCase10()
    {
        // decimal integer canonicalization
        $this->turtleTestCase('test-10');
    }

    public function testCase11()
    {
        // - and _ in names and qnames
        $this->turtleTestCase('test-11');
    }

    public function testCase12()
    {
        // tests for rdf:_<numbers> and other qnames starting with _
        $this->turtleTestCase('test-12');
    }

    public function testCase13()
    {
        // bare : allowed
        $this->turtleTestCase('test-13');
    }

    // Removed tests 14-16 because they take a long time to run

    /**
     * @group linux
     */
    public function testCase17()
    {
        // simple long literal
        $this->turtleTestCase('test-17');
    }

    /**
     * @group linux
     */
    public function testCase18()
    {
        // long literals with escapes
        $this->turtleTestCase('test-18');
    }

    public function testCase19()
    {
        // floating point number
        $this->turtleTestCase('test-19');
    }

    public function testCase20()
    {
        // empty literals, normal and long variant
        $this->turtleTestCase('test-20');
    }

    public function testCase21()
    {
        // positive integer, decimal and doubles
        $this->turtleTestCase('test-21');
    }

    public function testCase22()
    {
        // negative integer, decimal and doubles
        $this->turtleTestCase('test-22');
    }

    public function testCase23()
    {
        // long literal ending in double quote
        $this->turtleTestCase('test-23');
    }

    public function testCase24()
    {
        // boolean literals
        $this->turtleTestCase('test-24');
    }

    public function testCase25()
    {
        // comments
        $this->turtleTestCase('test-25');
    }

    public function testCase26()
    {
        // no final newline
        $this->turtleTestCase('test-26');
    }

    public function testCase27()
    {
        // duplicate prefix
        $this->turtleTestCase('test-27');
    }

    public function testTurtleSyntaxPnameEsc01()
    {
        $this->turtleTestCase('turtle-syntax-pname-esc-01');
    }

    public function testTurtleSyntaxPnameEsc02()
    {
        $this->turtleTestCase('turtle-syntax-pname-esc-02');
    }

    public function testTurtleSyntaxPnameEsc03()
    {
        $this->turtleTestCase('turtle-syntax-pname-esc-03');
    }

    public function testBase1()
    {
        // Resolution of a relative URI against an absolute base.
        $this->turtleTestCase('base1');
    }

    public function testRdfSchema()
    {
        $this->turtleTestCase('rdf-schema');
    }

    public function testQuotes1()
    {
        $this->turtleTestCase('quotes1');
    }

    public function testBad00()
    {
        // prefix name must end in a :
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected ':', found '<' on line 2, column 12"
        );
        $this->parseTurtle('turtle/bad-00.ttl');
    }

    public function testBad01()
    {
        // Forbidden by RDF - predicate cannot be blank
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected an RDF value here, found '[' on line 3, column 4"
        );
        $this->parseTurtle('turtle/bad-01.ttl');
    }

    public function testBad02()
    {
        // Forbidden by RDF - predicate cannot be blank
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected an RDF value here, found '[' on line 3, column 4"
        );
        $this->parseTurtle('turtle/bad-02.ttl');
    }

    public function testBad03()
    {
        // 'a' only allowed as a predicate
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected ':', found ' ' on line 3, column 3"
        );
        $this->parseTurtle('turtle/bad-03.ttl');
    }

    public function testBad04()
    {
        // No comma is allowed in collections
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected an RDF value here, found ',' on line 3, column 16"
        );
        $this->parseTurtle('turtle/bad-04.ttl');
    }

    public function testBad05()
    {
        // N3 {}s are not in Turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected an RDF value here, found '{' on line 3, column 1"
        );
        $this->parseTurtle('turtle/bad-05.ttl');
    }

    public function testBad06()
    {
        // is and of are not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected ':', found ' ' on line 3, column 7"
        );
        $this->parseTurtle('turtle/bad-06.ttl');
    }

    public function testBad07()
    {
        // paths are not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected an RDF value here, found '^' on line 3, column 3"
        );
        $this->parseTurtle('turtle/bad-07.ttl');
    }

    public function testBad08()
    {
        // @keywords is not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            'Turtle Parse Error: unknown directive "@keywords" on line 1, column 10'
        );
        $this->parseTurtle('turtle/bad-08.ttl');
    }

    public function testBad09()
    {
        // implies is not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected an RDF value here, found '=' on line 3, column 4"
        );
        $this->parseTurtle('turtle/bad-09.ttl');
    }

    public function testBad10()
    {
        // equivalence is not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            "Turtle Parse Error: expected an RDF value here, found '=' on line 3, column 4"
        );
        $this->parseTurtle('turtle/bad-10.ttl');
    }

    public function testBad11()
    {
        // @forAll is not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            'Turtle Parse Error: unknown directive "@forall" on line 3, column 8'
        );
        $this->parseTurtle('turtle/bad-11.ttl');
    }

    public function testBad12()
    {
        // @forSome is not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            'Turtle Parse Error: unknown directive "@forsome" on line 3, column 9'
        );
        $this->parseTurtle('turtle/bad-12.ttl');
    }

    public function testBad13()
    {
        // <= is not in turtle
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            'Turtle Parse Error: unexpected end of file while reading URI on line 4, column 1'
        );
        $this->parseTurtle('turtle/bad-13.ttl');
    }

    public function testBad14()
    {
        // Test long literals with missing end
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            'Turtle Parse Error: unexpected end of file while reading long string on line 7, column 1'
        );
        $this->parseTurtle('turtle/bad-14.ttl');
    }

    /**
     * https://github.com/easyrdf/easyrdf/issues/195
     */
    public function testIssue195()
    {
        $filename = 'turtle/gh195-dbpedia-ja-gauguin.ttl';

        $graph = new Graph();
        $triple_count = $this->turtleParser->parse(
            $graph,
            readFixture($filename),
            'turtle',
            $this->baseUri.basename($filename)
        );

        $this->assertEquals(1, $triple_count);
    }

    /**
     * https://github.com/easyrdf/easyrdf/issues/185
     */
    public function testIssue185()
    {
        $filename = 'turtle/gh185-dash-in-prefix.ttl';

        $graph = new Graph();
        $triple_count = $this->turtleParser->parse(
            $graph,
            readFixture($filename),
            'turtle',
            $this->baseUri.basename($filename)
        );

        $this->assertEquals(1, $triple_count);

        $data = $graph->toRdfPhp();

        foreach ($data as $resource => $properties) {
            $this->assertEquals('http://example.org/foo-bar#example', $resource);

            foreach ($properties as $k => $v) {
                $this->assertEquals('http://example.org/foo-bar#baz', $k);
                $this->assertEquals('test', $v[0]['value']);
            }
        }
    }

    /**
     * @see https://github.com/easyrdf/easyrdf/issues/140
     */
    public function testIssue140()
    {
        $this->markTestIncomplete('fix for bug #140 is not implemented yet');

        /*
        $filename = 'turtle/gh140-freebase.ttl';

        $graph = new Graph();

        $triple_count = $this->turtleParser->parse(
            $graph,
            readFixture($filename),
            'turtle',
            $this->baseUri.basename($filename)
        );

        $this->assertEquals(14, $triple_count);
        */
    }

    /**
     * Allow usage of dot in middle of name
     *
     * @see https://github.com/sweetrdf/easyrdf/issues/51
     */
    public function testIssue51()
    {
        $this->turtleTestCase('gh51-sweetrdf-dot-in-name');
    }

    public function testIssue51Bad()
    {
        // Test long literals with missing end
        $this->expectException('EasyRdf\Parser\Exception');
        $this->expectExceptionMessage(
            'Turtle Parse Error: Illegal predicate type: literal on line 7, column 19'
        );
        $this->parseTurtle('turtle/gh51-sweetrdf-dot-in-name-bad.ttl');
    }

    /**
     * Allow whitespace(s) between quoted string literal and langtag or datatype
     *
     * @see https://github.com/sweetrdf/easyrdf/issues/52
     */
    public function testIssue52()
    {
        $this->turtleTestCase('gh52-sweetrdf-whitespace-langtag');
    }

    /**
     * Bug in Turtle parser for boolean value that is directly followed by a semicolon
     *
     * @see https://github.com/sweetrdf/easyrdf/issues/58
     */
    public function testIssue58()
    {
        // Test file should parse without exceptions
        $this->expectNotToPerformAssertions();

        $this->parseTurtle('turtle/gh58-sweetrdf-bool-parser.ttl');
    }
}
