module vtree::faults;
faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT;

module vtree{ElemType};

import std::core::mem;
import std::core::mem::allocator;
import std::io;

struct VTree {
	Allocator allocator;
	usz elements;
	ElemType[] vector; // vector of element ids
	isz[] refs, ordered_refs;
}


macro VTree.ref_is_valid(&tree, isz ref) => (ref >= 0 && ref < tree.refs.len);
macro VTree.ref_is_present(&tree, isz ref) => tree.refs[ref] >= 0;
macro VTree.size(&tree) => tree.refs.len;

// macro to zero an element
macro @zero()
{
	$if @assignable_to(0, ElemType):
		return 0;
	$endif

	$if @assignable_to(null, ElemType):
		return null;
	$endif

	$if @assignable_to({}, ElemType):
		return {};
	$endif

	//$assert true == false : ElemType.nameof +++ " is not assignable to zero or equivalent";
}

fn void? VTree.init(&tree, usz size, Allocator allocator)
{
	tree.allocator = allocator;

	tree.vector = allocator::new_array(tree.allocator, ElemType, size);
	defer catch { (void)allocator::free(tree.allocator, tree.vector); }

	tree.refs = allocator::new_array(tree.allocator, isz, size);
	defer catch { (void)allocator::free(tree.allocator, tree.refs); }

	tree.ordered_refs = allocator::new_array(tree.allocator, isz, size);
	defer catch { (void)allocator::free(tree.allocator, tree.ordered_refs); }

	// set all refs to -1, meaning invalid (free) element
	tree.refs[..] = -1;

	tree.elements = 0;
}

fn void VTree.free(&tree)
{
	(void)allocator::free(tree.allocator, tree.vector);
	(void)allocator::free(tree.allocator, tree.refs);
	(void)allocator::free(tree.allocator, tree.ordered_refs);
}

fn void VTree.pack(&tree)
{
	// TODO: add a PACKED flag to skip this

	isz free_spot = -1;
	for (usz i = 0; i < tree.size(); i++) {
		if (tree.refs[i] == -1) {
			free_spot = i;
			continue;
		}

		// find a item that can be packed
		if (free_spot >= 0 && tree.refs[i] >= 0) {
			isz old_ref = i;

			// move the item
			tree.vector[free_spot] = tree.vector[i];
			tree.refs[free_spot]   = tree.refs[i];

			tree.vector[i] = {};
			tree.refs[i]   = -1;

			// and move all references
			for (usz j = 0; j < tree.size(); j++) {
				if (tree.refs[j] == old_ref) {
				    tree.refs[j] = free_spot;
				}
			}

			// mark the free spot as used
			free_spot = -1;
		}
	}
}

fn void? VTree.resize(&tree, usz newsize)
{
	// return error when shrinking with too many elements
	if (newsize < tree.elements) {
		return vtree::faults::CANNOT_SHRINK?;
	}

	// pack the vector when shrinking to avoid data loss
	if ((int)newsize < tree.size()) {
		// FIXME: packing destroys all references to elements of vec
		//        so shrinking may cause dangling pointers
		return vtree::faults::CANNOT_SHRINK?;
	}

	usz old_size = tree.size();

	tree.vector = ((ElemType*)allocator::realloc(tree.allocator, tree.vector, newsize*ElemType.sizeof))[:newsize];
	defer catch { (void)allocator::free(tree.allocator, tree.vector); }

	tree.refs = ((isz*)allocator::realloc(tree.allocator, tree.refs, newsize*isz.sizeof))[:newsize];
	defer catch { (void)allocator::free(tree.allocator, tree.refs); }

	tree.ordered_refs = ((isz*)allocator::realloc(tree.allocator, tree.ordered_refs, newsize*isz.sizeof))[:newsize];
	defer catch { (void)allocator::free(tree.allocator, tree.ordered_refs); }

	if (newsize > tree.size()) {
		tree.vector[old_size..newsize-1] = @zero();
		tree.refs[old_size..newsize-1] = -1;
	}
}

// add an element to the tree, return it's ref
fn isz? VTree.add(&tree, ElemType elem, isz parent)
{
	// invalid parent
	if (!tree.ref_is_valid(parent)) {
		return vtree::faults::INVALID_REFERENCE?;
	}

	// no space left
	if (tree.elements >= tree.size()) {
		return vtree::faults::TREE_FULL?;
	}

	// check if the parent exists
	// if there are no elements in the tree the first add will set the root
	if (!tree.ref_is_present(parent) && tree.elements != 0) {
		return vtree::faults::REFERENCE_NOT_PRESENT?;
	}

	// get the first free spot
	isz free_spot = -1;
	for (usz i = 0; i < tree.size(); i++) {
		if (tree.refs[i] == -1) {
			free_spot = i;
			break;
		}
	}
	if (free_spot < 0) {
		return vtree::faults::TREE_FULL?;
	}

	// finally add the element
	tree.vector[free_spot] = elem;
	tree.refs[free_spot]   = parent;
	tree.elements++;

	return free_spot;
}

// prune the tree starting from the ref
// returns the number of pruned elements
fn usz? VTree.prune(&tree, isz ref)
{
	if (!tree.ref_is_valid(ref)) {
		return vtree::faults::INVALID_REFERENCE?;
	}

	if (!tree.ref_is_present(ref)) {
		return 0;
	}

	tree.vector[ref] = @zero();
	tree.refs[ref]   = -1;
	tree.elements--;

	usz count = 1;
	for (usz i = 0; tree.elements > 0 && i < tree.size(); i++) {
		if (tree.refs[i] == ref) {
			count += tree.prune(i)!;
		}
	}

	return count;
}

fn usz VTree.nuke(&tree)
{
	tree.vector[0..] = @zero();
	tree.refs[0..] = -1;
	usz x = tree.elements;
	tree.elements = 0;
	return x;
}

// find the size of the subtree starting from ref
fn usz? VTree.subtree_size(&tree, isz ref)
{
	if (!tree.ref_is_valid(ref)) {
		return vtree::faults::INVALID_REFERENCE?;
	}

	if (!tree.ref_is_present(ref)) {
		return 0;
	}

	usz count = 1;
	for (usz i = 0; i < tree.size(); i++) {
		// only root has the reference to itself
		if (tree.refs[i] == ref && ref != i) {
			count += tree.subtree_size(i)!;
		}
	}

	return count;
}

fn bool? VTree.is_root(&tree, isz node) => node == tree.parentof(node)!;

// iterate through the first level children, use a cursor like strtok_r
fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
{
	if (cursor == null) {
		return vtree::faults::INVALID_ARGUMENT?;
	}

	// if the cursor is out of bounds then we are done for sure
	if (!tree.ref_is_valid(*cursor)) {
		return vtree::faults::INVALID_REFERENCE?;
	}

	// same for the parent, if it's invalid it can't have children
	if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) {
		return vtree::faults::INVALID_REFERENCE?;
	}

	// find the first child, update the cursor and return the ref
	for (isz i = *cursor; i < tree.size(); i++) {
		if (tree.refs[i] == parent) {
			*cursor = i + 1;
			return i;
		}
	}

	// if no children are found return -1
	*cursor = -1;
	return -1;
}

/* iterates trough every leaf of the subtree in the following manner
 * node [x], x: visit order
 *              [0]
 *             / | \
 *            / [2] [3]
 *          [1]      |
 *         /   \    [6]
 *       [4]   [5]
 */
fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor)
{
	if (cursor == null) {
		return vtree::faults::INVALID_ARGUMENT?;
	}

	isz[] queue = tree.ordered_refs;

	// TODO: this could also be done when adding or removing elements
	// first call, create a ref array ordered like we desire
	if (*cursor == -1) {
		*cursor = 0;
		queue[..] = -1;

		// iterate through the queue appending found children
		isz pos, off;
		do {
			// printf ("ref=%d\n", ref);
			for (isz i = 0; i < tree.size(); i++) {
				if (tree.refs[i] == ref) {
					queue[pos++] = i;
				}
			}

			for (; ref == queue[off] && off < tree.size(); off++);
			ref = queue[off];

		} while (tree.ref_is_valid(ref));
		// This line is why tree.ordered_refs has to be size+1
		queue[off + 1] = -1;
	}

	// PRINT_ARR(queue, tree.size());
	// return -1;

	// on successive calls just iterate through the queue until we find an
	// invalid ref, if the user set the cursor to -1 it means it has found what
	// he needed, so free
	if (*cursor < 0) {
		return -1;
	} else if (tree.ref_is_valid(*cursor)) {
		return queue[(*cursor)++];
	}

	return -1;
}

fn isz? VTree.parentof(&tree, isz ref)
{
	if (!tree.ref_is_valid(ref)) {
		return vtree::faults::INVALID_REFERENCE?;
	}

	if (!tree.ref_is_present(ref)) {
		return vtree::faults::REFERENCE_NOT_PRESENT?;
	}

	return tree.refs[ref];
}

fn ElemType? VTree.get(&tree, isz ref)
{
	if (!tree.ref_is_valid(ref)) {
		return vtree::faults::INVALID_REFERENCE?;
	}

	if (!tree.ref_is_present(ref)) {
		return vtree::faults::REFERENCE_NOT_PRESENT?;
	}

	return tree.vector[ref];
}

fn void VTree.print(&tree)
{
	for (isz i = 0; i < tree.size(); i++) {
		if (tree.refs[i] == -1) {
			continue;
		}
		io::printf("[%d] {parent=%d, data=", i, tree.refs[i]);
		io::print(tree.vector[i]);
		io::printn("}");
	}
}
