/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#pragma once

#include <osgEarth/Common>
#include <osgEarth/GeoData>
#include <osgEarth/Containers>
#include <vector>
#include <stack>
#include <queue>

namespace osgEarth
{
    using namespace osgEarth;

    /** Options for the Geometry::buffer() operation. */
    class BufferParameters
    {
    public:
        enum CapStyle  { CAP_DEFAULT, CAP_SQUARE, CAP_ROUND, CAP_FLAT };
        enum JoinStyle { JOIN_ROUND, JOIN_MITRE, JOIN_BEVEL};
        BufferParameters( CapStyle capStyle =CAP_DEFAULT, JoinStyle joinStyle = JOIN_ROUND, int cornerSegs =0, bool singleSided=false )
            : _capStyle(capStyle), _joinStyle(joinStyle),_cornerSegs(cornerSegs), _singleSided(singleSided) { }
        CapStyle  _capStyle;
        JoinStyle _joinStyle;
        int       _cornerSegs; // # of line segment making up a rounded corner
        bool      _singleSided; //Whether or not to do a single sided buffer
    };

    typedef std::vector<osg::Vec3d> Vec3dVector;

    /**
     * Baseline geometry class. All Geometry objects derive from this
     * class, even MultiGeometry.
     */
    class OSGEARTH_EXPORT Geometry : public osgEarth::InlineVector<osg::Vec3d,osg::Referenced>
    {
    public:
        enum Type {
            TYPE_UNKNOWN,
            TYPE_POINT,
            TYPE_POINTSET,
            TYPE_LINESTRING,
            TYPE_RING,
            TYPE_POLYGON,
            TYPE_TRIMESH,
            TYPE_MULTI
        };

        enum Orientation {
            ORIENTATION_CCW,
            ORIENTATION_CW,
            ORIENTATION_DEGENERATE
        };

    public:
        Geometry() : Geometry(TYPE_UNKNOWN) { }
        Geometry(const Geometry& rhs) = default;

        /** dtor - intentionally public */
        virtual ~Geometry();

    public:
        static const std::string& toString( Type t ) {
            static std::string s[8] = {
                "Point", "PointSet", "LineString", "Ring", "Polygon", "TriangleMesh", "MultiGeometry", "Unknown"
            };
            return
                t == TYPE_POINT ? s[0] :
                t == TYPE_POINTSET ? s[1] :
                t == TYPE_LINESTRING ? s[2] :
                t == TYPE_RING ? s[3] :
                t == TYPE_POLYGON ? s[4] :
                t == TYPE_TRIMESH ? s[5] :
                t == TYPE_MULTI ? s[6] : s[7];
        }

        /** Creates a geometry from a vector array */
        static Geometry* create( Type type, const Vec3dVector* toCopy );

        // true if osgEarth is compiled for buffering
        static bool hasBufferOperation();

    public:
        /**
         * Gets the total number of points in this geometry.
         */
        virtual int getTotalPointCount() const;

        /**
         * Gets the total number of geometry components
         */
        virtual unsigned getNumComponents() const { return 1; }

        /**
         * Gets the total number of geometries; it is the total of all parts of all
         * components. Also can be seen as the number of Geometry objects that would
         * be returned by a full GeometryIterator.
         */
        virtual unsigned getNumGeometries() const { return 1; }

        /**
         * Converts this geometry to another type. This function will return "this" if
         * the type is the same, and will return NULL if the conversion is impossible.
         */
        virtual Geometry* cloneAs( const Geometry::Type& newType ) const;

        /**
         * Creates a new Vec3Array (single-precision), copies the part into it, and
         * returns the new object.
         */
        osg::Vec3Array* createVec3Array() const;

        /**
         * Creates a new Vec3dArray (double-precision), copies the part into it, and
         * returns the new object.
         */
        osg::Vec3dArray* createVec3dArray() const;

        /**
         * Gets the bounds of this geometry
         */
        virtual Bounds getBounds() const;

        /**
         * Length of the [outermost] geometry.
         */
        virtual double getLength() const;

        /**
         * Whether the geometry is lines
         */
        bool isLinear() const { return getComponentType() == TYPE_LINESTRING || getComponentType() == TYPE_RING; }

        /**
         * Runs a buffer (dialate/erode) operation on this geometry and returns the
         * result in the output parameter. Returns true if the op succeeded.
         */
        bool buffer(
            double distance,
            osg::ref_ptr<Geometry>& output,
            const BufferParameters& bp =BufferParameters() ) const;

        /**
         * Runs an offset curve operation on this geometry and returns the
         * result in the output parameter. Returns true if the op succeeded.
        */
        bool offsetCurve(
            double distance,
            int quadSegs,
            BufferParameters::JoinStyle joinStyle,
            double mitreLimit,
            osg::ref_ptr<Geometry>& output
            ) const;

        /**
         * Crops this geometry to the region represented by the crop polygon, returning
         * the result in the output parameter. Returns true if the op succeeded.
         */
        virtual Geometry* crop(const class Ring* boundary) const;

        /**
         * Crops this geometry to the bounds, returning the result in the output parameter.
         * Returns true if the op succeeded.
         */
        Geometry* crop(const Bounds& bounds) const;

        /**
         * Creates the union of this geometry with the other geometry, returning
         * the result in the output parameter. Returns true if the op succeeded.
         */
        bool geounion(
            const Geometry* other,
            osg::ref_ptr<Geometry>& output ) const;

        /**
         * Boolean difference - subtracts diffPolygon from this geometry, and put the
         * result in output.
         */
        bool difference(
            const class Polygon* diffPolygon,
            osg::ref_ptr<Geometry>& output ) const;

        /**
         * Whether this geometry intersects with another geometry
         */
        bool intersects(
            const class Geometry* other
            ) const;

        /**
         * Simplifies this geometry, returning the result in the output parameter.
         */
        osg::ref_ptr<Geometry> simplify(double tolerance, bool preserveTopology) const;

        //! Calculate the signed distance (in the XY plane) from a point
        //! to this geometry.
        //! A negative distance indicates that the point is interior
        //! to a ring or polygon.
        virtual double getSignedDistance2D(
            const osg::Vec3d& point) const;

        /**
         * Localizes this geometry relative to its centroid, and returns the localization
         * offset.
         */
        osg::Vec3d localize();

        /**
         * Reverses a call the localize(), given the same offset returned by that method.
         */
        void delocalize( const osg::Vec3d& offset );

        /**
         * Reorders the points in the geometry so that, if the last point was connected
         * to the first in a ring, they would be would in the specified direction.
         */
        virtual void rewind( Orientation ori );

        /**
         * Makes the last point the same as the first point. Suitable for rings and polygons.
         */
        virtual void close();

        virtual void open() { }

        //! Removes consecutive duplicates in the geometry to prepare for tessellation.
        virtual void removeDuplicates();

        //! Removes any colinear points, i.e. points that can be safely removed without
        //! affecting the shape/area of the geometry.
        virtual void removeColinearPoints();

        //! Get the winding orientation of the geometry (if you consider the last point
        //! to connect back to the first in a ring.)
        Orientation getOrientation() const;

        //! Whether a closed geometry contains the 2D point
        virtual bool contains2D(double x, double y) const { return false; }

        //! Splits the geometry along the antimeridian. Only works for geodetic coordinates.
        //! Might return "this" if no split is necessary or not applicable.
        virtual Geometry* splitAcrossAntimeridian();


        //! Iterate over all the parts of a geometry (signature = void(Geometry* part))
        template<typename CALLABLE>
        inline void forEachPart(bool includePolygonHoles, CALLABLE&& func);
        template<typename CALLABLE>
        inline void forEachPart(CALLABLE&& func) { forEachPart(true, func); };

        //! Iterate over all the parts of a CONST geometry (signature = void(const Geometry* part))
        template<typename CALLABLE>
        inline void forEachPart(bool includePolygonHoles, CALLABLE&& func) const;
        template<typename CALLABLE>
        inline void forEachPart(CALLABLE&& func) const { forEachPart(true, func); };

    public:
        inline Type getType() const { return _type; }
        virtual Type getComponentType() const { return getType(); }
        virtual bool isValid() const { return size() >= 1; }

        //! Deep clone of the object
        virtual Geometry* clone() const = 0;

        //! Empty clone of the object (same type, no content)
        virtual Geometry* cloneEmpty() const = 0;

        void push_back(const osg::Vec3d& v ) {
            osgEarth::InlineVector<osg::Vec3d,osg::Referenced>::push_back(v); }
        void push_back(double x, double y) {
            osgEarth::InlineVector<osg::Vec3d,osg::Referenced>::push_back(osg::Vec3d(x,y,0.)); }
        void push_back(double x, double y, double z) {
            osgEarth::InlineVector<osg::Vec3d,osg::Referenced>::push_back(osg::Vec3d(x,y,z)); }

        virtual bool isRing() const { return getComponentType() == TYPE_RING || getComponentType() == TYPE_POLYGON; }
        virtual bool isPolygon() const { return getComponentType() == TYPE_POLYGON; }
        virtual bool isPointSet() const { return getComponentType()==TYPE_POINT || getComponentType()==TYPE_POINTSET; }
        virtual bool isLineString() const { return getComponentType() == TYPE_LINESTRING; }
        virtual bool isOpen() const { return true; }

    protected:
        Geometry(Type type, int capacity = 0);
        Geometry(Type type, const Vec3dVector* toCopy);

        Type _type = TYPE_UNKNOWN;
    };

    typedef std::vector< osg::ref_ptr<Geometry> > GeometryCollection;

    /**
     * An unordered collections of points.
     */
    class OSGEARTH_EXPORT PointSet : public Geometry
    {
    public:
        PointSet(int capacity = 0) : Geometry(TYPE_POINTSET, capacity) { }
        PointSet(const Vec3dVector* toCopy) : Geometry(TYPE_POINTSET, toCopy) { }
        PointSet(const PointSet& rhs) = default;
        Geometry* clone() const override { return new PointSet(*this); }
        Geometry* cloneEmpty() const override { return new PointSet(); }

        // Don't close point sets
        void close() override { }

    protected:
        PointSet(Type type, int capacity = 0) : Geometry(type, capacity) { }
        PointSet(Type type, const Vec3dVector* toCopy) : Geometry(type, toCopy) { }
    };

    /**
     * A single point.
     */
    class OSGEARTH_EXPORT Point : public PointSet
    {
    public:
        Point(int capacity = 0) : PointSet(TYPE_POINT, capacity) { }
        Point(const Vec3dVector* toCopy) : PointSet(TYPE_POINT, toCopy) { }
        Point(const Point& rhs) = default;
        Geometry* clone() const override { return new Point(*this); }
        Geometry* cloneEmpty() const override { return new Point(); }

        void set(const osg::Vec3d& value);

        // don't close
        void close() override { }
    };

    /**
     * An ordered set of points forming a single contiguous line string.
     */
    class OSGEARTH_EXPORT LineString : public Geometry
    {
    public:
        LineString(int capacity = 0) : Geometry(TYPE_LINESTRING, capacity) { }
        LineString(const Vec3dVector* toCopy) : Geometry(TYPE_LINESTRING, toCopy) { }
        LineString(const LineString& rhs) = default;
        Geometry* clone() const override { return new LineString(*this); }
        Geometry* cloneEmpty() const override { return new LineString(); }

        bool getSegment(double length, osg::Vec3d& start, osg::Vec3d& end);

        bool sample(double length, osg::Vec3d& point);

        void close() override;

        double getSignedDistance2D(const osg::Vec3d& point) const override;

        Geometry* crop(const class Ring* boundary) const override;

    public:
        bool isValid() const override { return size() >= 2; }
    };

    /**
     * A Ring is a closed region. It is open (the first and last
     * points are not the same). It has an orientation, i.e. it is either
     * wound clockwise or counter-clockwise.
     */
    class OSGEARTH_EXPORT Ring : public Geometry
    {
    public:
        Ring(int capacity = 0) : Geometry(TYPE_RING, capacity) { }
        Ring(const Vec3dVector* toCopy) : Ring(TYPE_RING, toCopy) { }
        Ring(const Ring& ring) = default;
        Geometry* clone() const override { return new Ring(*this); }
        Geometry* cloneEmpty() const override { return new Ring(); }

        // override
        virtual Geometry* cloneAs( const Geometry::Type& newType ) const;

        // tests whether the point falls within the ring
        bool contains2D( double x, double y ) const override;

        // gets the signed area of a part that is known to be open.
        double getSignedArea2D() const;

        // gets the length of the ring (override)
        double getLength() const;

        // ensures that the first and last points are not idential.
        void open() override;

        // ensures that the first and last points are identical.
        void close() override;

        // whether the ring is open (i.e. first and last points are different)
        bool isOpen() const override;

        // opens and winds the ring in the specified direction
        virtual void rewind(Orientation ori);

        double getSignedDistance2D(const osg::Vec3d& a) const override;

        // crop to a boundary
        Geometry* crop(const class Ring* boundary) const override;

    public:
        virtual Type getType() const { return Geometry::TYPE_RING; }
        virtual bool isValid() const { return size() >= 3; }

    protected:
        Ring(Type type, int capacity = 0) : Geometry(type, capacity) { }
        Ring(Type type, const Vec3dVector* toCopy);
    };

    typedef std::vector<osg::ref_ptr<Ring> > RingCollection;

    /**
     * A Polygon is a geometry that consists of one outer boundary Ring, and
     * zero or more inner "hole" rings. The boundary ring is would CCW, and the
     * inner "holes" are wound CW.
     */
    class OSGEARTH_EXPORT Polygon : public Ring
    {
    public:
        Polygon(int capacity = 0) : Ring(TYPE_POLYGON, capacity) { }
        Polygon(const Vec3dVector* toCopy) : Ring(TYPE_POLYGON, toCopy) { }
        Polygon(const Polygon& rhs);
        Geometry* clone() const override { return new Polygon(*this); }
        Geometry* cloneEmpty() const override { return new Polygon(); }

    public:
        virtual Type getType() const { return Geometry::TYPE_POLYGON; }
        virtual int getTotalPointCount() const;

        virtual unsigned getNumGeometries() const { return 1 + _holes.size(); }

        // tests whether the point falls within the polygon (but not its holes)
        virtual bool contains2D( double x, double y ) const override;

        virtual void open();

        virtual void close();

        virtual void removeDuplicates();

        virtual void removeColinearPoints();

        virtual double getSignedDistance2D(
            const osg::Vec3d& a) const override;

        Geometry* crop(const Ring* boundary) const override;

        Geometry* cloneAs( const Geometry::Type& newType ) const override;

    public:
        RingCollection& getHoles() { return _holes; }
        const RingCollection& getHoles() const { return _holes; }

    protected:
        RingCollection _holes;
    };

    class OSGEARTH_EXPORT TriMesh : public Geometry
    {
    public:
        TriMesh() : Geometry(TYPE_TRIMESH) { }
        TriMesh(const TriMesh& rhs);
        Geometry* clone() const override { return new TriMesh(*this); }
        Geometry* cloneEmpty() const override { return new TriMesh(); }

        unsigned getNumGeometries() const override { return 1u; }

        bool contains2D(double x, double y) const override;

    public:
        std::vector<unsigned> _indices;
    };

    /**
     * A collection of multiple geometries (aka, a "multi-part" geometry).
     */
    class OSGEARTH_EXPORT MultiGeometry : public Geometry
    {
    public:
        MultiGeometry() : Geometry(TYPE_MULTI) { }
        MultiGeometry(const MultiGeometry& rhs);
        MultiGeometry(const GeometryCollection& parts);
        Geometry* clone() const override { return new MultiGeometry(*this); }
        Geometry* cloneEmpty() const override { return new MultiGeometry(); }

    public:
        Type getComponentType() const override;
        int getTotalPointCount() const override;
        unsigned getNumComponents() const override { return _parts.size(); }

        unsigned getNumGeometries() const override;

        // gets the combined length of all parts
        double getLength() const override;

        // override
        Geometry* cloneAs( const Geometry::Type& newType ) const override;
        bool isValid() const override;
        Bounds getBounds() const override;
        void rewind( Orientation ori ) override;
        void removeDuplicates() override;
        void removeColinearPoints() override;
        void open() override;
        void close() override;
        double getSignedDistance2D(const osg::Vec3d& a) const override;
        bool contains2D(double x, double y) const override;
        Geometry* crop(const Ring* boundary) const override;

    public:
        GeometryCollection& getComponents() { return _parts; }
        const GeometryCollection& getComponents() const { return _parts; }

        Geometry* add(Geometry* geom) {
            OE_SOFT_ASSERT_AND_RETURN(geom != nullptr, nullptr);
            _parts.push_back(geom);
            return geom;
        }

    protected:
        GeometryCollection _parts;
    };

    /**
     * Iterates over a Geometry object, returning each component Geometry
     * in turn. The iterator automatically traverses MultiGeometry objects,
     * returning their components. The iterator NEVER returns an actual
     * MultiGeometry object.
     */
    class OSGEARTH_EXPORT GeometryIterator
    {
    public:
        //! Constructs a new iterator.
        //! @param geom Geometry over which to iterator
        //! @param traversePolyHoles Whether to include polygon holes in the traversal
        GeometryIterator(
            Geometry* geom,
            bool traversePolygonHoles = true);

        //! Whether next() will return another geometry
        bool hasMore() const;

        //! Returns the next geometry part when hasMore() == true
        Geometry* next();

        //! Visits each part and calls a user-defined functor
        inline void forEach(const std::function<void(Geometry* part)>& func) {
            while (hasMore()) func(next());
        }

    private:
        Geometry* _next;
        std::queue<Geometry*> _stack;
        bool _traverseMulti;
        bool _traversePolyHoles;

        void fetchNext();
    };

    /**
     * Iterates over a Geometry object, returning each component Geometry
     * in turn. The iterator automatically traverses MultiGeometry objects,
     * returning their components. The iterator NEVER returns an actual
     * MultiGeometry object.
     */
    class OSGEARTH_EXPORT ConstGeometryIterator
    {
    public:
        //! Null constructor (must call reset before using)
        ConstGeometryIterator();

        //! Constructs a new iterator.
        //! @param geom Geometry over which to iterator
        //! @param traversePolyHoles Whether to include polygon holes in the traversal
        ConstGeometryIterator(
            const Geometry* geom,
            bool traversePolygonHoles  =true );

        //! Sets the iterator to iterate a new geometry
        void reset(const Geometry* geom, bool traversePolygonHoles = true);

        //! Whether next() will return another geometry
        bool hasMore() const;

        //! Returns the next geometry part when hasMore() == true
        const Geometry* next();

        //! Visits each part and calls a user-defined functor
        //! Signature = void(const Geometry* part)
        template<typename CALLABLE>
        inline void forEach(CALLABLE&& func) { //const std::function<void(const Geometry* part)>& func) {
            while (hasMore()) func(next());
        }

    private:
        const Geometry* _next = nullptr;
        std::vector<const Geometry*> _stack;
        //std::stack<const Geometry*> _stack;
        bool _traverseMulti = true;
        bool _traversePolyHoles = true;

        void fetchNext();
    };

    typedef std::pair<osg::Vec3d, osg::Vec3d> Segment;

    /**
     * Iterates over geometry, returning each consecutive pair of points
     * as a line segment.
     */
    class OSGEARTH_EXPORT ConstSegmentIterator
    {
    public:
        //! Construct an iterator.
        //! @param verts Geometry over which to iterate segments
        //! @param forceClosedLoop connect the last and first point even if the
        //!    geometry is not a ring.
        ConstSegmentIterator(const Geometry* verts, bool forceClosedLoop = false) {
            _verts = &verts->asVector();
            _iter = verts->begin();
            _done = verts->size() < 2;
            _closeLoop = forceClosedLoop ? true : verts->getType() == verts->TYPE_RING || verts->getType() == verts->TYPE_POLYGON;
        }

        //! Whether next() will return a valid segment
        inline bool hasMore() const {
            return !_done;
        }

        //! Next segment when hasMore() == true
        const Segment& next() {
            _current.first = *_iter++;
            if (_iter == _verts->end()) {
                _iter = _verts->begin();
                _done = true;
            }
            else if (_iter + 1 == _verts->end() && !_closeLoop) {
                _done = true;
            }
            _current.second = *_iter;
            return _current;
        }

    private:
        const Vec3dVector* _verts;
        Vec3dVector::const_iterator _iter;
        bool _done;
        bool _closeLoop;
        Segment _current;
    };



    //! Iterate over all the parts of a geometry
    template<typename CALLABLE>
    inline void Geometry::forEachPart(bool includePolygonHoles, CALLABLE&& func)
    {
        GeometryIterator i(this, includePolygonHoles);
        i.forEach(func);
    }

    //! Iterate over all the parts of a geometry
    template<typename CALLABLE>
    inline void Geometry::forEachPart(bool includePolygonHoles, CALLABLE&& func) const
    {
        ConstGeometryIterator i(this, includePolygonHoles);
        i.forEach(func);
    }


} // namespace osgEarth
