/*****************************************************************************\
 *  xtree.c - functions used for tree data structure manament
 *****************************************************************************
 *  Copyright (C) 2012 CEA/DAM/DIF
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm 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 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include <stddef.h>

#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xtree.h"

/* free the node childs */
static void xtree_free_childs(xtree_t* tree, xtree_node_t* node)
{
	xtree_node_t* current_node = node;
	xtree_node_t* free_later = NULL;

	/* if (!tree || !tree->root || !node) return; comm: not a user func */
	if (current_node && current_node->start) {
		/* tree has childs, depth may have changed */
		tree->state &= ~XTREE_STATE_DEPTHCACHED;
	}

	while (current_node) {
		if (current_node->start) {
			current_node = current_node->start;
			continue;
		}
		if (current_node == node) {
			current_node->start = current_node->end = NULL;
			return;
		}
		free_later = current_node;
		if (current_node->parent) {
			current_node->parent->start = current_node->next;
		}
		current_node = current_node->parent;
		if (tree->free)
			tree->free(free_later);
		xfree(free_later);
		--tree->count;
	}
}

/* tries to free the leftest leaf each time, and remove it from the tree,
 * then go above, since node above finish to be a leaf itself.
 */
void xtree_free(xtree_t* tree)
{
	if (!tree || !tree->root)
		return;
	xtree_free_childs(tree, tree->root);

	if (tree->free)
		tree->free(tree->root);
	xfree(tree->root);

	xtree_init(tree, tree->free);
}

void xtree_init(xtree_t* tree, xtree_free_data_function_t freefunc)
{
	tree->root  = NULL;
	tree->free  = freefunc;
	tree->count = 0;
	tree->depth = 0;
	tree->state = XTREE_STATE_DEPTHCACHED;
}

void xtree_set_freefunc(xtree_t* tree, xtree_free_data_function_t freefunc)
{
	tree->free = freefunc;
}

xtree_node_t* xtree_get_parent(xtree_t* tree, xtree_node_t* node)
{
	if (!node || !tree || !tree->root)
		return NULL;
	return node->parent;
}

uint32_t xtree_get_count(xtree_t* tree)
{
	if (!tree)
		return UINT32_MAX;
	return tree->count;
}

xtree_node_t* xtree_add_child(xtree_t* tree,
			xtree_node_t* parent,
			void* data,
			uint8_t flags)
{
	xtree_node_t* newnode = NULL;

	if (!tree || (!parent && tree->root) || (parent && !tree->root)) {
		return NULL;
	}

	xassert(flags & (XTREE_APPEND | XTREE_PREPEND));

	newnode = xmalloc(sizeof(xtree_node_t));
	newnode->data     = data;
	newnode->parent   = parent;
	newnode->start    = NULL;
	newnode->end      = NULL;
	newnode->next     = NULL;
	newnode->previous = NULL;

	if (!parent) {
		newnode->next     = NULL;
		newnode->previous = NULL;
		tree->root	= newnode;
		tree->count       = 1;
		tree->depth       = 1;
		tree->state       = XTREE_STATE_DEPTHCACHED;
		return newnode;
	}

	if (flags & XTREE_APPEND) {
		newnode->previous = parent->end;
		newnode->next     = NULL;
		if (parent->end) {
			parent->end->next = newnode;
		} else {
			parent->start = newnode;
		}
		parent->end = newnode;
	} else {
		newnode->next     = parent->start;
		newnode->previous = NULL;
		if (parent->start) {
			parent->start->previous = newnode;
		} else {
			parent->end = newnode;
		}
		parent->start = newnode;
	}

	++tree->count;
	tree->state &= ~XTREE_STATE_DEPTHCACHED;
	if (flags & XTREE_REFRESH_DEPTH)
		xtree_refresh_depth(tree);

	return newnode;
}

xtree_node_t* xtree_add_sibling(xtree_t* tree,
				xtree_node_t* node,
				void* data,
				uint8_t flags)
{
	xtree_node_t* newnode = NULL;

	xassert(flags & (XTREE_APPEND | XTREE_PREPEND));

	if (!tree)
		return NULL;

	/* no node, same behaviour as add_child */
	if (!node) return xtree_add_child(tree, node, data, flags);

	/* root node has only childs */
	/* FIXME: better to call add_child instead here, or can be too
	 * confusing ?
	 */
	if (!node->parent)
		return NULL;

	newnode = xmalloc(sizeof(xtree_node_t));
	newnode->data     = data;
	newnode->parent   = node->parent;
	newnode->start    = NULL;
	newnode->end      = NULL;
	newnode->next     = NULL;
	newnode->previous = NULL;

	if (flags & XTREE_APPEND) {
		newnode->previous = node;
		newnode->next = node->next;
		node->next = newnode;
		if (newnode->next) {
			newnode->next->previous = newnode;
		} else {
			node->parent->end = newnode;
		}
	} else {
		newnode->next = node;
		newnode->previous = node->previous;
		node->previous = newnode;
		if (newnode->previous) {
			newnode->previous->next = newnode;
		} else {
			node->parent->start = newnode;
		}
	}

	++tree->count;
	tree->state &= ~XTREE_STATE_DEPTHCACHED;

	if (flags & XTREE_REFRESH_DEPTH)
		xtree_refresh_depth(tree);

	return newnode;
}

/* NOTE: 0 = no node since no depth so implies no root */
uint32_t xtree_depth_const(const xtree_t* tree)
{
	if (tree->state & XTREE_STATE_DEPTHCACHED)
		return tree->depth;

	return xtree_depth_const_node(tree, tree->root);
}

static uint8_t xtree_depth_helper(xtree_node_t* node,
		uint8_t which,
		uint32_t level,
		void* arg)
{
	uint32_t* max_level = (uint32_t*)arg;

	if (level >= *max_level) {
		*max_level = level;
	}

	return 1;
}

uint32_t xtree_depth_const_node(const xtree_t* tree, const xtree_node_t* node)
{
	uint32_t max_level = 0;

	if (!tree->root)
		return 0;
	xtree_walk((xtree_t*)tree,
		   NULL,
		   0,
		   UINT32_MAX,
		   xtree_depth_helper,
		   &max_level);
	return max_level + 1;
}

uint32_t xtree_depth(xtree_t* tree)
{
	xtree_refresh_depth(tree);
	return tree->depth;
}

void xtree_refresh_depth(xtree_t* tree)
{
	if (tree->state & XTREE_STATE_DEPTHCACHED)
		return;
	tree->depth  = xtree_depth_const_node(tree, tree->root);
	tree->state |= XTREE_STATE_DEPTHCACHED;
}

uint32_t xtree_node_depth(const xtree_node_t* node)
{
	uint32_t depth = 0;
	while (node) {
		++depth;
		node = node->parent;
	}
	return depth;
}

/* always tries to browse the tree in this order : most left child, if no
 * child, go to next sibling, then if no sibling, go up until a sibling is
 * found.
 */
xtree_node_t* xtree_walk(xtree_t* tree,
			 xtree_node_t* node,
			 uint32_t min_level,
			 uint32_t max_level,
			 xtree_walk_function_t action,
			 void* arg)
{
	xtree_node_t* current_node = NULL;
	uint32_t level = 0;

	if (!tree || !action)
		return NULL;
	if (!node)
		node = tree->root;

	current_node = node;
	while (current_node) {

		if (level >= min_level && !action(current_node,
						  XTREE_GROWING,
						  level,
						  arg)) {
			return current_node;
		}

		/* go down and continue counting */
		if (current_node->start) {
			if (level >= min_level && !action(current_node,
							  XTREE_PREORDER,
							  level,
							  arg)) {
				return current_node;
			}
			if (level < max_level) {
				current_node = current_node->start;
				++level;
				continue;
			}
		} else if (level >= min_level &&
			   !action(current_node, XTREE_LEAF, level, arg)) {
			return current_node;
		}

		/* while no next member go up */
		while (!current_node->next) {
			current_node = current_node->parent;
			--level;
			if (!current_node) {
				return NULL;
			} else if (current_node == node) {
				if (level >= min_level &&
				    !action(current_node,
					    XTREE_ENDORDER,
					    level,
					    arg)) {
					return current_node;
				}
				return NULL;
			} else if (level >= min_level && !action(current_node,
							  XTREE_ENDORDER,
							  level,
							  arg)) {
				return current_node;
			}
		}

		/* go to next sibling */
		if (current_node->next) {
			if ((level >= min_level) &&
			    !action(current_node->parent,
				    XTREE_INORDER,
				    level - 1,
				    arg)) {
				return current_node;
			}
			current_node = current_node->next;
		}
	}
	return NULL;
}

struct xtree_find_st {
	xtree_find_compare_t compare;
	const void* arg;
};

static uint8_t xtree_find_helper(xtree_node_t* node,
				 uint8_t which,
				 uint32_t level,
				 void* arg)
{
	struct xtree_find_st* st = (struct xtree_find_st*)arg;
	return st->compare(node->data, st->arg);
}

xtree_node_t* xtree_find(xtree_t* tree,
		xtree_find_compare_t compare,
		const void* arg)
{

	struct xtree_find_st st;
	if (!tree || !compare)
		return NULL;
	st.compare = compare;
	st.arg = arg;
	return xtree_walk(tree, NULL, 0, UINT32_MAX, xtree_find_helper, &st);
}

xtree_node_t* xtree_delete(xtree_t* tree, xtree_node_t* node)
{
	xtree_node_t* parent = NULL;

	if (!tree || !tree->root || !node)
		return NULL;
	if (node == tree->root) {
		xtree_free(tree);
		return NULL;
	}

	parent = node->parent;
	if (parent->start == node && parent->end == node) {
		parent->start = parent->end = NULL;
		tree->state &= ~XTREE_STATE_DEPTHCACHED;
	} else if (parent->start == node) {
		parent->start = node->next;
		node->next->previous = NULL;
	} else if (parent->end == node) {
		parent->end = node->previous;
		node->previous->next = NULL;
	} else {
		node->previous->next = node->next;
		node->next->previous = node->previous;
	}

	xtree_free_childs(tree, node);
	if (tree->free)
		tree->free(node);
	xfree(node);
	--tree->count;

	return parent;
}

#define XTREE_GET_PARENTS_FIRST_SIZE 64

xtree_node_t** xtree_get_parents(xtree_t* tree,
		xtree_node_t* node,
		uint32_t* size)
{
	xtree_node_t*  current_node  = NULL;
	xtree_node_t** parents_list  = NULL;
	uint32_t       parents_size  = 0;
	uint32_t       parents_count = 0;
	if (!tree || !tree->root || !node || !size)
		return NULL;

	parents_size = XTREE_GET_PARENTS_FIRST_SIZE;
	parents_list = xmalloc(sizeof(xtree_node_t *) * parents_size);

	current_node = node->parent;
	while (current_node) {
		if (parents_count >= parents_size) {
			parents_size = parents_count*2;
			parents_list = (xtree_node_t**)xrealloc(parents_list,
					sizeof(xtree_node_t*)*parents_size);
		}
		parents_list[parents_count] = current_node;
		++parents_count;
		current_node = current_node->parent;
	}

	if (parents_count != 0) {
		parents_list = (xtree_node_t**)xrealloc(parents_list,
				sizeof(xtree_node_t*)*(parents_count+1));
		/* safety measure, can be used as strlen if users assumes it */
		parents_list[parents_count] = NULL;
	}
	else {
		xfree(parents_list);
		parents_list = NULL;
	}
	*size = parents_count;
	return parents_list;
}

xtree_node_t* xtree_common(xtree_t* tree,
		const xtree_node_t* const* nodes,
		uint32_t size)
{
	xtree_node_t*  common_ancestor = NULL;
	xtree_node_t*  current_node    = NULL;
	uint32_t       i;
	uint8_t	found_common_ancestor;

	if (!tree || !tree->root || !nodes || !nodes[0] || !size ||
			!nodes[0]->parent)
		return NULL;

	common_ancestor = nodes[0]->parent;
	for (i = 1; i < size && common_ancestor; ++i) {
		found_common_ancestor = 0;
		while (common_ancestor && !found_common_ancestor) {
			if (!nodes[i]) return common_ancestor;
			current_node = nodes[i]->parent;
			while (current_node &&
			       current_node != common_ancestor) {
				current_node = current_node->parent;
			}
			if (current_node != common_ancestor) {
				common_ancestor = common_ancestor->parent;
			} else {
				found_common_ancestor = 1;
			}
		}
	}

	return common_ancestor;
}

#define XTREE_GET_LEAVES_FIRST_SIZE 64
struct xtree_get_leaves_st {
	xtree_node_t** list;
	uint32_t list_count;
	uint32_t size;
};

static uint8_t xtree_get_leaves_helper(xtree_node_t* node,
				       uint8_t which,
				       uint32_t level,
				       void* arg)
{
	struct xtree_get_leaves_st* st = (struct xtree_get_leaves_st*)arg;
	if (which == XTREE_LEAF) {
		if (st->list_count >= st->size) {
			st->size = st->list_count * 2;
			st->list = (xtree_node_t**)xrealloc(st->list,
					sizeof(xtree_node_t*)*st->size);
		}
		st->list[st->list_count] = node;
		++st->list_count;
	}
	return 1;
}

xtree_node_t** xtree_get_leaves(xtree_t* tree,
		xtree_node_t* node,
		uint32_t* size)
{
	struct xtree_get_leaves_st st;
	if (!tree || !size || !node) {
		/* testing node nulliness to return NULL since xtree_walk will
		 * be run for root node if node == NULL and return an
		 * unattended non null value. */
		return NULL;
	}
	if (!node->start) {
		/* if the node is a leave itself there is no leaves descending
		 * it, but tree walk will return the leave itself, so
		 * returning null before. */
		return NULL;
	}
	st.list_count = 0;
	st.size = XTREE_GET_LEAVES_FIRST_SIZE;
	st.list = xmalloc(sizeof(xtree_node_t *) * st.size);
	xtree_walk(tree, node, 0, UINT32_MAX, xtree_get_leaves_helper, &st);
	if (st.list_count != 0) {
		st.list = (xtree_node_t**)xrealloc(st.list,
				sizeof(xtree_node_t*)*(st.list_count+1));
		/* safety measure, can be used as strlen if users assumes it */
		st.list[st.list_count] = NULL;
	}
	else {
		xfree(st.list);
		st.list = NULL;
	}
	*size = st.list_count;
	return st.list;
}
