/**********************************************************************
 *
 * GEOS - Geometry Engine Open Source
 * http://geos.osgeo.org
 *
 * Copyright (C) 2006 Refractions Research Inc.
 *
 * 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.
 *
 **********************************************************************
 *
 * Last port: simplify/DouglasPeuckerSimplifier.java rev. 1.5 (JTS-1.7)
 *
 **********************************************************************/

#include <geos/simplify/DouglasPeuckerSimplifier.h>
#include <geos/simplify/DouglasPeuckerLineSimplifier.h>
#include <geos/geom/Geometry.h> // for Ptr typedefs
#include <geos/geom/LinearRing.h>
#include <geos/geom/MultiPolygon.h>
#include <geos/geom/CoordinateSequence.h> // for Ptr typedefs
#include <geos/geom/GeometryFactory.h>
#include <geos/geom/util/GeometryTransformer.h> // for DPTransformer inheritance
#include <geos/util/IllegalArgumentException.h>
#include <geos/util.h>

#include <memory> // for unique_ptr
#include <cassert>

#ifndef GEOS_DEBUG
#define GEOS_DEBUG 0
#endif

#if GEOS_DEBUG
#include <iostream>
#endif

using namespace geos::geom;

namespace geos {
namespace simplify { // geos::simplify

class DPTransformer: public geom::util::GeometryTransformer {

public:

    DPTransformer(double tolerance);

protected:

    CoordinateSequence::Ptr transformCoordinates(
        const CoordinateSequence* coords,
        const Geometry* parent) override;

    Geometry::Ptr transformLinearRing(
        const LinearRing* geom,
        const Geometry* parent) override;

    Geometry::Ptr transformPolygon(
        const Polygon* geom,
        const Geometry* parent) override;

    Geometry::Ptr transformMultiPolygon(
        const MultiPolygon* geom,
        const Geometry* parent) override;

private:

    /*
     * Creates a valid area geometry from one that possibly has
     * bad topology (i.e. self-intersections).
     * Since buffer can handle invalid topology, but always returns
     * valid geometry, constructing a 0-width buffer "corrects" the
     * topology.
     * Note this only works for area geometries, since buffer always returns
     * areas.  This also may return empty geometries, if the input
     * has no actual area.
     *
     * @param roughAreaGeom an area geometry possibly containing
     *        self-intersections
     * @return a valid area geometry
     */
    Geometry::Ptr createValidArea(const Geometry* roughAreaGeom);

    double distanceTolerance;

};

DPTransformer::DPTransformer(double t)
    :
    distanceTolerance(t)
{
    setSkipTransformedInvalidInteriorRings(true);
}

Geometry::Ptr
DPTransformer::createValidArea(const Geometry* roughAreaGeom)
{
    bool isValidArea = roughAreaGeom->getDimension() == 2 && roughAreaGeom->isValid();
    if (! isValidArea)
        return Geometry::Ptr(roughAreaGeom->buffer(0.0));
    return Geometry::Ptr(roughAreaGeom->clone());
}

CoordinateSequence::Ptr
DPTransformer::transformCoordinates(
    const CoordinateSequence* coords,
    const Geometry* parent)
{
    ::geos::ignore_unused_variable_warning(parent);

    bool preserveRingEndpoint = parent->getGeometryTypeId() != GEOS_LINEARRING;

    return DouglasPeuckerLineSimplifier::simplify(*coords, distanceTolerance, preserveRingEndpoint);
}

Geometry::Ptr
DPTransformer::transformLinearRing(
    const LinearRing* geom,
    const Geometry* parent)
{

#if GEOS_DEBUG
    std::cerr << "DPTransformer::transformLinearRing(LinearRing " << geom << ", Geometry " << parent << ");" << std::endl;
#endif

  	bool removeDegenerateRings = dynamic_cast<const Polygon*>(parent);
  	Geometry::Ptr simpResult( GeometryTransformer::transformLinearRing(geom, parent));
  	if (removeDegenerateRings && ! dynamic_cast<const LinearRing*>(simpResult.get()))
  		return nullptr;
  	return simpResult;
}

Geometry::Ptr
DPTransformer::transformPolygon(
    const Polygon* geom,
    const Geometry* parent)
{

#if GEOS_DEBUG
    std::cerr << "DPTransformer::transformPolygon(Polygon " << geom << ", Geometry " << parent << ");" << std::endl;
#endif

    Geometry::Ptr roughGeom(GeometryTransformer::transformPolygon(geom, parent));

    // don't try and correct if the parent is going to do this
    if(dynamic_cast<const MultiPolygon*>(parent)) {
        return roughGeom;
    }

    return createValidArea(roughGeom.get());
}

Geometry::Ptr
DPTransformer::transformMultiPolygon(
    const MultiPolygon* geom,
    const Geometry* parent)
{
#if GEOS_DEBUG
    std::cerr << "DPTransformer::transformMultiPolygon(MultiPolygon " << geom << ", Geometry " << parent << ");" <<
              std::endl;
#endif
    Geometry::Ptr roughGeom(GeometryTransformer::transformMultiPolygon(geom, parent));
    return createValidArea(roughGeom.get());
}

/************************************************************************/



//DouglasPeuckerSimplifier::

/*public static*/
Geometry::Ptr
DouglasPeuckerSimplifier::simplify(const Geometry* geom,
                                   double tolerance)
{
    DouglasPeuckerSimplifier tss(geom);
    tss.setDistanceTolerance(tolerance);
    return tss.getResultGeometry();
}

/*public*/
DouglasPeuckerSimplifier::DouglasPeuckerSimplifier(const Geometry* geom)
    :
    inputGeom(geom)
{
}

/*public*/
void
DouglasPeuckerSimplifier::setDistanceTolerance(double tol)
{
    if(tol < 0.0) {
        throw util::IllegalArgumentException("Tolerance must be non-negative");
    }
    distanceTolerance = tol;
}

Geometry::Ptr
DouglasPeuckerSimplifier::getResultGeometry() const
{
    if (inputGeom->isEmpty()) {
        return inputGeom->clone();
    }

    DPTransformer t(distanceTolerance);
    return t.transform(inputGeom);

}

} // namespace geos::simplify
} // namespace geos
