package com.tandbergtv.metadatamanager.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Field Tree used to represent the heirarchical structure of the Fields an
 * asset has
 * 
 * @author vgoyal
 * 
 */
public class FieldTree {
	private static final String _CUSTOMFIELD = "CustomField";
	private static final String _ATTRIBUTE = "/@";
	private static final String _CLOSING_BRACE = "}";
	private static final String _OPENING_BRACE = "{";
	private static final String _TNS = "/tns:";

	private FieldTreeNode rootElement;

	/**
	 * cunstructor - creates the rootelement and sets its name to "Fields"
	 */
	public FieldTree() {
		rootElement = new FieldTreeNode();
		rootElement.setName("Fields");
		rootElement.setCurrentIndex(1);
	}

	public FieldTree(String rootName, Integer currentIndex) {
		rootElement = new FieldTreeNode();
		rootElement.setName(rootName);
		rootElement.setCurrentIndex(currentIndex);
	}

	/**
	 * 
	 * @return
	 */
	public FieldTreeNode getRootElement() {
		return rootElement;
	}

	/**
	 * 
	 * @param rootElement
	 */
	public void setRootElement(FieldTreeNode rootElement) {
		this.rootElement = rootElement;
	}

	public String toString() {
		return toList().toString();
	}

	/**
	 * returns a list of all the nodes in the tree
	 * 
	 * @return
	 */
	public List<FieldTreeNode> toList() {
		List<FieldTreeNode> list = new ArrayList<FieldTreeNode>();
		walk(rootElement, list);
		return list;
	}

	/**
	 * walks the tree populating the list of nodes
	 * 
	 * @param node
	 * @param list
	 */
	private void walk(FieldTreeNode node, List<FieldTreeNode> list) {
		list.add(node);
		for (FieldTreeNode child : node.getChildren()) {
			walk(child, list);
		}
	}

	/**
	 * depth first traversal to set the correct curIndex for each node.
	 * 
	 * @param node
	 * @return
	 */
	public List<Field> depthFirstTraversal() {
		return depthFirstTraversal(new ArrayList<Field>(), rootElement, "");
	}

	private List<Field> depthFirstTraversal(List<Field> fieldList,
			FieldTreeNode node, String xpath) {

		if (!node.isLeafNode()) {
			xpath = xpath + _TNS + node.getName() + _OPENING_BRACE
					+ node.getCurrentIndex() + _CLOSING_BRACE;
			for (FieldTreeNode child : node.getChildren()) {
				depthFirstTraversal(fieldList, child, xpath);
			}
		} else {
			boolean isAttribute = node.isAttribute();
			if (isAttribute) {
				xpath = xpath + _ATTRIBUTE + node.getName() + _OPENING_BRACE
						+ node.getCurrentIndex() + _CLOSING_BRACE;
			} else {
				xpath = xpath + _TNS + node.getName() + _OPENING_BRACE
						+ node.getCurrentIndex() + _CLOSING_BRACE;
			}

			// extract indices from the xpath and add them to the field
			List<Integer> indices = new ArrayList<Integer>();
			String[] indexSplit = xpath.split("\\{");
			for (String indexStr : indexSplit) {
				int bracket = indexStr.indexOf(_CLOSING_BRACE);
				if (bracket > -1)
					indices.add(Integer
							.parseInt(indexStr.substring(0, bracket)));
			}

			// if there is an attribute, we don't include the index for it.
			// Remove the last entry in the index list
			if (isAttribute) {
				// in the below if loop, we want to set the indices correctly
				// for custom fields. if the same CF is present multiple times,
				// the final attribute "value" will have the different index. so
				// what we do is get that last index and mark it for the
				// previous item in the xpath. Check in the debugger :)
				// so [1,1,1,2] gets converted to [1,1,2]
				if(xpath.contains(_CUSTOMFIELD)) {
					int lastIndex = indices.get(indices.size()-1);
					indices = indices.subList(0, indices.size() - 1);
					indices.set(indices.size()-1, lastIndex);
				} else {
					indices = indices.subList(0, indices.size() - 1);
				}
			}

			Field f = new Field();
			f.setId(node.getField().getId());
			f.setDataType(node.getField().getDataType());
			f.setTtvXPath(xpath.replaceAll("\\{[0-9]*\\}", ""));
			f.setIndices(indices);
			f.setValue(node.getField().getValue());

			fieldList.add(f);

			xpath = "";
		}
		return fieldList;
	}

	/**
	 * Traverse tree by using a breadth first traversal.
	 * 
	 * @param queue
	 * 
	 * @param node
	 *            Node to start from.
	 * @return A list of all nodes that have been traversed.
	 */
	public List<FieldTreeNode> breadthFirstTraversal(
			ConcurrentLinkedQueue<FieldTreeNode> queue) {
		List<FieldTreeNode> resultList = new ArrayList<FieldTreeNode>();
		rootElement.setCurrentIndex(1);
		queue.offer(rootElement);
		resultList.add(rootElement);
		return breadthFirstTraversal(queue, resultList);
	}

	private List<FieldTreeNode> breadthFirstTraversal(
			ConcurrentLinkedQueue<FieldTreeNode> queue,
			List<FieldTreeNode> resultList) {
		while (!queue.isEmpty()) {
			FieldTreeNode node = queue.remove();

			HashMap<String, Integer> names = new HashMap<String, Integer>();

			for (FieldTreeNode child : node.getChildren()) {
				String name = child.getName();
				if (names.containsKey(name)) {
					Integer index = names.get(name);
					index++;
					child.setCurrentIndex(index);
					names.put(name, index);
				} else {
					child.setCurrentIndex(1);
					names.put(name, 1);
				}
				queue.offer(child);
				resultList.add(child);
			}
		}
		return resultList;
	}

}
