import java.io.Serializable;
import java.util.*;

/**
 * A binary tree in linked representation. Tree nodes are stored as objects consisting of
 * pointers to their left and right child and to their associated data.
 *
 * @param <A> The type of the data held in nodes of the tree.
 */
public class LinkedBinaryTree<A> implements BinaryTree<A> {

	/**
	 * NodeClass
	 *
	 * @param <Datatype>
	 */
	public class LinkedNode<Datatype> {
		
		/**
		 * Data of the  Node
		 */
		private Datatype 				m_data = null;
		
		/**
		 * Left Leaf; another node or null
		 */
		private LinkedNode<Datatype>	m_leftleaf = null;
		
		/**
		 * Right Leaf; another node or null
		 */
		private LinkedNode<Datatype>	m_rightleaf = null;
		
		/**
		 * Constructor for a Leaf
		 * @param data Data of the Leaf
		 */
		public LinkedNode(Datatype data)
		{
			this.m_data = data;
		}
		
		/**
		 * Constructor of a node with childs
		 * 
		 * @param data	Data of the Node
		 * @param leftleaf Data of left child
		 * @param rightleaf Data of right child
		 */
		public LinkedNode(Datatype data, LinkedNode<Datatype> leftleaf, LinkedNode<Datatype> rightleaf)
		{
			this.m_data = data;
			this.m_leftleaf = leftleaf;
			this.m_rightleaf = rightleaf;
		}

		/**
		 * @return stored Data
		 */
		public Datatype getData() {
			return m_data;
		}

		/**
		 * Set Data of the Node
		 * 
		 * @param data
		 */
		public void setData(Datatype data) {
			m_data = data;
		}

		/**
		 * @return Left Leaf or null
		 */
		public LinkedNode<Datatype> getLeftleaf() {
			return m_leftleaf;
		}

		/**
		 * Sets Left Son
		 * 
		 * @param leftleaf
		 */
		public void setLeftleaf(LinkedNode<Datatype> leftleaf) {
			m_leftleaf = leftleaf;
		}

		/**
		 * @return Right Leaf or null
		 */
		public LinkedNode<Datatype> getRightleaf() {
			return m_rightleaf;
		}

		/**
		 * Sets Right Son
		 * 
		 * @param rightleaf
		 */
		public void setRightleaf(LinkedNode<Datatype> rightleaf) {
			m_rightleaf = rightleaf;
		}
	}
	
	/**
	 * Rootnode
	 */
	private LinkedNode<A> root = null;
	
	/**
	 * Clones a Serializable Data object
	 * @param src
	 * @return cloned dataobject
	 */
	public static final Serializable cloneSerializable(Serializable src)
	{
		try {
			java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
			java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(baos);
			oos.writeObject(src);
			oos.flush();
			oos.close();
			java.io.ByteArrayInputStream bais = new
			java.io.ByteArrayInputStream(baos.toByteArray());
			java.io.ObjectInputStream ois = new java.io.ObjectInputStream(bais);
			Object dest = ois.readObject();
			ois.close();
			return (Serializable)dest;
		} catch (Throwable th) {
			return src;
		}
	}
	
	/**
	 * Definition of a Questionmark
	 */
	public static final String QUESTIONMARK = "?";
	
	/**
	 * Create a tree from a given animal game preorder. 
	 * 
	 * @param <B> The type of the data held in nodes of the tree.
	 * @param preorder The preorder to build the tree from.
	 * @return A new LinkedBinaryTree corresponding to the provided preorder.
	 */
	public static LinkedBinaryTree<String> createFromPreorder(List<String> preorder) {
		
		//Clone of preorder
		Vector<String> preordercopy = new Vector<String>();
		
		//Clone preorder
		for(int i=0;i<preorder.size();i++)
		{
			preordercopy.add((String) cloneSerializable(preorder.get(i)));
		}
		
		//Construct LinkedBinaryTree
		LinkedBinaryTree<String> result = new LinkedBinaryTree<String>();
		
		//Sets Root, calculated by recursiv_createFormPreorder
		result.setRoot(result.recursiv_createFromPreorder(preordercopy));
		
		//Preorder was empty or not valid
		if(result.getRoot() == null)
			throw new UnsupportedOperationException();	
		
		//Return constructed Tree
		return result;
	}
	
	/**
	 * Create tree from Preorder recursiv nonstatic and with A-Parameter
	 * 
	 * A must implement contains-function
	 * 
	 * @param preorder
	 * @return
	 */
	public LinkedNode<A> recursiv_createFromPreorder(List<A> preorder)
	{
		//check if there is data to parse
		if(	preorder.size() < 1)
			return null;
		
		//create new node for data
		LinkedNode<A> node = new LinkedNode<A>(preorder.get(0));
		
		//remove parsed data
		preorder.remove(0);
		
		//still more than one element?
		if(preorder.size() > 0){
			
			//left leaf
			if(preorder.get(0).toString().contains(QUESTIONMARK)) {
				//subtree
				node.setLeftleaf(recursiv_createFromPreorder(preorder));
			} else {
				//blatt
				node.setLeftleaf(new LinkedNode<A>(preorder.get(0)));
				preorder.remove(0);
			}
		}
		
		//still more than one element?
		if(preorder.size() > 0){
			
			//right leaf
			if(preorder.get(0).toString().contains(QUESTIONMARK)) {
				//subtree
				node.setRightleaf(recursiv_createFromPreorder(preorder));
			} else {
				//blatt
				node.setRightleaf(new LinkedNode<A>(preorder.get(0)));
				preorder.remove(0);
			}
		}
		
		//return node created
		return node;
	}
	
	/**
	 * Create a pre-order traversal sequence for this tree.
	 * 
	 * @return The pre-order sequence for this tree.
	 */
	@Override
	public List<A> preorder() {
		
		return recursiv_preorder(root);
	}

	/**
	 * Creates preorder from Tree, recursiv!
	 * 
	 * @param node Node to start from
	 * @return preorderlist
	 */
	private List<A> recursiv_preorder(LinkedNode<A> node){
		
		List<A> result = new Vector<A>();
		
		//still a node left?
		if(node != null)
		{
			//add node data
			result.add(node.getData());
			//add all left leaf data
			result.addAll(recursiv_preorder(node.getLeftleaf()));
			//add all right leaf data
			result.addAll(recursiv_preorder(node.getRightleaf()));
		}
			
		//return List
		return result;
	}
	
	/**
	 * @return root node or null
	 */
	private LinkedNode<A> getRoot() {
		return this.root;
	}

	/**
	 * Sets root node
	 * 
	 * @param root
	 */
	private void setRoot(LinkedNode<A> root) {
		this.root = root; 
	}

	@Override
	public BinaryTreeSearch<A> binaryTreeSearch() {
		// Create anonymous class implementing BinaryTreeSearch
		return new BinaryTreeSearch<A>() {
			
			/**
			 * Current Node is initialized with root
			 */
			private LinkedNode<A> currentNode = root;
			
			/**
			 * Parent Node is initialized with null
			 */
			private LinkedNode<A> parentNode = null;
			
			/**
			 * Are we in a left leaf(true) or in a right leaf(false)?
			 */
			private boolean leftleaf = false; 
			
			@Override
			public A getData() {
				
				//check if element is ok.
				if(	currentNode == null ||
					currentNode.getData() == null) {
					throw new NoSuchElementException();
				}
				
				//return data
				return currentNode.getData();
			}

			@Override
			public boolean isLeaf() {
			
				//check if element is ok.
				if(	currentNode == null ||
					currentNode.getData() == null) {
					throw new NoSuchElementException();
				}
				
				//check if there is left and right data, if not its a leaf
				if(	currentNode.getLeftleaf() == null &&
					currentNode.getRightleaf() == null)
				{
					return true;
				}
			
				//not a leaf
				return false;
			}

			@Override
			public void nextLeftChild() {
				
				//check if element is ok.
				if(	currentNode == null ||
					currentNode.getLeftleaf() == null) {
					throw new NoSuchElementException();
				}

				//set parent node
				parentNode = currentNode;
				//left side
				leftleaf = true;
				//set current node to left leaf
				currentNode = currentNode.getLeftleaf();
			}

			@Override
			public void nextRightChild() {
				
				//chekc if element is ok.
				if(	currentNode == null ||
					currentNode.getRightleaf() == null) {
					throw new NoSuchElementException();
				}

					parentNode = currentNode;
					leftleaf = false;
					currentNode = currentNode.getRightleaf();
			}

			@Override
			public void setNode(A data, A leftData, A rightData) {
				
				if(data == null)
					throw new UnsupportedOperationException();
					
				LinkedNode<A> left = null;
				LinkedNode<A> right = null;
				
				if(leftData != null)
				{
					left = new LinkedNode<A>(leftData);
				}
				
				if(rightData != null)
				{
					right = new LinkedNode<A>(rightData);
				}
				
				if(parentNode != null)
				{
					if(leftleaf) {
						parentNode.setLeftleaf(new LinkedNode<A>(data,left,right));
						currentNode = parentNode.getLeftleaf();
					} else {
						parentNode.setRightleaf(new LinkedNode<A>(data,left,right));
						currentNode = parentNode.getRightleaf();
					}
				} else
				{
					root = new LinkedNode<A>(data,left,right);
					currentNode = root;
				}
			}

			@Override
			public boolean hasLeftChild() {
				if(	currentNode != null &&
					currentNode.getLeftleaf() != null) {
					return true;
				}
				
				return false;
			}

			@Override
			public boolean hasRightChild() {
				if(	currentNode != null &&
					currentNode.getRightleaf() != null) {
					return true;
				}
					
				return false;
			}			
		};
	}

}
