/* osgEarth
 * Copyright 2025 Pelican Mapping
 * MIT License
 */
#pragma once
#include <osgEarthProcedural/Export>
#include <osgEarth/Common>
#include <osg/Vec3d>
#include <vector>
#include <deque>
#include <set>
#include <functional>
#include <unordered_map>
#include <unordered_set>
#include <cstdint>
#include <cmath>

namespace osgEarth {
    class Feature;
    class Geometry;
    class GeoExtent;
}

namespace osgEarth {
    namespace Procedural
    {
        class OSGEARTHPROCEDURAL_EXPORT RoadNetwork
        {
        public:
            
            struct QuantizedVec3d
            {
                // 1eN, where N = decimal places of precision for comparing 2D coordinates
                //constexpr double _precision = std::pow(1, DECIMAL_PLACES);
                const int _precision;
                const double _x, _y;
                const std::int64_t _xr, _yr; // quantized coordinates
                mutable double _z;

                QuantizedVec3d(const osg::Vec3d& p, int precision) :
                    _precision(precision),
                    //_precision(std::pow(10, precision)),
                    _x(p.x()), _y(p.y()), _z(p.z()),
                    _xr(static_cast<std::int64_t>(p.x() * std::pow(10, precision))),
                    _yr(static_cast<std::int64_t>(p.y() * std::pow(10, precision))) { }

                const double& x() const { return _x; }
                const double& y() const { return _y; }
                const double& z() const { return _z; }

                inline bool operator == (const QuantizedVec3d& rhs) const {
                    return _xr == rhs._xr && _yr == rhs._yr && _precision == rhs._precision;
                }

                inline bool operator != (const QuantizedVec3d& rhs) const {
                    return _xr != rhs._xr || _yr != rhs._yr || _precision != rhs._precision;
                }

                inline bool operator == (const osg::Vec3d& rhs) const {
                    return operator == (QuantizedVec3d(rhs, _precision));
                }

                inline bool operator != (const osg::Vec3d& rhs) const {
                    return operator != (QuantizedVec3d(rhs, _precision));
                }

                inline bool operator < (const QuantizedVec3d& rhs) const {
                    if (_xr < rhs._xr) return true;
                    if (_xr > rhs._xr) return false;
                    return _yr < rhs._yr;
                }
            };

            struct Way;

            /**
            * "Junction" is a point where one or more Ways meet. The Way itself
            * may have a linestring, but its endpoints are called Junctions.
            */
            struct Junction : public QuantizedVec3d
            {
                Junction(const osg::Vec3d& p, int precision) : QuantizedVec3d(p, precision) { }

                inline bool is_endpoint() const { return ways.size() == 1; }
                inline bool is_midpoint() const { return ways.size() > 1; }

                mutable std::vector<Way*> ways;
            };

            using JunctionSet = std::set<Junction>;

            /**
            * "Way" is a linear feature made up of two or more points and is a direct
            * representation of Geometry within a Feature.
            */
            struct Way
            {
                Way(const Junction& a, const Junction& b, Feature* f, Geometry* g) :
                    start(&a), end(&b), feature(f), geometry(g) { }
                const Junction* start = nullptr;
                const Junction* end = nullptr;
                double length = 0.0;
                Feature* feature = nullptr;
                Geometry* geometry = nullptr;
            };

            /**
            * "Relation" is just a string of "Way" objects that are connected by their endpoints
            * and (probably) represent the same conceptual feature (like a named road or a bridge)
            */
            struct Relation
            {
                std::vector<Way*> ways;
                double length = 0.0;
            };

            //! Construct a road network
            //! @param srs The spatial reference system for network data. It needs this so it
            //!    can pick the appropriate precision for quantizing the coordinates.
            RoadNetwork() = default;

            int precision = 3; // number of decimal places to use for quantizing coordinates
            JunctionSet junctions;
            JunctionSet midpoints;
            std::deque<Way> ways; // must use a container that doesn't invalidate points to members
            std::vector<Relation> relations;
            std::function<Way*(const Junction& junction, Way* incoming_way, const std::vector<Way*>& exclusions)> nextWayInRelation;
            std::function<bool(const Feature* a, const Feature* b)> canMerge;
            std::unordered_map<const Geometry*, std::pair<bool, bool>> geometryEndpointFlags; // front,back

            //! Add a (linear) feature. Each of its endpoints will become a Junction.
            //! One or both may already exist in the network. Each of its full
            //! Geometry's will become a Way.
            void addFeature(Feature* feature);

            //! Add a single node to the map
            const Junction& addJunction(const osg::Vec3d& p);

            //! Combine edges that share nodes into strings of edges called EdgeStrings.
            //! Find all endpoints (nodes with only one edge) and traverse until you find
            //! another node with only one edge. If there is a fork, use the user lambda
            //! functions to decide which way to go.
            void buildRelations();

            //! Get the features for which the centroid of the relation owning the
            //! feature in within the extent.
            void getFeatures(const GeoExtent& extent, std::unordered_set<std::int64_t>& output) const;

            //! Searches through each Relation and looks for opportunities to merge
            //! the comprising Ways into single features for the purposes of rendering.
            //! Sometimes two ways will abut, but they will not render as a connected
            //! feature - this helps mitigate that.
            //! TODO: FUTURE: create valid intersections at locations where incompatible
            //! features abut.
            void mergeRelations(std::vector<osg::ref_ptr<Feature>>& output);
        };
    }
}