/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2020 Paul Ramsey <pramsey@cleverelephant.ca>
 *
 * This is free software; you can redistribute and/or modify it under
 * the terms of the GNU Lesser General Licence as published
 * by the Free Software Foundation.
 * See the COPYING file for more information.
 *
 **********************************************************************/

#include <geos/noding/MCIndexNoder.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/PrecisionModel.h>
#include <geos/index/kdtree/KdTree.h>
#include <geos/index/kdtree/KdNode.h>
#include <geos/index/kdtree/KdNodeVisitor.h>
#include <geos/noding/SegmentString.h>
#include <geos/noding/NodedSegmentString.h>
#include <geos/noding/snap/SnappingNoder.h>
#include <geos/noding/snap/SnappingIntersectionAdder.h>

//#include <geos/profiler.h>

#include <algorithm> // for std::min and std::max
#include <memory>
#include <cmath>

using namespace geos::geom;
using namespace geos::index::kdtree;

namespace geos {
namespace noding {  // geos.noding
namespace snap {    // geos.noding.snap


/*public*/
void
SnappingNoder::computeNodes(const std::vector<SegmentString*>& inputSegStrings)
{
    std::vector<SegmentString*> snappedSS;
    snapVertices(inputSegStrings, snappedSS);
    auto result = snapIntersections(snappedSS);
    for (SegmentString* ss: snappedSS) {
        delete ss;
    }
    nodedResult = std::move(result);
}


/*private*/
void
SnappingNoder::snapVertices(const std::vector<SegmentString*>& segStrings, std::vector<SegmentString*>& nodedStrings)
{
    //geos::util::Profiler* profiler = geos::util::Profiler::instance();
    //auto sw = profiler->get(std::string("SnappingNoder::snapVertices"));
    //sw->start();

    seedSnapIndex(segStrings);
    for (SegmentString* ss: segStrings) {
        nodedStrings.push_back(snapVertices(ss).release());
    }

    //sw->stop();
    //std::cout << sw->name << " - " << sw->getTotFormatted() << std::endl;
}


/*private*/
void
SnappingNoder::seedSnapIndex(const std::vector<SegmentString*>& segStrings)
{
    double PHI_INV = (std::sqrt(5.0) - 1.0) / 2.0;

    for (SegmentString* ss: segStrings) {
        const auto& cs = ss->getCoordinates();
        int numPts = (int) cs->size();
        int numPtsToLoad = numPts / 100;
        double rand = 0.0;

        for (int i = 0; i < numPtsToLoad; i++) {
            // quasi-random sequence generated by additive recurrence with irrational constant
            rand = rand + PHI_INV;
            if (rand > 1) rand = rand - floor(rand);

            unsigned int index = (unsigned int) (numPts * rand);
            snapIndex.snap(cs->getAt(index));
        }
    }
}

/*private*/
std::unique_ptr<SegmentString>
SnappingNoder::snapVertices(const SegmentString* ss)
{
    auto snapCoords = snap(ss->getCoordinates().get());
    // FIXME remove hardcoded hasZ, hasM and derive from input
    return std::make_unique<NodedSegmentString>(std::move(snapCoords), false, false, ss->getData());
}


/*private*/
std::shared_ptr<CoordinateSequence>
SnappingNoder::snap(const CoordinateSequence *cs)
{
    auto snapCoords = std::make_shared<CoordinateSequence>();
    snapCoords->reserve(cs->size());

    cs->forEach<Coordinate>([&snapCoords, this](const Coordinate& origPt) {
        const Coordinate& pt = origPt.isValid() ? snapIndex.snap(origPt) : origPt;
        snapCoords->add(pt, false); // Remove repeated points
    });
    return snapCoords;
}


/*private*/
std::vector<std::unique_ptr<SegmentString>>
SnappingNoder::snapIntersections(std::vector<SegmentString*>& inputSS)
{
    SnappingIntersectionAdder intAdder(snapTolerance, snapIndex);
    /**
     * Use an overlap tolerance to ensure all
     * possible snapped intersections are found
     */
    MCIndexNoder noder(&intAdder, 2 * snapTolerance);
    noder.computeNodes(inputSS);
    return noder.getNodedSubstrings();
}

/*public*/
std::vector<std::unique_ptr<SegmentString>>
SnappingNoder::getNodedSubstrings()
{
    return std::move(nodedResult);
}


} // namespace geos.noding.snap
} // namespace geos.noding
} // namespace geos
