/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2010 Safe Software Inc.
 * Copyright (C) 2010 Sandro Santilli <strk@kbt.io>
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
 * Copyright (C) 2005 Refractions Research 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: operation/valid/IsValidOp.java r335 (JTS-1.12)
 *
 **********************************************************************/

#include "IndexedNestedRingTester.h"

#include <geos/export.h>
#include <geos/constants.h>
#include <geos/algorithm/LineIntersector.h>
#include <geos/algorithm/PointLocation.h>
#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/GeometryCollection.h>
#include <geos/geom/LineString.h>
#include <geos/geom/LinearRing.h>
#include <geos/geom/Location.h>
#include <geos/geom/MultiPolygon.h>
#include <geos/geom/Point.h>
#include <geos/geom/Polygon.h>
#include <geos/geomgraph/GeometryGraph.h>
#include <geos/geomgraph/Edge.h>
#include <geos/geomgraph/index/SegmentIntersector.h>
#include <geos/index/chain/MonotoneChainSelectAction.h>
#include <geos/operation/valid/ConnectedInteriorTester.h>
#include <geos/operation/valid/ConsistentAreaTester.h>
#include <geos/operation/valid/IsValidOp.h>
#include <geos/util/UnsupportedOperationException.h>


#include <cassert>
#include <cmath>
#include <typeinfo>
#include <set>

using namespace std;
using namespace geos::algorithm;
using namespace geos::geomgraph;
using namespace geos::geom;

namespace geos {
namespace operation { // geos.operation
namespace valid { // geos.operation.valid

/**
 * Find a point from the list of testCoords
 * that is NOT a node in the edge for the list of searchCoords
 *
 * @return the point found, or <code>null</code> if none found
 */
const Coordinate*
IsValidOp::findPtNotNode(const CoordinateSequence* testCoords,
                         const LinearRing* searchRing, GeometryGraph* graph)
{
    // find edge corresponding to searchRing.
    Edge* searchEdge = graph->findEdge(searchRing);
    // find a point in the testCoords which is not a node of the searchRing
    EdgeIntersectionList& eiList = searchEdge->getEdgeIntersectionList();
    // somewhat inefficient - is there a better way? (Use a node map, for instance?)
    auto npts = testCoords->getSize();
    for(unsigned int i = 0; i < npts; ++i) {
        const Coordinate& pt = testCoords->getAt(i);
        if(!eiList.isIntersection(pt)) {
            return &pt;
        }
    }
    return nullptr;
}


bool
IsValidOp::isValid()
{
    checkValid();
    return validErr == nullptr;
}

/* static public */
bool
IsValidOp::isValid(const Coordinate& coord)
{
    if(! std::isfinite(coord.x)) {
        return false;
    }
    if(! std::isfinite(coord.y)) {
        return false;
    }
    return true;
}

/* static public */
bool
IsValidOp::isValid(const Geometry& g)
{
    IsValidOp op(&g);
    return op.isValid();
}

TopologyValidationError*
IsValidOp::getValidationError()
{
    checkValid();
    return validErr;
}

void
IsValidOp::checkValid()
{
    if(isChecked) {
        return;
    }
    checkValid(parentGeometry);
    isChecked = true;
}

void
IsValidOp::checkValid(const Geometry* g)
{
    assert(validErr == nullptr);

    if(nullptr == g) {
        return;
    }

    // empty geometries are always valid!
    if(g->isEmpty()) {
        return;
    }

    if(const Point* x1 = dynamic_cast<const Point*>(g)) {
        checkValid(x1);
    }
    // LineString also handles LinearRings, so we check LinearRing first
    else if(const LinearRing* x2 = dynamic_cast<const LinearRing*>(g)) {
        checkValid(x2);
    }
    else if(const LineString* x3 = dynamic_cast<const LineString*>(g)) {
        checkValid(x3);
    }
    else if(const Polygon* x4 = dynamic_cast<const Polygon*>(g)) {
        checkValid(x4);
    }
    else if(const MultiPolygon* x5 = dynamic_cast<const MultiPolygon*>(g)) {
        checkValid(x5);
    }
    else if(const GeometryCollection* x6 =
                dynamic_cast<const GeometryCollection*>(g)) {
        checkValid(x6);
    }
    else {
        throw util::UnsupportedOperationException();
    }
}

/*
 * Checks validity of a Point.
 */
void
IsValidOp::checkValid(const Point* g)
{
    checkInvalidCoordinates(g->getCoordinatesRO());
}

/*
 * Checks validity of a LineString.  Almost anything goes for linestrings!
 */
void
IsValidOp::checkValid(const LineString* g)
{
    checkInvalidCoordinates(g->getCoordinatesRO());
    if(validErr != nullptr) {
        return;
    }

    GeometryGraph graph(0, g);
    checkTooFewPoints(&graph);
}

/**
 * Checks validity of a LinearRing.
 */
void
IsValidOp::checkValid(const LinearRing* g)
{
    checkInvalidCoordinates(g->getCoordinatesRO());
    if(validErr != nullptr) {
        return;
    }

    checkClosedRing(g);
    if(validErr != nullptr) {
        return;
    }

    GeometryGraph graph(0, g);
    checkTooFewPoints(&graph);
    if(validErr != nullptr) {
        return;
    }

    LineIntersector li;
    graph.computeSelfNodes(&li, true, true);
    checkNoSelfIntersectingRings(&graph);
}

/**
 * Checks the validity of a polygon.
 * Sets the validErr flag.
 */
void
IsValidOp::checkValid(const Polygon* g)
{
    checkInvalidCoordinates(g);
    if(validErr != nullptr) {
        return;
    }

    checkClosedRings(g);
    if(validErr != nullptr) {
        return;
    }

    GeometryGraph graph(0, g);

    checkTooFewPoints(&graph);
    if(validErr != nullptr) {
        return;
    }

    checkConsistentArea(&graph);
    if(validErr != nullptr) {
        return;
    }

    if(!isSelfTouchingRingFormingHoleValid) {
        checkNoSelfIntersectingRings(&graph);
        if(validErr != nullptr) {
            return;
        }
    }

    checkHolesInShell(g, &graph);
    if(validErr != nullptr) {
        return;
    }

    checkHolesNotNested(g, &graph);
    if(validErr != nullptr) {
        return;
    }

    checkConnectedInteriors(graph);
}

void
IsValidOp::checkValid(const MultiPolygon* g)
{
    auto ngeoms = g->getNumGeometries();
    vector<const Polygon*>polys(ngeoms);

    for(size_t i = 0; i < ngeoms; ++i) {
        const Polygon* p = dynamic_cast<const Polygon*>(g->getGeometryN(i));

        checkInvalidCoordinates(p);
        if(validErr != nullptr) {
            return;
        }

        checkClosedRings(p);
        if(validErr != nullptr) {
            return;
        }

        polys[i] = p;
    }

    GeometryGraph graph(0, g);

    checkTooFewPoints(&graph);
    if(validErr != nullptr) {
        return;
    }

    checkConsistentArea(&graph);
    if(validErr != nullptr) {
        return;
    }

    if(!isSelfTouchingRingFormingHoleValid) {
        checkNoSelfIntersectingRings(&graph);
        if(validErr != nullptr) {
            return;
        }
    }

    for(unsigned int i = 0; i < ngeoms; ++i) {
        const Polygon* p = polys[i];
        checkHolesInShell(p, &graph);
        if(validErr != nullptr) {
            return;
        }
    }

    for(unsigned int i = 0; i < ngeoms; ++i) {
        const Polygon* p = polys[i];
        checkHolesNotNested(p, &graph);
        if(validErr != nullptr) {
            return;
        }
    }

    checkShellsNotNested(g, &graph);
    if(validErr != nullptr) {
        return;
    }

    checkConnectedInteriors(graph);
}

void
IsValidOp::checkValid(const GeometryCollection* gc)
{
    for(size_t i = 0, ngeoms = gc->getNumGeometries(); i < ngeoms; ++i) {
        const Geometry* g = gc->getGeometryN(i);
        checkValid(g);
        if(validErr != nullptr) {
            return;
        }
    }
}

void
IsValidOp::checkTooFewPoints(GeometryGraph* graph)
{
    if(graph->hasTooFewPoints()) {
        validErr = new TopologyValidationError(
            TopologyValidationError::eTooFewPoints,
            graph->getInvalidPoint());
        return;
    }
}

/**
 * Checks that the arrangement of edges in a polygonal geometry graph
 * forms a consistent area.
 *
 * @param graph
 *
 * @see ConsistentAreaTester
 */
void
IsValidOp::checkConsistentArea(GeometryGraph* graph)
{
    ConsistentAreaTester cat(graph);
    bool isValidArea = cat.isNodeConsistentArea();

    if(!isValidArea) {
        validErr = new TopologyValidationError(
            TopologyValidationError::eSelfIntersection,
            cat.getInvalidPoint());
        return;
    }

    if(cat.hasDuplicateRings()) {
        validErr = new TopologyValidationError(
            TopologyValidationError::eDuplicatedRings,
            cat.getInvalidPoint());
    }
}


/*private*/
void
IsValidOp::checkNoSelfIntersectingRings(GeometryGraph* graph)
{
    vector<Edge*>* edges = graph->getEdges();
    for(unsigned int i = 0; i < edges->size(); ++i) {
        Edge* e = (*edges)[i];
        checkNoSelfIntersectingRing(e->getEdgeIntersectionList());
        if(validErr != nullptr) {
            return;
        }
    }
}

/*private*/
void
IsValidOp::checkNoSelfIntersectingRing(EdgeIntersectionList& eiList)
{
    set<const Coordinate*, CoordinateLessThen>nodeSet;
    bool isFirst = true;
    for(const EdgeIntersection& ei : eiList) {
        if(isFirst) {
            isFirst = false;
            continue;
        }
        if(nodeSet.find(&ei.coord) != nodeSet.end()) {
            validErr = new TopologyValidationError(
                TopologyValidationError::eRingSelfIntersection,
                ei.coord);
            return;
        }
        else {
            nodeSet.insert(&ei.coord);
        }
    }
}

/*private*/
void
IsValidOp::checkHolesInShell(const Polygon* p, GeometryGraph* graph)
{
    const LinearRing* shell = p->getExteriorRing();

    bool isShellEmpty = shell->isEmpty();

    locate::IndexedPointInAreaLocator ipial(*shell);
    auto nholes = p->getNumInteriorRing();

    for(size_t i = 0; i < nholes; ++i) {
        const LinearRing* hole = p->getInteriorRingN(i);

        if (hole->isEmpty()) continue;

        const Coordinate* holePt = findPtNotNode(hole->getCoordinatesRO(), shell, graph);
        /**
        * If no non-node hole vertex can be found, the hole must
        * split the polygon into disconnected interiors.
        * This will be caught by a subsequent check.
        */
        if (holePt == nullptr) return;
        bool outside = isShellEmpty || (Location::EXTERIOR == ipial.locate(holePt));
        if (outside) {
            validErr = new TopologyValidationError(
                TopologyValidationError::eHoleOutsideShell, *holePt);
            return;
        }
    }

}

/*private*/
void
IsValidOp::checkHolesNotNested(const Polygon* p, GeometryGraph* graph)
{
    //SimpleNestedRingTester nestedTester(graph);
    //SweeplineNestedRingTester nestedTester(graph);
    //QuadtreeNestedRingTester nestedTester(graph);
    IndexedNestedRingTester nestedTester(graph);

    auto nholes = p->getNumInteriorRing();
    for (size_t i = 0; i < nholes; ++i) {
        const LinearRing* innerHole = p->getInteriorRingN(i);

        //empty holes always pass
        if (innerHole->isEmpty()) {
            continue;
        }

        nestedTester.add(innerHole);
    }

    bool isNonNested = nestedTester.isNonNested();
    if (!isNonNested) {
        validErr = new TopologyValidationError(
            TopologyValidationError::eNestedHoles,
            *(nestedTester.getNestedPoint()));
    }
}

/*private*/
void
IsValidOp::checkShellsNotNested(const MultiPolygon* mp, GeometryGraph* graph)
{
    for (size_t i = 0, ngeoms = mp->getNumGeometries(); i < ngeoms; ++i) {
        const Polygon* p = dynamic_cast<const Polygon*>(
                               mp->getGeometryN(i));

        const LinearRing* shell = p->getExteriorRing();

        if (shell->isEmpty()) return;

        for (size_t j = 0; j < ngeoms; ++j) {
            if (i == j) {
                continue;
            }

            const Polygon* p2 = dynamic_cast<const Polygon*>(
                                    mp->getGeometryN(j));

            if (p2->isEmpty()) {
                continue;
            }

            checkShellNotNested(shell, p2, graph);

            if (validErr != nullptr) {
                return;
            }
        }
    }
}

/*private*/
void
IsValidOp::checkShellNotNested(const LinearRing* shell, const Polygon* p,
                               GeometryGraph* graph)
{
    const CoordinateSequence* shellPts = shell->getCoordinatesRO();

    // test if shell is inside polygon shell
    const LinearRing* polyShell = p->getExteriorRing();
    const CoordinateSequence* polyPts = polyShell->getCoordinatesRO();
    const Coordinate* shellPt = findPtNotNode(shellPts, polyShell, graph);

    // if no point could be found, we can assume that the shell
    // is outside the polygon
    if(shellPt == nullptr) {
        return;
    }

    bool insidePolyShell = PointLocation::isInRing(*shellPt, polyPts);
    if(!insidePolyShell) {
        return;
    }

    // if no holes, this is an error!
    auto nholes = p->getNumInteriorRing();
    if(nholes <= 0) {
        validErr = new TopologyValidationError(
            TopologyValidationError::eNestedShells,
            *shellPt);
        return;
    }

    /**
     * Check if the shell is inside one of the holes.
     * This is the case if one of the calls to checkShellInsideHole
     * returns a null coordinate.
     * Otherwise, the shell is not properly contained in a hole, which is
     * an error.
     */
    const Coordinate* badNestedPt = nullptr;
    for(size_t i = 0; i < nholes; ++i) {
        const LinearRing* hole = p->getInteriorRingN(i);
        badNestedPt = checkShellInsideHole(shell, hole, graph);
        if(badNestedPt == nullptr) {
            return;
        }
    }
    validErr = new TopologyValidationError(
        TopologyValidationError::eNestedShells, *badNestedPt
    );
}

/*private*/
const Coordinate*
IsValidOp::checkShellInsideHole(const LinearRing* shell,
                                const LinearRing* hole,
                                GeometryGraph* graph)
{
    const CoordinateSequence* shellPts = shell->getCoordinatesRO();
    const CoordinateSequence* holePts = hole->getCoordinatesRO();

    // TODO: improve performance of this - by sorting pointlists
    // for instance?
    const Coordinate* shellPt = findPtNotNode(shellPts, hole, graph);

    // if point is on shell but not hole, check that the shell is
    // inside the hole
    if(shellPt) {
        bool insideHole = PointLocation::isInRing(*shellPt, holePts);
        if(!insideHole) {
            return shellPt;
        }
    }

    const Coordinate* holePt = findPtNotNode(holePts, shell, graph);

    // if point is on hole but not shell, check that the hole is
    // outside the shell
    if(holePt) {
        bool insideShell = PointLocation::isInRing(*holePt, shellPts);
        if(insideShell) {
            return holePt;
        }
        return nullptr;
    }
    assert(0); // points in shell and hole appear to be equal
    return nullptr;
}

/*private*/
void
IsValidOp::checkConnectedInteriors(GeometryGraph& graph)
{
    ConnectedInteriorTester cit(graph);
    if(!cit.isInteriorsConnected()) {
        validErr = new TopologyValidationError(
            TopologyValidationError::eDisconnectedInterior,
            cit.getCoordinate());
    }
}


/*private*/
void
IsValidOp::checkInvalidCoordinates(const CoordinateSequence* cs)
{
    auto size = cs->size();
    for(size_t i = 0; i < size; ++i) {
        if(! isValid(cs->getAt(i))) {
            validErr = new TopologyValidationError(
                TopologyValidationError::eInvalidCoordinate,
                cs->getAt(i));
            return;

        }
    }
}

/*private*/
void
IsValidOp::checkInvalidCoordinates(const Polygon* poly)
{
    checkInvalidCoordinates(poly->getExteriorRing()->getCoordinatesRO());
    if(validErr != nullptr) {
        return;
    }

    auto nholes = poly->getNumInteriorRing();
    for(size_t i = 0; i < nholes; ++i) {
        checkInvalidCoordinates(
            poly->getInteriorRingN(i)->getCoordinatesRO()
        );
        if(validErr != nullptr) {
            return;
        }
    }
}

/*private*/
void
IsValidOp::checkClosedRings(const Polygon* poly)
{
    const LinearRing* lr = poly->getExteriorRing();
    checkClosedRing(lr);
    if(validErr) {
        return;
    }

    auto nholes = poly->getNumInteriorRing();
    for(size_t i = 0; i < nholes; ++i) {
        lr = (const LinearRing*)poly->getInteriorRingN(i);
        checkClosedRing(lr);
        if(validErr) {
            return;
        }
    }
}

/*private*/
void
IsValidOp::checkClosedRing(const LinearRing* ring)
{
    if(! ring->isClosed() && ! ring->isEmpty()) {
        validErr = new TopologyValidationError(
            TopologyValidationError::eRingNotClosed,
            ring->getCoordinateN(0));
    }
}

} // namespace geos.operation.valid
} // namespace geos.operation
} // namespace geos

