/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Package
///////////////
package org.apache.jena.ontology.impl;


// Imports
///////////////
import java.io.ByteArrayInputStream;
import java.util.*;

import org.apache.jena.enhanced.EnhGraph ;
import org.apache.jena.ontology.* ;
import org.apache.jena.ontology.impl.OntClassImpl ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.reasoner.Reasoner ;
import org.apache.jena.reasoner.ReasonerRegistry ;
import org.apache.jena.reasoner.test.TestUtil ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import junit.framework.TestCase;


/**
 * <p>
 * Unit tests on ont models with reasoning
 * </p>
 */
public class TestOntReasoning
    extends TestCase
{
    // Constants
    //////////////////////////////////
    public static final String BASE = "http://jena.hpl.hp.com/testing/ontology";
    public static final String NS = BASE + "#";

    // Static variables
    //////////////////////////////////

    // Instance variables
    //////////////////////////////////

    // Constructors
    //////////////////////////////////

    public TestOntReasoning( String name ) {
        super( name );
    }

    // External signature methods
    //////////////////////////////////

    @Override
    public void setUp() {
        // ensure the ont doc manager is in a consistent state
        OntDocumentManager.getInstance().reset( true );
    }


    public void testSubClassDirectTransInf1a() {
        OntModel m = ModelFactory.createOntologyModel( ProfileRegistry.OWL_LITE_LANG );

        OntClass A = m.createClass( NS + "A" );
        OntClass B = m.createClass( NS + "B" );
        OntClass C = m.createClass( NS + "C" );
        OntClass D = m.createClass( NS + "D" );

        A.addSubClass( B );
        A.addSubClass( C );
        C.addSubClass( D );

        iteratorTest( A.listSubClasses(), new Object[] {B, C, D} );
        iteratorTest( A.listSubClasses( true ), new Object[] {B, C} );
    }

    public void testSubClassDirectTransInf1b() {
        OntModel m = ModelFactory.createOntologyModel( ProfileRegistry.OWL_LITE_LANG );

        OntClass A = m.createClass( NS + "A" );
        OntClass B = m.createClass( NS + "B" );
        OntClass C = m.createClass( NS + "C" );
        OntClass D = m.createClass( NS + "D" );

        A.addSubClass( B );
        A.addSubClass( C );
        C.addSubClass( D );
        A.addSubClass( D );     // directly asserts a link that could be inferred

        iteratorTest( A.listSubClasses(), new Object[] {B, C, D} );
        iteratorTest( A.listSubClasses( true ), new Object[] {B, C} );
    }

    public void testSubClassDirectTransInf2a() {
        // test the code path for generating direct sc with no reasoner
        OntModelSpec spec = new OntModelSpec( OntModelSpec.OWL_LITE_MEM );
        spec.setReasonerFactory( null );
        OntModel m = ModelFactory.createOntologyModel( spec, null );

        OntClass A = m.createClass( NS + "A" );
        OntClass B = m.createClass( NS + "B" );
        OntClass C = m.createClass( NS + "C" );
        OntClass D = m.createClass( NS + "D" );

        A.addSubClass( B );
        A.addSubClass( C );
        C.addSubClass( D );

        iteratorTest( A.listSubClasses(), new Object[] {B, C} );
        iteratorTest( A.listSubClasses( true ), new Object[] {B, C} );
    }

    public void testSubClassDirectTransInf2b() {
        // test the code path for generating direct sc with no reasoner
        OntModelSpec spec = new OntModelSpec( OntModelSpec.OWL_LITE_MEM );
        spec.setReasonerFactory( null );
        OntModel m = ModelFactory.createOntologyModel( spec, null );

        OntClass A = m.createClass( NS + "A" );
        OntClass B = m.createClass( NS + "B" );
        OntClass C = m.createClass( NS + "C" );
        OntClass D = m.createClass( NS + "D" );

        A.addSubClass( B );
        A.addSubClass( C );
        C.addSubClass( D );
        A.addSubClass( D );     // directly asserts a link that could be inferred

        iteratorTest( A.listSubClasses(), new Object[] {B, C, D} );
        iteratorTest( A.listSubClasses( true ), new Object[] {B, C} );
    }

    public void testListSuperClassesDirect() {
        String ns = "http://example.org/test#";
        OntModel m0 = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM );
        OntClass c0 = m0.createClass( ns + "C0" );
        OntClass c1 = m0.createClass( ns + "C1" );
        OntClass c2 = m0.createClass( ns + "C2" );
        OntClass c3 = m0.createClass( ns + "C3" );

        c0.addSubClass( c1 );
        c1.addSubClass( c2 );
        c2.addEquivalentClass( c3 );

        // now c1 is the direct super-class of c2, even allowing for the equiv with c3
        assertFalse( "pass 1: c0 should not be a direct super of c2", c2.hasSuperClass( c0, true ) );
        assertFalse( "pass 1: c3 should not be a direct super of c2", c2.hasSuperClass( c3, true ) );
        assertFalse( "pass 1: c2 should not be a direct super of c2", c2.hasSuperClass( c2, true ) );
        assertTrue( "pass 1: c1 should be a direct super of c2", c2.hasSuperClass( c1, true ) );

        // second pass - with inference
        m0 = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM_RULE_INF );
        c0 = m0.createClass( ns + "C0" );
        c1 = m0.createClass( ns + "C1" );
        c2 = m0.createClass( ns + "C2" );
        c3 = m0.createClass( ns + "C3" );

        c0.addSubClass( c1 );
        c1.addSubClass( c2 );
        c2.addEquivalentClass( c3 );

        // now c1 is the direct super-class of c2, even allowing for the equiv with c3
        assertFalse( "pass 2: c0 should not be a direct super of c2", c2.hasSuperClass( c0, true ) );
        assertFalse( "pass 2: c3 should not be a direct super of c2", c2.hasSuperClass( c3, true ) );
        assertFalse( "pass 2: c2 should not be a direct super of c2", c2.hasSuperClass( c2, true ) );
        assertTrue( "pass 2: c1 should be a direct super of c2", c2.hasSuperClass( c1, true ) );
    }

    public void testSubPropertyDirectTransInf1a() {
        OntModel m = ModelFactory.createOntologyModel( ProfileRegistry.OWL_LITE_LANG );

        OntProperty p = m.createObjectProperty( NS + "p" );
        OntProperty q = m.createObjectProperty( NS + "q" );
        OntProperty r = m.createObjectProperty( NS + "r" );
        OntProperty s = m.createObjectProperty( NS + "s" );

        p.addSubProperty( q );
        p.addSubProperty( r );
        r.addSubProperty( s );

        iteratorTest( p.listSubProperties(), new Object[] {p,q,r,s} );
        iteratorTest( p.listSubProperties( true ), new Object[] {q,r} );
    }

    public void testSubPropertyDirectTransInf1b() {
        OntModel m = ModelFactory.createOntologyModel( ProfileRegistry.OWL_LITE_LANG );

        OntProperty p = m.createObjectProperty( NS + "p" );
        OntProperty q = m.createObjectProperty( NS + "q" );
        OntProperty r = m.createObjectProperty( NS + "r" );
        OntProperty s = m.createObjectProperty( NS + "s" );

        p.addSubProperty( q );
        p.addSubProperty( r );
        r.addSubProperty( s );
        p.addSubProperty( s );     // directly asserts a link that could be inferred

        iteratorTest( p.listSubProperties(), new Object[] {p,q,r,s} );
        iteratorTest( p.listSubProperties( true ), new Object[] {q,r} );
    }

    public void testSubPropertyDirectTransInf2a() {
        // test the code path for generating direct sc with no reasoner
        OntModelSpec spec = new OntModelSpec( OntModelSpec.OWL_LITE_MEM );
        spec.setReasonerFactory( null );
        OntModel m = ModelFactory.createOntologyModel( spec, null );

        OntProperty p = m.createObjectProperty( NS + "p" );
        OntProperty q = m.createObjectProperty( NS + "q" );
        OntProperty r = m.createObjectProperty( NS + "r" );
        OntProperty s = m.createObjectProperty( NS + "s" );

        p.addSubProperty( q );
        p.addSubProperty( r );
        r.addSubProperty( s );

        iteratorTest( p.listSubProperties(), new Object[] {q,r} );
        iteratorTest( p.listSubProperties( true ), new Object[] {q,r} );
    }

    public void testSubPropertyDirectTransInf2b() {
        // test the code path for generating direct sc with no reasoner
        OntModelSpec spec = new OntModelSpec( OntModelSpec.OWL_LITE_MEM );
        spec.setReasonerFactory( null );
        OntModel m = ModelFactory.createOntologyModel( spec, null );

        OntProperty p = m.createObjectProperty( NS + "p" );
        OntProperty q = m.createObjectProperty( NS + "q" );
        OntProperty r = m.createObjectProperty( NS + "r" );
        OntProperty s = m.createObjectProperty( NS + "s" );

        p.addSubProperty( q );
        p.addSubProperty( r );
        r.addSubProperty( s );
        p.addSubProperty( s );     // directly asserts a link that could be inferred

        iteratorTest( p.listSubProperties(), new Object[] {q,r,s} );
        iteratorTest( p.listSubProperties( true ), new Object[] {q,r} );
    }

    public void testListDeclaredProperties0() {
        OntModel m = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM_RULE_INF, null );

        // a simple class hierarchy  organism -> vertebrate -> mammal -> dog
        OntClass organism = m.createClass( NS + "Organism" );
        OntClass vertebrate = m.createClass( NS + "Vertebrate" );
        OntClass mammal = m.createClass( NS + "Mammal" );
        OntClass dog = m.createClass( NS + "Dog" );

        organism.addSubClass( vertebrate );
        vertebrate.addSubClass( mammal );
        mammal.addSubClass( dog );

        // hair as a covering
        OntClass covering = m.createClass( NS + "Covering" );
        Individual hair = m.createIndividual( NS+"hair", covering );

        // various properties
        DatatypeProperty limbsCount = m.createDatatypeProperty( NS + "limbsCount" );
        DatatypeProperty hasCovering = m.createDatatypeProperty( NS + "hasCovering" );
        DatatypeProperty numYoung = m.createDatatypeProperty( NS + "numYoung" );

        // vertebrates have limbs, mammals have live young
        limbsCount.addDomain( vertebrate );
        numYoung.addDomain( mammal );

        // mammals have-covering = hair
        Restriction r = m.createRestriction( hasCovering );
        r.convertToHasValueRestriction( hair );
        mammal.addSuperClass( r );

        iteratorTest( organism.listDeclaredProperties(), new Object[] {hasCovering} );
        iteratorTest( vertebrate.listDeclaredProperties(), new Object[] {limbsCount, hasCovering} );
        iteratorTest( mammal.listDeclaredProperties(), new Object[] {limbsCount, hasCovering, numYoung} );
        iteratorTest( dog.listDeclaredProperties(), new Object[] {limbsCount, hasCovering, numYoung} );
        iteratorTest( r.listDeclaredProperties(), new Object[] {hasCovering} );

        iteratorTest( organism.listDeclaredProperties(true), new Object[] {hasCovering} );
        iteratorTest( vertebrate.listDeclaredProperties(true), new Object[] {limbsCount} );
        iteratorTest( mammal.listDeclaredProperties(true), new Object[] {numYoung} );
        iteratorTest( dog.listDeclaredProperties(true), new Object[] {} );
        iteratorTest( r.listDeclaredProperties(true), new Object[] {hasCovering} );

        iteratorTest( organism.listDeclaredProperties(false), new Object[] {hasCovering} );
        iteratorTest( vertebrate.listDeclaredProperties(false), new Object[] {hasCovering,limbsCount} );
        iteratorTest( mammal.listDeclaredProperties(false), new Object[] {hasCovering,numYoung,limbsCount} );
        iteratorTest( dog.listDeclaredProperties(false), new Object[] {hasCovering,numYoung,limbsCount} );
        iteratorTest( r.listDeclaredProperties(false), new Object[] {hasCovering} );
    }

    /**
     * Test LDP with anonymous classes
     */
    public void testListDeclaredProperties1() {
        OntModel m = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM );
        OntProperty p = m.createOntProperty( NS + "p" );
        OntClass a = m.createClass( NS + "a" );
        Restriction r = m.createMinCardinalityRestriction( null, p, 1 );
        r.addSubClass( a );
        Iterator<OntProperty> i = a.listDeclaredProperties();
        TestUtil.assertIteratorLength( a.listDeclaredProperties(), 1 );
    }

    /** Test LDP with resources in different sub-models */
    public void testListDeclaredProperties2() {
        OntModel m0 = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM );

        // in model M0, p0 has class c0 in the domain
        OntClass c0 = m0.createClass( NS + "c0" );
        ObjectProperty p0 = m0.createObjectProperty( NS + "p0" );
        p0.setDomain( c0 );

        // in model M1, class c1 is a subClass of c0
        OntModel m1 = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM );
        OntClass c1 = m1.createClass( NS + "c1" );
        c1.addSuperClass( c0 );

        // simulate imports
        m1.addSubModel( m0 );

        // get a c0 reference from m1
        OntClass cc0 = m1.getOntClass( NS + "c0" );
        assertNotNull( cc0 );

        TestUtil.assertIteratorValues( this, c1.listDeclaredProperties(), new Object[] {p0} );
        TestUtil.assertIteratorValues( this, c0.listDeclaredProperties(false), new Object[] {p0} );
        TestUtil.assertIteratorValues( this, cc0.listDeclaredProperties(false), new Object[] {p0} );
    }

    /**
     * Problem reported by Andy Seaborne - combine abox and tbox in RDFS with
     * ontmodel
     */
    public void testRDFSAbox() {
        String sourceT =
            "<rdf:RDF "
                + "    xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'"
                + "    xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#'"
                + "   xmlns:owl=\"http://www.w3.org/2002/07/owl#\">"
                + "    <owl:Class rdf:about='http://example.org/foo#A'>"
                + "   </owl:Class>"
                + "</rdf:RDF>";

        String sourceA =
            "<rdf:RDF "
                + "    xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'"
                + "    xmlns:rdfs='http://www.w3.org/2000/01/rdf-schema#' "
                + "   xmlns:owl=\"http://www.w3.org/2002/07/owl#\">"
                + "    <rdf:Description rdf:about='http://example.org/foo#x'>"
                + "    <rdf:type rdf:resource='http://example.org/foo#A' />"
                + "   </rdf:Description>"
                + "</rdf:RDF>";

        Model tBox = ModelFactory.createDefaultModel();
        tBox.read(new ByteArrayInputStream(sourceT.getBytes()), "http://example.org/foo");

        Model aBox = ModelFactory.createDefaultModel();
        aBox.read(new ByteArrayInputStream(sourceA.getBytes()), "http://example.org/foo");

        Reasoner reasoner = ReasonerRegistry.getOWLReasoner();
        reasoner = reasoner.bindSchema(tBox);

        OntModelSpec spec = new OntModelSpec(OntModelSpec.OWL_MEM_RULE_INF);
        spec.setReasoner(reasoner);

        OntModel m = ModelFactory.createOntologyModel(spec, aBox);

        List<Individual> inds = new ArrayList<>();
        for (Iterator<Individual> i = m.listIndividuals(); i.hasNext();) {
            inds.add(i.next());
        }

        assertTrue("x should be an individual", inds.contains(m.getResource("http://example.org/foo#x")));

    }

    public void testInvokeDirectClassReasoning() {
        OntModel m = ModelFactory.createOntologyModel(OntModelSpec.RDFS_MEM_TRANS_INF, null);
        Resource a = m.createResource("http://example.org#A");
        Resource b = m.createResource("http://example.org#B");
        OntClass A = new OntClassImpl(a.asNode(), (EnhGraph) m) {
            @Override
            protected boolean hasSuperClassDirect(Resource cls) {
                throw new RuntimeException("did not find direct reasoner");
            }
        };

        // will throw an exception if the wrong code path is taken
        A.hasSuperClass(b, true);
    }

    public void testListIndividualsWithReasoner() {
        OntModel m = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM_RDFS_INF );
        OntClass C = m.createClass( NS + "C" );
        Resource a = m.createResource( NS + "a", C );

        TestUtil.assertIteratorValues( this, m.listIndividuals(), new Object[] {a} );
    }

    /**
     * Bug report by kers - maximal lower elements calculation not correct in models
     * with no reasoner. Manifests as direct sub-class bug.
     */
    public void testListSubClassesDirectNoReasoner() {
        OntModel m = ModelFactory.createOntologyModel( OntModelSpec.RDFS_MEM );
        OntClass r = m.createClass( NS + "r" );
        OntClass a = m.createClass( NS + "a" );
        OntClass b = m.createClass( NS + "b" );
        OntClass c = m.createClass( NS + "c" );
        OntClass d = m.createClass( NS + "d" );
        OntClass e = m.createClass( NS + "e" );
        OntClass f = m.createClass( NS + "f" );
        OntClass g = m.createClass( NS + "g" );

        g.addSuperClass( c );
        f.addSuperClass( c );
        e.addSuperClass( b );
        d.addSuperClass( b );
        c.addSuperClass( a );
        b.addSuperClass( a );

        // simulated closure
        r.addSubClass( a );
        r.addSubClass( b );
        r.addSubClass( c );
        r.addSubClass( d );
        r.addSubClass( e );
        r.addSubClass( f );
        r.addSubClass( g );

        TestUtil.assertIteratorValues( this, r.listSubClasses( true ), new Object[] {a} );
    }


    public void testOwlLiteClasses() {
        OntModel model = ModelFactory.createOntologyModel( OntModelSpec.OWL_LITE_MEM_TRANS_INF );

        OntClass b = model.createClass( NS + "B" );
        OntProperty p0 = model.createOntProperty( NS + "p0" );
        ObjectProperty p1 = model.createObjectProperty( NS + "p1" );
        DatatypeProperty p2 = model.createDatatypeProperty( NS + "p2" );

        Individual i0 = model.createIndividual( NS + "i0", b );

        model.setStrictMode( true );

        for (OntResource r: new OntResource[] {b,p0,p1,p2}) {
            assertFalse( r + " should not be an individual", r.canAs( Individual.class ));
        }
    }

    /** Bugrep from Benson Margulies: see
     * <a href="https://issues.apache.org/jira/browse/JENA-21">JENA-21</a>
     */
    public void testBM0() {
        OntModel m = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM_RDFS_INF );
        // should not throw NPE:
        m.listStatements( null, null, (RDFNode) null, null );
    }


    // Internal implementation methods
    //////////////////////////////////

    /** Test that an iterator delivers the expected values */
    protected void iteratorTest( Iterator<?> i, Object[] expected ) {
        Logger logger = LoggerFactory.getLogger( getClass() );
        List<Object> expList = new ArrayList<>();
        for ( Object anExpected : expected )
        {
            expList.add( anExpected );
        }

        while (i.hasNext()) {
            Object next = i.next();

            // debugging
            if (!expList.contains( next )) {
                logger.debug( getName() + " - Unexpected iterator result: " + next );
            }

            assertTrue( "Value " + next + " was not expected as a result from this iterator ", expList.contains( next ) );
            assertTrue( "Value " + next + " was not removed from the list ", expList.remove( next ) );
        }

        if (!(expList.size() == 0)) {
            logger.debug( getName() + " Expected iterator results not found" );
            for ( Object anExpList : expList )
            {
                logger.debug( getName() + " - missing: " + anExpList );
            }
        }
        assertEquals( "There were expected elements from the iterator that were not found", 0, expList.size() );
    }


    //==============================================================================
    // Inner class definitions
    //==============================================================================

}
