/**
 * 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.model;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import javax.faces.context.FacesContext;

import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.Range;

/**
 * That is intended for internal use
 * 
 * @author Nick Belaevski - nbelaevski@exadel.com created 16.11.2006
 * 
 */
public class TreeDataModel extends AbstractTreeDataModel {
	private TreeRowKey currentRowKey;

	private TreeRowKey oldRowKey;

	private TreeNode rowTreeData;

	public Object getRowKey() {
		return this.currentRowKey;
	}

	public void setRowKey(Object rowKey) {
		if (rowKey != null) {
			ListRowKey newRowKey = (ListRowKey) rowKey;
			this.currentRowKey = newRowKey;
		} else {
			this.currentRowKey = null;
			this.oldRowKey = null;
			this.rowTreeData = null;
		}
	}

	protected void doWalk(FacesContext context, DataVisitor dataVisitor,
			Range range, Object rowKey, Object argument, boolean last) throws IOException {
		ListRowKey listRowKey = (ListRowKey) rowKey;

		TreeNode node = locateTreeNode(listRowKey);

		if (node != null) {
			TreeRange treeRange = (TreeRange) range;

			if (treeRange == null || treeRange.processNode(listRowKey)) {

				if (node.getParent() != null) {
					processElement(context, dataVisitor, argument, listRowKey, last);
				}

				if (treeRange == null || treeRange.processChildren(listRowKey)) {
					if (!node.isLeaf()) {
						Iterator children = node.getChildren();

						Map.Entry childEntry = children.hasNext() ? (Map.Entry) children.next() : null;
						TreeNode childNode;
						Object identifier;

						if (childEntry != null) {
							childNode = (TreeNode) childEntry.getValue();
							identifier = childEntry.getKey();
						} else {
							childNode = null;
							identifier = null;
						}

						do {
							Map.Entry nextChildEntry = children.hasNext() ? (Map.Entry) children.next() : null;
							TreeNode nextChildNode;
							Object nextIdentifier;

							if (nextChildEntry != null) {
								nextChildNode = (TreeNode) nextChildEntry.getValue();
								nextIdentifier = nextChildEntry.getKey();
							} else {
								nextChildNode = null;
								nextIdentifier = null;
							}

							if (childNode != null) {

								boolean isLast = nextChildNode == null;

								ListRowKey newRowKey;
								if (rowKey != null) {						
									newRowKey = new ListRowKey(listRowKey, identifier);						
								} else {						
									newRowKey = new ListRowKey(identifier);						
								}

								this.doWalk(context, dataVisitor, range, newRowKey, argument, isLast);
							}

							identifier = nextIdentifier;
							childNode = nextChildNode;
						} while (childNode != null);
					}
				}
			}
		}
	}
	
	public void walk(FacesContext context, DataVisitor dataVisitor,
			Range range, Object rowKey, Object argument, boolean last) throws IOException {

		if (rowKey != null) {
			setRowKey(rowKey);
			if (!isRowAvailable()) {
				throw new IllegalStateException(
						"No tree element available or row key not set!");
			}
		}
		
		doWalk(context, dataVisitor, range, rowKey, argument, last);
	}

	public TreeNode locateTreeNode(TreeRowKey rowKey) {
		return locateTreeNode(rowKey, false);
	}

	public TreeNode locateTreeNode(TreeRowKey rowKey, boolean allowCreate) {
		boolean useCached = (rowTreeData != null && rowKey != null && rowKey.equals(this.oldRowKey));
		if (!useCached) {
			TreeNode rootNode = (TreeNode) getWrappedData();

			if (rootNode != null) {
				if (rowKey != null) {
					int commonPathLength = rowKey.getCommonPathLength(oldRowKey);
					if (oldRowKey == null) {
						rowTreeData = rootNode;
					} else {
						int rootOpsCount = rowKey.depth();
						int currentUpOpsCount = oldRowKey.depth() - commonPathLength;
						int currentOpsCount = currentUpOpsCount + rootOpsCount - commonPathLength;

						if (rootOpsCount > currentOpsCount) {
							for (int i = 0; i < oldRowKey.depth() 
							- commonPathLength; i++) {

								rowTreeData = rowTreeData.getParent();
							}
						} else {
							commonPathLength = 0;
							rowTreeData = rootNode;
							oldRowKey = null;
						}
					}
					oldRowKey = rowKey;
					Iterator iterator = rowKey.getSubPathIterator(commonPathLength);
					while (iterator.hasNext()) {
						//TODO nick - check rowTreeData for null
						
						Object pathSegment = iterator.next();
						TreeNode childRowTreeData = rowTreeData.getChild(pathSegment);

						if (childRowTreeData == null) {
							if (!allowCreate) {
								return null;
							} else {
								childRowTreeData = new TreeNodeImpl();
								rowTreeData.addChild(pathSegment, childRowTreeData);
							}
						}

						rowTreeData = childRowTreeData;
					}
				} else {
					return rootNode;
				}
			} else {
				return null;
			}
		}
		return rowTreeData;
	}

	public boolean isRowAvailable() {
		TreeNode data = locateTreeNode(this.currentRowKey);

		if (data != null) {
			return true;
		}

		return false;
	}

	public Object getRowData() {
		if (isRowAvailable()) {
			TreeNode treeNode = locateTreeNode(this.currentRowKey);
			if (treeNode != null) {
				return treeNode.getData();
			}

			return null;
		}
		

		throw new IllegalStateException(
				"No tree element available or row key not set!");
	}

	public boolean isLeaf() {
		if (isRowAvailable()) {
			TreeNode treeNode = locateTreeNode(this.currentRowKey);
			if (treeNode != null) {
				return treeNode.isLeaf();
			}
		}

		throw new IllegalStateException(
				"No tree element available or row key not set!");
	}

	public void walkModel(FacesContext context, DataVisitor visitor, Range range, Object key, Object argument, boolean last) throws IOException {
		walk(context, visitor, range, key, argument, last);
	}

	public TreeNode getTreeNode() {
		if (isRowAvailable()) {
			return locateTreeNode(this.currentRowKey);
		}

		throw new IllegalStateException(
				"No tree element available or row key not set!");
	}
}
