/**
 * License Agreement.
 *
 *  JBoss RichFaces - Ajax4jsf Component Library
 *
 * Copyright (C) 2007  Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.richfaces.component.state;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.faces.application.FacesMessage;
import javax.faces.component.StateHolder;
import javax.faces.context.FacesContext;

import org.ajax4jsf.model.DataComponentState;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.Range;
import org.richfaces.component.UITree;
import org.richfaces.component.state.events.TreeStateCommandsListener;
import org.richfaces.model.TreeRange;
import org.richfaces.model.TreeRowKey;

/**
 * @author Nick Belaevski - nbelaevski@exadel.com created 23.11.2006
 * 
 */
public class TreeState implements DataComponentState, TreeStateCommandsListener, StateHolder {
	private final class Visitor implements DataVisitor {
		private TreeRowKey key;
		private UITree tree;

		public Visitor(TreeRowKey key, UITree tree) {
			super();
			this.key = key;
			this.tree = tree;
		}

		public void process(FacesContext context, Object rowKey, Object argument)
		throws IOException {
			tree.setRowKey(context, rowKey);
			if (tree.isRowAvailable()) {
				TreeRowKey nextKey = (TreeRowKey) rowKey;

				if (!tree.isLeaf() && nextKey != null) {
					if (key == null || nextKey.isSubKey(key)
							|| nextKey.equals(key)) {
						if (tree.isImmediate()) {
							queuedExpandedNodes.add(nextKey);
						} else {
							expandedNodes.add(nextKey);
						}
						queuedCollapsedNodes.remove(nextKey);
					}
				}
			} else {
				FacesMessage message = new FacesMessage("Row key: " + rowKey
						+ " isn't available!");
				message.setSeverity(FacesMessage.SEVERITY_ERROR);

				context.addMessage(tree.getBaseClientId(context), message);
			}
		}
	}

	private final static TreeRange RANGE_UNCONSTRAINED = new TreeRange() {
		public boolean processChildren(TreeRowKey rowKey) {
			return true;
		}

		public boolean processNode(TreeRowKey rowKey) {
			return true;
		}
	};
	
	private boolean stopInCollapsed = false;

	private TreeRowKey selectedNode = null;

	private Set expandedNodes = new HashSet();

	private Set queuedExpandedNodes = new HashSet();

	private Set queuedCollapsedNodes = new HashSet();

	public TreeState(boolean stopInCollapsed) {
		super();
		this.stopInCollapsed = stopInCollapsed;
	}

	public boolean isExpanded(TreeRowKey rowKey) {
		if (rowKey == null) {
			return true;
		}

		return expandedNodes.contains(rowKey)
		|| queuedExpandedNodes.contains(rowKey);
	}

	public boolean isSelected(TreeRowKey rowKey) {
		return (rowKey == null && selectedNode == null) || (selectedNode != null && selectedNode.equals(rowKey));
	}

	public TreeRowKey getSelectedNode() {
		return selectedNode;
	}
	
	public void setSelected(TreeRowKey rowKey) {
		selectedNode = rowKey;
	}

	private boolean _transient;

	private transient TreeRange treeRange = null;

	public Range getRange() {
		if (treeRange != null) {
			return treeRange;
		}

		if (stopInCollapsed) {
			return new TreeRange() {

				public boolean processChildren(TreeRowKey rowKey) {
					if (rowKey == null) {
						return true;
					}

					return expandedNodes.contains(rowKey);
				}

				public boolean processNode(TreeRowKey rowKey) {
					return true;
				}
			};
		} else {
			return RANGE_UNCONSTRAINED;
		}
	}

	public boolean isTransient() {
		return _transient;
	}

	public void restoreState(FacesContext context, Object state) {
		Object[] _state = (Object[]) state;
		expandedNodes = (Set) _state[0];
		_transient = ((Boolean) _state[1]).booleanValue();
		stopInCollapsed = ((Boolean) _state[2]).booleanValue();
		selectedNode = (TreeRowKey) _state[3];
		queuedExpandedNodes = (Set) _state[4];
		queuedCollapsedNodes = (Set) _state[5];
	}

	public Object saveState(FacesContext context) {
		Object[] state = new Object[6];
		state[0] = expandedNodes;
		state[1] = new Boolean(_transient);
		state[2] = new Boolean(stopInCollapsed);
		state[3] = selectedNode;
		state[4] = queuedExpandedNodes;
		state[5] = queuedCollapsedNodes;
		return state;
	}

	public void setTransient(boolean newTransientValue) {
		this._transient = newTransientValue;
	}

	public boolean isStopInCollapsed() {
		return stopInCollapsed;
	}

	public void setStopInCollapsed(boolean stopInCollapsed) {
		this.stopInCollapsed = stopInCollapsed;
	}

	private void visitNodes(UITree tree, TreeRange treeRange, TreeRowKey rootKey)
	throws IOException {
		try {
			this.treeRange = treeRange;
			Object oldKey = tree.getRowKey();

			tree.walkModel(FacesContext.getCurrentInstance(), new Visitor(
					rootKey, tree), treeRange, rootKey, null);
			tree.setRowKey(oldKey);
		} finally {
			this.treeRange = null;
		}
	}

	public void expandAll(UITree tree) throws IOException {
		queuedCollapsedNodes.clear();
		// SubTreeChildrenAppender infoAppender = null;
		//
		// if (storedPersister != null) {
		// storedPersister.reset();
		// infoAppender = storedPersister.getAppender(null);
		// }

		visitNodes(tree, RANGE_UNCONSTRAINED, null);
	}

	public void collapseAll(UITree tree) throws IOException {
		expandedNodes.clear();
		queuedExpandedNodes.clear();
		// RequestUtils.setStoredPersister(tree.getBaseClientId(context),
		// context, null);
	}

	public void collapseNode(UITree tree, TreeRowKey rowKey) throws IOException {
		expandedNodes.remove(rowKey);
		queuedExpandedNodes.remove(rowKey);
		queuedCollapsedNodes.add(rowKey);
		// TreePersister storedPersister =
		// RequestUtils.getStoredPersister(tree.getBaseClientId(context),
		// context);
		// if (storedPersister != null) {
		// storedPersister.removeNode(rowKey);
		// }
	}

	public void expandNode(UITree tree, final TreeRowKey rowKey) throws IOException {
		// SubTreeChildrenAppender infoAppender;
		// if (storedPersister != null) {
		// infoAppender = storedPersister.getAppender(rowKey);
		// } else {
		// infoAppender = null;
		// }

		TreeRange range;
		// it's enough to traverse only subkeys of the node
		// we're opening
		range = new TreeRange() {
			public boolean processChildren(TreeRowKey nextKey) {
				return true;
			}

			public boolean processNode(TreeRowKey nextKey) {
				return (rowKey == null && nextKey == null)
				|| nextKey.equals(rowKey)
				|| nextKey.isSubKey(rowKey);
			}

		};
		visitNodes(tree, range, rowKey);

	}

	public void transferQueuedNodes() {
		expandedNodes.addAll(queuedExpandedNodes);
		queuedExpandedNodes.clear();
		expandedNodes.removeAll(queuedCollapsedNodes);
		queuedCollapsedNodes.clear();
	}

}
