/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2005-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: geomgraph/EdgeRing.java r428 (JTS-1.12+)
 *
 **********************************************************************/

#include <geos/util/Assert.h>
#include <geos/util/TopologyException.h>
#include <geos/algorithm/PointLocation.h>
#include <geos/algorithm/Orientation.h>
#include <geos/geomgraph/EdgeRing.h>
#include <geos/geomgraph/DirectedEdge.h>
#include <geos/geomgraph/DirectedEdgeStar.h>
#include <geos/geomgraph/Edge.h>
#include <geos/geomgraph/Node.h>
#include <geos/geomgraph/Label.h>
#include <geos/geomgraph/Position.h>
#include <geos/geom/CoordinateSequenceFactory.h>
#include <geos/geom/CoordinateArraySequence.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/geom/LinearRing.h>
#include <geos/geom/Location.h>
#include <geos/geom/Envelope.h>
#include <geos/util.h>

#include <vector>
#include <cassert>
#include <iostream> // for operator<<

using namespace std;
using namespace geos::algorithm;
using namespace geos::geom;

namespace geos {
namespace geomgraph { // geos.geomgraph

EdgeRing::EdgeRing(DirectedEdge* newStart,
                   const GeometryFactory* newGeometryFactory)
    :
    startDe(newStart),
    geometryFactory(newGeometryFactory),
    holes(),
    maxNodeDegree(-1),
    edges(),
    pts(new CoordinateArraySequence()),
    label(Location::UNDEF), // new Label(Location::UNDEF)),
    ring(nullptr),
    isHoleVar(false),
    shell(nullptr)
{
    /*
     * Commented out to fix different polymorphism in C++ (from Java)
     * Make sure these calls are made by derived classes !
     */
    //computePoints(start);
    //computeRing();
#ifdef GEOS_DEBUG
    cerr << "EdgeRing[" << this << "] ctor" << endl;
#endif
    testInvariant();
}

bool
EdgeRing::isIsolated()
{
    testInvariant();
    return (label.getGeometryCount() == 1);
}

bool
EdgeRing::isHole()
{
    testInvariant();

    // We can't tell if this is an hole
    // unless we computed the ring
    // see computeRing()
    assert(ring);

    return isHoleVar;
}


LinearRing*
EdgeRing::getLinearRing()
{
    testInvariant();
    return ring.get();
}

Label&
EdgeRing::getLabel()
{
    testInvariant();
    return label;
}

bool
EdgeRing::isShell()
{
    testInvariant();
    return (shell == nullptr);
}

EdgeRing*
EdgeRing::getShell()
{
    testInvariant();
    return shell;
}

void
EdgeRing::setShell(EdgeRing* newShell)
{
    shell = newShell;
    if(shell != nullptr) {
        shell->addHole(this);
    }
    testInvariant();
}

void
EdgeRing::addHole(EdgeRing* edgeRing)
{
    holes.emplace_back(edgeRing);
    testInvariant();
}

/*public*/
std::unique_ptr<Polygon>
EdgeRing::toPolygon(const GeometryFactory* p_geometryFactory)
{
    testInvariant();

    // We don't use "clone" here because
    // GeometryFactory::createPolygon really
    // wants a LinearRing
    auto shellLR = detail::make_unique<LinearRing>(*(getLinearRing()));
    if (holes.empty()) {
        return p_geometryFactory->createPolygon(std::move(shellLR));
    } else {
        size_t nholes = holes.size();
        std::vector<std::unique_ptr<LinearRing>> holeLR(nholes);
        for(size_t i = 0; i < nholes; ++i) {
            holeLR[i] = detail::make_unique<LinearRing>(*(holes[i]->getLinearRing()));
        }

        return p_geometryFactory->createPolygon(std::move(shellLR), std::move(holeLR));
    }
}

/*public*/
void
EdgeRing::computeRing()
{
    testInvariant();

    if(ring != nullptr) {
        return;    // don't compute more than once
    }
    isHoleVar = Orientation::isCCW(pts.get());
    ring = geometryFactory->createLinearRing(std::move(pts));

    testInvariant();
}

/*public*/
vector<DirectedEdge*>&
EdgeRing::getEdges()
{
    testInvariant();

    return edges;
}

/*protected*/
void
EdgeRing::computePoints(DirectedEdge* newStart)
// throw(const TopologyException &)
{
    startDe = newStart;
    DirectedEdge* de = newStart;
    bool isFirstEdge = true;
    do {
        //util::Assert::isTrue(de!=NULL,"EdgeRing::computePoints: found null Directed Edge");
        //assert(de!=NULL); // EdgeRing::computePoints: found null Directed Edge
        if(de == nullptr)
            throw util::TopologyException(
                "EdgeRing::computePoints: found null Directed Edge");

        if(de->getEdgeRing() == this)
            throw util::TopologyException(
                "Directed Edge visited twice during ring-building",
                de->getCoordinate());

        edges.push_back(de);
        const Label& deLabel = de->getLabel();
        assert(deLabel.isArea());
        mergeLabel(deLabel);
        addPoints(de->getEdge(), de->isForward(), isFirstEdge);
        isFirstEdge = false;
        setEdgeRing(de, this);
        de = getNext(de);
    }
    while(de != startDe);

    testInvariant();

}

/*public*/
int
EdgeRing::getMaxNodeDegree()
{

    testInvariant();

    if(maxNodeDegree < 0) {
        computeMaxNodeDegree();
    }
    return maxNodeDegree;
}

/*private*/
void
EdgeRing::computeMaxNodeDegree()
{
    maxNodeDegree = 0;
    DirectedEdge* de = startDe;
    do {
        Node* node = de->getNode();
        EdgeEndStar* ees = node->getEdges();
        assert(dynamic_cast<DirectedEdgeStar*>(ees));
        DirectedEdgeStar* des = static_cast<DirectedEdgeStar*>(ees);
        int degree = des->getOutgoingDegree(this);
        if(degree > maxNodeDegree) {
            maxNodeDegree = degree;
        }
        de = getNext(de);
    }
    while(de != startDe);
    maxNodeDegree *= 2;

    testInvariant();

}

/*public*/
void
EdgeRing::setInResult()
{
    DirectedEdge* de = startDe;
    do {
        de->getEdge()->setInResult(true);
        de = de->getNext();
    }
    while(de != startDe);

    testInvariant();

}

/*protected*/
void
EdgeRing::mergeLabel(const Label& deLabel)
{
    mergeLabel(deLabel, 0);
    mergeLabel(deLabel, 1);

    testInvariant();

}

/*protected*/
void
EdgeRing::mergeLabel(const Label& deLabel, int geomIndex)
{

    testInvariant();

    Location loc = deLabel.getLocation(geomIndex, Position::RIGHT);
    // no information to be had from this label
    if(loc == Location::UNDEF) {
        return;
    }

    // if there is no current RHS value, set it
    if(label.getLocation(geomIndex) == Location::UNDEF) {
        label.setLocation(geomIndex, loc);
        return;
    }
}

/*protected*/
void
EdgeRing::addPoints(Edge* edge, bool isForward, bool isFirstEdge)
{
    // EdgeRing::addPoints: can't add points after LinearRing construction
    assert(ring == nullptr);

    assert(edge);
    const CoordinateSequence* edgePts = edge->getCoordinates();

    assert(edgePts);
    size_t numEdgePts = edgePts->getSize();

    assert(pts);

    if(isForward) {
        size_t startIndex = 1;
        if(isFirstEdge) {
            startIndex = 0;
        }
        for(size_t i = startIndex; i < numEdgePts; ++i) {
            pts->add(edgePts->getAt(i));
        }
    }

    else { // is backward
        size_t startIndex = numEdgePts - 1;
        if(isFirstEdge) {
            startIndex = numEdgePts;
        }
        //for (int i=startIndex;i>=0;i--)
        for(size_t i = startIndex; i > 0; --i) {
            pts->add(edgePts->getAt(i - 1));
        }
    }

    testInvariant();

}

/*public*/
bool
EdgeRing::containsPoint(const Coordinate& p)
{

    testInvariant();

    assert(ring);

    const Envelope* env = ring->getEnvelopeInternal();
    assert(env);
    if(! env->contains(p)) {
        return false;
    }

    if(! PointLocation::isInRing(p, ring->getCoordinatesRO())) {
        return false;
    }

    for(const auto& hole : holes) {
        assert(hole);
        if(hole->containsPoint(p)) {
            return false;
        }
    }
    return true;
}

std::ostream&
operator<< (std::ostream& os, const EdgeRing& er)
{
    os << "EdgeRing[" << &er << "]: "
       << std::endl
       << "Points: " << er.pts.get()
       << std::endl;
    return os;
}

} // namespace geos.geomgraph
} // namespace geos

