package eu.dnetlib.miscutils.hstree;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class implement statically typed complete tree of finite depth. The type signature of each tree node determines
 * fully the depth and the types of all the children.
 * 
 * <pre>
 * class MyTree extends TreeNode&lt;RootPayload, L1Payload, TreeNode&lt;L1Payload, L2Payload, TreeNode&lt;L2Payload, Void, NilTreeNode&gt;&gt;&gt; {
 * 
 * }
 * </pre>
 * 
 * However since java doesn't have type inferencing we'll have to write often the full type of intermediate nodes,
 * especially during tree construction. Thus it's recommended that you split the chain into a number of dummy classes,
 * serving only to the purpuse of declaring the type chain:
 * 
 * <pre>
 * class MyTree extends TreeNode&lt;RootPayload, L1Payload, L1Tree&gt; {
 * }
 * 
 * class L1Tree extends TreeNode&lt;L1Payload, L2Payload, L2Tree&gt; {
 * }
 * 
 * class L2Tree extends TreeNode&lt;L2Payload, Void, NilTreeNode&gt; {
 * }
 * </pre>
 * 
 * NOTE: you could keep the whole definition inside a single file using inner classes.
 * 
 * @author marko
 * 
 * @param <T>
 *            Type of the payload (or resource)
 * @param <N>
 *            Type of the next level payload
 * @param <C>
 *            Type of the next level node (another TreeNode)
 */
public class TreeNode<T, N, V, C extends TreeNode<N, ?, V, ?>> {

	public static final String CHILDR_UNDER_LEAF = "cannot create children under leaf nodes";

	private static final Log log = LogFactory.getLog(TreeNode.class); // NOPMD by marko on 11/24/08 5:02 PM

	T resource;
	List<C> children = new ArrayList<C>();
	transient Class<? extends C> childNodeType = autoChildNodeType(getClass());

	/**
	 * Default constructor doesn't set the payload. This is used internally to construct a node. We cannot use the
	 * <code>TreeNode(T resource)</code> constructor only because it would force any implementor of "alias" subclasses
	 * of TreeNode to provide a dummy implementation of the constructor calling <code>super(resource)</code>
	 * 
	 * However the constructor is protected because users should only create objects through <code>addChild</code> or in
	 * alternative they should provide they own constructor ovverrides.
	 */
	protected TreeNode() {
		// no action
	}

	/**
	 * However the constructor is protected because users should only create objects through <code>addChild</code> or in
	 * alternative they should provide they own constructor ovverrides.
	 * 
	 * @param resource
	 *            payload
	 */
	protected TreeNode(final T resource) {
		this.resource = resource;
	}

	/**
	 * Call this method to add a child node.
	 * 
	 * Only the payload is needed, the tree node will be constructed automatically and it will be returned.
	 * 
	 * <pre>
	 * L1Child c1 = root.addChild(new L1Resource());
	 * c1.addChild(new L2Resource());
	 * c1.addChild(new L2Resource()).addChild(new L3Resource());
	 * </pre>
	 * 
	 * @param resource
	 *            payload
	 * @return the new node
	 */
	@SuppressWarnings("unchecked")
	public C addChild(final N resource) {
		try {
			if (childNodeType.equals(NilTreeNode.class))
				throw new IllegalStateException(CHILDR_UNDER_LEAF);

			C test;
			try {
				test = childNodeType.newInstance();
				test.setResource(resource);
			} catch (InstantiationException e) {
				/*
				 * handle the situation when someone wants to create a one argument constructor hiding the default
				 * constructor provided by the base class. Nodes should normally be constructed using addChild but we
				 * don't want to force users to do it, and if they do it they shouldn't be punished with obscure
				 * InstantiationException caused by our evil usage of reflection. Of course we should do much more here
				 * in order to be more robust in the general case.
				 */
				try {
					test = (C) childNodeType.getConstructors()[0].newInstance(resource); // NOPMD
				} catch (InvocationTargetException e1) {
					throw new IllegalStateException(e1);
				} catch (InstantiationException e1) {
					throw new IllegalStateException(e1);
				}
			}

			getChildren().add(test);
			return test;
		} catch (IllegalArgumentException e) {
			throw new IllegalStateException(e);
		} catch (SecurityException e) {
			throw new IllegalStateException(e);
		} catch (IllegalAccessException e) {
			throw new IllegalStateException(e);
		}
	}

	/**
	 * This method is especially useful for leaf nodes when you want to append several children to the same parent node,
	 * without having to declare a temporary variable to hold the parent node (which could have long and ugly type)
	 * 
	 * @param resource
	 * @return the parent node
	 */
	public TreeNode<T, N, V, C> appendChild(final N resource) {
		addChild(resource);
		return this;
	}

	/**
	 * This method applies a visitor to all nodes in a breadth first fashon
	 * 
	 * Currently null payloads are skipped
	 * 
	 * @see Visitor
	 * @param visitor
	 */
	public void breadthFirst(final V visitor) {
		final Queue<TreeNode<?, ?, V, ?>> queue = new LinkedList<TreeNode<?, ?, V, ?>>();

		queue.add(this);
		while (!queue.isEmpty()) {
			final TreeNode<?, ?, V, ?> current = queue.remove();
			log.info("visiting " + current);
			current.accept(visitor);
			queue.addAll(current.getChildren());
		}

	}

	/**
	 * depth first.
	 * 
	 * Currently null payloads are skipped
	 * 
	 * @see Visitor
	 * @param Visitor
	 */
	public void depthFirst(final V visitor) {
		baseDepthFirst(visitor);
	}

	public void baseDepthFirst(final V visitor) {
		accept(visitor);

		for (C el : children)
			el.baseDepthFirst(visitor);
	}

	/**
	 * For the curious, this method takes care of retrieving the runtime type information for the tree node element
	 * declared for children nodes.
	 * 
	 * Because of java type erasure, this is not strictly needed for making this kind of tree work, but it's needed if
	 * we want to enable people typing ugly long signatures and use type aliases as described in the class
	 * documentation; otherwise a cast exception would occur.
	 * 
	 * Aargh, any serious language which permits type polymorphysm should have a type alias feature
	 * 
	 * @param clazz
	 *            the class object of this class
	 * @return the class object of the node element describing children
	 */
	@SuppressWarnings({ "unchecked", "rawtypes"})
	protected Class<? extends C> autoChildNodeType(final Class<? extends TreeNode> clazz) {

		final Type superType = clazz.getGenericSuperclass();

		if (superType instanceof ParameterizedType) {
			final ParameterizedType paramSuperType = (ParameterizedType) superType;

			int argumentIndex;
			if (paramSuperType.getRawType() == TreeNode.class)
				argumentIndex = 3;
			else
				argumentIndex = 2;
				

			final Type argument = (paramSuperType).getActualTypeArguments()[argumentIndex];

			return getRawType(argument);
		} else {
			return autoChildNodeType((Class<? extends TreeNode>) superType);
		}
	}

	@SuppressWarnings("unchecked")
	protected <X> X getRawType(final Type type) {
		if (type instanceof ParameterizedType)
			return (X) ((ParameterizedType) type).getRawType();
		return (X) type;
	}

	public List<C> getChildren() {
		return children;
	}

	public void setChildren(final List<C> children) {
		this.children = children;
	}

	public T getResource() {
		return resource;
	}

	public void setResource(final T resource) {
		this.resource = resource;
	}

	public void accept(final V dummy) {
		log.fatal("should be ovverriden");
	}

}
