// Copyright 2000-2005, FreeHEP.
package hep.graphics.heprep.ref;

import java.awt.*;
import java.util.*;
import java.util.List;

import hep.graphics.heprep.*;

import org.freehep.util.DoubleHashtable;
import org.freehep.util.FastStack;

/**
 * Fast iterator, which allows for iteration of all HepRepInstances
 * in a HepRepInstanceTree or for iteration of a specific layer.
 * It also features a callback to a HepRepListener to signal
 * changes in attributes.
 *
 * @author M.Donszelmann
 * @version $Id: DefaultHepRepIterator.java,v 1.32 2005/02/27 22:58:49 duns Exp $
 */
public class DefaultHepRepIterator implements HepRepIterator {

    // state
    private List/*<HepRepInstanceTree>*/ instanceTrees;
    private Iterator treeIterator;
    private HepRepInstanceTree currentTree;
    private Iterator layerIterator;
    private String currentLayer;
    private boolean iterateFrames;
    private boolean inFrameLayer;
    private boolean inFrameLayerChanged;
    
    private Set types = null;

    // for the iteration
    private HepRepInstance nextInstance = null;            // next Instance
    private HepRepInstance currentInstance = null;         // current Instance
    
    // Fast Instance Stack
    private HepRepInstance[] instanceStack = new HepRepInstance[1000]; // HepRepInstances to be processed
    private int instanceStackTop = -1;

    // for the listeners
    private Map/*<LowerCaseName, HepRepAttributeListener>*/ attListeners;  // only one HepRepIteratorListener allowed per attribute
    private List/*<HepRepFrameLsisteners>*/ frameListeners; 

    // for the attributes
    private Map/*<LowerCaseName, HepRepAttValue>*/ attributes;         // current attributes subscribed to

    /**
     * Creates a HepRepIterator for the given list of InstanceTrees.
     * Layer changes are reported but ignored.
     *
     * @param instanceTrees to be iterated over.
     */
    public DefaultHepRepIterator(List/*<HepRepInstanceTree>*/ instanceTrees) {
        this(instanceTrees, null);
    }

    /**
     * Creates a HepRepIterator for the given list of InstanceTrees and set of layers.
     *
     * @param instanceTrees to be iterated over.
     * @param layers to be used in iteration.
     */
    public DefaultHepRepIterator(List/*<HepRepInstanceTree>*/ instanceTrees, List/*<String>*/ layers) {
        this(instanceTrees, layers, false);
    }

    /**
     * Creates a HepRepIterator for the given list of InstanceTrees and set of layers.
     *
     * @param instanceTrees to be iterated over.
     * @param layers to be used in iteration.
     * @param iterateFrames iterate separately over a frame layer for each layer.
     */
    public DefaultHepRepIterator(List/*<HepRepInstanceTree>*/ instanceTrees, List/*<String>*/ layers, boolean iterateFrames) {
        this(instanceTrees, layers, null, iterateFrames);
    }
        
    /**
     * Creates a HepRepIterator for the given list of InstanceTrees and set of layers.
     *
     * @param instanceTrees to be iterated over.
     * @param layers to be used in iteration.
     * @param types to be used in iteration.
     * @param iterateFrames iterate separately over a frame layer for each layer.
     */
    public DefaultHepRepIterator(List/*<HepRepInstanceTree>*/ instanceTrees, List/*<String>*/ layers, Set/*<Object>*/ types, boolean iterateFrames) {
        if ((layers == null) || (layers.size() == 0)) {
            layers = new ArrayList();
            layers.add(null);
        }
        layerIterator = layers.iterator();
        currentLayer = (String)layerIterator.next();
        if (currentLayer != null) currentLayer = currentLayer.intern();
       
        this.types = types;
       
        this.instanceTrees = instanceTrees;
        treeIterator = instanceTrees.iterator();
        currentTree = null;
        
        this.iterateFrames = iterateFrames;

        inFrameLayer = false;
        inFrameLayerChanged = true;

        //setup attribute tables
        attListeners = new HashMap();
        frameListeners = new ArrayList();
        attributes = new HashMap();
    }

    private void fillInstanceStack(Collection/*<HepRepInstance>*/ instances) {
        // put instances of this instancetree on stack for processing
        Iterator iterator = instances.iterator();
        while (iterator.hasNext()) {
            instanceStackTop++;
            if (instanceStackTop >= instanceStack.length) {
                // increment stack size
	            Object oldInstanceStack[] = instanceStack;
	            int newCapacity = (oldInstanceStack.length * 3)/2 + 1;
	            instanceStack = new HepRepInstance[newCapacity];
	            System.arraycopy(oldInstanceStack, 0, instanceStack, 0, oldInstanceStack.length); 
            }
            instanceStack[instanceStackTop] = (HepRepInstance)iterator.next();
        }
    }

    /**
     * Add a listener to be informed about a certain attribute's changes
     * while the iteration is ongoing.
     * Only one listener per attribute is allowed.
     *
     * @param name attribute name
     * @param l listener to be added.
     */
    public void addHepRepAttributeListener(String name, HepRepAttributeListener l) {
        String lowerCaseName = name.toLowerCase().intern();
        
        if (attListeners.get(lowerCaseName) != null) {
            System.err.println("DefaultHepRepIterator: previous HepRepAttributeListener on '"+lowerCaseName+"' bumped.");    
        } 
            
        attListeners.put(lowerCaseName, l);
    }

    /**
     * Remove a listener for a certain attribute.
     *
     * @param name attribute name
     * @param l listener to be removed.
     */
    public void removeHepRepAttributeListener(String name, HepRepAttributeListener l) {
        String lowerCaseName = name.toLowerCase();
        attListeners.remove(lowerCaseName);
    }

    public void addHepRepFrameListener(HepRepFrameListener l) {
        frameListeners.add(l);
    }

    public void removeHepRepFrameListener(HepRepFrameListener l) {
        frameListeners.remove(l);
    }

    private void informFrameListeners() {
        if (inFrameLayerChanged) {
            inFrameLayerChanged = false;
            for (Iterator i = frameListeners.iterator(); i.hasNext();  ) {
                HepRepFrameListener l = (HepRepFrameListener)i.next();
                l.setFrameLayer(inFrameLayer);
            }
        }
    }

    /**
     * Informs listeners of any attribute changes.
     */
    private void informAttributeListeners() {
        for (Iterator i = attListeners.keySet().iterator(); i.hasNext();  ) {
            String lowerCaseName = (String)i.next();
            HepRepAttValue nextAttValue = null;
           
            if (currentInstance != null) {
                nextAttValue = nextInstance.getAttValueFromNode(lowerCaseName);
                if (nextAttValue == null) {
                    if (currentInstance.getAttValueFromNode(lowerCaseName) == null) {
                        if (currentInstance.getType() == nextInstance.getType()) {
                            // notdef -> notdef, types equal: special DO NOTHING case.
                            continue;
                        }
                    }
                    
                    // * -> notdef: lookup
                    nextAttValue = nextInstance.getAttValue(lowerCaseName);
                }       
            } else {
                // first time: lookup
                nextAttValue = nextInstance.getAttValue(lowerCaseName);
            }    

            informAttributeListener(lowerCaseName, nextAttValue);
        }        
    }

    /**
     * Informs listener in case of a change in the given AttributeValue and cache the value.
     *
     * @param name name of the changed attributeValue (interned).
     * @param attValue the changed attributeValue.
     */
    private void informAttributeListener(String lowerCaseName, HepRepAttValue attValue) {
        HepRepAttributeListener listener = (HepRepAttributeListener)attListeners.get(lowerCaseName);
        if (listener == null) return;
        
        // look if changed
        Object oldAttValue = attributes.get(lowerCaseName);
        if (oldAttValue == attValue) return;
        if ((attValue != null) && (attValue.equals(oldAttValue))) return;

        // cache
        attributes.put(lowerCaseName, attValue);
        
        // new value removed ?
        if (attValue == null) {
            listener.removeAttribute(nextInstance, lowerCaseName);            
            return;
        }
        
        // inform
        switch (attValue.getType()) {
            case HepRepAttValue.TYPE_STRING:
                listener.setAttribute(nextInstance, lowerCaseName, attValue.getString(), attValue.getLowerCaseString(), attValue.showLabel());
                break;
            case HepRepAttValue.TYPE_COLOR:
                listener.setAttribute(nextInstance, lowerCaseName, attValue.getColor(), attValue.showLabel());
                break;
            case HepRepAttValue.TYPE_LONG:
                listener.setAttribute(nextInstance, lowerCaseName, attValue.getLong(), attValue.showLabel());
                break;
            case HepRepAttValue.TYPE_INT:
                listener.setAttribute(nextInstance, lowerCaseName, attValue.getInteger(), attValue.showLabel());
                break;
            case HepRepAttValue.TYPE_DOUBLE:
                listener.setAttribute(nextInstance, lowerCaseName, attValue.getDouble(), attValue.showLabel());
                break;
            case HepRepAttValue.TYPE_BOOLEAN:
                listener.setAttribute(nextInstance, lowerCaseName, attValue.getBoolean(), attValue.showLabel());
                break;
            default:
                System.err.println("Unknown type in DefaultHepRepIterator: '"+attValue.getType()+"'");
                listener.setAttribute(nextInstance, lowerCaseName, attValue.toString(), attValue.toString().toLowerCase(), attValue.showLabel());
                break;
        }
    }

    /**
     * @return the next HepRepInstance
     */
    public HepRepInstance nextInstance() {
        return (HepRepInstance)next();
    }

    /**
     * @return the next HepRepInstance
     */
    public Object next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        // inform about in/out frame changes
        informFrameListeners();

        // report all attribute changes
        informAttributeListeners();

        // move on
        currentInstance = nextInstance;
        nextInstance = null;

        return currentInstance;
    }

    /**
     * Prepares the next instance and returns true if exists. Successive
     * calls to hasNext() without calling next() or nextInstance() are ignored.
     *
     * The instances are walked through the tree in in-order traversal.
     *
     * @return true if there exists a next HepRepInstance
     */
    public boolean hasNext() {
        // no need if nextInstance is prepared
        if (nextInstance != null) return true;

        do {
            if (instanceStackTop < 0) {
                // stack is empty, what next...
                if (treeIterator.hasNext()) {
                    // get next tree
                    currentTree = (HepRepInstanceTree)treeIterator.next();
                } else if (inFrameLayer) {
                    // change to normal layer and re-fill for same layer
                    inFrameLayer = false;
                    inFrameLayerChanged = true;

                    // run over all trees again...
                    treeIterator = instanceTrees.iterator();
                    currentTree = (HepRepInstanceTree)treeIterator.next();
                    
                } else {
                    // select next layer if exists...
                    if (!layerIterator.hasNext()) {
                        nextInstance = null;
                        return false;
                    }   
                    currentLayer = (String)layerIterator.next();
                    if (currentLayer != null) {
                        currentLayer = currentLayer.intern();
                    }
                    // run over all trees again...
                    treeIterator = instanceTrees.iterator();
                    currentTree = (HepRepInstanceTree)treeIterator.next();
                    
                    if (iterateFrames) {
                        // change to frame layer
                        inFrameLayer = true;
                        inFrameLayerChanged = true;
                    }
                }    
                // fill stack from current tree
                fillInstanceStack(currentTree.getInstances());
            }

            // get stacked instance
            nextInstance = instanceStack[instanceStackTop];
            instanceStackTop--;

            // add children of this instance to the instanceStack
            fillInstanceStack(nextInstance.getInstances());

        // skip if not in layer, unless layer is null;
        // skip if not in types, unless types is null;
        // skip if iterating frames and inFrameLayer and instance has no frame
        } while (((currentLayer != null) && (nextInstance.getLayer() != currentLayer)) ||
                 ((types != null) && !types.contains(nextInstance.getType())) ||
                 (iterateFrames && inFrameLayer && !nextInstance.hasFrame()));

        // nextInstance is valid.
        return true;
    }

    /**
     * Returns true if the current instance, just delivered by nextInstance(), is to be drawn as a frame.
     */
    public boolean drawAsFrame() {
        return inFrameLayer;
    }

    /**
     * Removes the current instance, however this is not permitted.
     *
     * @throws UnsupportedOperationException in all cases.
     */
    public void remove() throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }
}
