#include "quadtreenode.hpp"

#include <cassert>

#include <osgUtil/CullVisitor>

#include "defs.hpp"
#include "viewdata.hpp"

namespace Terrain
{

    float distance(const osg::BoundingBox& box, const osg::Vec3f& v)
    {
        if (box.contains(v))
            return 0;
        else
        {
            osg::Vec3f maxDist(0, 0, 0);
            if (v.x() < box.xMin())
                maxDist.x() = box.xMin() - v.x();
            else if (v.x() > box.xMax())
                maxDist.x() = v.x() - box.xMax();
            if (v.y() < box.yMin())
                maxDist.y() = box.yMin() - v.y();
            else if (v.y() > box.yMax())
                maxDist.y() = v.y() - box.yMax();
            if (v.z() < box.zMin())
                maxDist.z() = box.zMin() - v.z();
            else if (v.z() > box.zMax())
                maxDist.z() = v.z() - box.zMax();
            return maxDist.length();
        }
    }

    ChildDirection reflect(ChildDirection dir, Direction dir2)
    {
        assert(dir != Root);
        const int lookupTable[4][4] = {
            // NW  NE  SW  SE
            { SW, SE, NW, NE }, // N
            { NE, NW, SE, SW }, // E
            { SW, SE, NW, NE }, // S
            { NE, NW, SE, SW } // W
        };
        return (ChildDirection)lookupTable[dir2][dir];
    }

    bool adjacent(ChildDirection dir, Direction dir2)
    {
        assert(dir != Root);
        const bool lookupTable[4][4] = {
            // NW    NE    SW     SE
            { true, true, false, false }, // N
            { false, true, false, true }, // E
            { false, false, true, true }, // S
            { true, false, true, false } // W
        };
        return lookupTable[dir2][dir];
    }

    QuadTreeNode* searchNeighbour(QuadTreeNode* currentNode, Direction dir)
    {
        if (currentNode->getDirection() == Root)
            return nullptr; // Arrived at root node, the root node does not have neighbours

        QuadTreeNode* nextNode;
        if (adjacent(currentNode->getDirection(), dir))
            nextNode = searchNeighbour(currentNode->getParent(), dir);
        else
            nextNode = currentNode->getParent();

        if (nextNode && nextNode->getNumChildren())
            return nextNode->getChild(reflect(currentNode->getDirection(), dir));
        else
            return nullptr;
    }

    QuadTreeNode::QuadTreeNode(QuadTreeNode* parent, ChildDirection direction, float size, const osg::Vec2f& center)
        : mParent(parent)
        , mDirection(direction)
        , mValidBounds(false)
        , mSize(size)
        , mCenter(center)
    {
        for (unsigned int i = 0; i < 4; ++i)
            mNeighbours[i] = nullptr;
    }

    QuadTreeNode::~QuadTreeNode() {}

    QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir)
    {
        return mNeighbours[dir];
    }

    float QuadTreeNode::distance(const osg::Vec3f& v) const
    {
        const osg::BoundingBox& box = getBoundingBox();
        return Terrain::distance(box, v);
    }

    void QuadTreeNode::initNeighbours()
    {
        for (int i = 0; i < 4; ++i)
            mNeighbours[i] = searchNeighbour(this, (Direction)i);

        for (unsigned int i = 0; i < getNumChildren(); ++i)
            getChild(i)->initNeighbours();
    }

    void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback)
    {
        if (!hasValidBounds())
            return;
        LodCallback::ReturnValue lodResult = lodCallback->isSufficientDetail(this, distance(viewPoint));
        if (lodResult == LodCallback::StopTraversal)
            return;
        else if (lodResult == LodCallback::Deeper && getNumChildren())
        {
            for (unsigned int i = 0; i < getNumChildren(); ++i)
                getChild(i)->traverseNodes(vd, viewPoint, lodCallback);
        }
        else
            vd->add(this);
    }

    void QuadTreeNode::setBoundingBox(const osg::BoundingBox& boundingBox)
    {
        mBoundingBox = boundingBox;
        mValidBounds = boundingBox.valid();
    }

    const osg::BoundingBox& QuadTreeNode::getBoundingBox() const
    {
        return mBoundingBox;
    }

    float QuadTreeNode::getSize() const
    {
        return mSize;
    }

    const osg::Vec2f& QuadTreeNode::getCenter() const
    {
        return mCenter;
    }

}
