#pragma once
#include <boost/shared_ptr.hpp>
#include <vector>
#include <algorithm>
#ifndef _USE_MATH_DEFINES
#define	_USE_MATH_DEFINES
#endif
#include <math.h>
#include "latitudelongitude.fs.h"

//#include <cereal/types/polymorphic.hpp>
//#include <cereal/types/base_class.hpp>

#define ALT_INVALID -9999
#define SPD_INVALID -1
#define	HDG_INVALID	-1
#define	DIST_INVALID	-1
#define	TEMP_INVALID	-999

#define	KMTONM(km) (km * 0.539957)
#define	NMTOKM(nm) (nm * 1.852)

class Winds
{
public:
	double alt = ALT_INVALID;
	double speed = SPD_INVALID;
	double direction = HDG_INVALID;

	bool isManual = false;

	static Winds Interpolate(std::vector<Winds> winds, double altitude);
	bool IsValid() const;

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

enum class TrackCode
{
	ArcToFix, //AF
	CourseToAltitude, //CA
	CourseToDMEDistance, //CD
	CourseToFix, //CF
	CourseToNextIntercept, //CI
	CourseToRadialTermination, //CR
	DirectToFix, //DF
	CourseFromFixToAltitude, //FA
	CourseFromFixToDMEDistance, //FD
	CourseFromFixToManualTermination, //FM
	TrackFromFixToDistance, //FC
	HoldToAltitude, //HA
	HoldAtFix, //HF
	HoldUntilManualTermination, //HM
	InitialFix, //IF
	ProcedureTurnToIntercept, //PI
	RadiusToFix, //RF
	TrackToFix, //TF
	HeadingToAltitude, //VA
	HeadingToDMEDistance, //VD
	HeadingToNextLegIntercept, //VI
	HeadingUntilManualTermination, //VM
	HeadingToRadialTermination, //VR
};

enum class NavDataObjType
{
	Indeterminate,
	TerminalLeg,
	Waypoint,
	Navaid,
	AirwayLeg,
	Airport,
	Discontinuity,
	Runway,
	LatitudeLongitude,
	PilotDefinedWaypoint,
	Hold
};

enum class AirwayStatus
{
	Incomplete,
	OK
};

enum class TurnDirection
{	
	Left,
	Right
};

enum class ObjectTurnDirection
{
	Left = -1,
	Unspecified = 0,
	Right = 1
};

enum class ObjectUsagePhase
{
	Unknown,
	Climb,
	Descent,
	MissedApproach
};

enum class ObjectUsage
{
	Leg,
	InitialPosition,
	Origin,	
	DepartureLeg,
	ArrivalLeg,
	ApproachLeg,
	Destination,
	TurnPoint,	
	ToC,
	ToD,
	SC
};

enum class AltitudeRestrictionType
{
	Above,
	Below,
	Between,
	Exact,
	Step,
	Unspecified
};

struct LateralSegment
{	
	LatitudeLongitude startPoint;
	LatitudeLongitude endPoint;
	
	double turnRadius = 0;	
	double turnStartRadial = 0;
	double turnEndRadial = 0;
	LatitudeLongitude turnCenterPoint;
	TurnDirection turnDir = TurnDirection::Left;

	bool afsComplete = false;

	LateralSegment() {}
	LateralSegment(LatitudeLongitude start, LatitudeLongitude end);
	LateralSegment(LatitudeLongitude center, LatitudeLongitude start, LatitudeLongitude end, TurnDirection direction);	

	double GetLength();
	
	//may need ground distance, vertical rate, delta altitude, etc.
	//use performance recording/monitoring software to sample data points from testers to build perf DB

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct VerticalSegment
{
	double endsAt = 1.0;
	double speed = SPD_INVALID;
	double altitude = ALT_INVALID;

	double speedRestriction = SPD_INVALID;	

	ObjectUsagePhase phase = ObjectUsagePhase::Unknown;

	//DEBUG
	double fpm = 0;
	double distToArr = 0;
	double gs = 0;
	//

	double fuelBurned = 0;

	VerticalSegment() {}
	VerticalSegment(double endsAt, double speed, double altitude, double speedRestriction, ObjectUsagePhase usagePhase, double FPM = 0, double DTA = 0, double GS = 0) : endsAt(endsAt), speed(speed), altitude(altitude), speedRestriction(speedRestriction), phase(usagePhase), fpm(FPM), distToArr(DTA), gs(GS) {}

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

enum class LocationType
{
	Absolute,	
	Hold,
	Relative,
	PerfBased,
	Abstract
};

struct NavDataObj
{
protected:
	int ID = -1;	
public:
	std::wstring identifier;
	LatitudeLongitude latlon;
	int Flag = 0;

	Winds winds[4];

	double tempAlt = ALT_INVALID;
	double temp = TEMP_INVALID;

	double altRestr = ALT_INVALID;
	double altRestrLower = ALT_INVALID;
	AltitudeRestrictionType altRestrictionType = AltitudeRestrictionType::Unspecified;

	double speed_restr = SPD_INVALID;
	bool isFlyover = false;

	//ObjectUsagePhase usagePhase = ObjectUsagePhase::Unknown;
	ObjectUsage usage = ObjectUsage::Leg;

	double progress = 0.0;

	double distToNext = DIST_INVALID;
	double distToArrival = DIST_INVALID;	
	double bearingFromTo = 0;
	double bearingToFrom = 0;

	double fuelBurnToNext = 0;
	bool efobSet = false;
	double efob = -1;

	double ete = -1;
	double eto = -1;
	double ato = -1;

	//Values for use in internal calculations only
	struct Calculation
	{
		double secToNextLeg = -1;
		double progToNextLeg = 0.0;
		double distToNextLeg = DIST_INVALID;		

		template <class B> void store(B& buf) const;
		template <class B> void parse(B& buf);
	} calc;
	//

	size_t currLatSegment = 0;
	std::vector<LateralSegment> latSegments;

	size_t currVertSegment = 0;
	std::vector< VerticalSegment> vertSegments;

	static std::shared_ptr<NavDataObj> Copy(std::shared_ptr<NavDataObj> obj);

	double GetPredictedAltitude() const;
	double GetPredictedSpeed() const;
	ObjectUsagePhase GetUsagePhase() const;

	int GetID() const;
	void SetID(int id);
	virtual bool IsConditional() const;
	virtual LocationType GetLocationType() const;
	virtual const NavDataObjType GetType() const = 0;
	virtual LatitudeLongitude GetLatitudeLongitude() const;
	virtual std::wstring GetIdentifier() const;
	virtual ~NavDataObj();

	template <class B> void store(B& buf) const;	
	template <class B> void parse(B& buf);
};

struct Runway : public NavDataObj {
	double true_heading;
	short length;
	short width;
	std::wstring surface;
	short elevation;
	virtual const NavDataObjType GetType() const;	
	Runway();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

enum class HoldType
{
	Standard,
	Manual,
	Database,
	DatabaseEdited
};

struct HoldParameters
{	
	std::wstring wptIdent;

	HoldType type = HoldType::Standard;
	bool holdDistManual = false;
	double holdDist = -1;
	bool holdTimeManual = true;
	double holdTime = -1;
	TurnDirection holdDir = TurnDirection::Right;	
	double holdInboundCourse = -1;
	double holdLimitTime = -1;
	double holdLimitFuel = -1;
	bool holdExit = false;

	double defaultCourse = -1;
	double defaultTime = -1;

	template <class B> void parse(B& buf);
	template <class B> void store(B& buf) const;
};

struct Hold : public NavDataObj, HoldParameters {	
	virtual const NavDataObjType GetType() const override;
	virtual bool IsConditional() const override;
	virtual LocationType GetLocationType() const override;
	double GetSpeedRestriction(double altitude) const;
	void Update(HoldParameters params);
	Hold();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

enum class NavaidType
{
	VOR = 1,
	VORTAC = 2,
	TACAN = 3,
	VORDME = 4,
	NDB = 5,
	NDBDME = 7,
	ILSDME = 8,
	DME = 9
};

struct Navaid : public NavDataObj {
	NavaidType type;
	std::wstring name;

	std::wstring country;

	int frequency;
	std::wstring channel;
	std::wstring usage;
	int elevation;
	double slaved_var;

	virtual const NavDataObjType GetType() const;

	Navaid();

	template <class B> void parse(B& buf);
	template <class B> void store(B& buf) const;
};

struct Waypoint : public NavDataObj {
	bool collocated;
	
	std::wstring name;

	std::wstring country;

	int navaidID;

	virtual const NavDataObjType GetType() const;

	Waypoint();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct AirwayLeg : public NavDataObj {
	int airway_id;
	std::wstring airway_ident;

	std::wstring level;
	
	std::wstring wpt1Ident;
	int wpt1ID;
	int wpt2ID;	
	
	bool is_start;
	bool is_end;
	
	virtual const NavDataObjType GetType() const;

	AirwayLeg();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct Airway {
	int ID;
	std::wstring identifier;

	AirwayStatus status;
	std::vector<AirwayLeg> legs;
	Airway();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct Discontinuity : public NavDataObj {
	virtual bool IsConditional() const override;
	virtual const NavDataObjType GetType() const override;
	virtual std::wstring GetIdentifier() const override;
	virtual LocationType GetLocationType() const override;	

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct LatitudeLongitudeWaypoint : public NavDataObj {	
	virtual LocationType GetLocationType() const override;
	virtual const NavDataObjType GetType() const;
	LatitudeLongitudeWaypoint();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct PilotDefinedWaypoint : public NavDataObj {	
	std::wstring pbd_place;
	int pbd_brg;
	int pbd_dist;

	int elev;
	int crs;
	int length;

	bool is_defined = false;
	
	bool IsRunway() const;
	virtual const NavDataObjType GetType() const;

	PilotDefinedWaypoint();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct TerminalLeg : public NavDataObj {
	int terminal_id;
	std::shared_ptr<NavDataObj> via;

	char type;	
	
	TrackCode track_code;	

	std::wstring transition;

	Waypoint wpt;
	Navaid nvd;	
	HoldParameters hold;

	ObjectTurnDirection turn_dir;
	double course;
	double distance;
	LatitudeLongitude refLatLon;

	LatitudeLongitude destLatlon;
	double bearToDest;
	double distToDest;	

	double vnav;
	int center_id;
	LatitudeLongitude center_latlon;	
	virtual const NavDataObjType GetType() const override;
	virtual LatitudeLongitude GetLatitudeLongitude() const override;

	double speed_limit;

	bool is_missed_app_point;
	bool is_final_app_fix;

	virtual bool IsConditional() const override;
	virtual LocationType GetLocationType() const override;
	TerminalLeg();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct ILS {
	int ID;
	std::wstring identifier;
	LatitudeLongitude latlon;

	int runway_id;
	int frequency;
	double gs_angle;

	short category;
	double loc_course;

	int crossing_height;
	int elevation;
	bool has_dme;

	ILS();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct TerminalProcedure {
	int ID;
	std::wstring identifier;

	//int airport_id;

	//std::wstring icao;

	char proc;

	//std::wstring full_name;

	//std::wstring runway;

	std::wstring GetFormattedName();

	std::vector<TerminalLeg> legs;

	bool is_ils;

	int runway_id;

	TerminalProcedure();

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct Airport : public NavDataObj {
	std::wstring name;
	int primaryid;
	short elevation;

	int transition_altitude;

	Airport();

	std::vector<Runway> runways;
	std::vector<TerminalProcedure> sids;
	std::vector<TerminalProcedure> stars;
	std::vector<TerminalProcedure> approaches;

	Airport& operator=(const Airport& otherapt);
	bool operator==(Airport& otherapt);
	bool operator!=(Airport& otherapt);
	virtual const NavDataObjType GetType() const;

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

struct Transition
{
	std::wstring identifier;

	template <class B> void store(B& buf) const;
	template <class B> void parse(B& buf);
};

class WorldData
{
public:
	static std::vector<LatitudeLongitude> LatLonSpread(LatitudeLongitude source, int numOfSteps);
	static int LatToX(double lat);
	static int LonToY(double lon);

	std::vector<Airport> airports[181][361];
	std::vector<Navaid> navaids[181][361];
	std::vector<Waypoint> waypoints[181][361];

	std::vector<Airway> airways;
	std::vector<AirwayLeg> airwayLegs;
	std::vector<ILS> ilses;
};

struct NavigationData {
private:
	void name_terminal_leg(TerminalLeg& tl);
	int which_terminal_leg_is_duplicate(TerminalLeg& tl1, TerminalLeg& tl2);		
	std::string dataDir;

public:
	bool is_usable;
	std::wstring cycle_name;	
	std::wstring cycle_start;
	std::wstring cycle_end;

	bool is_valid();

	bool lookup_airport_by_id(int id, Airport & apt);
	bool lookup_airport_by_icao(std::wstring icao, Airport & apt);
	bool lookup_waypoint_by_id(int wptid, Waypoint& wpt);
	bool lookup_navaid_by_id(int navid, Navaid& nd);
	bool lookup_airway_by_ident(std::wstring ident, Airway& aw);

	bool lookup_ils(int runway_id, ILS& ils);
	std::vector<ILS> lookup_ils_by_frequency(int frequency);
	std::vector<ILS> lookup_ils_by_ident(std::wstring ident);

	bool intersect_airways(int airwayid1, int airwayid2, AirwayLeg& leg);

	bool lookup_airway_legs(Airway awy, std::wstring waypoint1ident, std::wstring waypoint2ident, std::vector<AirwayLeg>& legs);

	std::vector<Navaid> lookup_navaids_by_ident(std::wstring ident);
	std::vector<Navaid> lookup_navaids_by_frequency(int frequency);
	std::vector<Waypoint> lookup_waypoints_by_ident(std::wstring ident);

	std::vector<Runway> lookup_runways(int airport_id, int terminal_id = -1);

	double get_runway_slope(int airport_id, int runway_id);

	WorldData worldData;

	std::vector<Transition> lookup_sid_transitions(int sid_id);
	std::vector<TerminalProcedure> lookup_sids(int airport_id, int runway_id = -1);
	std::vector<TerminalLeg> lookup_sid_legs(int sid_id, std::wstring rwy, std::wstring transition);

	std::vector<Transition> lookup_star_transitions(int star_id);
	std::vector<TerminalProcedure> lookup_stars(int airport_id, int runway_id = -1);
	std::vector<TerminalLeg> lookup_star_legs(int star_id, std::wstring rwy, std::wstring transition);

	std::vector<Transition> lookup_approach_transitions(int approach_id);
	std::vector<TerminalProcedure> lookup_approaches(int airport_id, int runway_id = -1, bool strict_runway_enforcement = false);
	std::vector<TerminalLeg> lookup_approach_legs(int approach_id, std::wstring transition);

	NavigationData(std::wstring dataDirectory);
	~NavigationData();
};

