/*
 * 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.riot.system.streammgr;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.vocabulary.LocationMappingVocab;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Alternative locations for URIs.  Maintains two maps:
 * single item alternatives and alternative prefixes.
 * To suggest an alternative location, first check the single items,
 * then check the prefixes.
 *
 * A LocationMapper can be configured by an RDF file.  The default for this
 * is "etc/location-mapping.n3".
 *
 * There is a default LocationMapper which is used by the global @link{StreamManager}.
 */

public class LocationMapper
{
    private static Logger log = LoggerFactory.getLogger(LocationMapper.class) ;
    private Map<String, String> altLocations = new ConcurrentHashMap<>();
    private Map<String, String> altPrefixes = new ConcurrentHashMap<>();

    /** Create a LocationMapper with no mapping yet */
    public LocationMapper() { }

    /** Deep copy of location and prefix maps */
    @Override
    public LocationMapper clone() {
        return clone(this);
    }

    private static LocationMapper clone(LocationMapper other) {
        LocationMapper mapper = new LocationMapper();
        mapper.altLocations.putAll(other.altLocations);
        mapper.altPrefixes.putAll(other.altPrefixes);
        return mapper;
    }

    public void copyFrom(LocationMapper lmap2) {
        this.altLocations.putAll(lmap2.altLocations);
        this.altPrefixes.putAll(lmap2.altPrefixes);
    }

    public boolean containsMapping(String uri) {
        return altMapping(uri, null) != null;
    }

    public String altMapping(String uri) {
        return altMapping(uri, uri);
    }

    /**
     * Apply mappings: first try for an exact alternative location, then try to
     * remap by prefix, finally, try the special case of filenames in a specific
     * base directory.
     *
     * @param uri
     * @param otherwise
     * @return The alternative location chosen
     */
    public String altMapping(String uri, String otherwise) {
        if ( altLocations.isEmpty() && altPrefixes.isEmpty() )
            return otherwise;
        if ( altLocations.containsKey(uri) )
            return altLocations.get(uri);
        String newStart = null;
        String oldStart = null;
        for ( String prefix : altPrefixes.keySet() )
        {
            if ( uri.startsWith( prefix ) )
            {
                String s = altPrefixes.get( prefix );
                if ( newStart == null || newStart.length() < s.length() )
                {
                    oldStart = prefix;
                    newStart = s;
                }
            }
        }

        if ( newStart != null )
            return newStart + uri.substring(oldStart.length());

        return otherwise;
    }

    public void addAltEntry(String uri, String alt) {
        altLocations.put(uri, alt);
    }

    public void addAltPrefix(String uriPrefix, String altPrefix) {
        altPrefixes.put(uriPrefix, altPrefix);
    }

    /** Iterate over all the entries registered */
    public Iterator<String> listAltEntries() {
        return altLocations.keySet().iterator();
    }

    /** Iterate over all the prefixes registered */
    public Iterator<String> listAltPrefixes() {
        return altPrefixes.keySet().iterator();
    }

    public void removeAltEntry(String uri) {
        altLocations.remove(uri);
    }

    public void removeAltPrefix(String uriPrefix) {
        altPrefixes.remove(uriPrefix);
    }

    public String getAltEntry(String uri) {
        return altLocations.get(uri);
    }

    public String getAltPrefix(String uriPrefix) {
        return altPrefixes.get(uriPrefix);
    }

    /** Iterate over all the entries registered */
    public boolean isEmpty() {
        return altLocations.isEmpty() && altPrefixes.isEmpty();
    }

    @Override
    public int hashCode() {
        int x = 0;
        x = x ^ altLocations.hashCode();
        x = x ^ altPrefixes.hashCode();
        return x;
    }

    @Override
    public boolean equals(Object obj) {
        if ( !(obj instanceof LocationMapper) )
            return false;
        LocationMapper other = (LocationMapper)obj;

        if ( !this.altLocations.equals(other.altLocations) )
            return false;

        if ( !this.altPrefixes.equals(other.altPrefixes) )
            return false;
        return true;
    }

    @Override
    public String toString() {
        String s = "";
        for ( String k : altLocations.keySet() )
        {
            String v = altLocations.get( k );
            s = s + "(Loc:" + k + "=>" + v + ") ";
        }

        for ( String k : altPrefixes.keySet() )
        {
            String v = altPrefixes.get( k );
            s = s + "(Prefix:" + k + "=>" + v + ") ";
        }
        return s;
    }

    public Model toModel() {
        Model m = ModelFactory.createDefaultModel();
        m.setNsPrefix("lmap", "http://jena.hpl.hp.com/2004/08/location-mapping#");
        toModel(m);
        return m;
    }

    public void toModel(Model model) {
        for ( String s1 : altLocations.keySet() )
        {
            Resource r = model.createResource();
            Resource e = model.createResource();
            model.add( r, LocationMappingVocab.mapping, e );

            String k = s1;
            String v = altLocations.get( k );
            model.add( e, LocationMappingVocab.name, k );
            model.add( e, LocationMappingVocab.altName, v );
        }

        for ( String s : altPrefixes.keySet() )
        {
            Resource r = model.createResource();
            Resource e = model.createResource();
            model.add( r, LocationMappingVocab.mapping, e );
            String k = s;
            String v = altPrefixes.get( k );
            model.add( e, LocationMappingVocab.prefix, k );
            model.add( e, LocationMappingVocab.altPrefix, v );
        }
    }
}
