/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2006 Refractions Research Inc.
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU Lesser General Public Licence as published
 * by the Free Software Foundation.
 * See the COPYING file for more information.
 *
 **********************************************************************
 *
 * Last port: index/strtree/STRtree.java rev. 1.11
 *
 **********************************************************************/

#include <geos/index/strtree/STRtree.h>
#include <geos/index/strtree/BoundablePair.h>
#include <geos/geom/Envelope.h>

#include <vector>
#include <cassert>
#include <cmath>
#include <algorithm> // std::sort
#include <iostream> // for debugging
#include <limits>
#include <geos/util/GEOSException.h>

using namespace std;
using namespace geos::geom;

namespace geos {
namespace index { // geos.index
namespace strtree { // geos.index.strtree


static bool
yComparator(Boundable* a, Boundable* b)
{
    assert(a);
    assert(b);
    const void* aBounds = a->getBounds();
    const void* bBounds = b->getBounds();
    assert(aBounds);
    assert(bBounds);
    const Envelope* aEnv = static_cast<const Envelope*>(aBounds);
    const Envelope* bEnv = static_cast<const Envelope*>(bBounds);

    // NOTE - mloskot:
    // The problem of instability is directly related to mathematical definition of
    // "strict weak ordering" as a fundamental requirement for binary predicate:
    //
    // if a is less than b then b is not less than a,
    // if a is less than b and b is less than c
    // then a is less than c,
    // and so on.
    //
    // For some very low values, this predicate does not fulfill this requiremnet,

    // NOTE - strk:
    // It seems that the '<' comparison here gives unstable results.
    // In particular, when inlines are on (for Envelope::getMinY and getMaxY)
    // things are fine, but when they are off we can even get a memory corruption !!
    //return STRtree::centreY(aEnv) < STRtree::centreY(bEnv);

    // NOTE - mloskot:
    // This comparison does not answer if a is "lower" than b
    // what is required for sorting. This comparison only answeres
    // if a and b are "almost the same" or different

    /*NOTE - cfis
      In debug mode VC++ checks the predicate in both directions.

      If !_Pred(_Left, _Right)
      Then an exception is thrown if _Pred(_Right, _Left).
      See xutility around line 320:

      	bool __CLRCALL_OR_CDECL _Debug_lt_pred(_Pr _Pred, _Ty1& _Left, _Ty2& _Right,
    	const wchar_t *_Where, unsigned int _Line)*/

    //return std::fabs( STRtree::centreY(aEnv) - STRtree::centreY(bEnv) ) < 1e-30

    // NOTE - strk:
    // See http://trac.osgeo.org/geos/ticket/293
    // as for why simple comparison (<) isn't used here
    return AbstractSTRtree::compareDoubles(STRtree::centreY(aEnv),
                                           STRtree::centreY(bEnv));
}

/*public*/
STRtree::STRtree(size_t p_nodeCapacity): AbstractSTRtree(p_nodeCapacity)
{
}

bool
STRtree::STRIntersectsOp::intersects(const void* aBounds, const void* bBounds)
{
    return ((Envelope*)aBounds)->intersects((Envelope*)bBounds);
}

/*private*/
std::unique_ptr<BoundableList>
STRtree::createParentBoundables(BoundableList* childBoundables, int newLevel)
{
    assert(!childBoundables->empty());
    int minLeafCount = (int) ceil((double)childBoundables->size() / (double)getNodeCapacity());

    std::unique_ptr<BoundableList> sortedChildBoundables(sortBoundables(childBoundables));

    std::unique_ptr< vector<BoundableList*> > verticalSlicesV(
        verticalSlices(sortedChildBoundables.get(), (int)ceil(sqrt((double)minLeafCount)))
    );

    std::unique_ptr<BoundableList> ret(
        createParentBoundablesFromVerticalSlices(verticalSlicesV.get(), newLevel)
    );
    for(size_t i = 0, vssize = verticalSlicesV->size(); i < vssize; ++i) {
        BoundableList* inner = (*verticalSlicesV)[i];
        delete inner;
    }

    return ret;
}

/*private*/
std::unique_ptr<BoundableList>
STRtree::createParentBoundablesFromVerticalSlices(std::vector<BoundableList*>* p_verticalSlices, int newLevel)
{
    assert(!p_verticalSlices->empty());
    std::unique_ptr<BoundableList> parentBoundables(new BoundableList());

    for(size_t i = 0, vssize = p_verticalSlices->size(); i < vssize; ++i) {
        std::unique_ptr<BoundableList> toAdd(
            createParentBoundablesFromVerticalSlice(
                (*p_verticalSlices)[i], newLevel)
        );
        assert(!toAdd->empty());

        parentBoundables->insert(
            parentBoundables->end(),
            toAdd->begin(),
            toAdd->end());
    }
    return parentBoundables;
}

/*protected*/
std::unique_ptr<BoundableList>
STRtree::createParentBoundablesFromVerticalSlice(BoundableList* childBoundables, int newLevel)
{
    return AbstractSTRtree::createParentBoundables(childBoundables, newLevel);
}

/*private*/
std::vector<BoundableList*>*
STRtree::verticalSlices(BoundableList* childBoundables, size_t sliceCount)
{
    size_t sliceCapacity = (size_t) ceil((double)childBoundables->size() / (double) sliceCount);
    vector<BoundableList*>* slices = new vector<BoundableList*>(sliceCount);

    size_t i = 0, nchilds = childBoundables->size();

    for(size_t j = 0; j < sliceCount; j++) {
        (*slices)[j] = new BoundableList();
        (*slices)[j]->reserve(sliceCapacity);
        size_t boundablesAddedToSlice = 0;
        while(i < nchilds && boundablesAddedToSlice < sliceCapacity) {
            Boundable* childBoundable = (*childBoundables)[i];
            ++i;
            (*slices)[j]->push_back(childBoundable);
            ++boundablesAddedToSlice;
        }
    }
    return slices;
}

/*public*/
std::pair<const void*, const void*>
STRtree::nearestNeighbour(ItemDistance* itemDist)
{
    BoundablePair bp(this->getRoot(), this->getRoot(), itemDist);
    return nearestNeighbour(&bp);
}

/*public*/
const void*
STRtree::nearestNeighbour(const Envelope* env, const void* item, ItemDistance* itemDist)
{
    build();

    ItemBoundable bnd = ItemBoundable(env, (void*) item);
    BoundablePair bp(getRoot(), &bnd, itemDist);

    return nearestNeighbour(&bp).first;
}

/*public*/
std::pair<const void*, const void*>
STRtree::nearestNeighbour(STRtree* tree, ItemDistance* itemDist)
{
    BoundablePair bp(getRoot(), tree->getRoot(), itemDist);
    return nearestNeighbour(&bp);
}

/*public*/
std::pair<const void*, const void*>
STRtree::nearestNeighbour(BoundablePair* initBndPair)
{
    return nearestNeighbour(initBndPair, std::numeric_limits<double>::infinity());
}

/*public*/
std::pair<const void*, const void*>
STRtree::nearestNeighbour(BoundablePair* initBndPair, double maxDistance)
{
    double distanceLowerBound = maxDistance;
    BoundablePair* minPair = nullptr;

    BoundablePair::BoundablePairQueue priQ;
    priQ.push(initBndPair);

    while(!priQ.empty() && distanceLowerBound > 0.0) {
        BoundablePair* bndPair = priQ.top();
        double currentDistance = bndPair->getDistance();

        /**
         * If the distance for the first node in the queue
         * is >= the current minimum distance, all other nodes
         * in the queue must also have a greater distance.
         * So the current minDistance must be the true minimum,
         * and we are done.
         */
        if(minPair && currentDistance >= distanceLowerBound) {
            break;
        }

        priQ.pop();

        /**
         * If the pair members are leaves
         * then their distance is the exact lower bound.
         * Update the distanceLowerBound to reflect this
         * (which must be smaller, due to the test
         * immediately prior to this).
         */
        if(bndPair->isLeaves()) {
            distanceLowerBound = currentDistance;
            minPair = bndPair;
        }
        else {
            /**
             * Otherwise, expand one side of the pair,
             * (the choice of which side to expand is heuristically determined)
             * and insert the new expanded pairs into the queue
             */
            bndPair->expandToQueue(priQ, distanceLowerBound);
        }

        if(bndPair != initBndPair && bndPair != minPair) {
            delete bndPair;
        }
    }

    /* Free any remaining BoundablePairs in the queue */
    while(!priQ.empty()) {
        BoundablePair* bndPair = priQ.top();
        priQ.pop();
        if(bndPair != initBndPair) {
            delete bndPair;
        }
    }

    if(!minPair) {
        throw util::GEOSException("Error computing nearest neighbor");
    }

    const void* item0 = dynamic_cast<const ItemBoundable*>(minPair->getBoundable(0))->getItem();
    const void* item1 = dynamic_cast<const ItemBoundable*>(minPair->getBoundable(1))->getItem();
    if(minPair != initBndPair) {
        delete minPair;
    }

    return std::pair<const void*, const void*>(item0, item1);
}

/*public*/
bool
STRtree::isWithinDistance(STRtree* tree, ItemDistance* itemDist, double maxDistance)
{
    BoundablePair bp(getRoot(), tree->getRoot(), itemDist);
    return isWithinDistance(&bp, maxDistance);
}

/*private*/
bool STRtree::isWithinDistance(BoundablePair* initBndPair, double maxDistance)
{
    double distanceUpperBound = std::numeric_limits<double>::infinity();

    // initialize search queue
    BoundablePair::BoundablePairQueue priQ;
    priQ.push(initBndPair);

    while(!priQ.empty()) {
        BoundablePair* bndPair = priQ.top();
        double currentDistance = bndPair->getDistance();

        /**
        * If the distance for the first pair in the queue
        * is >= maxDistance, other pairs
        * in the queue must also have a greater distance.
        * So can conclude no items are within the distance
        * and terminate with false
        */
        if (currentDistance > maxDistance)
            return false;

        /**
        * There must be some pair of items in the nodes which
        * are closer than the max distance,
        * so can terminate with true.
        *
        * NOTE: using the Envelope MinMaxDistance would provide a tighter bound,
        * but not sure how to compute this!
        */
        if (bndPair->maximumDistance() <= maxDistance)
            return true;

        /**
        * If the pair members are leaves
        * then their distance is an upper bound.
        * Update the distanceUpperBound to reflect this
        */
        if (bndPair->isLeaves()) {
            distanceUpperBound = currentDistance;

            // Current pair is closer than maxDistance
            // so can terminate with true
            if (distanceUpperBound <= maxDistance)
                return true;
        }
        else {
            /**
            * Otherwise, expand one side of the pair,
            * and insert the expanded pairs into the queue.
            * The choice of which side to expand is determined heuristically.
            */
            bndPair->expandToQueue(priQ, distanceUpperBound);
        }
    }
    return false;
}


class STRAbstractNode: public AbstractNode {
public:

    STRAbstractNode(int p_level, size_t capacity)
        :
        AbstractNode(p_level, capacity)
    {}

    ~STRAbstractNode() override
    {
        delete(Envelope*)bounds;
    }

protected:

    void*
    computeBounds() const override
    {
        Envelope* p_bounds = nullptr;
        const BoundableList& b = *getChildBoundables();

        if(b.empty()) {
            return nullptr;
        }

        BoundableList::const_iterator i = b.begin();
        BoundableList::const_iterator e = b.end();

        p_bounds = new Envelope(* static_cast<const Envelope*>((*i)->getBounds()));
        for(; i != e; ++i) {
            const Boundable* childBoundable = *i;
            p_bounds->expandToInclude((Envelope*)childBoundable->getBounds());
        }
        return p_bounds;
    }

};

/*protected*/
AbstractNode*
STRtree::createNode(int level)
{
    AbstractNode* an = new STRAbstractNode(level, nodeCapacity);
    nodes->push_back(an);
    return an;
}

/*public*/
void
STRtree::insert(const Envelope* itemEnv, void* item)
{
    if(itemEnv->isNull()) {
        return;
    }
    AbstractSTRtree::insert(itemEnv, item);
}

/*private*/
std::unique_ptr<BoundableList>
STRtree::sortBoundables(const BoundableList* input)
{
    assert(input);
    std::unique_ptr<BoundableList> output(new BoundableList(*input));
    assert(output->size() == input->size());

    sort(output->begin(), output->end(), yComparator);
    return output;
}

} // namespace geos.index.strtree
} // namespace geos.index
} // namespace geos

