/*
 * $RCSfile: BHNode.java,v $
 *
 * Copyright 1999-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 * $Revision: 1.7 $
 * $Date: 2008/02/28 20:17:19 $
 * $State: Exp $
 */

package javax.media.j3d;

import javax.vecmath.*;

abstract class BHNode {
    
    static final byte BH_TYPE_INTERNAL             = 1;
    static final byte BH_TYPE_LEAF                 = 2;
    
    static final int NUMBER_OF_PLANES              = 6;
    
    static final boolean debug = false;
    static final boolean debug2 = false;
    
    BHNode parent;  
    byte   nodeType;
    BoundingBox bHull = null;
    boolean mark;
    
    BHNode () {
	this.parent = null;
	mark = false;
    }
    
    BHNode (BHNode parent) {
	this.parent = parent;
	mark = false;
    }
    
    BHNode (BHNode parent, BoundingBox bHull) {
	this.parent = parent;
	mark = false;
	
	this.bHull = bHull;	
    }
    
    BHNode getParent () {
	return (this.parent) ;
    }
    
    abstract void computeBoundingHull();
    abstract void updateMarkedBoundingHull();
    abstract void destroyTree(BHNode[] bhArr, int[] index);
			      
    void setParent (BHNode node) {
	this.parent = node;
    }
    
    BoundingBox getBoundingHull() {
	return (this.bHull);
    }
    
    void setBoundingHull(BoundingBox bHull) {
	this.bHull = bHull;	
    }
    
    // given two nodes determine the bHull surrounding them, ie. the parent hull
    void combineBHull(BHNode node1, BHNode node2 ) {
	BoundingBox bHull1 = null;
	BoundingBox bHull2 = null;
	
	bHull1 = node1.getBoundingHull();
	bHull2 = node2.getBoundingHull();
	
	if(this.bHull==null)
	    this.bHull = new BoundingBox(bHull1);
	else
	    this.bHull.set(bHull1);
	
	this.bHull.combine(bHull2);
	
    }    
    
    // returns true iff the bHull is completely inside this
    // bounding hull i.e.  bHull values are strictly less
    // than or equal to all this.bHull values
    boolean isInside(BoundingBox bHull) {
	if(bHull == null)
	    return false;

	if( this.bHull.isEmpty() || bHull.isEmpty() ) {
	    return false;
        }
	
	if( this.bHull.upper.x < bHull.upper.x ||
	    this.bHull.upper.y < bHull.upper.y ||
	    this.bHull.upper.z < bHull.upper.z ||
	    this.bHull.lower.x > bHull.lower.x ||
	    this.bHull.lower.y > bHull.lower.y ||
	    this.bHull.lower.z > bHull.lower.z )
	    return false;
	else
	    return true;	
    }
    
    // finds the node matching the search element in the tree and returns
    // the node if found, else it returns null if the node couldn't be found
    BHNode findNode(BHNode node) {
	BHNode fNode = null;
	
	if ( this.nodeType == BHNode.BH_TYPE_LEAF) {
	    if ( this == node ) {
		return this;
	    } 
	} 
	else {
	    if (((BHInternalNode) this).rChild.isInside(node.bHull)) {
		fNode = ((BHInternalNode)this).rChild.findNode(node);
		if(fNode != null) {
		    return fNode;
		}
	    }
	    if (((BHInternalNode)this).lChild.isInside(node.bHull)) {
		return ((BHInternalNode)this).lChild.findNode(node);
	    }
	}
	return null;
    }
    
    void deleteFromParent() {
	BHInternalNode parent;

	// System.err.println("deleteFromParent - this " + this );
	parent = (BHInternalNode) (this.parent);
	if(parent != null) {
	    if(parent.rChild == this)
		parent.rChild = null;
	    else if(parent.lChild == this)
		parent.lChild = null;
	    else {
		if(debug2) { 
		    System.err.println("BHNode.java: Trouble! No match found. This can't happen.");
		    System.err.println("this " + this );
		    if ( this.nodeType == BHNode.BH_TYPE_INTERNAL) {
			System.err.println("rChild " + ((BHInternalNode)this).rChild +
					   " lChild " + ((BHInternalNode)this).lChild);
		    }
		    System.err.println("parent " + parent +
				       " parent.rChild " + parent.rChild +
				       " parent.lChild " + parent.lChild);
		}
	    }
	}
    }
    
    // delete all leaf nodes marked with DELETE_UPDATE and update the
    // bounds of the parents node
    BHNode deleteAndUpdateMarkedNodes() {
	
	if (this.mark == true) {
	    if (this.nodeType == BH_TYPE_LEAF) {
		this.deleteFromParent();
		return null;
		
	    } else {
		if(debug)
		    if(((BHInternalNode)(this)).rChild == ((BHInternalNode)(this)).lChild)
			System.err.println("rChild " + ((BHInternalNode)(this)).rChild +
					   " lChild " + ((BHInternalNode)(this)).lChild);
		
		
		if(((BHInternalNode)(this)).rChild != null)
		    ((BHInternalNode)(this)).rChild = 
			((BHInternalNode)(this)).rChild.deleteAndUpdateMarkedNodes();
		if(((BHInternalNode)(this)).lChild != null)	  
		    ((BHInternalNode)(this)).lChild = 
			((BHInternalNode)(this)).lChild.deleteAndUpdateMarkedNodes();
		
		if ((((BHInternalNode)(this)).rChild == null) &&
		    (((BHInternalNode)(this)).lChild == null)) {
		    this.deleteFromParent();
		    return null;
		} else {
		    if ( ((BHInternalNode)this).rChild == null ) {
			BHNode leftChild = ((BHInternalNode)this).lChild;
			leftChild.parent = this.parent;
			// delete self, return lChild
			this.deleteFromParent();
			return leftChild;
		    } else if ( ((BHInternalNode)this).lChild == null ) {
			BHNode rightChild = ((BHInternalNode)this).rChild;
			rightChild.parent = this.parent;
			// delete self, return rChild
			this.deleteFromParent();	       	    
			return rightChild;
		    } else {
			// recompute your bounds and return yourself
			this.combineBHull(((BHInternalNode)this).rChild, 
					       ((BHInternalNode)this).lChild);
			// update the parent's pointers
			((BHInternalNode)this).rChild.parent = this;
			((BHInternalNode)this).lChild.parent = this;
			this.mark = false;
			return this;
		    }
		}
	    }
	} else {
	    // mark is NOT set, simply return self
	    return this;
	}
    }
    
    
    // generic tree gathering statistics operations
    
    int countNumberOfInternals() {
	if ( this.nodeType == BHNode.BH_TYPE_LEAF ) {
	    return 0;
	} else {
	    return (((BHInternalNode)this).rChild.countNumberOfInternals() +
		    ((BHInternalNode)this).lChild.countNumberOfInternals() + 1 );
	}
    }
    
    // recursively traverse the tree and compute the total number of leaves
    int countNumberOfLeaves() {
	if ( this.nodeType == BHNode.BH_TYPE_LEAF ) {
	    return 1;
	} else {
	    return ( ((BHInternalNode)this).rChild.countNumberOfLeaves() +
		     ((BHInternalNode)this).lChild.countNumberOfLeaves() );
	}
    }
    
    
    // traverse tree and compute the maximum depth to a leaf
    int computeMaxDepth (int currentDepth) {
	if ( this.nodeType == BHNode.BH_TYPE_LEAF ) {
	    return (currentDepth);
	} else {
	    int rightDepth = ((BHInternalNode)this).rChild.computeMaxDepth(currentDepth + 1); 
	    int leftDepth = ((BHInternalNode)this).lChild.computeMaxDepth(currentDepth + 1);
	    if( rightDepth > leftDepth )
		return rightDepth;
	    return leftDepth;  
	}
    }
    
    // compute the average depth of the leaves ...
    float computeAverageLeafDepth ( int numberOfLeaves, int currentDepth ) {
	int sumOfDepths = this.computeSumOfDepths(0);
	return ( (float)sumOfDepths / (float)numberOfLeaves );
    }    
    
    int computeSumOfDepths ( int currentDepth ) {
	if ( this.nodeType == BHNode.BH_TYPE_LEAF ) {
	    return ( currentDepth );
	} else {
	    return (((BHInternalNode)this).rChild.computeSumOfDepths(currentDepth + 1) +
		    ((BHInternalNode)this).lChild.computeSumOfDepths(currentDepth + 1) ) ;
	}
    }
    
    
}
