/*
 * SPDX-License-Identifier: GPL-2.0-or-later
 * SPDX-FileCopyrightText: 2005 Jean-Yves Lefort <jylefort@FreeBSD.org>
 *
 * mk_viii.hxx -- Honeywell MK VIII EGPWS emulation
 * Written by Jean-Yves Lefort, started September 2005.
*/

#pragma once

#include <assert.h>

#include <vector>
#include <deque>
#include <map>

#include <simgear/props/props.hxx>
#include <simgear/props/tiedpropertylist.hxx>
#include <simgear/structure/subsystem_mgr.hxx>

class SGSampleGroup;

#include <Airports/runways.hxx>
#include <Airports/airport.hxx>
#include <Main/globals.hxx>
#include <Sound/voiceplayer.hxx>

#ifdef _MSC_VER
#  pragma warning( push )
#  pragma warning( disable: 4355 )
#endif


///////////////////////////////////////////////////////////////////////////////
// MK_VIII ////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

class MK_VIII : public SGSubsystem
{
    // keep in sync with Mode6Handler::altitude_callout_definitions[]
    static const unsigned n_altitude_callouts = 12;

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::Parameter ///////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    template <class T>
    class Parameter
    {
        T _value;

    public:
        bool ncd;

        inline Parameter ()
            : _value(0), ncd(true) {}

        inline T get () const { assert(! ncd); return _value; }
        inline T *get_pointer () { return &_value; }
        inline void set (T value) { ncd = false; _value = value; }
        inline void unset () { ncd = true; }

        inline void set (const Parameter<T> *parameter)
        {
            if (parameter->ncd)
                unset();
            else
                set(parameter->get());
        }

        inline void set (const Parameter<double> *parameter, double factor)
        {
            if (parameter->ncd)
                unset();
            else
                set(parameter->get() * factor);
        }
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::Sample //////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    template <class T>
    class Sample
    {
    public:
        double timestamp;
        T      value;

        inline Sample (T _value)
        : timestamp(globals->get_sim_time_sec()), value(_value) {}
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::Timer ///////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    class Timer
    {
        double start_time;

    public:
        bool   running;

        inline Timer ()
        : start_time(0.0), running(false) {}

        inline void start () { running = true; start_time = globals->get_sim_time_sec(); }
        inline void stop () { running = false; }
        inline double elapsed () const { assert(running); return globals->get_sim_time_sec() - start_time; }
        inline double start_or_elapsed ()
        {
            if (running)
                return elapsed();
            else
            {
                start();
                return 0;
            }
        }
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::PropertiesHandler ///////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    class PropertiesHandler : public FGVoicePlayer::PropertiesHandler
    {
        MK_VIII *mk;

    public:
        struct
        {
            SGPropertyNode_ptr ai_caged;
            SGPropertyNode_ptr ai_roll;
            SGPropertyNode_ptr ai_serviceable;
            SGPropertyNode_ptr altimeter_altitude;
            SGPropertyNode_ptr altimeter_serviceable;
            SGPropertyNode_ptr altitude;
            SGPropertyNode_ptr altitude_agl;
            SGPropertyNode_ptr altitude_gear_agl;
            SGPropertyNode_ptr altitude_radar_agl;
            SGPropertyNode_ptr orientation_roll;
            SGPropertyNode_ptr asi_serviceable;
            SGPropertyNode_ptr asi_speed;
            SGPropertyNode_ptr autopilot_heading_lock;
            SGPropertyNode_ptr flaps;
            SGPropertyNode_ptr gear_down;
            SGPropertyNode_ptr throttle;
            SGPropertyNode_ptr latitude;
            SGPropertyNode_ptr longitude;
            SGPropertyNode_ptr nav0_cdi_serviceable;
            SGPropertyNode_ptr nav0_gs_distance;
            SGPropertyNode_ptr nav0_gs_needle_deflection;
            SGPropertyNode_ptr nav0_gs_serviceable;
            SGPropertyNode_ptr nav0_has_gs;
            SGPropertyNode_ptr nav0_heading_needle_deflection;
            SGPropertyNode_ptr nav0_in_range;
            SGPropertyNode_ptr nav0_nav_loc;
            SGPropertyNode_ptr nav0_serviceable;
            SGPropertyNode_ptr power;
            SGPropertyNode_ptr replay_state;
            SGPropertyNode_ptr vs;
        } external_properties;

        inline PropertiesHandler (MK_VIII *device)
            : FGVoicePlayer::PropertiesHandler(), mk(device) {}

        PropertiesHandler() : FGVoicePlayer::PropertiesHandler(), mk(NULL) {}

        void init ();
    };

    public:
        PropertiesHandler     properties_handler;

    private:
        /////////////////////////////////////////////////////////////////////////////
        // MK_VIII::PowerHandler ////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////

        class PowerHandler
        {
            MK_VIII *mk;

            bool    serviceable;
            bool    powered;

            Timer   power_loss_timer;
            Timer   abnormal_timer;
            Timer   low_surge_timer;
            Timer   high_surge_timer;
            Timer   very_high_surge_timer;

            bool handle_abnormal_voltage (bool abnormal,
                                          Timer *timer,
                                          double max_duration);

            void power_on ();
            void power_off ();

        public:
            inline PowerHandler (MK_VIII *device)
            : mk(device), serviceable(false), powered(false) {}

            void bind (SGPropertyNode *node);
            void update ();
        };

        /////////////////////////////////////////////////////////////////////////////
        // MK_VIII::SystemHandler ///////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////

        class SystemHandler
        {
            MK_VIII *mk;

            double  boot_delay;
            Timer   boot_timer;

            int     last_replay_state;
            Timer   reposition_timer;

        public:
            typedef enum
            {
                STATE_OFF,
                STATE_BOOTING,
                STATE_ON,
                STATE_REPOSITION
            } State;

            State state;

            inline SystemHandler (MK_VIII *device)
                : mk(device), boot_delay(0.0), last_replay_state(0), state(STATE_OFF) {}

            void power_on ();
            void power_off ();
            void update ();
        };

        /////////////////////////////////////////////////////////////////////////////
        // MK_VIII::ConfigurationModule /////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////

        class ConfigurationModule
        {
        public:
            // keep in sync with IOHandler::present_status()
            typedef enum
            {
                CATEGORY_AIRCRAFT_MODE_TYPE_SELECT,
                CATEGORY_AIR_DATA_INPUT_SELECT,
                CATEGORY_POSITION_INPUT_SELECT,
                CATEGORY_ALTITUDE_CALLOUTS,
                CATEGORY_AUDIO_MENU_SELECT,
                CATEGORY_TERRAIN_DISPLAY_SELECT,
                CATEGORY_OPTIONS_SELECT_GROUP_1,
                CATEGORY_RADIO_ALTITUDE_INPUT_SELECT,
                CATEGORY_NAVIGATION_INPUT_SELECT,
                CATEGORY_ATTITUDE_INPUT_SELECT,
                CATEGORY_HEADING_INPUT_SELECT,
                CATEGORY_WINDSHEAR_INPUT_SELECT,
                CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT,
                CATEGORY_AUDIO_OUTPUT_LEVEL,
                CATEGORY_UNDEFINED_INPUT_SELECT_1,
                CATEGORY_UNDEFINED_INPUT_SELECT_2,
                CATEGORY_UNDEFINED_INPUT_SELECT_3,
                N_CATEGORIES
            } Category;

            typedef enum
            {
                STATE_OK,
                STATE_INVALID_DATABASE,
                STATE_INVALID_AIRCRAFT_TYPE
            } State;

            State state;

            int effective_categories[N_CATEGORIES];

            ConfigurationModule (MK_VIII *device);

            void boot ();
            void bind (SGPropertyNode *node);

        private:
            MK_VIII *mk;

            int categories[N_CATEGORIES];

            bool read_aircraft_mode_type_select (int value);
            bool read_air_data_input_select (int value);
            bool read_position_input_select (int value);
            bool read_altitude_callouts (int value);
            bool read_audio_menu_select (int value);
            bool read_terrain_display_select (int value);
            bool read_options_select_group_1 (int value);
            bool read_radio_altitude_input_select (int value);
            bool read_navigation_input_select (int value);
            bool read_attitude_input_select (int value);
            bool read_heading_input_select (int value);
            bool read_windshear_input_select (int value);
            bool read_input_output_discrete_type_select (int value);
            bool read_audio_output_level (int value);
            bool read_undefined_input_select (int value);

            static bool m6_t2_is_bank_angle (Parameter<double> *agl,
                                             double abs_roll_deg,
                                             bool ap_engaged);
            static bool m6_t4_is_bank_angle (Parameter<double> *agl,
                                             double abs_roll_deg,
                                             bool ap_engaged);
        };

        /////////////////////////////////////////////////////////////////////////////
        // MK_VIII::FaultHandler ////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////

        class FaultHandler
        {
            enum
            {
                INOP_GPWS = 1 << 0,
                INOP_TAD  = 1 << 1
            };

            MK_VIII *mk;

            static const unsigned int fault_inops[];

            bool has_faults (unsigned int inop);

        public:
            // keep in sync with IOHandler::present_status()

            typedef enum
            {
                FAULT_ALL_MODES_INHIBIT,
                FAULT_GEAR_SWITCH,
                FAULT_FLAPS_SWITCH,
                FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID,
                FAULT_SELF_TEST_INVALID,
                FAULT_GLIDESLOPE_CANCEL_INVALID,
                FAULT_STEEP_APPROACH_INVALID,
                FAULT_GPWS_INHIBIT,
                FAULT_TA_TCF_INHIBIT,
                FAULT_MODES14_INPUTS_INVALID,
                FAULT_MODE5_INPUTS_INVALID,
                FAULT_MODE6_INPUTS_INVALID,
                FAULT_BANK_ANGLE_INPUTS_INVALID,
                FAULT_TCF_INPUTS_INVALID,
                N_FAULTS
            } Fault;

            unsigned int faults;

            inline FaultHandler(MK_VIII *device) :
                    mk(device), faults(0) {
            }

            void boot();

            void set_fault(Fault fault);
            void unset_fault(Fault fault);

            bool has_faults() const;
        };

    public:

        /////////////////////////////////////////////////////////////////////////////
        // MK_VIII::IOHandler ///////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////
        class IOHandler
        {
        public:
            enum Lamp
            {
                LAMP_NONE,
                LAMP_GLIDESLOPE,
                LAMP_CAUTION,
                LAMP_WARNING
            };

            struct LampConfiguration
            {
                bool format2;
                bool flashing;
            };

            struct FaultsConfiguration
            {
                double max_flaps_down_airspeed;
                double max_gear_down_airspeed;
            };

            struct _s_Conf
            {
                const LampConfiguration   *lamp;
                const FaultsConfiguration *faults;
                bool                      flap_reversal;
                bool                      steep_approach_enabled;
                bool                      gpws_inhibit_enabled;
                bool                      momentary_flap_override_enabled;
                bool                      alternate_steep_approach;
                bool                      use_internal_gps;
                bool                      localizer_enabled;
                int                       altitude_source;
                bool                      use_attitude_indicator;
            } conf;

            struct _s_input_feeders
            {
                struct _s_discretes
                {
                    bool landing_gear;
                    bool landing_flaps;
                    bool glideslope_inhibit;
                    bool decision_height;
                    bool autopilot_engaged;
                } discretes;

                struct _s_arinc429
                {
                    bool uncorrected_barometric_altitude;
                    bool barometric_altitude_rate;
                    bool radio_altitude;
                    bool glideslope_deviation;
                    bool roll_angle;
                    bool localizer_deviation;
                    bool computed_airspeed;
                    bool decision_height;
                } arinc429;
            } input_feeders;

            struct _s_inputs
            {
                struct _s_discretes
                {
                    bool landing_gear;            // appendix E 6.6.2, 3.15.1.4
                    bool landing_flaps;           // appendix E 6.6.4, 3.15.1.2
                    bool momentary_flap_override; // appendix E 6.6.6, 3.15.1.6
                    bool self_test;               // appendix E 6.6.7, 3.15.1.10
                    bool glideslope_inhibit;      // appendix E 6.6.11, 3.15.1.1
                    bool glideslope_cancel;       // appendix E 6.6.13, 3.15.1.5
                    bool decision_height_100;
                    bool decision_height;         // appendix E 6.6.14, 3.10.2
                    bool mode6_low_volume;        // appendix E 6.6.15, 3.15.1.7
                    bool audio_inhibit;           // appendix E 6.6.16, 3.15.1.3
                    bool ta_tcf_inhibit;          // appendix E 6.6.20, 3.15.1.9
                    bool autopilot_engaged;       // appendix E 6.6.21, 3.15.1.8
                    bool steep_approach;          // appendix E 6.6.25, 3.15.1.11
                    bool gpws_inhibit;            // appendix E 6.6.27, 3.15.1.12
                } discretes;

                struct _s_arinc429
                {
                    Parameter<double> uncorrected_barometric_altitude;    // appendix E 6.2.1
                    Parameter<double> barometric_altitude_rate;           // appendix E 6.2.2
                    Parameter<double> gps_altitude;                       // appendix E 6.2.4
                    Parameter<double> gps_latitude;                       // appendix E 6.2.7
                    Parameter<double> gps_longitude;                      // appendix E 6.2.8
                    Parameter<double> gps_vertical_figure_of_merit;       // appendix E 6.2.13
                    Parameter<double> radio_altitude;                     // appendix E 6.2.29
                    Parameter<double> glideslope_deviation;               // appendix E 6.2.30
                    Parameter<double> roll_angle;                         // appendix E 6.2.31
                    Parameter<double> localizer_deviation;                // appendix E 6.2.33
                    Parameter<double> computed_airspeed;                  // appendix E 6.2.39
                    Parameter<double> decision_height;                    // appendix E 6.2.41
                } arinc429;
            } inputs;

            struct Outputs
            {
                struct _s_discretes
                {
                    bool gpws_warning;              // appendix E 7.4.1, 3.15.2.5
                    bool gpws_alert;                // appendix E 7.4.1, 3.15.2.6
                    bool audio_on;                  // appendix E 7.4.2, 3.15.2.10
                    bool gpws_inop;                 // appendix E 7.4.3, 3.15.2.3
                    bool tad_inop;                  // appendix E 7.4.3, 3.15.2.4
                    bool flap_override;             // appendix E 7.4.5, 3.15.2.8
                    bool glideslope_cancel;         // appendix E 7.4.6, 3.15.2.7
                    bool steep_approach;            // appendix E 7.4.12, 3.15.2.9
                } discretes;

                struct _s_arinc429
                {
                    int egpws_alert_discrete_1;     // appendix E 7.1.1.1
                    int egpwc_logic_discretes;      // appendix E 7.1.1.2
                    int mode6_callouts_discrete_1;  // appendix E 7.1.1.3
                    int mode6_callouts_discrete_2;  // appendix E 7.1.1.4
                    int egpws_alert_discrete_2;     // appendix E 7.1.1.5
                    int egpwc_alert_discrete_3;     // appendix E 7.1.1.6
                } arinc429;
            };

            Outputs outputs;

            struct _s_data
            {
                Parameter<double> barometric_altitude_rate;
                Parameter<double> decision_height;
                Parameter<double> geometric_altitude;
                Parameter<double> glideslope_deviation_dots;
                Parameter<double> gps_altitude;
                Parameter<double> gps_latitude;
                Parameter<double> gps_longitude;
                Parameter<double> gps_vertical_figure_of_merit;
                Parameter<double> localizer_deviation_dots;
                Parameter<double> radio_altitude;
                Parameter<double> roll_angle;
                Parameter<double> terrain_clearance;
            } data;

            IOHandler (MK_VIII *device);

            void boot ();
            void post_boot ();
            void power_off ();

            void enter_ground ();
            void enter_takeoff ();

            void update_inputs ();
            void update_input_faults ();
            void update_alternate_discrete_input (bool *ptr);
            void update_internal_latches ();

            void update_egpws_alert_discrete_1 ();
            void update_egpwc_logic_discretes ();
            void update_mode6_callouts_discrete_1 ();
            void update_mode6_callouts_discrete_2 ();
            void update_egpws_alert_discrete_2 ();
            void update_egpwc_alert_discrete_3 ();
            void update_outputs ();
            void reposition ();

            void update_lamps ();
            void set_lamp (Lamp lamp);

            bool gpws_inhibit () const;
            bool real_flaps_down () const;
            bool flaps_down () const;
            bool flap_override () const;
            bool steep_approach () const;
            bool momentary_steep_approach_enabled () const;

            void bind (SGPropertyNode *node);

            MK_VIII *mk;

        private:

            ///////////////////////////////////////////////////////////////////////////
            // MK_VIII::IOHandler::TerrainClearanceFilter /////////////////////////////
            ///////////////////////////////////////////////////////////////////////////

            class TerrainClearanceFilter
            {
                typedef std::deque< Sample<double> > samples_type;
                samples_type      samples;
                double            value;
                double            last_update;

            public:
                inline TerrainClearanceFilter ()
                    : value(0.0), last_update(-1.0) {}

                double update (double agl);
                void reset ();
            };

            ///////////////////////////////////////////////////////////////////////////
            // MK_VIII::IOHandler (continued) /////////////////////////////////////////
            ///////////////////////////////////////////////////////////////////////////

            TerrainClearanceFilter terrain_clearance_filter;

            Lamp  _lamp;
            Timer lamp_timer;

            Timer audio_inhibit_fault_timer;
            Timer landing_gear_fault_timer;
            Timer flaps_down_fault_timer;
            Timer momentary_flap_override_fault_timer;
            Timer self_test_fault_timer;
            Timer glideslope_cancel_fault_timer;
            Timer steep_approach_fault_timer;
            Timer gpws_inhibit_fault_timer;
            Timer ta_tcf_inhibit_fault_timer;

            bool last_landing_gear;
            bool last_real_flaps_down;

            typedef std::deque< Sample< Parameter<double> > > altitude_samples_type;
            altitude_samples_type altitude_samples;

            struct
            {
                bool glideslope_cancel;
            } power_saved;

            void update_terrain_clearance ();
            void reset_terrain_clearance ();

            void handle_input_fault (bool test, FaultHandler::Fault fault);
            void handle_input_fault (bool test,
                                     Timer *timer,
                                     double max_duration,
                                     FaultHandler::Fault fault);

            void tie_input (SGPropertyNode *node,
                            const char *name,
                            bool *input,
                            bool *feed = NULL);
            void tie_input (SGPropertyNode *node,
                            const char *name,
                            Parameter<double> *input,
                            bool *feed = NULL);
            void tie_output (SGPropertyNode *node,
                            const char *name,
                            bool *output);
            void tie_output (SGPropertyNode *node,
                             const char *name,
                             int *output);

        public:

            bool get_discrete_input (bool *ptr) const;
            void set_discrete_input (bool *ptr, bool value);

            void present_status ();
            void present_status_section (const char *name);
            void present_status_item (const char *name, const char *value = NULL);
            void present_status_subitem (const char *name);

            bool get_present_status () const;
            void set_present_status (bool value);

            bool *get_lamp_output (Lamp lamp);
        };

        class VoicePlayer : public FGVoicePlayer
        {
        public:
            VoicePlayer (MK_VIII *device) :
              FGVoicePlayer(&device->properties_handler, "mk-viii")
              {}

            ~VoicePlayer() {}
            void init ();

            struct
            {
                Voice *application_data_base_failed;
                Voice *bank_angle;
                Voice *bank_angle_bank_angle;
                Voice *bank_angle_bank_angle_3;
                Voice *bank_angle_inop;
                Voice *bank_angle_pause_bank_angle;
                Voice *bank_angle_pause_bank_angle_3;
                Voice *callouts_inop;
                Voice *configuration_type_invalid;
                Voice *dont_sink;
                Voice *dont_sink_pause_dont_sink;
                Voice *five_hundred_above;
                Voice *glideslope;
                Voice *glideslope_inop;
                Voice *gpws_inop;
                Voice *hard_glideslope;
                Voice *minimums;
                Voice *minimums_100;
                Voice *minimums_minimums;
                Voice *retard;
                Voice *pull_up;
                Voice *sink_rate;
                Voice *sink_rate_pause_sink_rate;
                Voice *soft_glideslope;
                Voice *terrain;
                Voice *terrain_pause_terrain;
                Voice *too_low_flaps;
                Voice *too_low_gear;
                Voice *too_low_terrain;
                Voice *altitude_callouts[n_altitude_callouts];
            } voices;

        };

        private:
            /////////////////////////////////////////////////////////////////////////////
            // MK_VIII::SelfTestHandler /////////////////////////////////////////////////
            /////////////////////////////////////////////////////////////////////////////

            class SelfTestHandler
            {
                MK_VIII *mk;

                typedef enum
                {
                    CANCEL_NONE,
                    CANCEL_SHORT,
                    CANCEL_LONG
                } Cancel;

                enum
                {
                    ACTION_SLEEP            = 1 << 0,
                    ACTION_VOICE            = 1 << 1,
                    ACTION_DISCRETE_ON_OFF  = 1 << 2,
                    ACTION_DONE             = 1 << 3
                };

                typedef struct
                {
                    unsigned int    flags;
                    double          sleep_duration;
                    bool            *discrete;
                } Action;

                Cancel              cancel;
                Action              action;
                int                 current;
                bool                button_pressed;
                double              button_press_timestamp;
                IOHandler::Outputs  saved_outputs;
                double              sleep_start;

                bool _was_here (int position);

                Action sleep (double duration);
                Action play (VoicePlayer::Voice *voice);
                Action discrete_on (bool *discrete, double duration);
                Action discrete_on_off (bool *discrete, double duration);
                Action discrete_on_off (bool *discrete, VoicePlayer::Voice *voice);
                Action done ();

                Action run ();

                void start ();
                void stop ();
                void shutdown ();

            public:
                typedef enum
                {
                    STATE_NONE,
                    STATE_START,
                    STATE_RUNNING
                } State;

                State state;

                inline SelfTestHandler (MK_VIII *device)
                : mk(device), button_pressed(false), state(STATE_NONE) {}

                inline void power_off () { stop(); }
                inline void set_inop () { stop(); }
                void handle_button_event (bool value);
                bool update ();
            };

            /////////////////////////////////////////////////////////////////////////////
            // MK_VIII::AlertHandler ////////////////////////////////////////////////////
            /////////////////////////////////////////////////////////////////////////////

            class AlertHandler
            {
                MK_VIII             *mk;

                unsigned int        old_alerts;
                unsigned int        voice_alerts;
                unsigned int        repeated_alerts;
                VoicePlayer::Voice  *altitude_callout_voice;

                void reset ();
                inline bool has_alerts (unsigned int test) const { return (alerts & test) != 0; }
                inline bool has_old_alerts (unsigned int test) const { return (old_alerts & test) != 0; }
                inline bool must_play_voice (unsigned int test) const { return ! has_old_alerts(test) || (repeated_alerts & test) != 0; }
                bool select_voice_alerts (unsigned int test);

            public:
                enum
                {
                    ALERT_MODE1_PULL_UP                         = 1 << 0,
                    ALERT_MODE1_SINK_RATE                       = 1 << 1,

                    ALERT_MODE2A_PREFACE                        = 1 << 2,
                    ALERT_MODE2B_PREFACE                        = 1 << 3,
                    ALERT_MODE2A                                = 1 << 4,
                    ALERT_MODE2B                                = 1 << 5,
                    ALERT_MODE2B_LANDING_MODE                   = 1 << 6,
                    ALERT_MODE2A_ALTITUDE_GAIN                  = 1 << 7,
                    ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING  = 1 << 8,

                    ALERT_MODE3                                 = 1 << 9,

                    ALERT_MODE4_TOO_LOW_FLAPS                   = 1 << 10,
                    ALERT_MODE4_TOO_LOW_GEAR                    = 1 << 11,
                    ALERT_MODE4AB_TOO_LOW_TERRAIN               = 1 << 12,
                    ALERT_MODE4C_TOO_LOW_TERRAIN                = 1 << 13,

                    ALERT_MODE5_SOFT                            = 1 << 14,
                    ALERT_MODE5_HARD                            = 1 << 15,

                    ALERT_MODE6_MINIMUMS                        = 1 << 16,

                    ALERT_MODE6_ALTITUDE_CALLOUT                = 1 << 17,
                    ALERT_MODE6_LOW_BANK_ANGLE_1                = 1 << 18,
                    ALERT_MODE6_HIGH_BANK_ANGLE_1               = 1 << 19,
                    ALERT_MODE6_LOW_BANK_ANGLE_2                = 1 << 20,
                    ALERT_MODE6_HIGH_BANK_ANGLE_2               = 1 << 21,
                    ALERT_MODE6_LOW_BANK_ANGLE_3                = 1 << 22,
                    ALERT_MODE6_HIGH_BANK_ANGLE_3               = 1 << 23,

                    ALERT_TCF_TOO_LOW_TERRAIN                   = 1 << 24,

                    ALERT_MODE6_MINIMUMS_100                    = 1 << 28,
                    ALERT_MODE6_RETARD                          = 1 << 29,
                };

                enum
                {
                    ALERT_FLAG_REPEAT = 1 << 0
                };

                unsigned int alerts;

                inline AlertHandler (MK_VIII *device)
                    : mk(device) {}

                void boot ();
                void reposition ();
                void update ();

                void set_alerts (unsigned int _alerts,
                                 unsigned int flags = 0,
                                 VoicePlayer::Voice *_altitude_callout_voice = NULL);
                void unset_alerts (unsigned int _alerts);

                inline void repeat_alert (unsigned int alert) { set_alerts(alert, ALERT_FLAG_REPEAT); }
                inline void set_altitude_callout_alert (VoicePlayer::Voice *voice) { set_alerts(ALERT_MODE6_ALTITUDE_CALLOUT, 0, voice); }
            };

            /////////////////////////////////////////////////////////////////////////////
            // MK_VIII::StateHandler ////////////////////////////////////////////////////
            /////////////////////////////////////////////////////////////////////////////

  class StateHandler
  {
    MK_VIII *mk;

    Timer potentially_airborne_timer;

    void update_ground ();
    void enter_ground ();
    void leave_ground ();

    void update_takeoff ();
    void enter_takeoff ();
    void leave_takeoff ();

  public:
    bool ground;
    bool takeoff;

    inline StateHandler (MK_VIII *device)
      : mk(device), ground(true), takeoff(true) {}

    void post_reposition ();
    void update ();
  };

  /////////////////////////////////////////////////////////////////////////////
  // MK_VIII::Mode1Handler ////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////

  class Mode1Handler
  {
    MK_VIII *mk;

    Timer pull_up_timer;
    Timer sink_rate_timer;

    double sink_rate_tti; // time-to-impact in minutes

    double get_pull_up_bias ();
    bool is_pull_up ();

    double get_sink_rate_bias ();
    bool is_sink_rate ();
    double get_sink_rate_tti ();

    void update_pull_up ();
    void update_sink_rate ();

  public:
    typedef struct
    {
      bool    flap_override_bias;
      int     min_agl;
      double  (*pull_up_min_agl1) (double vs);
      int     pull_up_max_agl1;
      double  (*pull_up_min_agl2) (double vs);
      int     pull_up_max_agl2;
    } EnvelopesConfiguration;

    struct
    {
      const EnvelopesConfiguration *envelopes;
    } conf;

    inline Mode1Handler (MK_VIII *device)
      : mk(device), sink_rate_tti(0.0) {}

    void update ();
  };

  /////////////////////////////////////////////////////////////////////////////
  // MK_VIII::Mode2Handler ////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////////////////

  class Mode2Handler
  {

    ///////////////////////////////////////////////////////////////////////////
    // MK_VIII::Mode2Handler::ClosureRateFilter ///////////////////////////////
    ///////////////////////////////////////////////////////////////////////////

    class ClosureRateFilter
    {
        /////////////////////////////////////////////////////////////////////////
        // MK_VIII::Mode2Handler::ClosureRateFilter::PassFilter /////////////////
        /////////////////////////////////////////////////////////////////////////

        class PassFilter
        {
            double a0;
            double a1;
            double b1;

            double last_input;
            double last_output;

        public:
            inline PassFilter (double _a0, double _a1, double _b1)
                : a0(_a0), a1(_a1), b1(_b1), last_input(0.0), last_output(0.0) {}

            inline double filter (double input)
            {
                last_output = a0 * input + a1 * last_input + b1 * last_output;
                last_input = input;

                return last_output;
            }

            inline void reset ()
            {
                last_input = 0;
                last_output = 0;
            }
        };

        /////////////////////////////////////////////////////////////////////////
        // MK_VIII::Mode2Handler::ClosureRateFilter (continued) /////////////////
        /////////////////////////////////////////////////////////////////////////

        MK_VIII *mk;

        Timer             timer;
        Parameter<double> last_ra;    // last radio altitude
        Parameter<double> last_ba;    // last barometric altitude
        PassFilter        ra_filter;  // radio altitude rate filter
        PassFilter        ba_filter;  // barometric altitude rate filter

        double limit_radio_altitude_rate (double r);

    public:
        Parameter<double> output;

        inline ClosureRateFilter (MK_VIII *device)
            : mk(device),
              ra_filter(0.05, 0, 0.95),        // low-pass filter
              ba_filter(0.93, -0.93, 0.86) {}    // high-pass-filter

        void init ();
        void update ();
    };

        ///////////////////////////////////////////////////////////////////////////
        // MK_VIII::Mode2Handler (continued) //////////////////////////////////////
        ///////////////////////////////////////////////////////////////////////////

        MK_VIII *mk;

        ClosureRateFilter closure_rate_filter;

        Timer   takeoff_timer;
        Timer   pull_up_timer;

        double  a_start_time;
        Timer   a_altitude_gain_timer;
        double  a_altitude_gain_alt;

        void check_pull_up (unsigned int preface_alert, unsigned int alert);

        bool b_conditions ();

        bool is_a ();
        bool is_b ();

        void update_a ();
        void update_b ();

    public:
        typedef struct
        {
            int airspeed1;
            int airspeed2;
        } Configuration;

        const Configuration *conf;

        inline Mode2Handler (MK_VIII *device)
          : mk(device), closure_rate_filter(device) {}

        void boot ();
        void power_off ();
        void leave_ground ();
        void enter_takeoff ();
        void update ();
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::Mode3Handler ////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    class Mode3Handler
    {
        MK_VIII *mk;

        bool    armed;
        bool    has_descent_alt;
        double  descent_alt;
        double  bias;

        double max_alt_loss (double _bias);
        double get_bias (double initial_bias, double alt_loss);
        bool is (double *alt_loss);

    public:
        typedef struct
        {
            int    min_agl;
            int    (*max_agl) (bool flap_override);
            double    (*max_alt_loss) (bool flap_override, double agl);
        } Configuration;

        const Configuration *conf;

        inline Mode3Handler (MK_VIII *device)
            : mk(device), armed(false), has_descent_alt(false) {}

        void enter_takeoff ();
        void update ();
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::Mode4Handler ////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    class Mode4Handler
    {
    public:
        typedef struct
        {
            int       airspeed1;
            int       airspeed2;
            int       min_agl1;
            double    (*min_agl2) (double airspeed);
            int       min_agl3;
        } EnvelopesConfiguration;

        typedef struct
        {
            const EnvelopesConfiguration *ac;
            const EnvelopesConfiguration *b;
        } ModesConfiguration;

        struct
        {
            VoicePlayer::Voice        *voice_too_low_gear;
            const ModesConfiguration  *modes;
        } conf;

        inline Mode4Handler (MK_VIII *device)
            : mk(device),ab_bias(0.0),ab_expanded_bias(0.0),c_bias(0.0) {}

        double get_upper_agl (const EnvelopesConfiguration *c);
        void update ();

    private:
        MK_VIII *mk;

        double  ab_bias;
        double  ab_expanded_bias;
        double  c_bias;

        const EnvelopesConfiguration *get_ab_envelope ();
        double get_bias (double initial_bias, double min_agl);
        void handle_alert (unsigned int alert, double min_agl, double *bias);

        void update_ab ();
        void update_ab_expanded ();
        void update_c ();
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::Mode5Handler ////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    class Mode5Handler
    {
        MK_VIII *mk;

        Timer   hard_timer;
        Timer   soft_timer;

        double  soft_bias;

        bool is_hard ();
        bool is_soft (double bias);

        double get_soft_bias (double initial_bias);

        void update_hard (bool is);
        void update_soft (bool is);

    public:
        inline Mode5Handler (MK_VIII *device)
          : mk(device), soft_bias(0.0) {}

        void update ();
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::Mode6Handler ////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    class Mode6Handler
    {
    public:
        // keep in sync with altitude_callout_definitions[]
        typedef enum
        {
            ALTITUDE_CALLOUT_2500,
            ALTITUDE_CALLOUT_1000,
            ALTITUDE_CALLOUT_500,
            ALTITUDE_CALLOUT_400,
            ALTITUDE_CALLOUT_300,
            ALTITUDE_CALLOUT_200,
            ALTITUDE_CALLOUT_100,
            ALTITUDE_CALLOUT_50,
            ALTITUDE_CALLOUT_40,
            ALTITUDE_CALLOUT_30,
            ALTITUDE_CALLOUT_20,
            ALTITUDE_CALLOUT_10
        } AltitudeCallout;

        typedef bool (*BankAnglePredicate) (Parameter<double> *agl,
                                            double abs_roll_deg,
                                            bool ap_engaged);

        struct
        {
            bool                retard_enabled;
            bool                minimums_above_100_enabled;
            bool                minimums_enabled;
            bool                smart_500_enabled;
            VoicePlayer::Voice  *above_field_voice;

            unsigned int        altitude_callouts_enabled;
            bool                bank_angle_enabled;
            BankAnglePredicate  is_bank_angle;
        } conf;

        static const int altitude_callout_definitions[];

        inline Mode6Handler (MK_VIII *device)
            : mk(device) {}

        void boot ();
        void power_off ();
        void enter_takeoff ();
        void leave_takeoff ();
        void set_volume (float volume);
        bool altitude_callouts_enabled ();
        void update ();

    private:
        MK_VIII *mk;

        bool                last_decision_height;
        bool                last_decision_height_100;
        Parameter<double>   last_radio_altitude;
        Parameter<double>   last_altitude_above_field;

        bool altitude_callouts_issued[n_altitude_callouts];
        bool minimums_issued;
        bool minimums_above_100_issued;
        bool above_field_issued;
        bool throttle_retarded;

        Timer           runway_timer;
        Parameter<bool> has_runway;

        struct
        {
            double elevation; // elevation in feet
        } runway;

        void reset_minimums ();
        void reset_altitude_callouts ();
        bool is_playing_altitude_callout ();
        bool is_near_minimums (double callout);
        bool is_outside_band (double elevation, double callout);
        bool inhibit_smart_500 ();

        void update_minimums ();
        void update_altitude_callouts ();

        bool test_runway (const FGRunway *_runway);
        bool test_airport (const FGAirport *airport);
        void update_runway ();

        void get_altitude_above_field (Parameter<double> *parameter);
        void update_above_field_callout ();

        bool is_bank_angle (double abs_roll_angle, double bias);
        bool is_high_bank_angle ();
        unsigned int get_bank_angle_alerts ();
        void update_bank_angle ();

        class AirportFilter : public FGAirport::AirportFilter
        {
        public:
            AirportFilter(Mode6Handler *s)
            : self(s) {}

            virtual bool passAirport(FGAirport *a) const;

            virtual FGPositioned::Type maxType() const {
                return FGPositioned::AIRPORT;
            }

        private:
            Mode6Handler* self;
        };
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII::TCFHandler //////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    class TCFHandler
    {
        typedef struct
        {
            SGGeod    position;   // position of threshold
            double    heading;    // runway heading
        } RunwayEdge;

        MK_VIII *mk;

        static const double k;

        Timer           runway_timer;
        bool            has_runway;

        struct
        {
            SGGeod        center;         // center point
            double        elevation;      // elevation in feet
            double        half_length;    // runway half length, in nautical miles
            double        half_width_m;   // runway half width, in meters
            RunwayEdge    edge;           // runway threshold
            SGVec3d       bias_points[2]; // vertices of the bias area
        } runway;

        double bias;
        double *reference;
        double initial_value;

        void update_runway ();

        bool is_inside_edge_triangle (RunwayEdge *edge);
        bool is_inside_bias_area ();

        bool is_tcf ();
        bool is_rfcf ();

        class AirportFilter : public FGAirport::AirportFilter
        {
        public:
            AirportFilter(MK_VIII *device)
                : mk(device) {}

            virtual bool passAirport(FGAirport *a) const;
        private:
            MK_VIII* mk;
        };

    public:
        struct
        {
            bool enabled;
        } conf;

        inline TCFHandler (MK_VIII *device)
        : mk(device) {}

        void update ();
    };

    /////////////////////////////////////////////////////////////////////////////
    // MK_VIII (continued) //////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////

    std::string           name;
    int                   num;

    PowerHandler          power_handler;
    SystemHandler         system_handler;
    ConfigurationModule   configuration_module;
    FaultHandler          fault_handler;
    IOHandler             io_handler;
    VoicePlayer           voice_player;
    SelfTestHandler       self_test_handler;
    AlertHandler          alert_handler;
    StateHandler          state_handler;
    Mode1Handler          mode1_handler;
    Mode2Handler          mode2_handler;
    Mode3Handler          mode3_handler;
    Mode4Handler          mode4_handler;
    Mode5Handler          mode5_handler;
    Mode6Handler          mode6_handler;
    TCFHandler            tcf_handler;

    struct
    {
        int runway_database;
    } conf;

public:
    MK_VIII (SGPropertyNode *node);

    // Subsystem API.
    void bind() override;
    void init() override;
    void unbind() override;
    void update(double dt) override;

    // Subsystem identification.
    static const char* staticSubsystemClassId() { return "mk-viii"; }
};

#ifdef _MSC_VER
#  pragma warning( pop )
#endif
