/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed 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.
 */
/*
 * $Id: KeyIndex.java,v 1.13 2004/02/16 22:54:59 minchau Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.dom;

import java.util.StringTokenizer;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.DOMEnhancedForDTM;
import com.sun.org.apache.xalan.internal.xsltc.runtime.Hashtable;
import com.sun.org.apache.xalan.internal.xsltc.util.IntegerArray;
import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.dtm.ref.DTMAxisIteratorBase;

/**
 * @author Morten Jorgensen
 * @author Santiago Pericas-Geertsen
 */
public class KeyIndex extends DTMAxisIteratorBase {

    /**
     * A mapping between values and nodesets.
     */
    private Hashtable _index = new Hashtable();

    /**
     * The node set associated to the current value passed
     * to lookupKey();
     */
    private IntegerArray _nodes = null;

    /**
     * The XSLTC DOM object if this KeyIndex is being used to implement the
     * id() function.
     */
    private DOM        _dom;
    
    private DOMEnhancedForDTM    _enhancedDOM;

    /**
     * Store position after call to setMark()
     */
    private int _markedPosition = 0;

    public KeyIndex(int dummy) {
    }

    public void setRestartable(boolean flag) {
    }

    /**
     * Adds a node to the node list for a given value. Nodes will
     * always be added in document order.
     */
    public void add(Object value, int node) {
	IntegerArray nodes;
	if ((nodes = (IntegerArray) _index.get(value)) == null) {
	    _index.put(value, nodes = new IntegerArray());
	}
	nodes.add(node);
    }

    /**
     * Merge the current value's nodeset set by lookupKey() with _nodes.
     */
    public void merge(KeyIndex other) {
	if (other == null) return;

	if (other._nodes != null) {
	    if (_nodes == null) {
		_nodes = other._nodes;
	    }
	    else {
		_nodes.merge(other._nodes);
	    }
	}
    }

    /**
     * This method must be called by the code generated by the id() function
     * prior to returning the node iterator. The lookup code for key() and
     * id() differ in the way the lookup value can be whitespace separated
     * list of tokens for the id() function, but a single string for the
     * key() function.
     */
    public void lookupId(Object value) {
	// Clear _nodes array
	_nodes = null;

	final StringTokenizer values = new StringTokenizer((String) value);
	while (values.hasMoreElements()) {
            final String token = (String) values.nextElement();
	    IntegerArray nodes = (IntegerArray) _index.get(token);

            if (nodes == null && _enhancedDOM != null
                && _enhancedDOM.hasDOMSource()) {
                nodes = getDOMNodeById(token);
            }

	    if (nodes == null) continue;

	    if (_nodes == null) {
		_nodes = nodes;
	    }
	    else {
		_nodes.merge(nodes);
	    }
	}
    }

    /**
     * Return an IntegerArray for the DOM Node which has the given id.
     * 
     * @param id The id
     * @return A IntegerArray representing the Node whose id is the given value.
     */
    public IntegerArray getDOMNodeById(String id) {
        IntegerArray nodes = null;
        if (_enhancedDOM != null) {
            int ident = _enhancedDOM.getElementById(id);
            if (ident != DTM.NULL) {
	        nodes = new IntegerArray();
	    	_index.put(id, nodes);
		nodes.add(ident);
            }
        }
        return nodes; 	
    }
    
    /**
     * This method must be called by the code generated by the key() function
     * prior to returning the node iterator.
     */
    public void lookupKey(Object value) {
	_nodes = (IntegerArray) _index.get(value);
	_position = 0;
    }

    /** 
     * Callers should not call next() after it returns END.
     */
    public int next() {
	if (_nodes == null) return DTMAxisIterator.END;

	return (_position < _nodes.cardinality()) ? 
	    _dom.getNodeHandle(_nodes.at(_position++)) : DTMAxisIterator.END;
    }

    public int containsID(int node, Object value) { 
	final String string = (String)value;
	if (string.indexOf(' ') > -1) {
	    final StringTokenizer values = new StringTokenizer(string);

	    while (values.hasMoreElements()) {
                final String token = (String) values.nextElement();
		IntegerArray nodes = (IntegerArray) _index.get(token);

		if (nodes == null && _enhancedDOM != null
                    && _enhancedDOM.hasDOMSource()) {
		    nodes = getDOMNodeById(token);	
		}
		if (nodes != null && nodes.indexOf(node) >= 0) {
		    return 1;
		}
	    }
	    return 0;
	}
	else {
	    IntegerArray nodes = (IntegerArray) _index.get(value);
            if (nodes == null && _enhancedDOM != null && _enhancedDOM.hasDOMSource()) {
                nodes = getDOMNodeById(string);
            }
	    return (nodes != null && nodes.indexOf(node) >= 0) ? 1 : 0;
	}
    }

    public int containsKey(int node, Object value) { 
	final IntegerArray nodes = (IntegerArray) _index.get(value);
	return (nodes != null && nodes.indexOf(node) >= 0) ? 1 : 0;
    }

    /**
     * Resets the iterator to the last start node.
     */
    public DTMAxisIterator reset() {
	_position = 0;
	return this;
    }

    /**
     * Returns the number of elements in this iterator.
     */
    public int getLast() {
	return (_nodes == null) ? 0 : _nodes.cardinality();
    }

    /**
     * Returns the position of the current node in the set.
     */
    public int getPosition() {
	return _position;
    }

    /**
     * Remembers the current node for the next call to gotoMark().
     */
    public void setMark() {
	_markedPosition = _position;
    }

    /**
     * Restores the current node remembered by setMark().
     */
    public void gotoMark() {
	_position = _markedPosition;
    }

    /** 
     * Set start to END should 'close' the iterator, 
     * i.e. subsequent call to next() should return END.
     */
    public DTMAxisIterator setStartNode(int start) {
	if (start == DTMAxisIterator.END) {
	    _nodes = null;
	}
	else if (_nodes != null) {
	    _position = 0;
	}
	return (DTMAxisIterator) this;
    }
    
    /** 
     * Get start to END should 'close' the iterator, 
     * i.e. subsequent call to next() should return END.
     */
    public int getStartNode() {      
        return 0;
    }

    /**
     * True if this iterator has a reversed axis.
     */
    public boolean isReverse() {
	return(false);
    }

    /**
     * Returns a deep copy of this iterator.
     */
    public DTMAxisIterator cloneIterator() {
	KeyIndex other = new KeyIndex(0);
	other._index = _index;
	other._nodes = _nodes;
	other._position = _position;
	return (DTMAxisIterator) other;
    }
    
    public void setDom(DOM dom) {
    	_dom = dom;
    	if (dom instanceof DOMEnhancedForDTM) {
    	    _enhancedDOM = (DOMEnhancedForDTM)dom;
    	}
    	else if (dom instanceof DOMAdapter) {
    	    DOM idom = ((DOMAdapter)dom).getDOMImpl();
    	    if (idom instanceof DOMEnhancedForDTM) {
    	        _enhancedDOM = (DOMEnhancedForDTM)idom;
    	    }
    	}
    }
}
