//
//  KROctreeNode.cpp
//  KREngine
//
//  Created by Kearwood Gilbert on 2012-08-29.
//  Copyright (c) 2012 Kearwood Software. All rights reserved.
//

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

KROctreeNode::KROctreeNode(KROctreeNode *parent, const KRAABB &bounds) : m_bounds(bounds)
{
    m_parent = parent;
    
    for(int i=0; i<8; i++) m_children[i] = NULL;
    
    m_occlusionQuery = 0;
    m_occlusionTested = false;
    m_activeQuery = false;
}

KROctreeNode::KROctreeNode(KROctreeNode *parent, const KRAABB &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;
    
    m_occlusionQuery = 0;
    m_occlusionTested = false;
    m_activeQuery = false;
}

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

    if(m_occlusionTested) {
        GLDEBUG(glDeleteQueriesEXT(1, &m_occlusionQuery));
    }
}


void KROctreeNode::beginOcclusionQuery()
{
    if(!m_occlusionTested){
        GLDEBUG(glGenQueriesEXT(1, &m_occlusionQuery));
#if TARGET_OS_IPHONE
        GLDEBUG(glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, m_occlusionQuery));
#else
        GLDEBUG(glBeginQuery(GL_SAMPLES_PASSED, m_occlusionQuery));
#endif
        m_occlusionTested = true;
        m_activeQuery = true;
    }
}

void KROctreeNode::endOcclusionQuery()
{
    if(m_activeQuery) {
        // Only end a query if we started one
#if TARGET_OS_IPHONE
        GLDEBUG(glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT));
#else
        GLDEBUG(glEndQuery(GL_SAMPLES_PASSED));
#endif
    }
}


KRAABB 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);
    }
}

KRAABB KROctreeNode::getChildBounds(int iChild)
{
    KRVector3 center = m_bounds.center();
    
    return KRAABB(
       KRVector3(
                 (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),
       KRVector3(
                 (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()
{
    return m_parent;
}

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

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


bool KROctreeNode::lineCast(const KRVector3 &v0, const KRVector3 &v1, KRHitInfo &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 KRVector3 &v0, const KRVector3 &dir, KRHitInfo &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 KRVector3 &v0, const KRVector3 &v1, float radius, KRHitInfo &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 {
    */
    
        KRAABB swept_bounds = KRAABB(KRVector3(KRMIN(v0.x, v1.x) - radius, KRMIN(v0.y, v1.y) - radius, KRMIN(v0.z, v1.z) - radius), KRVector3(KRMAX(v0.x, v1.x) + radius, KRMAX(v0.y, v1.y) + radius, KRMAX(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;
}

