/*
 * Copyright (C) 2018-2020 Andy Gegg
 *
 *	This file is part of the Garden Notebook application
 *
 * The Garden Notebook application is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
	Change log
	2.6.0   Add checks for empty tree processing.
 */

package uk.co.gardennotebook.util;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;

/**
*	A simple structure to hold the tree of items in a story line.
*	No attempt is made to provide a fully featured tree structure.<BR>
*	Each node has zero or more children, each of which can be the root
*	of a subtree, i.e. this is a multiply branching tree modelled as an
*	acyclic directed graph.<BR>
*	Each child node holds a value and a level, the topmost (root) item being at
*	level 0.
*
*	@author	Andy Gegg
*	@version	2.6.0
*	@since	1.0
*	@param <T>	The type of item stored at each node and leaf of the tree.
*/
public final class StoryLineTree<T>
{
	private static final Logger LOGGER = LogManager.getLogger();

	private final List<StoryLineTree<T>> children = new ArrayList<>();
	private final T root;
	private final int level;

	/**
	 * Returns an empty tree structure.
	 * 
	 * @param <T>  the type of the values for each node of the tree.
	 * @return a tree with no nodes.
	 */
	public static <T> StoryLineTree<T> emptyTree()
	{
		return new StoryLineTree<>(null);
	}
	
	/**
	 * Returns a tree with the given item at its root.
	 * 
	 * @param root the root item in the new tree
	 */
	public StoryLineTree(final T root)
	{
		this(root, 0);
	}
	
	/**
	 * Returns a new (sub)tree at the given level.
	 * 
	 * @param root	the root for the new subtree
	 * @param level the level at which the new subtree lies within the overall tree
	 */
	public StoryLineTree(T root, int level)
	{
		this.root = root;
		this.level = level;
	}
	
	/**
	 * Adds a new node on the current tree.
	 * The value of the node is {@code leaf}, its level will be 1 greater
	 * than the parent node (i.e. the root node of the current (sub)tree).
	 * 
	 * @param leaf	the item to be added
	 * @return a subtree with {@code leaf} as its root node value
	 */
	public StoryLineTree<T> addLeaf(T leaf)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("addLeaf(): leaf:{}", leaf);
		StoryLineTree<T> newLeaf = new StoryLineTree<>(leaf, level+1);
		children.add(newLeaf);
		return LOGGER.traceExit(log4jEntryMsg, newLeaf);
	}
	
	/**
	 * Returns the value at the root of the current (sub)tree.
     * 
	 * @return the value at the root of the current (sub)tree
	 */
	public T getValue()
	{
		LOGGER.traceEntry("getValue(): root:{}", root);
		return root;
	}
	
	/**
	 * Returns the level of the root of the current (sub)tree.
     * 
	 * @return the level of the root of the current (sub)tree
	 */
	public int getLevel()
	{
		LOGGER.traceEntry("getLevel(): level:{}", level);
		return level;
	}
	
	/**
	 * Return the direct children of the current (sub)tree.
     * 
	 * @return	a list of (sub)trees 
	 */
	public List<StoryLineTree<T>> getChildren()
	{
		return children;
	}
	
	/**
	 * Performs a depth first walk of the tree.
	 * 
	 * @param visitor	a function to handle the current tree node
	 *					first argument on call is the node's value (getValue())
	 *					second argument is the node's level in the tree (getLevel())
	 */
	public void walkTree(BiConsumer<T, Integer> visitor)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("walkTree(): root: {}", root);
        
        //  2.6.0
        if (isEmpty())
            return;
		visitor.accept(getValue(), getLevel());
		for (StoryLineTree<T> kid : getChildren())
		{
			kid.walkTree(visitor);
		}
	}

    /**
     * Test if this is the empty tree.
     * 
     * @return  true if the tree is empty
     */
    public boolean isEmpty()
    {
		LOGGER.traceEntry("isEmpty(): root:{}", root);
        return root==null;
    }
	
	/**
	 * Copies the current tree replacing the <b>value</b> at each node with a
	 * value of a different type.
     * 
	 * @param <S>	the type of the value of the new nodes.
	 * @param copier	a function to translate the value at each node.
	 * @return a new tree with the same structure but with translated node values.
	 */
	public <S> StoryLineTree<S> copyTree(Function<T,S> copier)
	{
		LOGGER.traceEntry("copyTree(): root: {}", root);
        if (root == null)
        {
            return StoryLineTree.emptyTree();
        }
		StoryLineTree<S> result = new StoryLineTree<>(copier.apply(root), level);
		LOGGER.debug("copied level: {}, value: {}", level, root);
		for (StoryLineTree<T> kid : getChildren())
		{
			result.getChildren().add(kid.copyTree(copier));
		}
		return result;
	}
	
	@Override
	public String toString()
	{
//		return "StoryLineTree: level: "+level+", value: "+root;
		StringBuilder sb = new StringBuilder("StoryLineTree: level: ").append(level).append(", value: ").append(root);
		sb.append(" [");
		for (StoryLineTree<T> kid : children)
		{
			sb.append(kid.toString());
		}
		sb.append("] ");
		return sb.toString();
	}
}
