/* bzflag
 * Copyright (c) 1993-2017 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "common.h"

// system headers
#include <math.h>

// implementation header
#include "Octree.h"

// local headers
#include "Occluder.h"

// common headers
#include "Extents.h"
#include "Intersect.h"

#include "StateDatabase.h"
static bool F2BSORT = true;//FIXME


// FIXME - do something about occluders vs. gridding


static const int fullListBreak = 3; // FIXME
static const float testFudge = 0.1f;

static int maxDepth = 0;
static int minElements = 16;

static int leafNodes = 0;
static int totalNodes = 0;
static int totalElements = 0;

// FIXME - make them class static members once the memory is flattened
static int CullListSize = 0;
static int CullListCount = 0;
static SceneNode** CullList = NULL;
static const Frustum* CullFrustum = NULL;
static int ShadowCount = 0;
static const float (*ShadowPlanes)[4];

static OccluderManager OcclMgrs[2];
static OccluderManager* OcclMgr = &OcclMgrs[0];


#ifdef USE_REAL_INLINE
inline static void addCullListNode (SceneNode* node)
{
  CullList[CullListCount] = node;
  CullListCount++;
  return;
}
#else
#define addCullListNode(node)       \
  CullList[CullListCount] = node;   \
  CullListCount++;
#endif


inline static void squeezeChildren (OctreeNode** children)
{
  for (int dst = 0; dst < 8; dst++) {
    if (children[dst] == NULL) {
      // replace with the next non-NULL
      for (int src = (dst + 1); src < 8; src++) {
	if (children[src] != NULL) {
	  children[dst] = children[src];
	  children[src] = NULL;
	  break;
	}
      }
    }
  }
  return;
}


//////////////////////////////////////////////////////////////////////////////
//
// The Octree
//

Octree::Octree()
{
  root = NULL;
  CullList = NULL;
  CullFrustum = NULL;
  CullListSize = 0;
  CullListCount = 0;
  OcclMgr = &OcclMgr[0];
  return;
}


Octree::~Octree()
{
  clear();
  return;
}


void Octree::setOccluderManager(int occlmgr)
{
  if (occlmgr == 1) {
    OcclMgr = &OcclMgrs[1]; // mirror
  } else {
    OcclMgr = &OcclMgrs[0]; // normal
  }
  return;
}


void Octree::clear ()
{
  delete root;
  root = NULL;
  CullList = NULL;
  CullFrustum = NULL;
  CullListSize = 0;
  CullListCount = 0;
  OcclMgrs[0].clear();
  OcclMgrs[1].clear();
  return;
}


void Octree::addNodes (SceneNode** list, int listSize, int depth, int elements)
{
  int i;

  if (root) {
    clear();
  }

  // just in case
  for (i = 0; i < listSize; i++) {
    list[i]->octreeState = SceneNode::OctreeCulled;
  }

  maxDepth = depth;
  minElements = elements;

  CullList = list;
  CullListSize = listSize;

  getExtents (list, listSize);

  // making babies
  root = new OctreeNode(0, extents, list, listSize);

  leafNodes = 0;
  totalNodes = 0;
  totalElements = 0;
  root->tallyStats();

  logDebugMessage(2,"Octree scene nodes = %i\n", listSize);
  for (i = 0; i < 3; i++) {
    logDebugMessage(2,"  grid extent[%i] = %f, %f\n", i, extents.mins[i],
					       extents.maxs[i]);
  }
  for (i = 0; i < 3; i++) {
    logDebugMessage(2,"  visual extent[%i] = %f, %f\n", i, visualExtents.mins[i],
						 visualExtents.maxs[i]);
  }
  logDebugMessage(2,"Octree leaf nodes  = %i\n", leafNodes);
  logDebugMessage(2,"Octree total nodes = %i\n", totalNodes);
  logDebugMessage(2,"Octree total elements = %i\n", totalElements);

  return;
}


int Octree::getFrustumList (SceneNode** list, int listSize,
			    const Frustum* frustum) const
{
  if (!root) {
    return 0;
  }
  F2BSORT = BZDB.isTrue("f2bsort");

  if (listSize > CullListSize) {
    printf ("Octree::getFrustumList() internal error!\n");
    exit (1);
  }

  CullFrustum = frustum;
  CullList = list;
  CullListCount = 0;

  // update the occluders before using them
  OcclMgr->update(CullFrustum);

  // get the nodes
  root->getFrustumList();

  // pick new occluders
  OcclMgr->select(CullList, CullListCount);

  return CullListCount;
}


int Octree::getRadarList (SceneNode** list, int listSize,
			  const Frustum* frustum) const
{
  // This is basically the same as Octree::getFrustumList(),
  // except that it doesn't use the occluders. This duplication
  // was done to try and speed-up this low level code.

  if (!root) {
    return 0;
  }

  if (listSize > CullListSize) {
    printf ("Octree::getRadarList() internal error!\n");
    exit (1);
  }

  CullFrustum = frustum;
  CullList = list;
  CullListCount = 0;

  // get the nodes
  root->getRadarList();

  return CullListCount;
}


int Octree::getShadowList (SceneNode** list, int listSize,
			   int planeCount, const float (*planes)[4]) const
{
  if (!root) {
    return 0;
  }

  if (listSize > CullListSize) {
    printf ("Octree::getShadowList() internal error!\n");
    exit (1);
  }

  ShadowCount = planeCount;
  ShadowPlanes = planes;

  CullList = list;
  CullListCount = 0;

  // update the occluders before using them
  // FIXME: use occluders later?  OcclMgr->update(CullFrustum);

  // get the nodes
  root->getShadowList();

  return CullListCount;
}


void Octree::getExtents (SceneNode** list, int listSize)
{
  int i;

  Extents tmpExts;

  for (i = 0; i < listSize; i++) {
    SceneNode* node = list[i];
    const Extents& exts = node->getExtents();
    tmpExts.expandToBox(exts);
  }

  visualExtents = tmpExts;

  // find the longest axis
  float width = -MAXFLOAT;
  for (i = 0; i < 3; i++) {
    float axisWidth = tmpExts.getWidth(i);
    if (axisWidth > width) {
      width = axisWidth;
    }
  }

  extents = tmpExts;

  // make it a cube, with Z on its minimum
  for (i = 0; i < 2; i++) {
    const float axisWidth = tmpExts.getWidth(i);
    if (axisWidth < width) {
      const float adjust = 0.5f * (width - axisWidth);
      extents.mins[i] = extents.mins[i] - adjust;
      extents.maxs[i] = extents.maxs[i] + adjust;
    }
  }
  extents.maxs[2] = extents.mins[2] + width;

  return;
}


void Octree::draw () const
{
  if (!root) {
    return;
  }

  GLboolean usingTextures;
  glGetBooleanv(GL_TEXTURE_2D, &usingTextures);
  glDisable (GL_TEXTURE_2D);

  // CullFrustum needs to still be valid here
  // It should still exist in SceneRender.cxx
  // when this function is called.
  root->draw ();
  OcclMgr->update(CullFrustum);
  OcclMgr->draw();

  if (usingTextures) {
    glEnable (GL_TEXTURE_2D);
  }

  return;
}


//////////////////////////////////////////////////////////////////////////////
//
// The Nodes
//


OctreeNode::OctreeNode(unsigned char _depth, const Extents& exts,
		       SceneNode** _list, int _listSize)
{
  int i;

  depth = _depth;

  for (i = 0; i < 8; i++) {
    children[i] = NULL;
    squeezed[i] = NULL;
  }
  childCount = 0;

  // copy the incoming list
  const int listBytes = _listSize * sizeof (SceneNode*);
  list = (SceneNode**) malloc (listBytes);
  memcpy (list, _list, listBytes);

  // copy the extents, and make a slighty puffed up version
  extents = exts;
  Extents testExts;
  testExts = exts;
  testExts.addMargin(testFudge);

  // find all of the intersecting nodes
  listSize = 0;
  for (i = 0; i < _listSize; i++) {
    SceneNode* node = _list[i];
    if (node->inAxisBox (testExts)) {
      list[listSize] = node;
      listSize++;
    }
  }

  // count will remain as the total numbers of
  // scene nodes that intersect with this cell
  count = listSize;

  // resize the list to save space
  // FIXME: Correctly handle the situation of the realloc failing (which would return a NULL pointer)
  list = (SceneNode**) realloc (list, count * sizeof (SceneNode*));

  // return if this is a leaf node
  if (((int)depth >= maxDepth) || (listSize <= minElements)) {
    resizeCell();
    //logDebugMessage(4, ("LEAF NODE: depth = %d, items = %i\n", depth, count);
    return;
  }

  // sow the seeds
  depth++;
  makeChildren();

  // non NULLs first
  squeezeChildren (squeezed);

  // resize this branch cell
  resizeCell();

  // leave some lists for FullyVisible grabs
  if ((depth % fullListBreak) != 0) {
    listSize = 0;
    free (list);
    list = NULL;
  }

  //logDebugMessage(4, ("BRANCH NODE: depth = %d, squeezed = %i\n", depth, childCount);

  return;
}


void OctreeNode::makeChildren ()
{
  int side[3];    // the axis sides  (0 or 1)
  Extents exts;
  float center[3];

  // setup the center point
  for (int i = 0; i < 3; i++) {
    center[i] = 0.5f * (extents.maxs[i] + extents.mins[i]);
  }

  childCount = 0;
  const float* extentSet[3] = { extents.mins, center, extents.maxs };

  for (side[0] = 0; side[0] < 2; side[0]++) {
    for (side[1] = 0; side[1] < 2; side[1]++) {
      for (side[2] = 0; side[2] < 2; side[2]++) {

	// calculate the child's extents
	for (int a = 0; a < 3; a++) {
	  exts.mins[a] = extentSet[side[a]+0][a];
	  exts.maxs[a] = extentSet[side[a]+1][a];
	}

	int kid = side[0] + (2 * side[1]) + (4 * side[2]);

	squeezed[kid] = new OctreeNode (depth, exts, list, count);

	if (squeezed[kid]->getCount() == 0) {
	  delete squeezed[kid];
	  squeezed[kid] = NULL;
	}
	else {
	  childCount++;
	}
	children[kid] = squeezed[kid];
      }
    }
  }

  return;
}


void OctreeNode::resizeCell()
{
  int i;
  Extents absExts;

  for (i = 0; i < count; i++) {
    SceneNode* node = list[i];
    const Extents& nodeExts = node->getExtents();
    absExts.expandToBox(nodeExts);
  }

  for (i = 0; i < 3; i++) {
    if (absExts.mins[i] > extents.mins[i])
      extents.mins[i] = absExts.mins[i];
    if (absExts.maxs[i] < extents.maxs[i])
      extents.maxs[i] = absExts.maxs[i];
  }

  return;
}


OctreeNode::~OctreeNode()
{
  for (int i = 0; i < 8; i++) {
    delete squeezed[i];
  }
  free (list);
  return;
}


void OctreeNode::getFrustumList () const
{
  IntersectLevel level = testAxisBoxInFrustum (extents, CullFrustum);

  if (level == Outside) {
    return;
  }

  IntersectLevel occLevel = OcclMgr->occlude (extents, count);
  if (occLevel == Contained) {
    return;
  }

  if ((level == Contained) && (occLevel == Outside)) {
    getFullyVisible ();
  }

  // this cell is only partially contained within
  // the frustum and is not being fully occluded

  // is the viewer in this node?
  // add the closest nodes first

  if (childCount > 0) {
    if (F2BSORT) {
      const float* dir = CullFrustum->getDirection();
      unsigned char dirbits = 0;
      if (dir[0] < 0.0f) dirbits |= (1 << 0);
      if (dir[1] < 0.0f) dirbits |= (1 << 1);
      if (dir[2] < 0.0f) dirbits |= (1 << 2);
      const OctreeNode* onode;

  #define GET_NODE(x)		\
    onode = children[(x)];	\
    if (onode != NULL) {	\
      onode->getFrustumList ();	\
    }

  #define GET_FULL_NODE(x)		\
    onode = children[(x)];		\
    if (onode != NULL) {		\
      onode->getFullyVisibleOcclude ();	\
    }

    if (occLevel == Outside) {
	GET_NODE(dirbits);				// 0:  0,0,0
	dirbits ^= (1 << 0);
	GET_NODE(dirbits);				// 1:  1,0,0
	dirbits ^= (1 << 0) | (1 << 1);
	GET_NODE(dirbits);				// 2:  0,1,0
	dirbits ^= (1 << 1) | (1 << 2);
	GET_NODE(dirbits);				// 3:  0,0,1
	dirbits ^= (1 << 0) | (1 << 1) | (1 << 2);
	GET_NODE(dirbits);				// 4:  1,1,0
	dirbits ^= (1 << 1) | (1 << 2);
	GET_NODE(dirbits);				// 5:  1,0,1
	dirbits ^= (1 << 0) | (1 << 1);
	GET_NODE(dirbits);				// 6:  0,1,1
	dirbits ^= (1 << 0);
	GET_NODE(dirbits);				// 7:  1,1,1
      } else {
	GET_FULL_NODE(dirbits);				// 0:  0,0,0
	dirbits ^= (1 << 0);
	GET_FULL_NODE(dirbits);				// 1:  1,0,0
	dirbits ^= (1 << 0) | (1 << 1);
	GET_FULL_NODE(dirbits);				// 2:  0,1,0
	dirbits ^= (1 << 1) | (1 << 2);
	GET_FULL_NODE(dirbits);				// 3:  0,0,1
	dirbits ^= (1 << 0) | (1 << 1) | (1 << 2);
	GET_FULL_NODE(dirbits);				// 4:  1,1,0
	dirbits ^= (1 << 1) | (1 << 2);
	GET_FULL_NODE(dirbits);				// 5:  1,0,1
	dirbits ^= (1 << 0) | (1 << 1);
	GET_FULL_NODE(dirbits);				// 6:  0,1,1
	dirbits ^= (1 << 0);
	GET_FULL_NODE(dirbits);				// 7:  1,1,1
      }
    } else {
      if (occLevel == Outside) {
	for (int i = 0; i < childCount; i++) {
	  squeezed[i]->getFrustumList ();
	}
      } else {
	for (int i = 0; i < childCount; i++) {
	  squeezed[i]->getFullyVisibleOcclude ();
	}
      }
    }
  }
  else {
    for (int i = 0; i < listSize; i++) {
      SceneNode* node = list[i];
      if (node->octreeState == SceneNode::OctreeCulled) {
	addCullListNode (node);
	node->octreeState = SceneNode::OctreePartial;
      }
    }
  }

  return;
}
/*
  // FIXME - faster then the recursive version?

  static struct NodeStack {
    OctreeNode* node;
    int child;
  } nodeStack[OctreeNode::maxDepth];
  static int level;
  static int childNum;
  static OctreeNode* working;

  level = 0;
  nodeStack[0].child = 0;
  working = (OctreeNode*) this;

  while (level >= 0) {
    childNum = nodeStack[level].child;
    if (working->getChildren() > childNum) {
      nodeStack[level].node = working;
      nodeStack[level].child++;
      working = working->getChild(childNum);
      level++;
      nodeStack[level].child = 0;
    } else {
      static int i;
      for (i = 0; i < working->getListSize(); i++) {
	static SceneNode* node;
	node = working->getList()[i];
	if (node->octreeState == SceneNode::OctreeCulled) {
	  addCullListNode (node);
	}
	node->octreeState = SceneNode::OctreeVisible;
      }
      level--;
      working = nodeStack[level].node;
    }
  }
*/


void OctreeNode::getRadarList () const
{
  IntersectLevel level = testAxisBoxInFrustum (extents, CullFrustum);

  if (level == Outside) {
    return;
  }

  if (level == Contained) {
    getFullyVisible ();
    return;
  }

  // this cell is only partially contained within
  // the frustum and is not being fully occluded

  if (childCount > 0) {
    for (int i = 0; i < childCount; i++) {
      squeezed[i]->getRadarList ();
    }
  }
  else {
    for (int i = 0; i < listSize; i++) {
      SceneNode* node = list[i];
      if (node->octreeState == SceneNode::OctreeCulled) {
	addCullListNode (node);
	node->octreeState = SceneNode::OctreePartial;
      }
    }
  }

  return;
}


void OctreeNode::getFullyVisible () const
{
  if ((childCount > 0) && (listSize == 0)) {
    for (int i = 0; i < childCount; i++) {
      squeezed[i]->getFullyVisible ();
    }
  }
  else {
    for (int i = 0; i < listSize; i++) {
      SceneNode* node = list[i];
      SceneNode::CullState& state = node->octreeState;
      if (state == SceneNode::OctreeCulled) {
	addCullListNode (node);
      }
      state = SceneNode::OctreeVisible;
    }
  }
  return;
}


void OctreeNode::getFullyVisibleOcclude () const
{
  IntersectLevel occLevel = OcclMgr->occlude (extents, count);
  if (occLevel == Contained) {
    return;
  }

  if (occLevel == Outside) {
    getFullyVisible ();
  }

  // this cell is only partially contained within
  // the frustum and is not being fully occluded

  if (childCount > 0) {
    for (int i = 0; i < childCount; i++) {
      squeezed[i]->getFullyVisibleOcclude ();
    }
  }
  else {
    for (int i = 0; i < listSize; i++) {
      SceneNode* node = list[i];
      if (node->octreeState == SceneNode::OctreeCulled) {
	addCullListNode (node);
	node->octreeState = SceneNode::OctreePartial;
      }
    }
  }

  return;
}


void OctreeNode::getShadowList () const
{
  IntersectLevel level = testAxisBoxOcclusion (extents,
					       ShadowPlanes, ShadowCount);
  if (level == Outside) {
    return;
  }

  if (level == Contained) {
    getFullyShadow ();
    return;
  }

  // this cell is only partially contained within the shadow
  // volume we'll have to test all of its sub-cells as well.

  if (childCount > 0) {
    for (int i = 0; i < childCount; i++) {
      squeezed[i]->getShadowList ();
    }
  }
  else {
    for (int i = 0; i < listSize; i++) {
      SceneNode* node = list[i];
      if (node->octreeState == SceneNode::OctreeCulled) {
	addCullListNode (node);
	node->octreeState = SceneNode::OctreePartial;
      }
    }
  }

  return;
}


void OctreeNode::getFullyShadow () const
{
  if ((childCount > 0) && (listSize == 0)) {
    for (int i = 0; i < childCount; i++) {
      squeezed[i]->getFullyShadow ();
    }
  }
  else {
    for (int i = 0; i < listSize; i++) {
      SceneNode* node = list[i];
      SceneNode::CullState& state = node->octreeState;
      if (state == SceneNode::OctreeCulled) {
	addCullListNode (node);
      }
      state = SceneNode::OctreeVisible;
    }
  }
  return;
}


const Extents& OctreeNode::getExtents() const
{
  return extents;
}


void OctreeNode::tallyStats()
{
  totalNodes++;
  totalElements += listSize;

  if (childCount > 0) {
    for (int i = 0; i < childCount; i++) {
      squeezed[i]->tallyStats();
    }
  } else {
    leafNodes++;
  }

  return;
}


void OctreeNode::draw ()
{
  GLfloat red[4] = {1.0f, 0.0f, 0.0f, 0.75f};    // red
  GLfloat blue[4] = {0.0f, 0.0f, 1.0f, 0.75f};   // blue
  GLfloat green[4] = {0.0f, 1.0f, 0.0f, 0.75f};  // green
  GLfloat yellow[4] = {1.0f, 1.0f, 0.0f, 0.75f}; // yellow
  GLfloat purple[4] = {1.0f, 0.0f, 1.0f, 0.75f}; // purple
  GLfloat *color = purple;
  int x, y, z, c;
  float points[5][3];
  IntersectLevel frustumCull = Contained;
  bool occludeCull = false;

  if (CullFrustum != NULL) {
    frustumCull = testAxisBoxInFrustum (extents, CullFrustum);
    occludeCull = OcclMgr->occludePeek (extents);
  }

  // choose the color
  switch (frustumCull) {
    case Outside:
      color = purple;
      break;
    case Partial:
      if (!occludeCull) {
	color = blue;
      } else {
	color = green;
      }
      break;
    case Contained:
      if (!occludeCull) {
	color = red;
      } else {
	color = yellow;
      }
      break;
  }
  glColor4fv (color);

  const float* exts[2] = { extents.mins, extents.maxs };

  // draw Z-normal squares
  for (z = 0; z < 2; z++) {
    for (c = 0; c < 4; c++) {
      x = ((c + 0) % 4) / 2;
      y = ((c + 1) % 4) / 2;
      points[c][0] = exts[x][0];
      points[c][1] = exts[y][1];
      points[c][2] = exts[z][2];
    }
    memcpy (points[4], points[0], sizeof (points[4]));
    glBegin (GL_LINE_STRIP);
    for (int i = 0; i < 5; i++) {
      glVertex3fv (points[i]);
    }
    glEnd ();
  }

  // draw the corner edges
  for (c = 0; c < 4; c++) {
    x = ((c + 0) % 4) / 2;
    y = ((c + 1) % 4) / 2;
    for (z = 0; z < 2; z++) {
      points[z][0] = exts[x][0];
      points[z][1] = exts[y][1];
      points[z][2] = exts[z][2];
    }
    glBegin (GL_LINE_STRIP);
    glVertex3fv (points[0]);
    glVertex3fv (points[1]);
    glEnd ();
  }

  // draw the kids
  for (c = 0; c < childCount; c++) {
    squeezed[c]->draw ();
  }

  return;
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
