/*
 * 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.graph.test;

/**
    An extension of JenaTestBase (which see) with Graph-specific methods.
*/

import java.io.FileNotFoundException;
import java.lang.reflect.Constructor ;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.jena.graph.* ;
import org.apache.jena.ontology.impl.TestListSyntaxCategories ;
import org.apache.jena.shared.JenaException ;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.test.JenaTestBase ;
import org.apache.jena.util.CollectionFactory ;
import org.apache.jena.util.IteratorCollection ;
import org.apache.jena.util.iterator.ExtendedIterator ;

public class GraphTestBase extends JenaTestBase
    {
	protected static String getFileName( String fn )
   	{
   		URL u = TestListSyntaxCategories.class.getClassLoader().getResource( fn );
   		if (u == null)
   		{
   			throw new RuntimeException( new FileNotFoundException( fn ));
   		}
   		try {
   			return u.toURI().toString();
   		} catch (URISyntaxException e) {
   			throw new RuntimeException( e );
   		}
   	}
    public GraphTestBase( String name )
    	{ super( name ); }
    	
    /**
        Answer a Node as described by <code>x</code>; a shorthand for
        <code>NodeCreateUtils.create(x)</code>, which see.
    */
    public static Node node( String x )
        { return NodeCreateUtils.create( x ); }
        
    /**
        Answer a set containing the elements from the iterator <code>it</code>;
        a shorthand for <code>IteratorCollection.iteratorToSet(it)</code>,
        which see.
    */
    public static <T> Set<T> iteratorToSet( Iterator<? extends T> it )
        { return IteratorCollection.iteratorToSet( it ); }
    
    /**
        Answer a list containing the elements from the iterator <code>it</code>,
        in order; a shorthand for <code>IteratorCollection.iteratorToList(it)</code>,
        which see.
    */
    public static <T> List<T> iteratorToList( Iterator<? extends T> it )
        { return IteratorCollection.iteratorToList( it ); }
                
    /**
        Answer a set of the nodes described (as per <code>node()</code>) by
        the space-separated substrings of <code>nodes</code>.
    */
    public Set<Node> nodeSet( String nodes )
        {
        Set<Node> result = CollectionFactory.createHashedSet();
        StringTokenizer st = new StringTokenizer( nodes );
        while (st.hasMoreTokens()) result.add( node( st.nextToken() ) );
        return result;
        }
    
    /**
        Answer a set of the elements of <code>A</code>.
    */
    public <T> Set<T> arrayToSet( T [] A )
        { return CollectionFactory.createHashedSet( Arrays.asList( A ) ); }
    
    /**
        Answer a triple described by the three space-separated node descriptions
        in <code>fact</code>; a shorthand for <code>NodeCreateUtils.createTriple(fact)</code>,
        which see.
    */
    public static Triple triple( String fact )
        { return NodeCreateUtils.createTriple( fact ); }
    
    /**
        Answer a triple described by the three space-separated node descriptions
        in <code>fact</code>, using prefix-mappings from <code>pm</code>; a 
        shorthand for <code>Triple.create(pm, fact)</code>, which see.
    */
    public static Triple triple( PrefixMapping pm, String fact )
        { return NodeCreateUtils.createTriple( pm, fact ); }
        
    /**
        Answer an array of triples; each triple is described by one of the
        semi-separated substrings of <code>facts</code>, as per 
        <code>triple</code> with prefix-mapping <code>Extended</code>.
    */
    public static Triple [] tripleArray( String facts )
        {
        ArrayList<Triple> al = new ArrayList<>();
        StringTokenizer semis = new StringTokenizer( facts, ";" );
        while (semis.hasMoreTokens()) al.add( triple( PrefixMapping.Extended, semis.nextToken() ) );   
        return al.toArray( new Triple [al.size()] );
        }
    
    /**
        Answer a set of triples where the elements are described by the
        semi-separated substrings of <code>facts</code>, as per 
        <code>triple</code>.
    */
    public static Set<Triple> tripleSet( String facts )
        {
        Set<Triple> result = new HashSet<>();
        StringTokenizer semis = new StringTokenizer( facts, ";" );
        while (semis.hasMoreTokens()) result.add( triple( semis.nextToken() ) );   
        return result;
        }
       
    /**
        Answer a list of nodes, where the nodes are described by the 
        space-separated substrings of <code>items</code> as per
        <code>node()</code>.
    */
    public static List<Node> nodeList( String items )
        {
        ArrayList<Node> nl = new ArrayList<>();
        StringTokenizer nodes = new StringTokenizer( items );
        while (nodes.hasMoreTokens()) nl.add( node( nodes.nextToken() ) );   
        return nl;
        }   
    
    /**
        Answer an array of nodes, where the nodes are described by the 
        space-separated substrings of <code>items</code> as per
    */
    public static Node [] nodeArray( String items )
        {
        List<Node> nl = nodeList( items );  
        return nl.toArray( new Node [nl.size()] );
        } 
    
    /**
        Answer the graph <code>g</code> after adding to it every triple
        encoded in <code>s</code> in the fashion of <code>tripleArray</code>,
        a semi-separated sequence of space-separated node descriptions.
    */
    public static Graph graphAdd( Graph g, String s )
        {
        StringTokenizer semis = new StringTokenizer( s, ";" );
        while (semis.hasMoreTokens()) g.add( triple( PrefixMapping.Extended, semis.nextToken() ) );     
        return g;
        }
    
    /**
        Answer a new memory-based graph with Extended prefixes.
    */
    public static Graph newGraph()
        {
        Graph result = Factory.createGraphMem();
        result.getPrefixMapping().setNsPrefixes( PrefixMapping.Extended );
        return result;
        }
    
    /**
        Answer a new memory-based graph with initial contents as described
        by <code>s</code> in the fashion of <code>graphAdd()</code>.
        Not over-ridable; do not use for abstraction.
    */
    public static Graph graphWith( String s )
        { return graphAdd( newGraph(), s ); }
    
    /**
        Assert that the graph <code>g</code> is isomorphic to the graph 
        described by <code>template</code> in the fashion of 
        <code>graphWith</code>.
    */
    public static void assertEqualsTemplate( String title, Graph g, String template )
        { assertIsomorphic( title, graphWith( template ), g ); }
                
    /**
        Assert that the supplied graph <code>got</code> is isomorphic with the
        the desired graph <code>expected</code>; if not, display a readable
        description of both graphs.
    */
    public static void assertIsomorphic( String title, Graph expected, Graph got )
        {
        if (!expected.isIsomorphicWith( got ))
            {
            Map<Node, Object> map = CollectionFactory.createHashedMap();
            fail( title + ": wanted " + nice( expected, map ) + "\nbut got " + nice( got, map ) );
            }
        }
    
    /**
        Answer a string which is a newline-separated list of triples (as
        produced by niceTriple) in the graph <code>g</code>. The map 
        <code>bnodes</code> maps already-seen bnodes to their "nice" strings. 
    */
    public static String nice( Graph g, Map<Node, Object> bnodes )
        {
        StringBuffer b = new StringBuffer( g.size() * 100 );
        ExtendedIterator<Triple> it = GraphUtil.findAll( g );
        while (it.hasNext()) niceTriple( b, bnodes, it.next() );
        return b.toString();
        }
    
    /**
        Append to the string buffer <code>b</code> a "nice" representation 
        of the triple <code>t</code> on a new line, using (and updating)
        <code>bnodes</code> to supply "nice" strings for any blank nodes.
    */
    protected static void niceTriple( StringBuffer b, Map<Node, Object> bnodes, Triple t )
        {
        b.append( "\n    " );
        appendNode( b, bnodes, t.getSubject() );
        appendNode( b, bnodes, t.getPredicate() );
        appendNode( b, bnodes, t.getObject() );
        }

    /**
        A counter for new bnode strings; it starts at 1000 so as to make
        the bnode strings more uniform (at least for the first 9000 bnodes).
    */
    protected static int bnc = 1000;
    
    /**
        Append to the string buffer <code>b</code> a space followed by the
        "nice" representation of the node <code>n</code>. If <code>n</code>
        is a bnode, re-use any existing string for it from <code>bnodes</code>
        or make a new one of the form <i>_bNNNN</i> with NNNN a new integer.
    */
    protected static void appendNode( StringBuffer b, Map<Node, Object> bnodes, Node n )
        {
        b.append( ' ' );
        if (n.isBlank())
            {
            Object already = bnodes.get( n );
            if (already == null) bnodes.put( n, already = "_b" + bnc++ );
            b.append( already );
            }
        else
            b.append( nice( n ) );
        }
    
    /**
        Answer the "nice" representation of this node, the string returned by
        <code>n.toString(PrefixMapping.Extended,true)</code>.
    */
    protected static String nice( Node n )
        { return n.toString( PrefixMapping.Extended, true ); }
            
    /**
        Assert that the computed graph <code>got</code> is isomorphic with the
        desired graph <code>expected</code>; if not, fail with a default
        message (and pretty output of the graphs).
    */
    public static void assertIsomorphic( Graph expected, Graph got )
        { assertIsomorphic( "graphs must be isomorphic", expected, got ); }

    /**
        Assert that the graph <code>g</code> must contain the triple described
        by <code>s</code>; if not, fail with pretty output of both graphs
        and a message containing <code>name</code>.
    */
    public static void assertContains( String name, String s, Graph g )
        {
        assertTrue( name + " must contain " + s, g.contains( triple( s ) ) );
        }
    
    /**
        Assert that the graph <code>g</code> contains all the triples described
        by the string <code>s</code; if not, fail with a message containing
        <code>name</code>.
    */
    public static void assertContainsAll( String name, Graph g, String s )
        {
        StringTokenizer semis = new StringTokenizer( s, ";" );
        while (semis.hasMoreTokens()) assertContains( name, semis.nextToken(), g );       
        }
    
    /**
        Assert that the graph <code>g</code> does not contain the triple
        described by <code>s<code>; if it does, fail with a message containing
        <code>name</code>.
    */
    public static void assertOmits( String name, Graph g, String s )
        {
        assertFalse( name + " must not contain " + s, g.contains( triple( s ) ) );
        }
    
    /**
        Assert that the graph <code>g</code> contains none of the triples
        described by <code>s</code> in the usual way; otherwise, fail with
        a message containing <code>name</code>.
    */
    public static void assertOmitsAll( String name, Graph g, String s )
        {
        StringTokenizer semis = new StringTokenizer( s, ";" );
        while (semis.hasMoreTokens()) assertOmits( name, g, semis.nextToken() );     
        }
        
    /**
        Assert that <code>g</code> contains the triple described by 
        <code>fact</code> in the usual way.
    */
    public static boolean contains( Graph g, String fact )
        { return g.contains( triple( fact ) ); }
    
    /**
        Assert that <code>g</code> contains every triple in <code>triples</code>.
    */
    public void testContains( Graph g, Triple [] triples )
        {
            for ( Triple triple : triples )
            {
                assertTrue( "contains " + triple, g.contains( triple ) );
            }
        }

    /**
        Assert that <code>g</code> contains every triple in <code>triples</code>.
    */
    public void testContains( Graph g, List<Triple> triples )
        {
            for ( Triple triple : triples )
            {
                assertTrue( g.contains( triple ) );
            }
        }

    /**
        Assert that <code>g</code> contains every triple in <code>it</code>.
    */
    public void testContains( Graph g, Iterator<Triple> it )
        { while (it.hasNext()) assertTrue( g.contains( it.next() ) ); }

    /**
        Assert that <code>g</code> contains every triple in <code>other</code>.
    */
    public void testContains( Graph g, Graph other )
        { testContains( g, GraphUtil.findAll( other ) ); }
    
    /**
        Assert that <code>g</code> contains none of the triples in 
        <code>triples</code>.
    */
    public void testOmits( Graph g, Triple [] triples )
        {
            for ( Triple triple : triples )
            {
                assertFalse( "", g.contains( triple ) );
            }
        }
    
    /**
        Assert that <code>g</code> contains none of the triples in 
        <code>triples</code>.
    */
    public void testOmits( Graph g, List<Triple> triples )
        {
            for ( Triple triple : triples )
            {
                assertFalse( "", g.contains( triple ) );
            }
        }
    
    /**
        Assert that <code>g</code> contains none of the triples in 
        <code>it</code>.
    */
    public void testOmits( Graph g, Iterator<Triple> it )
        { while (it.hasNext()) assertFalse( "", g.contains( it.next() ) ); }
    
    /**
        Assert that <code>g</code> contains none of the triples in 
        <code>other</code>.
    */
    public void testOmits( Graph g, Graph other )
        { testOmits( g, GraphUtil.findAll( other ) ); }

    /**
        Answer an instance of <code>graphClass</code>. If <code>graphClass</code> has
        a constructor that takes a <code>ReificationStyle</code> argument, then that
        constructor is run on <code>style</code> to get the instance. Otherwise, if it has a #
        constructor that takes an argument of <code>wrap</code>'s class before the
        <code>ReificationStyle</code>, that constructor is used; this allows non-static inner
        classes to be used for <code>graphClass</code>, with <code>wrap</code> being
        the outer class instance. If no suitable constructor exists, a JenaException is thrown.
        
        @param wrap the outer class instance if graphClass is an inner class
        @param graphClass a class implementing Graph
        @return an instance of graphClass with the given style
        @throws RuntimeException or JenaException if construction fails
     */
    public static Graph getGraph( Object wrap, Class<? extends Graph> graphClass) 
        {
        try
            {
            Constructor<?> cons = getConstructor( graphClass, new Class[] {} );
            if (cons != null) return (Graph) cons.newInstance( new Object[] { } );
            Constructor<?> cons2 = getConstructor( graphClass, new Class [] {wrap.getClass()} );
            if (cons2 != null) return (Graph) cons2.newInstance( new Object[] { wrap} );
            throw new JenaException( "no suitable graph constructor found for " + graphClass );
            }
        catch (RuntimeException e)
            { throw e; }
        catch (Exception e)
            { throw new JenaException( e ); }
        }
    }
