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

import static org.junit.jupiter.api.Assertions.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;

import org.apache.jena.test.JenaTestLib;

public abstract class BaseTestPrefixMapping {

    /**
     * Subclasses implement to return a new, empty prefixMapping of their preferred
     * kind.
     */
    abstract protected PrefixMapping getMapping();

    static final String crispURI = "http://crisp.nosuch.net/";
    static final String ropeURI = "scheme:rope/string#";
    static final String butterURI = "ftp://ftp.nowhere.at.all/cream#";

    @Test
    public void testEmptyPrefix() {
        addGetTest("", crispURI);
    }

    @Test
    public void testStrPrefix1() {
        addGetTest("abc", "http://example/");
    }

    @Test
    public void testStrPrefix2() {
        // U+1F607 - smiling face with halo
        String prefix = new String(Character.toChars(0x1F607));
        addGetTest(prefix, "http://example/");
    }

    private void addGetTest(String prefix, String uri) {
        PrefixMapping pmap = getMapping();
        pmap.setNsPrefix(prefix, uri);
        assertEquals(uri, pmap.getNsPrefixURI(prefix));
    }

    static final String[] badNames = {"<hello>", "foo:bar", "with a space", "-argument"};

    @Test
    public void testCheckNames() {
        PrefixMapping ns = getMapping();
        for ( String bad : badNames ) {
            try {
                ns.setNsPrefix(bad, crispURI);
                fail("'" + bad + "' is an illegal prefix and should be trapped");
            } catch (PrefixMapping.IllegalPrefixException e) {
                JenaTestLib.pass();
            }
        }
    }

    @Test
    public void testNullURITrapped() {
        try {
            getMapping().setNsPrefix("xy", null);
            fail("should trap null URI in setNsPrefix");
        } catch (NullPointerException e) {
            JenaTestLib.pass();
        }
    }

    @Test
    public void testPrefixMappingMapping() {
        String toast = "ftp://ftp.nowhere.not/";
        JenaTestLib.assertDiffer("crisp and toast must differ", crispURI, toast);
        PrefixMapping ns = getMapping();
        assertEquals(null, ns.getNsPrefixURI("crisp"));
        assertEquals(null, ns.getNsPrefixURI("toast"));
        assertEquals(null, ns.getNsPrefixURI("butter"));
        ns.setNsPrefix("crisp", crispURI);
        assertEquals(crispURI, ns.getNsPrefixURI("crisp"));
        assertEquals(null, ns.getNsPrefixURI("toast"));
        assertEquals(null, ns.getNsPrefixURI("butter"));
        ns.setNsPrefix("toast", toast);
        assertEquals(crispURI, ns.getNsPrefixURI("crisp"));
        assertEquals(toast, ns.getNsPrefixURI("toast"));
        assertEquals(null, ns.getNsPrefixURI("butter"));
    }

    @Test
    public void testReversePrefixMapping() {
        PrefixMapping ns = getMapping();
        String uriA = "http://jena.hpl.hp.com/A#", uriB = "http://jena.hpl.hp.com/";
        String uriC = "http://jena.hpl.hp.com/Csharp/";
        String prefixA = "aa", prefixB = "bb";
        ns.setNsPrefix(prefixA, uriA).setNsPrefix(prefixB, uriB);
        assertEquals(null, ns.getNsURIPrefix(uriC));
        assertEquals(prefixA, ns.getNsURIPrefix(uriA));
        assertEquals(prefixB, ns.getNsURIPrefix(uriB));
    }

    @Test
    public void testPrefixMappingMap() {
        PrefixMapping ns = getCrispyRope();
        Map<String, String> map = ns.getNsPrefixMap();
        assertEquals(2, map.size(), "map should have two elements");
        assertEquals(crispURI, map.get("crisp"));
        assertEquals("scheme:rope/string#", map.get("rope"));
    }

    @Test
    public void testPrefixMappingSecret() {
        PrefixMapping ns = getCrispyRope();
        Map<String, String> map = ns.getNsPrefixMap();
        try {
            map.put("crisp", "with/onions");
            map.put("sandwich", "with/cheese");
        } catch (UnsupportedOperationException ex) {}
        assertEquals(crispURI, ns.getNsPrefixURI("crisp"));
        assertEquals(ropeURI, ns.getNsPrefixURI("rope"));
        assertEquals(null, ns.getNsPrefixURI("sandwich"));
    }

    private PrefixMapping getCrispyRope() {
        PrefixMapping ns = getMapping();
        ns.setNsPrefix("crisp", crispURI);
        ns.setNsPrefix("rope", ropeURI);
        return ns;
    }

    static final String[] dontChange = {"", "http://www.somedomain.something/whatever#", "crispy:cabbage", "cris:isOnInfiniteEarths",
        "rop:tangled/web", "roped:abseiling"};

    static final String[][] expansions = {{"crisp:pathPart", crispURI + "pathPart"}, {"rope:partPath", ropeURI + "partPath"},
        {"crisp:path:part", crispURI + "path:part"},};

    @Test
    public void testExpandPrefix() {
        PrefixMapping ns = getMapping();
        ns.setNsPrefix("crisp", crispURI);
        ns.setNsPrefix("rope", ropeURI);
        for ( String aDontChange : dontChange ) {
            assertEquals(aDontChange, ns.expandPrefix(aDontChange), "should be unchanged");
        }
        for ( String[] expansion : expansions ) {
            assertEquals(expansion[1], ns.expandPrefix(expansion[0]), "should expand correctly");
        }
    }

    @Test
    public void testUseEasyPrefix() {
        testUseEasyPrefix("prefix mapping impl", getMapping());
        testShortForm("prefix mapping impl", getMapping());
    }

    public static void testUseEasyPrefix(String title, PrefixMapping ns) {
        testShortForm(title, ns);
    }

    public static void testShortForm(String title, PrefixMapping ns) {
        ns.setNsPrefix("crisp", crispURI);
        ns.setNsPrefix("butter", butterURI);
        assertEquals("", ns.shortForm(""), title);
        assertEquals(ropeURI, ns.shortForm(ropeURI), title);
        assertEquals("crisp:tail", ns.shortForm(crispURI + "tail"), title);
        assertEquals("butter:here:we:are", ns.shortForm(butterURI + "here:we:are"), title);
    }

    @Test
    public void testEasyQName() {
        PrefixMapping ns = getMapping();
        String alphaURI = "http://seasonal.song/preamble/";
        ns.setNsPrefix("alpha", alphaURI);
        assertEquals("alpha:rowboat", ns.qnameFor(alphaURI + "rowboat"));
    }

    @Test
    public void testNoQNameNoPrefix() {
        PrefixMapping ns = getMapping();
        String alphaURI = "http://seasonal.song/preamble/";
        ns.setNsPrefix("alpha", alphaURI);
        assertEquals(null, ns.qnameFor("eg:rowboat"));
    }

    @Test
    public void testNoQNameBadLocal() {
        PrefixMapping ns = getMapping();
        String alphaURI = "http://seasonal.song/preamble/";
        ns.setNsPrefix("alpha", alphaURI);
        assertEquals(null, ns.qnameFor(alphaURI + "12345"));
    }

    @Test
    public void testQnameFromEmail() {
        String uri = "http://some.long.uri/for/a/namespace#";
        PrefixMapping ns = getMapping();
        ns.setNsPrefix("x", uri);
        assertEquals(null, ns.qnameFor(uri));
        assertEquals(null, ns.qnameFor(uri + "non/fiction"));
    }

    @Test
    public void testAddOtherPrefixMapping() {
        PrefixMapping a = getMapping();
        PrefixMapping b = getMapping();
        assertFalse(a == b, "must have two diffferent maps");
        a.setNsPrefix("crisp", crispURI);
        a.setNsPrefix("rope", ropeURI);
        b.setNsPrefix("butter", butterURI);
        assertEquals(null, b.getNsPrefixURI("crisp"));
        assertEquals(null, b.getNsPrefixURI("rope"));
        b.setNsPrefixes(a);
        checkContainsMapping(b);
    }

    private void checkContainsMapping(PrefixMapping b) {
        assertEquals(crispURI, b.getNsPrefixURI("crisp"));
        assertEquals(ropeURI, b.getNsPrefixURI("rope"));
        assertEquals(butterURI, b.getNsPrefixURI("butter"));
    }

    @Test
    public void testAddMap() {
        PrefixMapping b = getMapping();
        Map<String, String> map = new HashMap<>();
        map.put("crisp", crispURI);
        map.put("rope", ropeURI);
        b.setNsPrefix("butter", butterURI);
        b.setNsPrefixes(map);
        checkContainsMapping(b);
    }

    @Test
    public void testAddDefaultMap() {
        PrefixMapping pm = getMapping();
        PrefixMapping root = PrefixMapping.Factory.create();
        pm.setNsPrefix("a", "aPrefix:");
        pm.setNsPrefix("b", "bPrefix:");
        root.setNsPrefix("a", "pootle:");
        root.setNsPrefix("z", "bPrefix:");
        root.setNsPrefix("c", "cootle:");
        assertSame(pm, pm.withDefaultMappings(root));
        assertEquals("aPrefix:", pm.getNsPrefixURI("a"));
        assertEquals(null, pm.getNsPrefixURI("z"));
        assertEquals("bPrefix:", pm.getNsPrefixURI("b"));
        assertEquals("cootle:", pm.getNsPrefixURI("c"));
    }

    @Test
    public void testSecondPrefixRetainsExistingMap() {
        PrefixMapping A = getMapping();
        A.setNsPrefix("a", crispURI);
        A.setNsPrefix("b", crispURI);
        assertEquals(crispURI, A.getNsPrefixURI("a"));
        assertEquals(crispURI, A.getNsPrefixURI("b"));
    }

    @Test
    public void testSecondPrefixReplacesReverseMap() {
        PrefixMapping A = getMapping();
        A.setNsPrefix("a", crispURI);
        A.setNsPrefix("b", crispURI);
        assertEquals("b", A.getNsURIPrefix(crispURI));
    }

    @Test
    public void testSecondPrefixDeletedUncoversPreviousMap() {
        PrefixMapping A = getMapping();
        A.setNsPrefix("x", crispURI);
        A.setNsPrefix("y", crispURI);
        A.removeNsPrefix("y");
        assertEquals("x", A.getNsURIPrefix(crispURI));
    }

    @Test
    public void testEmptyDoesNotWipeURI() {
        PrefixMapping pm = getMapping();
        pm.setNsPrefix("frodo", ropeURI);
        pm.setNsPrefix("", ropeURI);
        assertEquals(ropeURI, pm.getNsPrefixURI("frodo"));
    }

    @Test
    public void testSameURIKeepsDefault() {
        PrefixMapping A = getMapping();
        A.setNsPrefix("", crispURI);
        A.setNsPrefix("crisp", crispURI);
        assertEquals(crispURI, A.getNsPrefixURI(""));
    }

    @Test
    public void testReturnsSelf() {
        PrefixMapping A = getMapping();
        assertSame(A, A.setNsPrefix("crisp", crispURI));
        assertSame(A, A.setNsPrefixes(A));
        assertSame(A, A.setNsPrefixes(new HashMap<String, String>()));
        assertSame(A, A.removeNsPrefix("rhubarb"));
    }

    @Test
    public void testRemovePrefix() {
        String hURI = "http://test.remove.prefixes/prefix#";
        String bURI = "http://other.test.remove.prefixes/prefix#";
        PrefixMapping A = getMapping();
        A.setNsPrefix("hr", hURI);
        A.setNsPrefix("br", bURI);
        A.removeNsPrefix("hr");
        assertEquals(null, A.getNsPrefixURI("hr"));
        assertEquals(bURI, A.getNsPrefixURI("br"));
    }

    @Test
    public void testClear() {
        String hURI = "http://test.remove.prefixes/prefix#";
        String bURI = "http://other.test.remove.prefixes/prefix#";
        PrefixMapping A = getMapping();
        A.setNsPrefix("hr", hURI);
        A.setNsPrefix("br", bURI);
        A.clearNsPrefixMap();
        assertEquals(null, A.getNsPrefixURI("hr"));
        assertEquals(null, A.getNsPrefixURI("br"));
        assertEquals(null, A.getNsURIPrefix(hURI));
        assertEquals(null, A.getNsURIPrefix(bURI));
    }

    @Test
    public void testNoMapping() {
        String hURI = "http://test.prefixes/prefix#";
        PrefixMapping A = getMapping();
        assertTrue(A.hasNoMappings());
        A.setNsPrefix("hr", hURI);
        assertFalse(A.hasNoMappings());
    }

    @Test
    public void testNumPrefixes() {
        String hURI = "http://test.prefixes/prefix#";
        PrefixMapping A = getMapping();
        assertEquals(0, A.numPrefixes());
        A.setNsPrefix("hr", hURI);
        assertEquals(1, A.numPrefixes());
    }

    @Test
    public void testEquality() {
        testEquals("");
        testEquals("", "x=a", false);
        testEquals("x=a", "", false);
        testEquals("x=a");
        testEquals("x=a y=b", "y=b x=a", true);
        testEquals("x=a x=b", "x=b x=a", false);
    }

    protected void testEquals(String S) {
        testEquals(S, S, true);
    }

    protected void testEquals(String S, String T, boolean expected) {
        testEqualsBase(S, T, expected);
        testEqualsBase(T, S, expected);
    }

    public void testEqualsBase(String S, String T, boolean expected) {
        testEquals(S, T, expected, getMapping(), getMapping());
        testEquals(S, T, expected, PrefixMapping.Factory.create(), getMapping());
    }

    protected void testEquals(String S, String T, boolean expected, PrefixMapping A, PrefixMapping B) {
        fill(A, S);
        fill(B, T);
        String title = "usual: '" + S + "', testing: '" + T + "', should be " + (expected ? "equal" : "different");
        assertEquals(expected, A.samePrefixMappingAs(B), title);
        assertEquals(expected, B.samePrefixMappingAs(A), title);
    }

    protected void fill(PrefixMapping pm, String settings) {
        List<String> L = JenaTestLib.listOfStrings(settings);
        for ( String setting : L ) {
            int eq = setting.indexOf('=');
            pm.setNsPrefix(setting.substring(0, eq), setting.substring(eq + 1));
        }
    }

    @Test
    public void testAllowNastyNamespace() {
        getMapping().setNsPrefix("abc", "def");
    }

    @Test
    public void testLock() {
        PrefixMapping A = getMapping();
        assertSame(A, A.lock());
        try {
            A.setNsPrefix("crisp", crispURI);
            fail("mapping should be frozen");
        } catch (PrefixMapping.JenaLockedException e) {
            JenaTestLib.pass();
        }
        try {
            A.setNsPrefixes(A);
            fail("mapping should be frozen");
        } catch (PrefixMapping.JenaLockedException e) {
            JenaTestLib.pass();
        }
        try {
            A.setNsPrefixes(new HashMap<String, String>());
            fail("mapping should be frozen");
        } catch (PrefixMapping.JenaLockedException e) {
            JenaTestLib.pass();
        }
        try {
            A.removeNsPrefix("toast");
            fail("mapping should be frozen");
        } catch (PrefixMapping.JenaLockedException e) {
            JenaTestLib.pass();
        }
    }
}
