/*
 * 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 java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.apache.jena.graph.Node ;
import org.apache.jena.graph.test.GraphTestBase ;
import org.apache.jena.graph.test.NodeCreateUtils ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.util.CollectionFactory ;
import org.junit.Assert;

/**
 * provides useful functionality for testing models, eg building small models
 * from strings, testing equality, etc.
 */

public class ModelTestBase extends GraphTestBase
{
	protected static Model aModel = ModelTestBase.extendedModel();

	protected static final Model empty = ModelFactory.createDefaultModel();

	/**
	 * Fail if the two models are not isomorphic. See
	 * assertIsoModels(String,Model,Model).
	 */
	public static void assertIsoModels( final Model wanted, final Model got )
	{
		ModelTestBase.assertIsoModels("models must be isomorphic", wanted, got);
	}

	/**
	 * test that two models are isomorphic and fail if they are not.
	 * 
	 * @param title
	 *            a String appearing at the beginning of the failure message
	 * @param wanted
	 *            the model value that is expected
	 * @param got
	 *            the model value to check
	 * @exception junit.framework.AssertionFailedError if the models are not isomorphic
	 */
	public static void assertIsoModels( final String title, final Model wanted,
			final Model got )
	{
		if (wanted.isIsomorphicWith(got) == false)
		{
			final Map<Node, Object> map = CollectionFactory.createHashedMap();
			Assert.fail(title + ": expected "
					+ GraphTestBase.nice(wanted.getGraph(), map)
					+ "\n but had " + GraphTestBase.nice(got.getGraph(), map));
		}
	}

	/**
	 * Answer a default model; it exists merely to abbreviate the rather long
	 * explicit
	 * invocation.
	 * 
	 * @return a new default [aka memory-based] model
	 */
	public static Model createMemModel()
	{
		return ModelFactory.createDefaultModel();
	}

	/**
	 * make a model, give it Extended prefixes
	 */
	public static Model createModel()
	{
		final Model result = ModelFactory.createDefaultModel();
		result.setNsPrefixes(PrefixMapping.Extended);
		return result;
	}

	protected static Model extendedModel()
	{
		final Model result = ModelFactory.createDefaultModel();
		result.setNsPrefixes(PrefixMapping.Extended);
		return result;
	}

	public static Literal literal( final Model m, final String s )
	{
		return ModelTestBase.rdfNode(m, s).as(Literal.class);
	}

	/**
	 * add to a model all the statements expressed by a string.
	 * 
	 * @param m
	 *            the model to be updated
	 * @param facts
	 *            a sequence of semicolon-separated "S P O" facts
	 * @return the updated model
	 */
	public static Model modelAdd( final Model m, final String facts )
	{
		final StringTokenizer semis = new StringTokenizer(facts, ";");
		while (semis.hasMoreTokens())
		{
			m.add(ModelTestBase.statement(m, semis.nextToken()));
		}
		return m;
	}

	/**
	 * makes a model initialised with statements parsed from a string.
	 * 
	 * @param facts
	 *            a string in semicolon-separated "S P O" format
	 * @return a model containing those facts
	 */
	public static Model modelWithStatements( final String facts )
	{
		return ModelTestBase.modelAdd(ModelTestBase.createModel(), facts);
	}

	protected static String nice( final RDFNode n )
	{
		return GraphTestBase.nice(n.asNode());
	}

	public static Property property( final Model m, final String s )
	{
		return ModelTestBase.rdfNode(m, s).as(Property.class);
	}

	public static Property property( final String s )
	{
		return ModelTestBase.property(ModelTestBase.aModel, s);
	}

	public static RDFNode rdfNode( final Model m, final String s )
	{
		return m.asRDFNode(NodeCreateUtils.create(m, s));
	}

	public static <T extends RDFNode> T rdfNode( final Model m, final String s,
			final Class<T> c )
	{
		return ModelTestBase.rdfNode(m, s).as(c);
	}

	protected static Resource resource()
	{
		return ResourceFactory.createResource();
	}

	public static Resource resource( final Model m, final String s )
	{
		return (Resource) ModelTestBase.rdfNode(m, s);
	}

	public static Resource resource( final String s )
	{
		return ModelTestBase.resource(ModelTestBase.aModel, s);
	}

	/**
	 * Create an array of Resources from a whitespace-separated string
	 * 
	 * @param m
	 *            a model to serve as a resource factory
	 * @param items
	 *            a whitespace-separated sequence to feed to resource
	 * @return a RDFNode[] of the parsed resources
	 */
	public static Resource[] resources( final Model m, final String items )
	{
		final ArrayList<Resource> rl = new ArrayList<>();
		final StringTokenizer st = new StringTokenizer(items);
		while (st.hasMoreTokens())
		{
			rl.add(ModelTestBase.resource(m, st.nextToken()));
		}
		return rl.toArray(new Resource[rl.size()]);
	}

	/**
	 * Answer the set of resources given by the space-separated
	 * <code>items</code> string. Each resource specification is interpreted
	 * as per <code>resource</code>.
	 */
	public static Set<Resource> resourceSet( final String items )
	{
		final Set<Resource> result = new HashSet<>();
		final StringTokenizer st = new StringTokenizer(items);
		while (st.hasMoreTokens())
		{
			result.add(ModelTestBase.resource(st.nextToken()));
		}
		return result;
	}

	/**
	 * create a Statement in a given Model with (S, P, O) extracted by parsing a
	 * string.
	 * 
	 * @param m
	 *            the model the statement is attached to
	 * @param fact
	 *            "S P O" string.
	 * @return m.createStatement(S, P, O)
	 */
	public static Statement statement( final Model m, final String fact )
	{
		final StringTokenizer st = new StringTokenizer(fact);
		final Resource sub = ModelTestBase.resource(m, st.nextToken());
		final Property pred = ModelTestBase.property(m, st.nextToken());
		final RDFNode obj = ModelTestBase.rdfNode(m, st.nextToken());
		return m.createStatement(sub, pred, obj);
	}

	public static Statement statement( final String fact )
	{
		return ModelTestBase.statement(ModelTestBase.aModel, fact);
	}

	/**
	 * Create an array of Statements parsed from a semi-separated string.
	 * 
	 * @param m
	 *            a model to serve as a statement factory
	 * @param facts
	 *            a sequence of semicolon-separated "S P O" facts
	 * @return a Statement[] of the (S P O) statements from the string
	 */
	public static Statement[] statements( final Model m, final String facts )
	{
		final ArrayList<Statement> sl = new ArrayList<>();
		final StringTokenizer st = new StringTokenizer(facts, ";");
		while (st.hasMoreTokens())
		{
			sl.add(ModelTestBase.statement(m, st.nextToken()));
		}
		return sl.toArray(new Statement[sl.size()]);
	}

	public ModelTestBase( final String name )
	{
		super(name);
	}

}
