package relational.email;

import javax.swing.tree.*;
import javax.swing.event.*;
import java.util.Vector;
import java.lang.reflect.*;
import relational.*;

/**
 * A model that manages the WhereClause displayed in a WherePanel.
 * Based on ExpressionTreeModel.java from Java Swing ch 17 pg 574-5
 * (O'Reilly)
 */
public class WhereTreeModel
	implements TreeModel {

	Vector listeners;
	Where root;

	public WhereTreeModel(Where root) {
		this.root = root;
	}

	public void gratuitousRootEvent() {
		fireTreeNodesChanged(root,
			new Object[] {root}, new int[] {-1}, new Object[] {root});
	}

	public void insertNode(Where parent, Object node, int index) {
		// parent.setChild(index, node);
		throw new IllegalArgumentException("not sure you can do that yet");
	}

	// Methods from TreeModel
	public Object getChild(Object node, int index) {
		if (node instanceof Where) {
			return ((Where)node).getChild(index);
		} 
		return null;
	}

	public int getIndexOfChild(Object parent, Object child) {
		// all known where clauses have at most 2 children, so
		// use this sleazy hack for now
		if (getChild(parent, 0)==child) {
			return 0;
		} else if (getChild(parent, 1)==child) {
			return 1;
		}
		return -1;
	}

	public Object getRoot() {
		return root;
	}

	public int getChildCount(Object parent) {
		if (parent instanceof Where) {
			return ((Where)parent).getChildCount();
		} 
		return 0;
	}

	public boolean isLeaf(Object node) {
		return (getChildCount(node)==0);
	}

	// A new value has been entered into our tree
	public void valueForPathChanged(TreePath path, Object newValue) {
		// can change Wheres to Wheres...
		Object[] p = path.getPath();
		Object[] pp;
		int index;
		Object node;
		node = p[p.length - 1]; 	// the modified node
		System.err.println("valueForPathChanged("+node+", "+newValue+")");
		if (node instanceof Where) {
			// blow away rest of tree
			if (p.length==1) {
				root = whereFor(newValue);
				System.err.println("tree model root is now "+root);
				node = root;
				index = -1;
				pp = new Object[] {root};
			} else {
				// modify parent object
				Where parent = (Where) p[p.length - 2];
				index = getIndexOfChild(parent, node);
				parent.setChild(index, whereFor(newValue));
				// create a new path object to reflect that parent changed
				pp = new Object[p.length-1];
				System.arraycopy(p, 0, pp, 0, pp.length);
			}
			int[] ci = new int[] { index };			// child indices in parent
			Object [] cc = new Object[] { node };	// child objects in parent
			fireTreeStructureChanged(this, pp, ci, cc);
		} else if (node instanceof String) {
			Where parent = (Where) p[p.length - 2];
			index = getIndexOfChild(parent, node);
			System.err.println("node is a string: "+node);
			System.err.println("parent is a whereEquals: "+parent);
			System.err.println("index is 1: "+index);
			if ((parent instanceof WhereEquals) && index==1) {
				parent.setChild(1, newValue);
				pp = p;
				int[] ci = new int[] { index };		// child indices in parent
				Object [] cc = new Object[] { node };// child objects in parent
				System.err.println("value changed; firing, parent now "+parent);
				fireTreeStructureChanged(this, pp, ci, cc);
			} else if ((parent instanceof WhereEquals) && index==0) {
				System.err.println("changing field not supported yet");
			} else {
				System.err.println("this case shouldn't happen.");
			}
		} else {
			System.out.println("Node type "+node.getClass()+" unimpl.");
			return;
		}
	}

	protected Where whereFor(Object newValue) {
		if (newValue instanceof Where) {
			return (Where) newValue;
		}

		String s = (String) newValue;
		if (s.equals("AND")) {
			return Where.and(Where.always(), Where.always());
		} else if (s.equals("=")) {
			Class headerClass = Header.class;
			return Where.equals(Header.f_name, "the");
		} else if (s.equals("Always")) {
			return Where.always();
		} else {
			return null;
		}
	}

	public void addTreeModelListener(TreeModelListener tml) {
		if (listeners == null) { listeners = new Vector(); }
		listeners.addElement(tml);
	}
	
	public void removeTreeModelListener(TreeModelListener tml) {
		if (listeners != null) { listeners.removeElement(tml); }
	}

	// Update our tree because expanding and collapsing changes the way we
	// represent OpNodes.
	public void refresh(TreeExpansionEvent tee) {
		int[] ci = new int[] { -1 };
		fireTreeNodesChanged(tee.getSource(), tee.getPath().getPath(), ci, null);
	}

	// And last but not least, fire off an event to anyone who cares
	// to hear about model changes.
	protected void fireTreeNodesChanged(Object source, Object[] path,
										int[] ci, Object[] cc)
	{ // Short, but not very thread safe!!
		if (listeners != null) {
			TreeModelEvent tme = new TreeModelEvent(source, path, ci, cc);
			for (int i = 0; i < listeners.size(); i++) {
				((TreeModelListener)listeners.elementAt(i)).
					treeNodesChanged(tme);
			}
		}
	}

	protected void fireTreeStructureChanged(Object source, Object[] path,
										int[] ci, Object[] cc)
	{
		if (listeners != null) {
			TreeModelEvent tme = new TreeModelEvent(source, path, ci, cc);
			for (int i = 0; i < listeners.size(); i++) {
				((TreeModelListener)listeners.elementAt(i)).
					treeStructureChanged(tme);
			}
		}
	}
}
