/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

import org.apache.jena.rdf.model.impl.RDFReaderFImpl;
import org.apache.jena.rdf.model.impl.RDFWriterFImpl;
import org.apache.jena.sys.JenaSystem;
import org.apache.jena.util.CollectionFactory;
import org.apache.jena.util.iterator.ExtendedIterator;
import org.apache.jena.util.iterator.WrappedIterator;

import static junit.framework.TestCase.*;

public class JenaTestLib {

    /**
     * assert that the two objects must be unequal according to .equals().
     *
     * @param title a labelling string for the assertion failure text
     * @param x an object to test; the subject of a .equals()
     * @param y the other object; the argument of the .equals()
     */
    public static void assertDiffer(String title, Object x, Object y) {
        if ( x == null ? y == null : x.equals(y) )
            fail((title == null ? "objects should be different, but both were: " : title) + x);
    }

    /**
     * assert that the two objects must be unequal according to .equals().
     *
     * @param x an object to test; the subject of a .equals()
     * @param y the other object; the argument of the .equals()
     */
    public static void assertDiffer(Object x, Object y) {
        assertDiffer(null, x, y);
    }

    /**
     * assert that the object <code>x</code> must be of the class
     * <code>expected</code>.
     */
    public static void assertInstanceOf(Class<? > expected, Object x) {
        if ( x == null )
            fail("expected instance of " + expected + ", but had null");
        if ( !expected.isInstance(x) )
            fail("expected instance of " + expected + ", but had instance of " + x.getClass());
    }

    /**
     * Answer a Set formed from the elements of the List <code>L</code>.
     */
    public static <T> Set<T> listToSet(List<T> L) {
        return CollectionFactory.createHashedSet(L);
    }

    /**
     * Answer a List of the substrings of <code>s</code> that are separated by
     * spaces.
     */
    public static List<String> listOfStrings(String s) {
        List<String> result = new ArrayList<>();
        StringTokenizer st = new StringTokenizer(s);
        while (st.hasMoreTokens())
            result.add(st.nextToken());
        return result;
    }

    /**
     * Answer a Set of the substrings of <code>s</code> that are separated by spaces.
     */
    public static Set<String> setOfStrings(String s) {
        Set<String> result = new HashSet<>();
        StringTokenizer st = new StringTokenizer(s);
        while (st.hasMoreTokens())
            result.add(st.nextToken());
        return result;
    }

    /**
     * Answer a list containing the single object <code>x</code>.
     */
    public static <T> List<T> listOfOne(T x) {
        List<T> result = new ArrayList<>();
        result.add(x);
        return result;
    }

    /**
     * Answer a Set containing the single object <code>x</code>.
     */
    public static <T> Set<T> setOfOne(T x) {
        Set<T> result = new HashSet<>();
        result.add(x);
        return result;
    }

    /**
     * Answer a fresh list which is the concatenation of <code>L</code> then
     * <code>R</code>. Neither <code>L</code> nor <code>R</code> is updated.
     */
    public static <T> List<T> append(List<? extends T> L, List<? extends T> R) {
        List<T> result = new ArrayList<>(L);
        result.addAll(R);
        return result;
    }

    /**
     * Answer an iterator over the space-separated substrings of <code>s</code>.
     */
    public static ExtendedIterator<String> iteratorOfStrings(String s) {
        return WrappedIterator.create(listOfStrings(s).iterator());
    }

    /**
     * Do nothing; a way of notating that a test has succeeded, useful in the body of
     * a catch-block to silence excessively [un]helpful disgnostics.
     */
    public static void pass() {}

    /**
     * Answer the constructor of the class <code>c</code> which takes arguments of
     * the type(s) in <code>args</code>, or <code>null</code> if there isn't one.
     */
    public static Constructor<? > getConstructor(Class<? > c, Class<? >[] args) {
        try {
            return c.getConstructor(args);
        } catch (NoSuchMethodException e) {
            return null;
        }
    }

    /**
     * Answer true iff the method <code>m</code> is a public method which fits the
     * pattern of being a test method, ie, test*() returning void.
     */
    public static boolean isPublicTestMethod(Method m) {
        return Modifier.isPublic(m.getModifiers()) && isTestMethod(m);
    }

    /**
     * Answer true iff the method <code>m</code> has a name starting "test", takes no
     * arguments, and returns void; must catch junit tests, in other words.
     */
    public static boolean isTestMethod(Method m) {
        return m.getName().startsWith("test") && m.getParameterTypes().length == 0 && m.getReturnType().equals(Void.TYPE);
    }

    /**
     * Answer true iff <code>subClass</code> is the same class as
     * <code>superClass</code>, if its superclass <i>is</i> <code>superClass</code>,
     * or if one of its interfaces hasAsInterface that class.
     */
    public static boolean hasAsParent(Class<? > subClass, Class<? > superClass) {
        if ( subClass == superClass || subClass.getSuperclass() == superClass )
            return true;
        Class<? >[] is = subClass.getInterfaces();
        for ( Class<? > i1 : is ) {
            if ( hasAsParent(i1, superClass) ) {
                return true;
            }
        }
        return false;
    }

    /**
     * Fail unless <code>subClass</code> has <code>superClass</code> as a parent,
     * either a superclass or an implemented (directly or not) interface.
     */
    public static void assertHasParent(Class<? > subClass, Class<? > superClass) {
        if ( hasAsParent(subClass, superClass) == false )
            fail("" + subClass + " should have " + superClass + " as a parent");
    }

    private static volatile boolean initialized = false;
    public static void setup() {
        if ( initialized )
            return;
        synchronized(JenaTestLib.class) {
            if ( initialized )
                return;
            initialized = true;
            JenaSystem.init();
            // Include parsers and writers needed for the tests.
            // These are not up-to-date but enough to work with the test suite.
            RDFReaderFImpl.alternative(new X_RDFReaderF());
            RDFWriterFImpl.alternative(new X_RDFWriterF());
        }
    }
}
