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

import java.util.HashMap ;
import java.util.Iterator ;
import java.util.Map ;

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
{
    static Logger log = LoggerFactory.getLogger(LocationMapper.class)  ;
    Map<String, String> altLocations = new HashMap<>() ;
    Map<String, String> altPrefixes = new HashMap<>() ;
    
    /** 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) ;
    }

    @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 );
        }
    }
}
