/* Copyright (c) 2011, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.gui.main;

import java.util.ArrayList;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import com.cburch.logisim.circuit.CircuitState;
import com.cburch.logisim.comp.Component;

public class SimulationTreeModel implements TreeModel {
	private ArrayList<TreeModelListener> listeners;
	private SimulationTreeCircuitNode root;
	private CircuitState currentView;
	
	public SimulationTreeModel(CircuitState rootState) {
		this.listeners = new ArrayList<TreeModelListener>();
		this.root = new SimulationTreeCircuitNode(this, null,
				rootState, null);
		this.currentView = null;
	}
	
	public CircuitState getRootState() {
		return root.getCircuitState();
	}
	
	public CircuitState getCurrentView() {
		return currentView;
	}
	
	public void setCurrentView(CircuitState value) {
		CircuitState oldView = currentView;
		if (oldView != value) {
			currentView = value;

			SimulationTreeCircuitNode node1 = mapToNode(oldView);
			if (node1 != null) fireNodeChanged(node1);

			SimulationTreeCircuitNode node2 = mapToNode(value);
			if (node2 != null) fireNodeChanged(node2);
		}
	}
	
	private SimulationTreeCircuitNode mapToNode(CircuitState state) {
		TreePath path = mapToPath(state);
		if (path == null) {
			return null;
		} else {
			return (SimulationTreeCircuitNode) path.getLastPathComponent();
		}
	}
	
	public TreePath mapToPath(CircuitState state) {
		if (state == null) return null;
		ArrayList<CircuitState> path = new ArrayList<CircuitState>();
		CircuitState current = state;
		CircuitState parent = current.getParentState();
		while (parent != null && parent != state) {
			path.add(current);
			current = parent;
			parent = current.getParentState();
		}
		
		Object[] pathNodes = new Object[path.size() + 1];
		pathNodes[0] = root;
		int pathPos = 1;
		SimulationTreeCircuitNode node = root;
		for (int i = path.size() - 1; i >= 0; i--) {
			current = path.get(i);
			SimulationTreeCircuitNode oldNode = node;
			for (int j = 0, n = node.getChildCount(); j < n; j++) {
				Object child = node.getChildAt(j);
				if (child instanceof SimulationTreeCircuitNode) {
					SimulationTreeCircuitNode circNode = (SimulationTreeCircuitNode) child;
					if (circNode.getCircuitState() == current) {
						node = circNode;
						break;
					}
				}
			}
			if (node == oldNode) {
				return null;
			}
			pathNodes[pathPos] = node;
			pathPos++;
		}
		return new TreePath(pathNodes);
	}
	
	protected SimulationTreeNode mapComponentToNode(Component comp) {
		return null;
	}

	public void addTreeModelListener(TreeModelListener l) {
		listeners.add(l);
	}

	public void removeTreeModelListener(TreeModelListener l) {
		listeners.remove(l);
	}
	
	protected void fireNodeChanged(Object node) {
		TreeModelEvent e = new TreeModelEvent(this, findPath(node));
		for (TreeModelListener l : listeners) {
			l.treeNodesChanged(e);
		}
	}
	
	protected void fireStructureChanged(Object node) {
		TreeModelEvent e = new TreeModelEvent(this, findPath(node));
		for (TreeModelListener l : listeners) {
			l.treeStructureChanged(e);
		}
	}
	
	private TreePath findPath(Object node) {
		ArrayList<Object> path = new ArrayList<Object>();
		Object current = node;
		while (current instanceof TreeNode) {
			path.add(0, current);
			current = ((TreeNode) current).getParent();
		}
		if (current != null) {
			path.add(0, current);
		}
		return new TreePath(path.toArray());
	}

	public Object getRoot() {
		return root;
	}

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

	public Object getChild(Object parent, int index) {
		if (parent instanceof TreeNode) {
			return ((TreeNode) parent).getChildAt(index);
		} else {
			return null;
		}
	}

	public int getIndexOfChild(Object parent, Object child) {
		if (parent instanceof TreeNode && child instanceof TreeNode) {
			return ((TreeNode) parent).getIndex((TreeNode) child);
		} else {
			return -1;
		}
	}

	public boolean isLeaf(Object node) {
		if (node instanceof TreeNode) {
			return ((TreeNode) node).getChildCount() == 0;
		} else {
			return true;
		}
	}

	public void valueForPathChanged(TreePath path, Object newValue) {
		throw new UnsupportedOperationException();
	}
}
