/**
 * 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.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Default {@link TreeRowKey} implementation based on {@link ArrayList}
 * @author Nick Belaevski - nbelaevski@exadel.com
 * created 17.11.2006
 */
public class ListRowKey extends TreeRowKey {

	private ArrayList path;

	/**
	 * 
	 */
	private static final long serialVersionUID = 7718335783201397177L;

	public String toString() {
		return getPath();
	}

	/**
	 * Default constructor
	 */
	public ListRowKey() {
		super();
		this.path = new ArrayList();
	}

	/**
	 * Copy constructor
	 * @param parentRowKey row key to clone
	 */
	public ListRowKey(ListRowKey parentRowKey) {
		super();
		if (parentRowKey != null) {
			this.path = (ArrayList) parentRowKey.path.clone();
		} else {
			this.path = new ArrayList();
		}
	}

	/**
	 * Appending constructor
	 * @param parentRowKey base row key
	 * @param pathElement path segment to append to base row key
	 */
	public ListRowKey(ListRowKey parentRowKey, Object pathElement) {
		this(parentRowKey);
		this.path.add(pathElement);
	}

	private static ArrayList parsePath(String path) {
		ArrayList result = new ArrayList();

		String trimmedPath = path.trim();

		StringBuffer sb = new StringBuffer();
		boolean escapedState = false;
		int pathLength = trimmedPath.length();

		//unescape
		for (int i = 0; i < pathLength; i++) {
			char c = trimmedPath.charAt(i);
			
			if (SEPARATOR_ESCAPE_CHAR == c) {
				if (escapedState) {
					sb.append(c);
				}
				escapedState = !escapedState;
			} else {
				if (c == AbstractTreeDataModel.SEPARATOR) { 
					if (escapedState) {
						sb.append(c);
					} else {
						result.add(sb.toString());
						sb = new StringBuffer();
					}
				} else {
					sb.append(c);
				}

				escapedState = false;
			}
		}

		if (sb.length() != 0) {
			result.add(sb.toString());
		}
		
		return result;
	}

	/**
	 * List constructor
	 * @param list List of strings to create corresponding row key from
	 */
	public ListRowKey(List list) {
		super();

		this.path = new ArrayList(list);
	}
	
	/**
	 * Path object constructor
	 * @param path first path segment
	 */
	public ListRowKey(Object path) {
		super();
		this.path = new ArrayList(1);
		this.path.add(path);
	}

	/**
	 * Path string constructor
	 * @param path path string to create corresponding row key from
	 */
	public ListRowKey(String path) {
		super();
		this.path = parsePath(path);
	}

	public int depth() {
		return path.size();
	}

	public Iterator iterator() {
		return path.iterator();
	}

	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((path == null) ? 0 : path.hashCode());
		return result;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final ListRowKey other = (ListRowKey) obj;
		if (path == null) {
			if (other.path != null)
				return false;
		} else if (!path.equals(other.path))
			return false;
		return true;
	}

	public Iterator getSubPathIterator(int fromIndex) {
		return path.listIterator(fromIndex);
	}

	public ListRowKey getSubKey(int fromIndex) {
		return new ListRowKey(path.subList(fromIndex, path.size()));
	}
	
	public boolean isSubKey(TreeRowKey rowKey) {
		if (rowKey instanceof ListRowKey) {
			ListRowKey listRowKey = (ListRowKey) rowKey;

			return depth() == getCommonPathLength(listRowKey);
		} else {
			return super.isSubKey(rowKey);
		}
	}

	public String getPath() {
		StringBuffer result = new StringBuffer();
		Iterator iterator = path.iterator();
		boolean hasNext = iterator.hasNext();

		while (hasNext) {
			String pathSegment = iterator.next().toString();

			StringBuffer escapedSubPath = new StringBuffer();
			for (int i = 0; i < pathSegment.length(); i++) {
				char ch = pathSegment.charAt(i);

				//escape
				if (AbstractTreeDataModel.SEPARATOR == ch || ListRowKey.SEPARATOR_ESCAPE_CHAR == ch) {
					escapedSubPath.append(ListRowKey.SEPARATOR_ESCAPE_CHAR);
				}

				escapedSubPath.append(ch);
			}
			
			result.append(escapedSubPath.toString());
			
			hasNext = iterator.hasNext();

			if (hasNext) {
				result.append(AbstractTreeDataModel.SEPARATOR);
			}
		}

		return result.toString();
	}

	public int getCommonPathLength(TreeRowKey otherRowKey) {
		if (otherRowKey == null)
			return 0;
		Iterator iterator = this.iterator();
		Iterator otherIterator = otherRowKey.iterator();
		int length = 0;
		while (iterator.hasNext() && otherIterator.hasNext()
				&& iterator.next().equals(otherIterator.next()))
			length++;
		return length;
	}
	
	public Object get(int i) {
		return path.get(i);
	}
}
