#include "line_splitter.h"
#include "common.h"

#include <maps/libs/common/include/exception.h>

#include <queue>

namespace maps {
namespace wiki {
namespace importer {

namespace {

constexpr double MAX_VERTICES_FACTOR = 0.8;
constexpr double MAX_LENGTH_FACTOR = 0.8;

struct LinePart
{
    LinePart(const OGRLineString* inputLine, int minIndex, int maxIndex);

    OGRLineStringPtr geom;
    double length;
};

LinePart::LinePart(const OGRLineString* inputLine, int minIndex, int maxIndex)
    : geom(static_cast<OGRLineString*>(OGRGeometryFactory::createGeometry(wkbLineString)))
    , length(0)
{
    ASSERT(inputLine);
    REQUIRE(minIndex >= 0, "Min index " << minIndex << " is below zero");
    REQUIRE(maxIndex < inputLine->getNumPoints(),
        "Max index " << maxIndex << " is greater or equal num points " << inputLine->getNumPoints());
    REQUIRE(minIndex < maxIndex, "Min index " << minIndex << " is greater max index " << maxIndex);

    OGRPoint point;
    for (int index = minIndex; index <= maxIndex; ++index) {
        inputLine->getPoint(index, &point);
        geom->addPoint(&point);
    }

    length = getRealLength(geom.get());
    REQUIRE(length > 0.0, "Wrong length " << length);
}

} // namespace

LineSplitter::LineSplitter(const cfg::Restrictions& restrictions)
    : restrictions_(restrictions)
{
    REQUIRE(restrictions_.gabarits(), "No gabarits");
    REQUIRE(restrictions_.gabarits()->length(), "No length in gabarits");
}

std::vector<OGRLineStringPtr> LineSplitter::split(const OGRLineString* inputLine) const
{
    REQUIRE(inputLine, "Null geometry");
    REQUIRE(inputLine->getNumPoints() >= 2, "Too few points in the input line");

    auto maxVertices = static_cast<int>(restrictions_.maxVertexes());
    if (inputLine->getNumPoints() >= maxVertices) {
        maxVertices = static_cast<int>(maxVertices * MAX_VERTICES_FACTOR); //more freedom for editing in editor
    }

    auto maxLength = *restrictions_.gabarits()->length();
    auto inputLength = getRealLength(inputLine);
    if (inputLength >= maxLength) {
        maxLength *= MAX_LENGTH_FACTOR; //more freedom for editing in editor
    }

    std::vector<OGRLineStringPtr> outputLines;

    std::queue<LinePart> queue;
    queue.emplace(inputLine, 0, inputLine->getNumPoints() - 1);
    while (!queue.empty()) {
        auto& front = queue.front();
        if (front.geom->getNumPoints() < maxVertices &&
            front.length < maxLength) {
            outputLines.push_back(std::move(front.geom));
        } else {
            REQUIRE(front.geom->getNumPoints() > 2,
                "Too long interval between points " << front.length << ". Max length " << maxLength);

            int middleIndex = front.geom->getNumPoints() / 2;
            queue.emplace(front.geom.get(), 0, middleIndex);
            queue.emplace(front.geom.get(), middleIndex, front.geom->getNumPoints() - 1);
        }
        queue.pop();
    }

    return outputLines;
}

} // namespace importer
} // namespace wiki
} // namespace maps
