/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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 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/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "hsTypes.h"
#include "Max.h"


#include "plDistTree.h"

plDistTree::plDistTree()
:	fRoot(-1)
{
}

plDistTree::~plDistTree()
{
}

void plDistTree::Reset()
{
	fRoot = -1;
	fNodes.Reset();
}

void plDistTree::AddBoxIData(const Box3& box, const Box3& fade, UInt32 iData)
{
	fRoot = IAddNodeRecur(fRoot, box, fade, iData);
}

BOOL plDistTree::BoxClear(const Box3& box, const Box3& fade) const
{
	return IBoxClearRecur(fRoot, box, fade);
}

BOOL plDistTree::PointClear(const Point3& pt, const Box3& fade) const
{
	return IPointClearRecur(fRoot, pt, fade);
}

BOOL plDistTree::IFadesClear(const Box3& fade0, const Box3& fade1) const
{
	// Only two ways fade can come out non-overlapping.
	// Either fade0 fades out before fade1 fades in, or v.v.

	// First case, does fade0 fade out?
	if( fade0.Max()[2] > 0 )
	{
		// does fade1 fade in?
		if( fade1.Min()[2] < 0 )
		{
			// Okay they do, does fade0 fade out before fade1 fades in?
			if( fade0.Max()[0] <= fade1.Min()[0] )
				return true;
		}
	}
	// Second case, same thing but reversed order
	if( fade1.Max()[2] > 0 )
	{
		// does fade0 fade in?
		if( fade0.Min()[2] < 0 )
		{
			// Okay they do, does fade1 fade out before fade0 fades in?
			if( fade1.Max()[0] <= fade0.Min()[0] )
				return true;
		}
	}
	return false;
}

BOOL plDistTree::IBox0ContainsBox1(const Box3& box0, const Box3& box1, const Box3& fade0, const Box3& fade1) const
{
#ifdef MAX_CONTAINS_WORKS
	if( !box0.Contains(box1) )
		return false;
#else MAX_CONTAINS_WORKS
	if( (box0.Min()[0] > box1.Min()[0])
		||(box0.Min()[1] > box1.Min()[1])
		||(box0.Min()[2] > box1.Min()[2])
		||(box0.Max()[0] < box1.Max()[0])
		||(box0.Max()[1] < box1.Max()[1])
		||(box0.Max()[2] < box1.Max()[2]) )
		return false;
#endif // MAX_CONTAINS_WORKS

	if( IFadesClear(fade0, fade1) )
		return false;

	return true;
}

BOOL plDistTree::IBoxesClear(const Box3& box0, const Box3& box1) const
{
	return (box0.Min()[0] > box1.Max()[0])
		||(box0.Max()[0] < box1.Min()[0])

		||(box0.Min()[1] > box1.Max()[1])
		||(box0.Max()[1] < box1.Min()[1])

		||(box0.Min()[2] > box1.Max()[2])
		||(box0.Max()[2] < box1.Min()[2]);
}

BOOL plDistTree::IBoxClearRecur(Int32 iNode, const Box3& box, const Box3& fade) const
{
	if( iNode < 0 )
		return true;

	if( IBoxesClear(fNodes[iNode].fBox, box) )
		return true;

	if( IFadesClear(fNodes[iNode].fFade, fade) )
		return true;

	if( fNodes[iNode].IsLeaf() )
		return false;

	int i;
	for( i = 0; i < 8; i++ )
	{
		if( !IBoxClearRecur(fNodes[iNode].fChildren[i], box, fade) )
			return false;
	}
	return true;
}

BOOL plDistTree::IPointClearRecur(Int32 iNode, const Point3& pt, const Box3& fade) const
{
	if( iNode < 0 )
		return true;

	if( !fNodes[iNode].fBox.Contains(pt) )
		return true;

	if( IFadesClear(fNodes[iNode].fFade, fade) )
		return true;

	if( fNodes[iNode].IsLeaf() )
		return false;

	int i;
	for( i = 0; i < 8; i++ )
	{
		if( !IPointClearRecur(fNodes[iNode].fChildren[i], pt, fade) )
			return false;
	}
	return true;
}


Int32 plDistTree::IAddNodeRecur(Int32 iNode, const Box3& box, const Box3& fade, UInt32 iData)
{
	// if iNode < 0, make a node for box and return that.
	if( iNode < 0 )
	{
		return INextNode(box, fade, iData);
	}

	// if the box is contained
	//		if this node is a leaf, pitch the box
	//
	//		else
	//			recur on one of 8 children, based on
	//			box's center relative to node center.

	// if the box doesn't intercect this node,
	//		replace this node with a node of combined boxes.
	//		this node becomes sibling of box.

	// if the box does intercect, but isn't contained
	//		same thing.


#if 0
	if( IBox0ContainsBox1(fNodes[iNode].fBox, box, fNodes[iNode].fFade, fade) )
	{
		if( !fNodes[iNode].IsLeaf() )
#else
	if( !fNodes[iNode].IsLeaf() && IBox0ContainsBox1(fNodes[iNode].fBox, box, fNodes[iNode].fFade, fade) )
	{
#endif
		{
			Int32 iChild = IGetChild(fNodes[iNode].fBox, box);
		
			Int32 iChildNode = IAddNodeRecur(fNodes[iNode].fChildren[iChild], box, fade, iData);
			fNodes[iNode].fChildren[iChild] = iChildNode;

			fNodes[iNode].fBox += fNodes[fNodes[iNode].fChildren[iChild]].fBox;
		}
		return iNode;
	}
	else
	{
		return IMergeNodes(iNode, box, fade, iData);
	}
}

Int32 plDistTree::IMergeNodes(Int32 iNode, const Box3& box, const Box3& fade, UInt32 iData)
{
	Box3 parBox = box;
	parBox += fNodes[iNode].fBox;

	Int32 pNode = INextNode(parBox, NonFade(), UInt32(-1));
	Int32 iChild = IGetChild(parBox, box);

	Int32 cNode = INextNode(box, fade, iData);

	fNodes[pNode].fChildren[iChild] = cNode;

	// Put the original node in the opposite quadrant from the child.
	// This handles the case where one of the bounds completely contains
	// the other. The octant structure of the tree isn't relied on, it
	// only helps balance the tree. So being wrong here won't hurt anything.
	iChild = iChild ^ 0x7;

	fNodes[pNode].fChildren[iChild] = iNode;

	fNodes[pNode].SetIsLeaf(false);

	return pNode;
}

Int32 plDistTree::IGetChild(const Box3& parent, const Box3& child) const
{
	Point3 parCenter = parent.Center();
	Point3 chiCenter = child.Center();

	Int32 idx = ((parCenter[0] < chiCenter[0]) << 0)
		| ((parCenter[1] < chiCenter[1]) << 1)
		| ((parCenter[2] < chiCenter[2]) << 2);
	return idx;
}

Int32 plDistTree::INextNode(const Box3& box, const Box3& fade, UInt32 iData)
{
	Int32 iNode = fNodes.GetCount();

	fNodes.Push();

	fNodes[iNode].fFlags = plDistNode::kIsLeaf;
	fNodes[iNode].fBox = box;
	fNodes[iNode].fFade = fade;
	fNodes[iNode].fIData = iData;
	fNodes[iNode].fChildren[0]
		= fNodes[iNode].fChildren[1]
		= fNodes[iNode].fChildren[2]
		= fNodes[iNode].fChildren[3]
		= fNodes[iNode].fChildren[4]
		= fNodes[iNode].fChildren[5]
		= fNodes[iNode].fChildren[6]
		= fNodes[iNode].fChildren[7] = -1;

	return iNode;
}

void plDistTree::HarvestBox(const Box3& box, Tab<Int32>& out) const
{
	IHarvestBoxRecur(fRoot, box, out);
}

void plDistTree::IHarvestBoxRecur(Int32 iNode, const Box3& box, Tab<Int32>& out) const
{
	if( iNode < 0 )
		return;

	if( IBoxesClear(fNodes[iNode].fBox, box) )
		return;

	if( fNodes[iNode].IsLeaf() )
	{
		out.Append(1, &iNode);
	}
	else
	{
		int i;
		for( i = 0; i < 8; i++ )
			IHarvestBoxRecur(fNodes[iNode].fChildren[i], box, out);
	}
}