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

import static org.apache.jena.util.iterator.WrappedIterator.create;

import java.util.*;
import java.util.function.Predicate;

import org.apache.jena.graph.* ;
import org.apache.jena.graph.Triple.* ;
import org.apache.jena.util.iterator.* ;

/**
	NodeToTriplesMap: a map from nodes to sets of triples.
	Subclasses must override at least one of useXXXInFilter methods.
*/
public class NodeToTriplesMap extends NodeToTriplesMapBase 
    {    
    public NodeToTriplesMap( Field indexField, Field f2, Field f3 )
        { super( indexField, f2, f3 ); }

    @Override public boolean add( Triple t ) 
        {
        Object o = getIndexField( t );
        
        // Feb 2016 : no measurable difference.
        //OpenSetBunch s = (OpenSetBunch) bunchMap.getOrSet(o, (k)->createSetBunch()) ;
        
        OpenSetBunch s = (OpenSetBunch) bunchMap.get( o );
        if (s == null) bunchMap.put( o, s = createSetBunch() );
        if (s.baseSet().add( t )) { size += 1; return true; } else return false; 
        }

    private static class OpenSetBunch extends SetBunch
        {
        private static final TripleBunch empty = new ArrayBunch();
        
        public OpenSetBunch()
            { super( empty ); }
        
        public Set<Triple> baseSet()
            { return elements; }
        }
    
    private OpenSetBunch createSetBunch()
        { return new OpenSetBunch(); }
    
    @Override public boolean remove( Triple t )
        { 
        Object o = getIndexField( t );
        OpenSetBunch s = (OpenSetBunch) bunchMap.get( o );
        if (s == null)
            return false;
        else
            {
            Set<Triple> base = s.baseSet();
            boolean result = base.remove( t );
            if (result) size -= 1;
            if (base.isEmpty()) bunchMap.remove( o );
            return result;
        	} 
        }
    
    @Override public ExtendedIterator<Triple> iterator( Object o, HashCommon.NotifyEmpty container )
        {
        TripleBunch b = bunchMap.get( o );
        return b == null ? NullIterator.<Triple>instance() : b.iterator();
        }
    
    @Override public boolean contains( Triple t )
        { 
        TripleBunch s = bunchMap.get( getIndexField( t ) );
        return s == null ? false : s.contains( t );
        }

    protected static boolean equalsObjectOK( Triple t )
        { 
        Node o = t.getObject();
        return o.isLiteral() ? o.getLiteralDatatype() == null : true;
        }

    @Override
    public boolean containsBySameValueAs( Triple t )
        { return equalsObjectOK( t ) ? contains( t ) : slowContains( t ); }
    
    protected boolean slowContains( Triple t )
        { 
        TripleBunch s = bunchMap.get( getIndexField( t ) );
        if (s == null)
            return false;
        else
            {
            Iterator<Triple> it = s.iterator();
            while (it.hasNext()) if (t.matches( it.next() )) return true;
            return false;
            }
        }
    
	public ExtendedIterator<Triple> iterateAll(Triple pattern) {
		Predicate<Triple> filter = indexField.filterOn(pattern)
				.and(f2.filterOn(pattern)).and(f3.filterOn(pattern));
		return create(iterateAll()).filterKeep(filter);  
	}

    @Override public ExtendedIterator<Triple> iterator( Node index, Node n2, Node n3 )
        {
        TripleBunch s = bunchMap.get( index.getIndexingValue() );
        if (s == null) return NullIterator.<Triple>instance();
        final Predicate<Triple> filter = f2.filterOn( n2 ).and( f3.filterOn( n3 ) );
        return create(s.iterator()).filterKeep(filter);    
        }

    /**
        Answer an iterator over all the triples that are indexed by the item <code>y</code>.
        Note that <code>y</code> need not be a Node (because of indexing values).
    */
    @Override public Iterator<Triple> iteratorForIndexed( Object y )
        { return get( y ).iterator();  }
    
    /** 
        @see org.apache.jena.mem.Temp#get(java.lang.Object)
    */
    private TripleBunch get( Object y )
        { return bunchMap.get( y ); }
    }
