/**
 * route.hxx - defines basic route and route-element classes. Route elements
 * are specialised into waypoints and related things. Routes are any class that
 * owns a collection (list, tree, graph) of route elements - such as airways,
 * procedures or a flight plan.
 */
// SPDX-FileCopyrightText: 2009 James Turner
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

// std
#include <vector>
#include <map>
#include <iosfwd>
#include <optional>

// Simgear
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/props/props.hxx>

// forward decls
class FGPositioned;
class SGPath;
class FGAirport;

namespace flightgear
{

// forward decls
class RouteBase;
class Waypt;
class NavdataVisitor;
class Airway;

using AirwayRef = SGSharedPtr<Airway>;
typedef SGSharedPtr<Waypt> WayptRef;

typedef enum {
    WPT_MAP = 1 << 0,        ///< missed approach point
    WPT_IAF = 1 << 1,        ///< initial approach fix
    WPT_FAF = 1 << 2,        ///< final approach fix
    WPT_OVERFLIGHT = 1 << 3, ///< must overfly the point directly
    WPT_TRANSITION = 1 << 4, ///< transition to/from enroute structure
    WPT_MISS = 1 << 5,       ///< segment is part of missed approach
    /// waypoint position is dynamic, i.e moves based on other criteria,
    /// such as altitude, inbound course, or so on.
    WPT_DYNAMIC = 1 << 6,
    /// waypoint was created automatically (not manually entered/loaded)
    /// for example waypoints from airway routing or a procedure
    WPT_GENERATED = 1 << 7,

    WPT_DEPARTURE = 1 << 8,
    WPT_ARRIVAL = 1 << 9,

    /// waypoint generated by VNAV / speed management profile,
    /// for step climbs or top of descent
    WPT_PSEUDO = 1 << 10,
    WPT_APPROACH = 1 << 11,

    /// waypoint produced by expanding a VIA segment
    WPT_VIA = 1 << 12,

    /// waypoint should not be shown in UI displays, etc
    /// this is used to implement FMSs which delete waypoints after passing them
    WPT_HIDDEN = 1 << 13
} WayptFlag;

typedef enum {
	RESTRICT_NONE,
	RESTRICT_AT,
	RESTRICT_ABOVE,
	RESTRICT_BELOW,
    RESTRICT_BETWEEN,
  SPEED_RESTRICT_MACH,  ///< encode an 'AT' restriction in Mach, not IAS
  RESTRICT_DELETE,      ///< ignore underlying restriction (on a leg)
  RESTRICT_COMPUTED,    ///< data is computed, not a real restriction
  SPEED_COMPUTED_MACH   ///< variant on above to encode a Mach value
} RouteRestriction;

typedef enum {
    DEFAULT_UNITS = 0,
    ALTITUDE_FEET,
    ALTITUDE_METER,
    ALTITUDE_FLIGHTLEVEL,
    SPEED_KNOTS,
    SPEED_MACH,
    SPEED_KPH
} RouteUnits;

RouteRestriction restrictionFromString(const std::string& t);

bool isMachRestrict(RouteRestriction rr);

double convertSpeedUnits(RouteUnits aSrc, RouteUnits aDest, double aAltitudeFt, double aValue);
double convertAltitudeUnits(RouteUnits aSrc, RouteUnits aDest, double aValue);

/**
 * Abstract base class for waypoints (and things that are treated similarly
 * by navigation systems). More precisely this is route path elements,
 * including their terminator.
 */
class Waypt : public SGReferenced
{
public:
  virtual ~Waypt();

    RouteBase* owner() const
    { return const_cast<RouteBase*>(_owner); }

    virtual SGGeod position() const = 0;

    /**
	 * The Positioned associated with this element, if one exists
	 */
    virtual FGPositioned* source() const
    {
        return nullptr;
    }

    double altitudeFt() const;

    double speed(RouteUnits aUnits = DEFAULT_UNITS) const;

    double altitude(RouteUnits aUnits = DEFAULT_UNITS) const;

    double constraintAltitude(RouteUnits aUnits = DEFAULT_UNITS) const;

// wrapper - asserts if restriction type is _MACH
  double speedKts() const;

// wrapper - asserts if restriction type is not _MACH
  double speedMach() const;

  virtual RouteRestriction altitudeRestriction() const
  {
      return _altRestrict;
  }

  virtual RouteRestriction speedRestriction() const
  {
      return _speedRestrict;
  }

  void setAltitude(double aAlt, RouteRestriction aRestrict, RouteUnits aUnits = DEFAULT_UNITS);
  void setSpeed(double aSpeed, RouteRestriction aRestrict, RouteUnits aUnits = DEFAULT_UNITS);

    void setConstraintAltitude(double aAlt);

    /**
   * Identifier associated with the waypoint. Human-readable, but
   * possibly quite terse, and definitely not unique.
   */
    virtual std::string ident() const;

    /**
	 * Test if the specified flag is set for this element
	 */
    virtual bool flag(WayptFlag aFlag) const;

    virtual unsigned int flags() const
    {
        return _flags;
    }

  void setFlag(WayptFlag aFlag, bool aV = true);

  /**
   * Factory method
   */
  static WayptRef createFromProperties(RouteBase* aOwner, SGPropertyNode_ptr aProp);

  /**
        Create a waypoint from the route manager's standard string format:
     *  - simple identifier
     *  - decimal-lon,decimal-lat
     *  - airport-id/runway-id
     *  - navaid/radial-deg/offset-nm
     */
  static WayptRef createFromString(RouteBase* aOwner, const std::string& s, const SGGeod& vicinity);

  static WayptRef fromLatLonString(RouteBase* aOwner, const std::string& target);


  void saveAsNode(SGPropertyNode* node) const;

  /**
   * Test if this element and another are 'the same', i.e matching
   * ident and lat/lon are approximately equal
   */
  bool matches(Waypt* other) const;

  /**
 * Test if this element and positioned are the same, i.e matching
 * ident and lat/lon are approximately equal
 */
  bool matches(FGPositioned* aPos) const;

  /**
   * Test if this element and a position 'the same'
   * this can be defined by either position, ident or both
   */
  bool matches(const SGGeod& aPos) const;

  virtual std::string type() const = 0;

  /**
   * Magentic variation at/in the vicinity of the waypoint.
   * For some waypoint types this will always return 0.
   */
  virtual double magvarDeg() const;

  /**
   * return the associated heading  or radial for this waypoint.
   * The exact meaning varies by type - for a hold it's the inbound radial,
   * for a DME intercept it's the heading to hold, and so on.
   */
  virtual double headingRadialDeg() const;

  /**
   * @brief icaoDescription - description of the waypoint in ICAO route plan format
   * @return
   */
  virtual std::string icaoDescription() const;

protected:
  friend class NavdataVisitor;

  Waypt(RouteBase* aOwner);

  /**
   * Persistence helper - read node properties from a file
   */
    virtual bool initFromProperties(SGPropertyNode_ptr aProp);

    /**
   * Persistence helper - save this element to a node
   */
    virtual void writeToProperties(SGPropertyNode_ptr aProp) const;

    typedef Waypt*(FactoryFunction)(RouteBase* aOwner);
    static void registerFactory(const std::string aNodeType, FactoryFunction* aFactory);

    double _altitude = 0.0;
    /// some restriction types specify two altitudes, in which case this is the second value, corresponding to
    ///  AltitudeCons in the level-D XML procedures format.
    std::optional<double> _constraintAltitude;

    RouteUnits _altitudeUnits = ALTITUDE_FEET;

	double _speed = 0.0;
    RouteUnits _speedUnits = SPEED_KNOTS;

    RouteRestriction _altRestrict = RESTRICT_NONE;
    RouteRestriction _speedRestrict = RESTRICT_NONE;

private:

  /**
   * Create an instance of a concrete subclass, or throw an exception
   */
  static WayptRef createInstance(RouteBase* aOwner, const std::string& aTypeName);

    const RouteBase* _owner = nullptr;
	unsigned short _flags = 0;
    mutable double _magVarDeg = 0.0; ///< cached mag var at this location
};

typedef std::vector<WayptRef> WayptVec;

class RouteBase : public SGReferenced
{
public:
  virtual ~RouteBase();

  /**
   *
   */
  virtual std::string ident() const = 0;

  static void loadAirportProcedures(const SGPath& aPath, FGAirport* aApt);

  static void dumpRouteToKML(const WayptVec& aRoute, const std::string& aName);

  static void dumpRouteToKMLLineString(const std::string& aIdent,
    const WayptVec& aRoute, std::ostream& aStream);
private:

};

} // of namespace flightgear
