/*
 * 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.reasoner.rulesys.impl;

import java.util.*;

import org.apache.jena.graph.Node ;
import org.apache.jena.graph.NodeFactory ;
import org.apache.jena.reasoner.InfGraph ;
import org.apache.jena.util.OneToManyMap ;

/**
 * In some rules we need to be able to create temporary property values 
 * which are inferred from ontology constraints but not present in the ground
 * data. This structure is used to manage a pool of such temporary nodes.
 * It is only needed in situations where the data can not be added directly
 * to a deductions graph due to the risk of concurrent access.
 */

// Implementation note: We need to map from a pair of values (instance and prop).
// The current implementation in terms on NodePair will turn over storage during
// lookup. Could replace this with a cascaded hash table.

public class TempNodeCache {

    /** Map from instance+property to value */
    protected OneToManyMap<NodePair, Node> ipMap = new OneToManyMap<>();
    
    /** Map from temp to RDF class, if any */
    protected Map<Node, Node> classMap = new HashMap<>();
    
    /**
     * Constructor.
     * @param infgraph Parent inference graph, used to be needed for synchronization, don't think
     * we need it any more 
     */
    public TempNodeCache(InfGraph infgraph) {
    }
    
    /**
     * Retrieve or create a bNode representing an inferred property value.
     * @param instance the base instance node to which the property applies
     * @param prop the property node whose value is being inferred
     * @param pclass the (optional, can be null) class for the inferred value.
     * @return the bNode representing the property value 
     */
    public synchronized Node getTemp(Node instance, Node prop, Node pclass) {
        NodePair ip = new NodePair(instance, prop);
        Node result = null;
        for (Iterator<Node> i = ipMap.getAll(ip); i.hasNext(); ) {
            Node t = i.next();
            if (pclass != null) {
                Object tClass = classMap.get(t);
                if (tClass != null && tClass.equals(pclass)) {
                    result = t;
                    break;
                }
            } else {
                result = t;
                break;
            }
        }
        if (result == null) {
            // No value yet, so create one
            result = NodeFactory.createBlankNode();
            ipMap.put(ip, result);
            if (pclass != null) {
                classMap.put(result, pclass);
            }
        }
        return result;
    }
    
    /**
     * Inner class used to hold and hash a node pair.
     */
    public static class NodePair {
        /** first node in the pair */
        protected Node first;
        
        /** second node in the pair */
        protected Node second;
        
        /** Constructor */
        public NodePair(Node first, Node second) {
            this.first = first;
            this.second = second;
        }
        
        /**
         * Return the first node in the pair.
         */
        public Node getFirst() {
            return first;
        }
        
        /**
         * Return the second node in the pair.
         */
        public Node getSecond() {
            return second;
        }
        
        /**
         * Equality of each component.
         */
        @Override
        public boolean equals(Object o) {
            return o instanceof NodePair &&
                        first.equals(((NodePair)o).first) && 
                        second.equals(((NodePair)o).second); 
        }
        /**
         * Simple combined hashcode.
         */
        @Override
        public int hashCode() {
            return first.hashCode() ^ (second.hashCode() << 1);
        }

    }
    
}
