//
//  KROctreeNode.cpp
//  Kraken Engine
//
//  Copyright 2026 Kearwood Gilbert. All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
//  
//  1. Redistributions of source code must retain the above copyright notice, this list of
//  conditions and the following disclaimer.
//  
//  2. Redistributions in binary form must reproduce the above copyright notice, this list
//  of conditions and the following disclaimer in the documentation and/or other materials
//  provided with the distribution.
//  
//  THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//  
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of Kearwood Gilbert.
//

#include "KROctreeNode.h"
#include "nodes/KRNode.h"
#include "nodes/KRCollider.h"

using namespace hydra;

KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds) : m_bounds(bounds)
{
  m_parent = parent;

  for (int i = 0; i < 8; i++) {
    m_children[i] = NULL;
  }
}

KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds, int iChild, KROctreeNode* pChild) : m_bounds(bounds)
{
  // This constructor is used when expanding the octree and replacing the root node with a new root that encapsulates it
  m_parent = parent;

  for (int i = 0; i < 8; i++) {
    m_children[i] = NULL;
  }
  m_children[iChild] = pChild;
  pChild->m_parent = this;
}

KROctreeNode::~KROctreeNode()
{
  for (int i = 0; i < 8; i++) {
    if (m_children[i] != NULL) {
      delete m_children[i];
    }
  }
}


AABB KROctreeNode::getBounds()
{
  return m_bounds;
}

void KROctreeNode::add(KRNode* pNode)
{
  int iChild = getChildIndex(pNode);
  if (iChild == -1) {
    m_sceneNodes.insert(pNode);
    pNode->addToOctreeNode(this);
  } else {
    if (m_children[iChild] == NULL) {
      m_children[iChild] = new KROctreeNode(this, getChildBounds(iChild));
    }
    m_children[iChild]->add(pNode);
  }
}

AABB KROctreeNode::getChildBounds(int iChild)
{
  Vector3 center = m_bounds.center();

  return AABB::Create(
     Vector3::Create(
    (iChild & 1) == 0 ? m_bounds.min.x : center.x,
    (iChild & 2) == 0 ? m_bounds.min.y : center.y,
    (iChild & 4) == 0 ? m_bounds.min.z : center.z),
     Vector3::Create(
    (iChild & 1) == 0 ? center.x : m_bounds.max.x,
    (iChild & 2) == 0 ? center.y : m_bounds.max.y,
    (iChild & 4) == 0 ? center.z : m_bounds.max.z)
  );
}

int KROctreeNode::getChildIndex(KRNode* pNode)
{
  for (int iChild = 0; iChild < 8; iChild++) {
    if (getChildBounds(iChild).contains(pNode->getBounds())) {
      return iChild;
    }
  }
  return -1;
}

void KROctreeNode::trim()
{
  for (int iChild = 0; iChild < 8; iChild++) {
    if (m_children[iChild]) {
      if (m_children[iChild]->isEmpty()) {
        delete m_children[iChild];
        m_children[iChild] = NULL;
      }
    }
  }
}

void KROctreeNode::remove(KRNode* pNode)
{
  m_sceneNodes.erase(pNode);
}

void KROctreeNode::update(KRNode* pNode)
{

}

bool KROctreeNode::isEmpty() const
{
  for (int i = 0; i < 8; i++) {
    if (m_children[i]) {
      return false;
    }
  }
  return m_sceneNodes.empty();
}

bool KROctreeNode::canShrinkRoot() const
{
  int cChildren = 0;
  for (int i = 0; i < 8; i++) {
    if (m_children[i]) {
      cChildren++;
    }
  }
  return cChildren <= 1 && m_sceneNodes.empty();
}

KROctreeNode* KROctreeNode::stripChild()
{
  // Return the first found child and update its reference to NULL so that the destructor will not free it.  This is used for shrinking the octree
  // NOTE: The caller of this function will be responsible for freeing the child object.  It is also possible to return a NULL
  for (int i = 0; i < 8; i++) {
    if (m_children[i]) {
      KROctreeNode* child = m_children[i];
      child->m_parent = NULL;
      m_children[i] = NULL;
      return child;
    }
  }
  return NULL;
}


KROctreeNode* KROctreeNode::getParent() const
{
  return m_parent;
}

KROctreeNode** KROctreeNode::getChildren()
{
  return m_children;
}

std::set<KRNode*>& KROctreeNode::getSceneNodes()
{
  return m_sceneNodes;
}


bool KROctreeNode::lineCast(const Vector3& v0, const Vector3& v1, HitInfo& hitinfo, unsigned int layer_mask)
{
  bool hit_found = false;
  if (hitinfo.didHit() && v1 != hitinfo.getPosition()) {
    // Optimization: If we already have a hit, only search for hits that are closer
    hit_found = lineCast(v0, hitinfo.getPosition(), hitinfo, layer_mask);
  } else {
    if (getBounds().intersectsLine(v0, v1)) {
      for (std::set<KRNode*>::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) {
        KRCollider* collider = dynamic_cast<KRCollider*>(*nodes_itr);
        if (collider) {
          if (collider->lineCast(v0, v1, hitinfo, layer_mask)) hit_found = true;
        }
      }

      for (int i = 0; i < 8; i++) {
        if (m_children[i]) {
          if (m_children[i]->lineCast(v0, v1, hitinfo, layer_mask)) {
            hit_found = true;
          }
        }
      }
    }
  }

  return hit_found;
}

bool KROctreeNode::rayCast(const Vector3& v0, const Vector3& dir, HitInfo& hitinfo, unsigned int layer_mask)
{
  bool hit_found = false;
  if (hitinfo.didHit()) {
    // Optimization: If we already have a hit, only search for hits that are closer
    hit_found = lineCast(v0, hitinfo.getPosition(), hitinfo, layer_mask); // Note: This is purposefully lineCast as opposed to RayCast
  } else {
    if (getBounds().intersectsRay(v0, dir)) {
      for (std::set<KRNode*>::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) {
        KRCollider* collider = dynamic_cast<KRCollider*>(*nodes_itr);
        if (collider) {
          if (collider->rayCast(v0, dir, hitinfo, layer_mask)) hit_found = true;
        }
      }

      for (int i = 0; i < 8; i++) {
        if (m_children[i]) {
          if (m_children[i]->rayCast(v0, dir, hitinfo, layer_mask)) {
            hit_found = true;
          }
        }
      }
    }
  }

  return hit_found;
}

bool KROctreeNode::sphereCast(const Vector3& v0, const Vector3& v1, float radius, HitInfo& hitinfo, unsigned int layer_mask)
{
  bool hit_found = false;
  /*
   // FINDME, TODO - Adapt this optimization to work with sphereCasts

  if(hitinfo.didHit() && v1 != hitinfo.getPosition()) {
      // Optimization: If we already have a hit, only search for hits that are closer
      hit_found = sphereCast(v0, hitinfo.getPosition(), radius, hitinfo, layer_mask);
  } else {
  */

  AABB swept_bounds = AABB::Create(Vector3::Create(std::min(v0.x, v1.x) - radius, std::min(v0.y, v1.y) - radius, std::min(v0.z, v1.z) - radius), Vector3::Create(std::max(v0.x, v1.x) + radius, std::max(v0.y, v1.y) + radius, std::max(v0.z, v1.z) + radius));
  // FINDME, TODO - Investigate AABB - swept sphere intersections or OBB - AABB intersections: "if(getBounds().intersectsSweptSphere(v0, v1, radius)) {"
  if (getBounds().intersects(swept_bounds)) {

    for (std::set<KRNode*>::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) {
      KRCollider* collider = dynamic_cast<KRCollider*>(*nodes_itr);
      if (collider) {
        if (collider->sphereCast(v0, v1, radius, hitinfo, layer_mask)) hit_found = true;
      }
    }

    for (int i = 0; i < 8; i++) {
      if (m_children[i]) {
        if (m_children[i]->sphereCast(v0, v1, radius, hitinfo, layer_mask)) {
          hit_found = true;
        }
      }
    }
  }
  // }

  return hit_found;
}

