/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   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.

====================================================================== */

package org.bodington.util;

import javax.swing.tree.*;
import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.bodington.database.PrimaryKey;

public class VisitationMutableTreeNode 
	extends DefaultMutableTreeNode 
	implements java.io.Serializable
	{
	
	private boolean contains_visitation_object=false;
	private int left_index=0;
	private int right_index=Integer.MAX_VALUE;
	boolean changed=false;

	//only used by root node
	private Hashtable keyed_nodes=null;

	public void indexSaved()
		{
		changed=false;
		}
	public boolean indexChanged()
		{
		return changed;
		}
	
	public void setLeftIndex( int l )
		{
		if ( l == left_index ) return;
		left_index = l;
		changed = true;
		}
	public int getLeftIndex()
		{
		return left_index;
		}
	public void setRightIndex( int r )
		{
		if ( r==right_index ) return;
		right_index = r;
		changed=true;
		}
	public int getRightIndex()
		{
		return right_index;
		}

	public void setUserObject( Object obj )
		{
		contains_visitation_object = false;
		setLeftIndex( -1 );
		setRightIndex( -1 );
		changed = false;
		super.setUserObject( obj );
		}
	
	public void setVisitationObject( VisitationObject v )
		{
		if ( getParent() != null || getChildCount()>0 )
			throw new IllegalStateException( "Illegal attempt to change the contents of a tree node." );
		
		contains_visitation_object = true;
		setLeftIndex( v.getLeftIndex() );
		setRightIndex( v.getRightIndex() );
		changed = false;
		super.setUserObject( v.getPrimaryKey() );
		}
		
	public PrimaryKey getPrimaryKey()
		{
		if ( !contains_visitation_object )	return null;
		Object obj = getUserObject();
		if ( obj==null )					return null;
		if ( !(obj instanceof PrimaryKey) ) return null;
		return (PrimaryKey)obj;
		}

	public void setParent( MutableTreeNode newParent )
		{
		boolean root_no_more = ( getParent()==null && newParent !=null );
		super.setParent( newParent );

		// if this was a root and now isn't
		// ditch the hashtable of nodes.
		if ( root_no_more )
			keyed_nodes = null;
		}


	public Enumeration findChangedNodes()
		{
		if ( getParent() != null )
			{
			VisitationMutableTreeNode root = (VisitationMutableTreeNode)getRoot();
			return root.findChangedNodes();
			}

		Vector changed = new Vector();
		Enumeration enumeration = this.preorderEnumeration();
		VisitationMutableTreeNode node;
		while ( enumeration.hasMoreElements() )
			{
			node=(VisitationMutableTreeNode)enumeration.nextElement();
			if ( node.contains_visitation_object )
				if ( node.indexChanged() )
					changed.addElement( node );
			}
			
		return changed.elements();
		}

	public VisitationMutableTreeNode findNode( PrimaryKey key )
		{
		if ( getParent() != null )
			{
			VisitationMutableTreeNode root = (VisitationMutableTreeNode)getRoot();
			return root.findNode( key );
			}

		if ( keyed_nodes == null )
			return null;
			
		return (VisitationMutableTreeNode)keyed_nodes.get( key );
		}

	private void addToLookup( VisitationMutableTreeNode node )
		{
		if ( getParent() != null )
			{
			VisitationMutableTreeNode root = (VisitationMutableTreeNode)getRoot();
			root.addToLookup( node );
			return;
			}
			
		if ( !node.contains_visitation_object )
			return;

		PrimaryKey key = node.getPrimaryKey();
		if ( key==null )
			return;

		if ( keyed_nodes == null )
			keyed_nodes = new Hashtable();

		keyed_nodes.put( key, node );
		}
		
	public void add( MutableTreeNode newChild )
		throws IllegalArgumentException, IllegalStateException
		{
		insert( newChild, this.getChildCount() );
		}
		
		
	public void insert( MutableTreeNode newChild, int pos )
		throws IllegalArgumentException, IllegalStateException
		{
		VisitationMutableTreeNode v_root, v_child, node;
		Enumeration v_set;
		int insert_at, childcount;
		PrimaryKey key;
		
		if ( !(newChild instanceof VisitationMutableTreeNode) )
			throw new IllegalStateException( "Can't move elements of that class in this tree." );
		if ( newChild.getParent() != null )
			throw new IllegalStateException( "Can't move elements in this tree." );
		if ( newChild.getChildCount() >0 )
			throw new IllegalStateException( "Can't add branches to this tree." );
		
		v_child = (VisitationMutableTreeNode) newChild;
		v_root = getVisitationRoot();
		
		if ( v_root == null )
			{
			v_child.setLeftIndex( 0 );
			v_child.setRightIndex( 1 );
			addToLookup( v_child );
			super.insert( newChild, pos );
			return;
			}
		
		childcount = this.getChildCount();
		if ( pos<0 || pos > childcount )
			pos = childcount;
		
		if ( pos<childcount )
			{
			node = (VisitationMutableTreeNode)this.getChildAt( pos );
			insert_at = node.getLeftIndex();
			}
		else
			insert_at = this.getRightIndex();
		
		v_child.setLeftIndex( insert_at );
		v_child.setRightIndex( insert_at+1 );
		
		v_set = v_root.preorderEnumeration();
		
		while ( v_set.hasMoreElements() )
			{
			node = (VisitationMutableTreeNode) v_set.nextElement();
			
			if ( node.getLeftIndex() >= insert_at )
				node.setLeftIndex( node.getLeftIndex()+2 );
			if ( node.getRightIndex() >= insert_at )
				node.setRightIndex( node.getRightIndex()+2 );
			}
			
		addToLookup( v_child );
		super.insert( newChild, pos );
		}
	
	private synchronized int reindexChildren( int visitation )
		{
		VisitationMutableTreeNode v_node;
		
		this.setLeftIndex( visitation++ );
		
		if ( this.getChildCount()>0 )
			{
			for (	v_node = (VisitationMutableTreeNode)this.getFirstChild(); 
					v_node !=null; 
					v_node = (VisitationMutableTreeNode)v_node.getNextSibling() )
				{
				visitation = v_node.reindexChildren( visitation );
				}
			}
		
		this.setRightIndex( visitation++ );
		
		return visitation;
		}
	
	public synchronized VisitationMutableTreeNode getVisitationRoot()
		{
		int i;
		TreeNode[] path;
		TreeNode node;
		VisitationMutableTreeNode v_root=null, v_node;
		Enumeration v_set;
		
		// find the node at that is the container for the
		// same visitation set as this node and reindex the
		// left an right indexes for all ancestors.

		if ( !this.contains_visitation_object )
			return null;
		
		//get path to root
		path = this.getPath();
		if ( path.length == 0 )
			return null;

		if ( path.length == 1 )
			{
			v_root = (VisitationMutableTreeNode)path[0];
			if ( v_root.contains_visitation_object )
				return v_root;
			return null;
			}
		
		//work back down root to find first node of visitation set
		for ( i=(path.length-2); i>=0; i-- )
			{
			v_node = (VisitationMutableTreeNode)path[i];
			// if this isn't the right type of node previous node
			// must have been the v_root
			if ( !v_node.contains_visitation_object )
				{
				v_root = (VisitationMutableTreeNode)path[i+1];
				break;
				}
			}
		// if all the nodes in the path are the right type then
		// the root is at the start of the path.
		if ( i<0 )
			v_root = (VisitationMutableTreeNode)path[0];
			
		return v_root;
		}
		
	public synchronized void reindex()
		{
		VisitationMutableTreeNode v_root=null;

		v_root = getVisitationRoot();
		
		// reindex children (recursive method)
		if ( v_root!=null )
			v_root.reindexChildren( 0 );
		}
	}
