/*
 * 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 org.apache.jena.rdf.model.test;

import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.graph.test.NodeCreateUtils ;
import org.apache.jena.rdf.model.AnonId;
import org.apache.jena.rdf.model.Literal;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.impl.ModelCom ;
import org.apache.jena.shared.PropertyNotFoundException;

public abstract class AbstractTestModel extends ModelTestBase
    {
    public AbstractTestModel( String name )
        { super(name); }

    public abstract Model getModel();

    private Model model;

    @Override
    public void setUp()
        { model = getModel(); }

    @Override
    public void tearDown()
        { model.close(); }

    public void testTransactions() {
        if ( model.supportsTransactions() )
            model.executeInTxn(() -> {}) ;
    }

    public void testCreateResourceFromNode()
        {
        RDFNode S = model.getRDFNode( NodeCreateUtils.create( "spoo:S" ) );
        assertInstanceOf( Resource.class, S );
        assertEquals( "spoo:S", ((Resource) S).getURI() );
        }

    public void testCreateLiteralFromNode()
        {
        RDFNode S = model.getRDFNode( NodeCreateUtils.create( "42" ) );
        assertInstanceOf( Literal.class, S );
        assertEquals( "42", ((Literal) S).getLexicalForm() );
        }

   public void testCreateBlankFromNode()
        {
        RDFNode S = model.getRDFNode( NodeCreateUtils.create( "_Blank" ) );
        assertInstanceOf( Resource.class, S );
        assertEquals( new AnonId( "_Blank" ), ((Resource) S).getId() );
        }

    public void testIsEmpty()
        {
        Statement S1 = statement( model, "model rdf:type nonEmpty" );
        Statement S2 = statement( model, "pinky rdf:type Pig" );
        assertTrue( model.isEmpty() );
        model.add( S1 );
        assertFalse( model.isEmpty() );
        model.add( S2 );
        assertFalse( model.isEmpty() );
        model.remove( S1 );
        assertFalse( model.isEmpty() );
        model.remove( S2 );
        assertTrue( model.isEmpty() );
        }

    public void testContainsResource()
        {
        modelAdd( model, "x R y; _a P _b" );
        assertTrue( model.containsResource( resource( model, "x" ) ) );
        assertTrue( model.containsResource( resource( model, "R" ) ) );
        assertTrue( model.containsResource( resource( model, "y" ) ) );
        assertTrue( model.containsResource( resource( model, "_a" ) ) );
        assertTrue( model.containsResource( resource( model, "P" ) ) );
        assertTrue( model.containsResource( resource( model, "_b" ) ) );
        assertFalse( model.containsResource( resource( model, "i" ) ) );
        assertFalse( model.containsResource( resource( model, "_j" ) ) );
        }

    /**
        Test the new version of getProperty(), which delivers null for not-found
        properties.
    */
    public void testGetProperty()
        {
        modelAdd( model, "x P a; x P b; x R c" );
        Resource x = resource( model, "x" );
        assertEquals( resource( model, "c" ), x.getProperty( property( model, "R" ) ).getObject() );
        RDFNode ob = x.getProperty( property( model, "P" ) ).getObject();
        assertTrue( ob.equals( resource( model, "a" ) ) || ob.equals( resource( model, "b" ) ) );
        assertNull( x.getProperty( property( model, "noSuchPropertyHere" ) ) );
        }

    /**
     * Tests {@link Resource#getProperty(Property, String)} and
     * {@link Resource#getRequiredProperty(Property, String)}.
     */
    public void testGetPropertyWithLanguage()
        {
        model.add(ModelTestBase.resource(model, "x"), ModelTestBase.property(model, "P"), "a", "pt");
        model.add(ModelTestBase.resource(model, "x"), ModelTestBase.property(model, "P"), "b", "en");
        model.add(ModelTestBase.resource(model, "x"), ModelTestBase.property(model, "P"), "c", "es");
        model.add(ModelTestBase.resource(model, "x"), ModelTestBase.property(model, "R"), "d", "fr");
        model.add(ModelTestBase.resource(model, "x"), ModelTestBase.property(model, "R"), "e", "de");

        {//Tests {@link Resource#getProperty(Property, String)}
        final Resource x = resource( model, "x" );
        assertEquals("a", x.getProperty( property( model, "P" ), "pt").getString() );
        assertEquals("b", x.getProperty( property( model, "P" ), "en").getString() );
        assertNull(x.getProperty( property( model, "P" ), "ja") );
        final Literal l = x.getProperty( property(model, "R") ).getLiteral();
        assertTrue( "d".equals( l.getString() ) || "e".equals( l.getString() ) );
        }

        {//Tests {@link Resource#getRequiredProperty(Property, String)}
        final Resource x = resource( model, "x" );
        assertEquals("a", x.getRequiredProperty( property( model, "P" ), "pt").getString() );
        assertEquals("b", x.getRequiredProperty( property( model, "P" ), "en").getString() );
        try {
            x.getRequiredProperty( property( model, "P" ), "ja");
            fail("Must thrown PropertyNotFoundException.");
        } catch (PropertyNotFoundException e) {}
        final Literal l = x.getRequiredProperty( property(model, "R") ).getLiteral();
        assertTrue( "d".equals( l.getString() ) || "e".equals( l.getString() ) );
        assertTrue("de".equals( l.getLanguage() ) || "fr".equals( l.getLanguage() ) );
        }
        }

    public void testToStatement()
        {
        Triple t = triple( "a P b" );
        Statement s = model.asStatement( t );
        assertEquals( node( "a" ), s.getSubject().asNode() );
        assertEquals( node( "P" ), s.getPredicate().asNode() );
        assertEquals( node( "b" ), s.getObject().asNode() );
        }

    public void testAsRDF()
        {
        testPresentAsRDFNode( node( "a" ), Resource.class );
        testPresentAsRDFNode( node( "17" ), Literal.class );
        testPresentAsRDFNode( node( "_b" ), Resource.class );
        }

    private void testPresentAsRDFNode( Node n, Class<? extends RDFNode> nodeClass )
        {
        RDFNode r = model.asRDFNode( n );
        assertSame( n, r.asNode() );
        assertInstanceOf( nodeClass, r );
        }

    public void testURINodeAsResource()
        {
        Node n = node( "a" );
        Resource r = model.wrapAsResource( n );
        assertSame( n, r.asNode() );
        }

    public void testLiteralNodeAsResourceFails()
        {
        try
            {
            model.wrapAsResource( node( "17" ) );
            fail( "should fail to convert literal to Resource" );
            }
        catch (UnsupportedOperationException e)
            { pass(); }
        }

    public void testRemoveAll()
        {
        testRemoveAll( "" );
        testRemoveAll( "a RR b" );
        testRemoveAll( "x P y; a Q b; c R 17; _d S 'e'" );
        testRemoveAll( "subject Predicate 'object'; http://nowhere/x scheme:cunning not:plan" );
        }

    protected void testRemoveAll( String statements )
        {
        modelAdd( model, statements );
        assertSame( model, model.removeAll() );
        assertEquals( "model should have size 0 following removeAll(): ", 0, model.size() );
        }

    /**
 		Test cases for RemoveSPO(); each entry is a triple (add, remove, result).
	 	<ul>
	 	<li>add - the triples to add to the graph to start with
	 	<li>remove - the pattern to use in the removal
	 	<li>result - the triples that should remain in the graph
	 	</ul>
	*/
	protected String[][] cases =
		{
	            { "x R y", "x R y", "" },
	            { "x R y; a P b", "x R y", "a P b" },
	            { "x R y; a P b", "?? R y", "a P b" },
	            { "x R y; a P b", "x R ??", "a P b" },
	            { "x R y; a P b", "x ?? y", "a P b" },
	            { "x R y; a P b", "?? ?? ??", "" },
	            { "x R y; a P b; c P d", "?? P ??", "x R y" },
	            { "x R y; a P b; x S y", "x ?? ??", "a P b" },
		};

    /**
 	Test that remove(s, p, o) works, in the presence of inferencing graphs that
 	mean emptyness isn't available. This is why we go round the houses and
 	test that expected ~= initialContent + addedStuff - removed - initialContent.
 	*/
	public void testRemoveSPO()
	    {
	    ModelCom mc = (ModelCom) ModelFactory.createDefaultModel();
            for ( String[] aCase : cases )
            {
                for ( int j = 0; j < 3; j += 1 )
                {
                    Model content = getModel();
                    Model baseContent = copy( content );
                    modelAdd( content, aCase[0] );
                    Triple remove = triple( aCase[1] );
                    Node s = remove.getSubject(), p = remove.getPredicate(), o = remove.getObject();
                    Resource S = (Resource) ( s.equals( Node.ANY ) ? null : mc.getRDFNode( s ) );
                    Property P = ( ( p.equals( Node.ANY ) ? null : mc.getRDFNode( p ).as( Property.class ) ) );
                    RDFNode O = o.equals( Node.ANY ) ? null : mc.getRDFNode( o );
                    Model expected = modelWithStatements( aCase[2] );
                    content.removeAll( S, P, O );
                    Model finalContent = copy( content ).remove( baseContent );
                    assertIsoModels( aCase[1], expected, finalContent );
                }
            }
	    }

    public void testIsClosedDelegatedToGraph()
        {
        Model m = getModel();
        assertFalse( m.isClosed() );
        m.close();
        assertTrue( m.isClosed() );
        }

	protected Model copy( Model m )
	    {
	    return ModelFactory.createDefaultModel().add( m );
	    }
    }
