/**
 * route.hxx - defines basic route and route-element classes. Route elements
 * are specialised into waypoints and related things. Routes are any class tha
 * owns a collection (list, tree, graph) of route elements - such as airways,
 * procedures or a flight plan.
 */
 
// Written by James Turner, started 2009.
//
// Copyright (C) 2009  Curtis L. Olson
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#ifndef FG_ROUTE_HXX
#define FG_ROUTE_HXX

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

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

// forward decls
class FGPositioned;
class SGPath;
class FGRunway;

#include <Airports/simple.hxx>
typedef SGSharedPtr<FGAirport> FGAirportRef;

namespace flightgear
{

// forward decls
class RouteBase;
class Waypt;
class NavdataVisitor;
class SID;
class STAR;
class Transition;
  
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
} WayptFlag;

typedef enum {
	RESTRICT_NONE,
	RESTRICT_AT,
	RESTRICT_ABOVE,
	RESTRICT_BELOW,
  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;

bool isMachRestrict(RouteRestriction rr);
  
/**
 * Abstract base class for waypoints (and things that are treated similarly
 * by navigation systems)
 */
class Waypt : public SGReferenced
{
public:
  virtual ~Waypt();
  
	RouteBase* owner() const 
		{ return _owner; }
  
  /**
   * Return true course (in degrees) and distance (in metres) from the provided
   * position to this waypoint
   */
  virtual std::pair<double, double> courseAndDistanceFrom(const SGGeod& aPos) const;
                  	
	virtual SGGeod position() const = 0;
	
	/**
	 * The Positioned associated with this element, if one exists
	 */
	virtual FGPositioned* source() const
		{ return NULL; }
	
	virtual double altitudeFt() const 
		{ return _altitudeFt; }
		
  virtual double speed() const
    { return _speed; }
  
// 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);
  void setSpeed(double aSpeed, RouteRestriction aRestrict);
  
  /**
   * Identifier assoicated with the waypoint. Human-readable, but
   * possibly quite terse, and definitiely 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);
  
  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* aOther) 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 assoicated 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;
protected:
  friend class NavdataVisitor;
  
	Waypt(RouteBase* aOwner);
  
  /**
   * Persistence helper - read node properties from a file
   */
  virtual void 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 _altitudeFt;
	double _speed; // knots IAS or mach
	RouteRestriction _altRestrict;
	RouteRestriction _speedRestrict;
private:

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

	RouteBase* _owner;
	unsigned short _flags;
  mutable double _magVarDeg; 
};

typedef std::vector<WayptRef> WayptVec;
  
class RouteBase
{
public:
  /**
   *
   */
  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:

};
  
class FlightPlan : public RouteBase
{
public:
  FlightPlan();
  virtual ~FlightPlan();
  
  virtual std::string ident() const;
  void setIdent(const std::string& s);
  
  FlightPlan* clone(const std::string& newIdent = std::string()) const;
  
  /**
   * flight-plan leg encapsulation
   */
  class Leg
  {
  public:
    FlightPlan* owner() const
    { return _parent; }
    
    Waypt* waypoint() const
    { return _waypt; }
    
    // reutrn the next leg after this one
    Leg* nextLeg() const;
    
    unsigned int index() const;
    
    int altitudeFt() const;		
    int speed() const;
    
    int speedKts() const;
    double speedMach() const;
    
    RouteRestriction altitudeRestriction() const;    
    RouteRestriction speedRestriction() const;
    
    void setSpeed(RouteRestriction ty, double speed);
    void setAltitude(RouteRestriction ty, int altFt);
    
    double courseDeg() const;
    double distanceNm() const;
    double distanceAlongRoute() const;
  private:
    friend class FlightPlan;
    
    Leg(FlightPlan* owner, WayptRef wpt);
    
    Leg* cloneFor(FlightPlan* owner) const;
    
    FlightPlan* _parent;
    RouteRestriction _speedRestrict, _altRestrict;
    int _speed;
    int _altitudeFt;
    WayptRef _waypt;
    /// length of this leg following the flown path
    mutable double _pathDistance;
    mutable double _courseDeg;
    /// total distance of this leg from departure point
    mutable double _distanceAlongPath; 
  };
  
  class Delegate
  {
  public:
    virtual ~Delegate();
    
    virtual void departureChanged() { }
    virtual void arrivalChanged() { }
    virtual void waypointsChanged() { }
    
    virtual void currentWaypointChanged() { }
  
  protected:
    Delegate();
    
  private:
    void removeInner(Delegate* d);
    
    void runDepartureChanged();
    void runArrivalChanged();
    void runWaypointsChanged();
    void runCurrentWaypointChanged();
    
    friend class FlightPlan;
    
    Delegate* _inner;
  };
  
  Leg* insertWayptAtIndex(Waypt* aWpt, int aIndex);
  void insertWayptsAtIndex(const WayptVec& wps, int aIndex);
  
  void deleteIndex(int index);
  void clear();
  int clearWayptsWithFlag(WayptFlag flag);
  
  int currentIndex() const
  { return _currentIndex; }
  
  void setCurrentIndex(int index);
  
  Leg* currentLeg() const;
  Leg* nextLeg() const;
  Leg* previousLeg() const;
  
  int numLegs() const
  { return _legs.size(); }
  
  Leg* legAtIndex(int index) const;
  int findLegIndex(const Leg* l) const;
  
  int findWayptIndex(const SGGeod& aPos) const;
  
  bool load(const SGPath& p);
  bool save(const SGPath& p);
  
  FGAirportRef departureAirport() const
  { return _departure; }
  
  FGAirportRef destinationAirport() const
  { return _destination; }
  
  FGRunway* departureRunway() const
  { return _departureRunway; }
  
  FGRunway* destinationRunway() const
  { return _destinationRunway; }
  
  Approach* approach() const
  { return _approach; }
  
  void setDeparture(FGAirport* apt);
  void setDeparture(FGRunway* rwy);
  
  SID* sid() const
  { return _sid; }
  
  Transition* sidTransition() const;
  
  void setSID(SID* sid, const std::string& transition = std::string());
  
  void setSID(Transition* sidWithTrans);
  
  void setDestination(FGAirport* apt);
  void setDestination(FGRunway* rwy);
  
  /**
    * note setting an approach will implicitly update the destination
    * airport and runway to match
    */
  void setApproach(Approach* app);
  
  STAR* star() const
  { return _star; }
  
  Transition* starTransition() const;
  
  void setSTAR(STAR* star, const std::string& transition = std::string());
  
  void setSTAR(Transition* starWithTrans);
  
  double totalDistanceNm() const
  { return _totalDistance; }
  
  /**
   * Create a WayPoint from a string in the following format:
   *  - simple identifier
   *  - decimal-lon,decimal-lat
   *  - airport-id/runway-id
   *  - navaid/radial-deg/offset-nm
   */
  WayptRef waypointFromString(const std::string& target);
  
  void setDelegate(Delegate* d);
  void removeDelegate(Delegate* d);
private:
  
  bool loadPlainTextRoute(const SGPath& path);
  
  void loadVersion1XMLRoute(SGPropertyNode_ptr routeData);
  void loadVersion2XMLRoute(SGPropertyNode_ptr routeData);
  void loadXMLRouteHeader(SGPropertyNode_ptr routeData);
  WayptRef parseVersion1XMLWaypt(SGPropertyNode* aWP);
  
  double magvarDegAt(const SGGeod& pos) const;
  
  std::string _ident;
  int _currentIndex;
  
  FGAirportRef _departure, _destination;
  FGRunway* _departureRunway, *_destinationRunway;
  SID* _sid;
  STAR* _star;
  Approach* _approach;
  std::string _sidTransition, _starTransition;
  
  double _totalDistance;
  void rebuildLegData();
  
  typedef std::vector<Leg*> LegVec;
  LegVec _legs;
  
  Delegate* _delegate;
};
  
} // of namespace flightgear

#endif // of FG_ROUTE_HXX