/*
	hierarchy.c

	General hierarchy handling

	Copyright (C) 2021 Bill Currke

	This program 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.

	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, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#include "QF/scene/hierarchy.h"
#include "QF/scene/transform.h"

#include "scn_internal.h"

static void
hierarchy_UpdateTransformIndices (hierarchy_t *hierarchy, uint32_t start,
								  int offset)
{
	for (size_t i = start; i < hierarchy->transform.size; i++) {
		if (hierarchy->transform.a[i]) {
			hierarchy->transform.a[i]->index += offset;
		}
	}
}

static void
hierarchy_UpdateChildIndices (hierarchy_t *hierarchy, uint32_t start,
							  int offset)
{
	for (size_t i = start; i < hierarchy->childIndex.size; i++) {
		hierarchy->childIndex.a[i] += offset;
	}
}

static void
hierarchy_UpdateParentIndices (hierarchy_t *hierarchy, uint32_t start,
							   int offset)
{
	for (size_t i = start; i < hierarchy->parentIndex.size; i++) {
		hierarchy->parentIndex.a[i] += offset;
	}
}

static void
hierarchy_calcLocalInverse (hierarchy_t *h, uint32_t index)
{
	// This takes advantage of the fact that localMatrix is a simple
	// homogenous scale/rotate/translate matrix with no shear
	vec4f_t     x = h->localMatrix.a[index][0];
	vec4f_t     y = h->localMatrix.a[index][1];
	vec4f_t     z = h->localMatrix.a[index][2];
	vec4f_t     t = h->localMatrix.a[index][3];

	// "one" is to ensure both the scalar and translation have 1 in their
	// forth components
	vec4f_t     one = { 0, 0, 0, 1 };
	vec4f_t     nx = { x[0], y[0], z[0], 0 };
	vec4f_t     ny = { x[1], y[1], z[1], 0 };
	vec4f_t     nz = { x[2], y[2], z[2], 0 };
	vec4f_t     nt = one - t[0] * nx - t[1] * ny - t[2] * nz;
	// vertical dot product!!!
	vec4f_t     s = 1 / (nx * nx + ny * ny + nz * nz + one);
	h->localInverse.a[index][0] = nx * s;
	h->localInverse.a[index][1] = ny * s;
	h->localInverse.a[index][2] = nz * s;
	h->localInverse.a[index][3] = nt * s;
}

void
Hierarchy_UpdateMatrices (hierarchy_t *h)
{
	for (size_t i = 0; i < h->localInverse.size; i++) {
		if (h->modified.a[i]) {
			hierarchy_calcLocalInverse (h, i);
		}
	}
	if (h->modified.a[0]) {
		memcpy (h->worldMatrix.a[0],
				h->localMatrix.a[0], sizeof (mat4_t));
		memcpy (h->worldInverse.a[0],
				h->localInverse.a[0], sizeof (mat4_t));
		h->worldRotation.a[0] = h->localRotation.a[0];
		h->worldScale.a[0] = h->localScale.a[0];
	}
	for (size_t i = 1; i < h->worldMatrix.size; i++) {
		uint32_t    parent = h->parentIndex.a[i];

		if (h->modified.a[i] || h->modified.a[parent]) {
			mmulf (h->worldMatrix.a[i],
				   h->worldMatrix.a[parent], h->localMatrix.a[i]);
			h->modified.a[i] = 1;
		}
	}
	for (size_t i = 1; i < h->worldInverse.size; i++) {
		uint32_t    parent = h->parentIndex.a[i];

		if (h->modified.a[i] || h->modified.a[parent]) {
			mmulf (h->worldInverse.a[i],
				   h->localInverse.a[i], h->worldInverse.a[parent]);
		}
	}
	for (size_t i = 1; i < h->worldRotation.size; i++) {
		uint32_t    parent = h->parentIndex.a[i];
		if (h->modified.a[i] || h->modified.a[parent]) {
			h->worldRotation.a[i] = qmulf (h->worldRotation.a[parent],
										   h->localRotation.a[i]);
		}
	}
	for (size_t i = 1; i < h->worldScale.size; i++) {
		uint32_t    parent = h->parentIndex.a[i];
		if (h->modified.a[i] || h->modified.a[parent]) {
			h->worldScale.a[i] = m3vmulf (h->worldMatrix.a[parent],
										  h->localScale.a[i]);
		}
	}
	memset (h->modified.a, 0, h->modified.size);
}

static void
hierarchy_open (hierarchy_t *hierarchy, uint32_t index, uint32_t count)
{
	DARRAY_OPEN_AT (&hierarchy->transform, index, count);
	DARRAY_OPEN_AT (&hierarchy->entity, index, count);
	DARRAY_OPEN_AT (&hierarchy->childCount, index, count);
	DARRAY_OPEN_AT (&hierarchy->childIndex, index, count);
	DARRAY_OPEN_AT (&hierarchy->parentIndex, index, count);
	DARRAY_OPEN_AT (&hierarchy->name, index, count);
	DARRAY_OPEN_AT (&hierarchy->tag, index, count);
	DARRAY_OPEN_AT (&hierarchy->modified, index, count);
	DARRAY_OPEN_AT (&hierarchy->localMatrix, index, count);
	DARRAY_OPEN_AT (&hierarchy->localInverse, index, count);
	DARRAY_OPEN_AT (&hierarchy->worldMatrix, index, count);
	DARRAY_OPEN_AT (&hierarchy->worldInverse, index, count);
	DARRAY_OPEN_AT (&hierarchy->localRotation, index, count);
	DARRAY_OPEN_AT (&hierarchy->localScale, index, count);
	DARRAY_OPEN_AT (&hierarchy->worldRotation, index, count);
	DARRAY_OPEN_AT (&hierarchy->worldScale, index, count);
}

static void
hierarchy_close (hierarchy_t *hierarchy, uint32_t index, uint32_t count)
{
	if (count) {
		DARRAY_CLOSE_AT (&hierarchy->transform, index, count);
		DARRAY_CLOSE_AT (&hierarchy->entity, index, count);
		DARRAY_CLOSE_AT (&hierarchy->childCount, index, count);
		DARRAY_CLOSE_AT (&hierarchy->childIndex, index, count);
		DARRAY_CLOSE_AT (&hierarchy->parentIndex, index, count);
		DARRAY_CLOSE_AT (&hierarchy->name, index, count);
		DARRAY_CLOSE_AT (&hierarchy->tag, index, count);
		DARRAY_CLOSE_AT (&hierarchy->modified, index, count);
		DARRAY_CLOSE_AT (&hierarchy->localMatrix, index, count);
		DARRAY_CLOSE_AT (&hierarchy->localInverse, index, count);
		DARRAY_CLOSE_AT (&hierarchy->worldMatrix, index, count);
		DARRAY_CLOSE_AT (&hierarchy->worldInverse, index, count);
		DARRAY_CLOSE_AT (&hierarchy->localRotation, index, count);
		DARRAY_CLOSE_AT (&hierarchy->localScale, index, count);
		DARRAY_CLOSE_AT (&hierarchy->worldRotation, index, count);
		DARRAY_CLOSE_AT (&hierarchy->worldScale, index, count);
	}
}

static void
hierarchy_move (hierarchy_t *dst, const hierarchy_t *src,
				uint32_t dstIndex, uint32_t srcIndex, uint32_t count)
{
	memcpy (&dst->transform.a[dstIndex], &src->transform.a[srcIndex],
			count * sizeof(dst->transform.a[0]));
	memset (&src->transform.a[srcIndex], 0,
			count * sizeof(dst->transform.a[0]));
	memcpy (&dst->entity.a[dstIndex], &src->entity.a[srcIndex],
			count * sizeof(dst->entity.a[0]));
	memcpy (&dst->name.a[dstIndex], &src->name.a[srcIndex],
			count * sizeof(dst->name.a[0]));
	memcpy (&dst->tag.a[dstIndex], &src->tag.a[srcIndex],
			count * sizeof(dst->tag.a[0]));
	memset (&dst->modified.a[dstIndex], 1, count * sizeof(dst->modified.a[0]));
	memcpy (&dst->localMatrix.a[dstIndex], &src->localMatrix.a[srcIndex],
			count * sizeof(dst->localMatrix.a[0]));
	memcpy (&dst->localInverse.a[dstIndex], &src->localInverse.a[srcIndex],
			count * sizeof(dst->localInverse.a[0]));
	memcpy (&dst->localRotation.a[dstIndex], &src->localRotation.a[srcIndex],
			count * sizeof(dst->localRotation.a[0]));
	memcpy (&dst->localScale.a[dstIndex], &src->localScale.a[srcIndex],
			count * sizeof(dst->localScale.a[0]));

	for (uint32_t i = 0; i < count; i++) {
		dst->transform.a[dstIndex + i]->hierarchy = dst;
		dst->transform.a[dstIndex + i]->index = dstIndex + i;
	}
}

static void
hierarchy_init (hierarchy_t *dst, uint32_t index,
				uint32_t parentIndex, uint32_t childIndex, uint32_t count)
{
	memset (&dst->transform.a[index], 0,
			count * sizeof(dst->transform.a[0]));
	memset (&dst->entity.a[index], 0, count * sizeof(dst->entity.a[0]));
	memset (&dst->name.a[index], 0, count * sizeof(dst->name.a[0]));
	memset (&dst->tag.a[index], 0, count * sizeof(dst->tag.a[0]));
	memset (&dst->modified.a[index], 1, count * sizeof(dst->modified.a[0]));

	for (uint32_t i = 0; i < count; i++) {
		mat4fidentity (dst->localMatrix.a[index]);
		mat4fidentity (dst->localInverse.a[index]);
		dst->localRotation.a[index] = (vec4f_t) { 0, 0, 0, 1 };
		dst->localScale.a[index] = (vec4f_t) { 1, 1, 1, 1 };

		dst->parentIndex.a[index + i] = parentIndex;
		dst->childCount.a[index + i] = 0;
		dst->childIndex.a[index + i] = childIndex;
	}
}

static uint32_t
hierarchy_insert (hierarchy_t *dst, const hierarchy_t *src,
				  uint32_t dstParent, uint32_t srcRoot, uint32_t count)
{
	uint32_t    insertIndex;	// where the transforms will be inserted
	uint32_t    childIndex;		// where the transforms' children will inserted

	// The newly added transforms are always last children of the parent
	// transform
	insertIndex = dst->childIndex.a[dstParent] + dst->childCount.a[dstParent];

	// By design, all of a transform's children are in one contiguous block,
	// and the blocks of children for each transform are ordered by their
	// parents. Thus the child index of each transform increases monotonically
	// for each child index in the array, regardless of the level of the owning
	// transform (higher levels always come before lower levels).
	uint32_t    neighbor = insertIndex - 1;	// insertIndex never zero
	childIndex = dst->childIndex.a[neighbor] + dst->childCount.a[neighbor];

	// Any transforms that come after the inserted transforms need to have
	// thier indices adjusted.
	hierarchy_UpdateTransformIndices (dst, insertIndex, count);
	// The parent transform's child index is not affected, but the child
	// indices of all transforms immediately after the parent transform are.
	hierarchy_UpdateChildIndices (dst, dstParent + 1, count);
	hierarchy_UpdateParentIndices (dst, childIndex, count);

	// The beginning of the block of children for the new transforms was
	// computed from the pre-insert indices of the related transforms, thus
	// the index must be updated by the number of transforms being inserted
	// (it would have been updated thusly if the insert was done before
	// updating the indices of the other transforms).
	childIndex += count;

	hierarchy_open (dst, insertIndex, count);
	if (src) {
		hierarchy_move (dst, src, insertIndex, srcRoot, count);
	} else {
		hierarchy_init (dst, insertIndex, dstParent, childIndex, count);
	}
	for (uint32_t i = 0; i < count; i++) {
		dst->parentIndex.a[insertIndex + i] = dstParent;
		dst->childIndex.a[insertIndex + i] = childIndex;
		dst->childCount.a[insertIndex + i] = 0;
	}

	dst->childCount.a[dstParent] += count;
	return insertIndex;
}

static void
hierarchy_insert_children (hierarchy_t *dst, const hierarchy_t *src,
						   uint32_t dstParent, uint32_t srcRoot)
{
	uint32_t    insertIndex;
	uint32_t    childIndex = src->childIndex.a[srcRoot];
	uint32_t    childCount = src->childCount.a[srcRoot];

	if (childCount) {
		insertIndex = hierarchy_insert (dst, src, dstParent,
										childIndex, childCount);
		for (uint32_t i = 0; i < childCount; i++) {
			hierarchy_insert_children (dst, src, insertIndex + i,
									   childIndex + i);
		}
	}
}

uint32_t
Hierarchy_InsertHierarchy (hierarchy_t *dst, const hierarchy_t *src,
						   uint32_t dstParent, uint32_t srcRoot)
{
	uint32_t    insertIndex;

	if (dstParent == null_transform) {
		if (dst->transform.size) {
			Sys_Error ("attempt to insert root in non-empty hierarchy");
		}
		hierarchy_open (dst, 0, 1);
		hierarchy_move (dst, src, 0, srcRoot, 1);
		dst->parentIndex.a[0] = null_transform;
		dst->childIndex.a[0] = 1;
		dst->childCount.a[0] = 0;
		insertIndex = 0;
	} else {
		if (!dst->transform.size) {
			Sys_Error ("attempt to insert non-root in empty hierarchy");
		}
		insertIndex = hierarchy_insert (dst, src, dstParent, srcRoot, 1);
	}
	// if src is null, then inserting a new transform which has no children
	if (src) {
		hierarchy_insert_children (dst, src, insertIndex, srcRoot);
	}
	Hierarchy_UpdateMatrices (dst);
	return insertIndex;
}

static void
hierarchy_remove_children (hierarchy_t *hierarchy, uint32_t index)
{
	uint32_t    childIndex = hierarchy->childIndex.a[index];
	uint32_t    childCount = hierarchy->childCount.a[index];
	uint32_t    parentIndex = hierarchy->parentIndex.a[index];
	uint32_t    nieceIndex = null_transform;

	if (parentIndex != null_transform) {
		uint32_t    siblingIndex = hierarchy->childIndex.a[parentIndex];
		siblingIndex += hierarchy->childCount.a[parentIndex] - 1;
		nieceIndex = hierarchy->childIndex.a[siblingIndex];
	}
	for (uint32_t i = childCount; i-- > 0; ) {
		hierarchy_remove_children (hierarchy, childIndex + i);
	}
	hierarchy_close (hierarchy, childIndex, childCount);
	hierarchy->childCount.a[index] = 0;

	if (childCount) {
		hierarchy_UpdateTransformIndices (hierarchy, childIndex, -childCount);
		hierarchy_UpdateChildIndices (hierarchy, index, -childCount);
		if (nieceIndex != null_transform) {
			hierarchy_UpdateParentIndices (hierarchy, nieceIndex, -childCount);
		}
	}
}

void
Hierarchy_RemoveHierarchy (hierarchy_t *hierarchy, uint32_t index)
{
	uint32_t    parentIndex = hierarchy->parentIndex.a[index];
	uint32_t    childIndex = hierarchy->childIndex.a[index];
	uint32_t    siblingIndex = null_transform;
	if (parentIndex != null_transform) {
		siblingIndex = hierarchy->childIndex.a[parentIndex];
	}
	hierarchy_remove_children (hierarchy, index);
	hierarchy_close (hierarchy, index, 1);
	if (siblingIndex != null_transform) {
		hierarchy_UpdateTransformIndices (hierarchy, index, -1);
		hierarchy_UpdateChildIndices (hierarchy, siblingIndex, -1);
		hierarchy_UpdateParentIndices (hierarchy, childIndex - 1, -1);
	}
}

hierarchy_t *
Hierarchy_New (scene_t *scene, int createRoot)
{
	scene_resources_t *res = scene->resources;
	hierarchy_t *hierarchy = PR_RESNEW_NC (res->hierarchies);
	hierarchy->scene = scene;

	hierarchy->prev = &scene->hierarchies;
	hierarchy->next = scene->hierarchies;
	if (scene->hierarchies) {
		scene->hierarchies->prev = &hierarchy->next;
	}
	scene->hierarchies = hierarchy;

	size_t      grow = 16;
	DARRAY_INIT (&hierarchy->transform, grow);
	DARRAY_INIT (&hierarchy->entity, grow);
	DARRAY_INIT (&hierarchy->childCount, grow);
	DARRAY_INIT (&hierarchy->childIndex, grow);
	DARRAY_INIT (&hierarchy->parentIndex, grow);
	DARRAY_INIT (&hierarchy->name, grow);
	DARRAY_INIT (&hierarchy->tag, grow);
	DARRAY_INIT (&hierarchy->modified, grow);
	DARRAY_INIT (&hierarchy->localMatrix, grow);
	DARRAY_INIT (&hierarchy->localInverse, grow);
	DARRAY_INIT (&hierarchy->worldMatrix, grow);
	DARRAY_INIT (&hierarchy->worldInverse, grow);
	DARRAY_INIT (&hierarchy->localRotation, grow);
	DARRAY_INIT (&hierarchy->localScale, grow);
	DARRAY_INIT (&hierarchy->worldRotation, grow);
	DARRAY_INIT (&hierarchy->worldScale, grow);

	if (createRoot) {
		hierarchy_open (hierarchy, 0, 1);
		hierarchy_init (hierarchy, 0, null_transform, 1, 1);
	}

	return hierarchy;
}

void
Hierarchy_Delete (hierarchy_t *hierarchy)
{
	if (hierarchy->next) {
		hierarchy->next->prev = hierarchy->prev;
	}
	*hierarchy->prev = hierarchy->next;

	scene_resources_t *res = hierarchy->scene->resources;
	for (size_t i = 0; i < hierarchy->transform.size; i++) {
		PR_RESFREE (res->transforms, hierarchy->transform.a[i]);
	}
	DARRAY_CLEAR (&hierarchy->transform);
	DARRAY_CLEAR (&hierarchy->entity);
	DARRAY_CLEAR (&hierarchy->childCount);
	DARRAY_CLEAR (&hierarchy->childIndex);
	DARRAY_CLEAR (&hierarchy->parentIndex);
	DARRAY_CLEAR (&hierarchy->name);
	DARRAY_CLEAR (&hierarchy->tag);
	DARRAY_CLEAR (&hierarchy->modified);
	DARRAY_CLEAR (&hierarchy->localMatrix);
	DARRAY_CLEAR (&hierarchy->localInverse);
	DARRAY_CLEAR (&hierarchy->worldMatrix);
	DARRAY_CLEAR (&hierarchy->worldInverse);
	DARRAY_CLEAR (&hierarchy->localRotation);
	DARRAY_CLEAR (&hierarchy->localScale);
	DARRAY_CLEAR (&hierarchy->worldRotation);
	DARRAY_CLEAR (&hierarchy->worldScale);
	PR_RESFREE (res->hierarchies, hierarchy);
}
