/***************************************************************************************************** 
This is a part of the code used in our autopilot panel page in the GTN750.
This code is open source to help aircraft providers building their autopilot gauge.
You can use it partially or totally, modify it and deliver it the autopilot gauge in your aircraft
package without any authorization from us.
However, you don't have the right to replace the original GTN750 package file by your own version.

This code is using the PMS50 APGA module that manages the autopilot panel logic.
If the APGA module is installed in the aircraft, the GTN750 autopilot panel (GTN750_Autopilot)
is using it.
Otherwise, it uses a local copy of the APGA found in this this file (PMS50_APGA_LOCAL class).
You can get further informtion about the APGA in the APGA documentation found in the Integration
folder of the GTN750 package.

To simplify the code, the button elements are defined in the base class. Here is the list:

- AP button: this.apButton
- FD button: this.fdButton
- YD button: this.ydButton
- ARM button: this.armButton
- HDG button: this.hdgButton
- NAV button: this.navButton
- APR button: this.aprButton
- REV button: this.revButton
- ALT button: this.altButton
- VS button: this.vsButton
- FLC button: this.flcButton
- VNV button: this.vnavButton
- UP button: this.upButton
- DN button: this.downButton
- ARM button: this.armButton
- Altitude select button (inner): this.altSelInnerButton
- Altitude select button (outer): this.altSelOuterButton


******************************************************************************************************/

var altPreselMode = {
    none: 0,
    auto: 1,
    manual: 2
};

// Lateral modes
var lateralMode = {
    none: 0,
    roll: 1,
    hdg: 2,
    gps: 3,
    vor: 4,
    loc: 5,
    bc: 6,
    aprgps: 7,
    aprvor: 8,
    aprloc: 9,
    lvl: 10,
    cga: 11,
    clvl: 12
};

// Vertical modes
var verticalMode = {
    none: 0,
    pitch: 1,
    vs: 2,
    alt: 3,
    gs: 4,
    gp: 5,
    flc: 6,
    vnav: 7,
    cga: 8,
    clvl: 9
};

// Combined modes
var combinedMode = {
    none: 0,
    cga: 1,
    clvl: 2
};

var vsEngageMode = {
    none: 0,
    reset: 1,
    default: 2,
    current: 3
};

// Target altitude phases
var selectedAltitudePhase = {
    none: 0,    // No ALTS
    engaged: 1, // Altitude diff is greater than alert threshold.
    alert: 2,   // Altitude diff is between alert and near thresholds.
    near: 3,    // Altitude diff is less than near threshold.
    ready: 4,   // Vertical mode is ALT and Altitude diff greater than the reached threshold.
    reached: 5  // Vertical mode is ALT and Altitude diff is less than reached threshold. Phase returns to none after a time out.
};

// Fp approach type
var approachType = {
    none: 0,
    gps: 1,
    vor: 2,
    ndb: 3,
    ils: 4,
    loc: 5,
    sdf: 6,
    lda: 7,
    vordme: 8,
    ndbdme: 9,
    rnav: 10,
    bc: 11
};

// GTN750 Autopilot page
class GTN750_Autopilot extends GTN750_Autopilot_Base {
    constructor(_param1 = "", _param2 = true, _param3 = "", _param4 = "", _param5 = false, _param6 = null) {
        super(_param1, _param2, _param3, _param4, _param5, _param6);

        // Local and external APGA
        this.apga = new PMS50_APGA_LOCAL();  // The autopilot hidden gauge that does the job behind
        this.externAPGAInstalled = false;  // Tells if there is alraedy an APAGE Gauge in the aircraft
        this.externAPGANoUpdatableAltSelector = false;  // Tells if the external APGA has an updatable Alt selector

        // Internal state
        this.lateralMode = lateralMode.none;  // Current lateral mode
        this.armedLateralMode = lateralMode.none;  // Armed lateral mode
        this.verticalMode = verticalMode.none;    // Current vertical mode
        this.armedVerticalMode = verticalMode.none;  // Armed vertical mode
        this.selectedAltitudePhase = selectedAltitudePhase.none;  // Selected altitude phase
        this.capturedAltitude = -1; // Captured altitude in Alt mode (-1 if not in ALT mode)

        // For display
        this.blinkCounter = 0;  // Used for trim indicator
        this.rightBlockReinitTime = 0;  // Used for temporary display of the top right block
        this.rightBlockCurrDisplay = 0; // Top right block mode display type (0 = FT, 1 = VS or KTS following current vertical mode)
        this.mouseInInner = false;  // Used in toolbar panel for mouse wheel 
    }
    init(_root) {
        // Init the base class
        super.init(_root);

        // Local APGA init
        this.apga.altPreselMode = this.altPreselMode;
        this.apga.highAltPrecision = this.highAltPrecision;
        this.apga.init();

        // Mouse wheel inits
        this.altSelInnerButton.addEventListener("mouseenter", () => {this.mouseInInner = true});
        this.altSelInnerButton.addEventListener("mouseleave", () => {this.mouseInInner = false});
        this.altSelOuterButton.addEventListener("mouseover", () => {this.rightBlockReinitTime = 1});
        this.altSelInnerButton.addEventListener("mouseover", () => {this.rightBlockReinitTime = 1});

    }
    onUpdate(_deltaTime) {

        // Base class update first
        if(!super.onUpdate(_deltaTime))
            return;

        this.WTT = SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_INSTALLED", "bool");
        this.hasVnav = this.WTT && SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_HAS_VNAV", "bool");

        // Check if there is an installed extern AP hidden gauge in the aircraft
        if(!this.checkedExternAPGA) {
            this.externAPGAInstalled = SimVar.GetSimVarValue("L:PMS50_APGA_RUNNING", "Bool");
            if(this.externAPGAInstalled) {
                // For an extern APGA, we take its paramers as ours
                this.altPreselMode = SimVar.GetSimVarValue("L:PMS50_APGA_ALT_SEL_MODE", "number");
                this.highAltPrecision = SimVar.GetSimVarValue("L:PMS50_APGA_HIGHT_ALT_PRECISION", "number");
                this.externAPGANoUpdatableAltSelector = SimVar.GetSimVarValue("L:PMS50_APGA_ALTSEL_NOT_UPDATABLE", "Bool");
                if(this.externAPGANoUpdatableAltSelector) {
                    this.altSelInnerButton.style.visibility = "hidden";
                    this.altSelOuterButton.style.visibility = "hidden";
                    this.useAircraftSelector.style.visibility = "visible";
                }
                else
                    this.useAircraftSelector.style.visibility = "hidden";
            }
            else
                this.useAircraftSelector.style.visibility = "hidden";
            this.checkedExternAPGA = true;
        }
        
        // Local APGA Update
        if(!this.externAPGAInstalled)
            this.apga.onUpdate(_deltaTime);

        // Update the current lateral mode
        this.lateralMode = SimVar.GetSimVarValue("L:PMS50_APGA_ACTIVE_LATERAL_MODE", "number");

        // Update the current vertical mode
        this.verticalMode = SimVar.GetSimVarValue("L:PMS50_APGA_ACTIVE_VERTICAL_MODE", "number");

        // Update the armed lateral mode
        this.armedLateralMode = SimVar.GetSimVarValue("L:PMS50_APGA_ARMED_LATERAL_MODE", "number");

        // Update the armed vertical mode
        this.armedVerticalMode = SimVar.GetSimVarValue("L:PMS50_APGA_ARMED_VERTICAL_MODE", "number");

        // Update the selected altitude phase
        this.selectedAltitudePhase = SimVar.GetSimVarValue("L:PMS50_APGA_SELECTED_ALTITUDE_PHASE", "number");

        // Update the captured altitude (current altitude in ALT mode)
        this.capturedAltitude = SimVar.GetSimVarValue("L:PMS50_APGA_CAPTURED_ALTITUDE_VALID", "Bool") ? SimVar.GetSimVarValue("L:PMS50_APGA_CAPTURED_ALTITUDE", "feet") : -1;

        // Set the state of autopilot buttons (button light)
        diffAndSetAttribute(this.apButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_AP_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.fdButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_FD_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.ydButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_YD_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.cgaButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_GA_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.clvlButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_LVL_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.hdgButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_HDG_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.navButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_NAV_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.aprButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_APR_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.revButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_REV_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.altButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_ALT_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.vsButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_VS_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.flcButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_FLC_BUTTON_STATE", "Bool") ? "Active" : "");
        diffAndSetAttribute(this.vnavButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_VNAV_BUTTON_STATE", "Bool") ? "Active" : "");
        let armButtonState = SimVar.GetSimVarValue("L:PMS50_APGA_ARM_BUTTON_STATE", "Bool") ? "Active" : "";
        diffAndSetAttribute(this.armButton, "state", SimVar.GetSimVarValue("L:PMS50_APGA_CAN_ARM", "Bool") ? armButtonState : "Disabled");

        // Display the AP indicator
        this.APdisplay.style.visibility = SimVar.GetSimVarValue("L:PMS50_APGA_AP", "Bool") ? "visible" : "hidden";
        
        // Display the active lateral mode
        diffAndSetText(this.LeftDisplayTop, this.getActiveLateralModeForDisplay());

        // Display the armed lateral mode
        diffAndSetText(this.LeftDisplayBot, this.getArmedLateralModeForDisplay());
        this.LeftARM.style.visibility = this.armedLateralMode != lateralMode.none ? "visible" : "hidden";

        // Display the active vertical mode
        diffAndSetText(this.MidDisplayTop, this.getActiveVerticalModeForDisplay());

        // Display the armed vertical mode
        let armedString = this.getArmedVerticalModeForDisplay();
        diffAndSetText(this.MidDisplayBot, armedString);
        this.MidARM.style.visibility = armedString.length ? "visible" : "hidden";

        // Enable up and down buttons in VS or FLC vertical mode only
        let upDownState = SimVar.GetSimVarValue("L:PMS50_APGA_UP_DOWN_BUTTON_VALID", "Bool");
        diffAndSetAttribute(this.upButton, "state", upDownState ? "" : "Disabled");
        diffAndSetAttribute(this.downButton, "state", upDownState ? "" : "Disabled");
        // And set the corresponding unit
        diffAndSetText(this.PanelFpm, this.verticalMode == verticalMode.flc ? "KTS" : "FPM");

        // display the arm button only in manual arm mode
        this.armButton.parentNode.style.visibility = this.altPreselMode == altPreselMode.manual ? "visible": "hidden";

        // display the vnav button only if vnav available
        this.vnavButton.parentNode.style.visibility = this.hasVnav ? "visible": "hidden";


        // Display the altitude selection buttons for auto and manual arm modes
        if(!this.externAPGANoUpdatableAltSelector) {
            this.altSelInnerButton.style.visibility = this.altPreselMode != altPreselMode.none ? "visible": "hidden";
            this.altSelOuterButton.style.visibility = this.altPreselMode != altPreselMode.none ? "visible": "hidden";
        }

        // Sets the blink counter (used for trim display indicator only)
        this.blinkCounter = (this.blinkCounter + _deltaTime) % 1000000;

        // Reset the top right display to altitude if not in VS or FLC vertical mode
        if(this.verticalMode != verticalMode.vs && this.verticalMode != verticalMode.flc) {
            this.rightBlockReinitTime = 0;
            this.rightBlockCurrDisplay = 0;
        }

        if(this.mouseInUpDown) {
            if(this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc) {
                this.rightBlockReinitTime = 500;
                this.rightBlockCurrDisplay = 1;
            }
        }

        // Set the top right display block (altitude, VS or airspeed following the current vertical mode and altitude selection mode)
        if(this.altPreselMode == altPreselMode.none) {
            // No altitude selection

            // We display VS or airspeed if the vertical modes are set
            this.rightBlockCurrDisplay = this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc ? 1 : 0;
            // Temporary display not used if no altitude selection
            this.rightBlockReinitTime = 0;

            if(this.verticalMode == verticalMode.alt) {
                // Alt mode
                // We display the captured altitude

                // sets the styling
                diffAndSetAttribute(this.RightBlock, "state", this.capturedAltitude != -1 ? "FT" : "");
                // Display the captured altitude
                diffAndSetText(this.RightDisplayTop, this.getCapturedAltitudeForDisplay());
            }
            else {
                // sets the styling (FPM styling is used for both VS or airspeed display)
                diffAndSetAttribute(this.RightBlock, "state", this.rightBlockCurrDisplay == 1 ? "FPM" : "");

                // Display VS or airspeed or nothing
                let displayValue = "";
                if(this.rightBlockCurrDisplay == 1) {
                    if(this.verticalMode == verticalMode.vs)
                        displayValue = this.getVerticalSpeedSelectedForDisplay();
                    else if(this.verticalMode == verticalMode.flc)
                        displayValue = this.getAirSpeedSelectedForDisplay();
                }
                diffAndSetText(this.RightDisplayTop, displayValue);
            }
        }
        else {
            // altitude selection (auto or manual arm)

            if (this.rightBlockReinitTime > 0) {
                // reset the display after the time out
                this.rightBlockReinitTime -= _deltaTime;
                if (this.rightBlockReinitTime <= 0) {
                    this.rightBlockReinitTime = 0;
                    this.rightBlockCurrDisplay = 0;
                }
                else if (this.rightBlockCurrDisplay == 1) {
                    // No timeout. Stil displaying VS or airspeed following vertical mode
                    diffAndSetAttribute(this.RightBlock, "state", "FPM");
                    diffAndSetText(this.RightDisplayTop, this.verticalMode == verticalMode.vs ? this.getVerticalSpeedSelectedForDisplay() : this.getAirSpeedSelectedForDisplay());
                }
            }
            if (this.rightBlockCurrDisplay == 0) {
                // Display sim/internal selected altitude following alt sel mode (or captured altitude if ALT mode and mouse cursor on ALT button)
                diffAndSetAttribute(this.RightBlock, "state", "FT");
                diffAndSetText(this.RightDisplayTop, this.mouseInAlt && this.verticalMode == verticalMode.alt && this.capturedAltitude != -1 ? this.getCapturedAltitudeForDisplay() : this.getAltitudeSelectedForDisplay());
            }
        }


        // Set the altitude alert display
        this.AlertDisplay.style.visibility = SimVar.GetSimVarValue("L:PMS50_APGA_SELECTED_ALTITUDE_PHASE", "number") == selectedAltitudePhase.alert ? "visible" : "hidden";

        // Adjust the trim display
        let neededTrim = SimVar.GetSimVarValue("L:PMS50_APGA_NEED_TRIM_VALUE", "feet per minute");
        let hidden = (this.blinkGetState(600, 300) ? false : true);
        if (neededTrim < -100 && this.verticalMode != verticalMode.none && this.verticalMode != verticalMode.pitch) {
            this.PTDisplay.classList.toggle('hide', hidden);
            this.UPArrow.classList.toggle('hide', hidden);
            this.DownArrow.style.visibility = "hidden";
        }
        else if (neededTrim > 100 && this.verticalMode != verticalMode.none && this.verticalMode != verticalMode.pitch) {
            this.PTDisplay.classList.toggle('hide', hidden);
            this.UPArrow.style.visibility = "hidden";
            this.DownArrow.classList.toggle('hide', hidden);
        }
        else {
            this.PTDisplay.style.visibility = "hidden";
            this.UPArrow.style.visibility = "hidden";
            this.DownArrow.style.visibility = "hidden";
        }
    }

    // called when entering this page
    onEnter() {
        super.onEnter();
        this.mouseInInner = false;
    }
    // called when leaving this page
    onExit() {
        super.onExit();
    }

    // Process UI events
    // Generally, we jast have to send a single order to the APGA and do the display stuff if required
    onEvent(_event) {
        // Managed by the base class first (returns true if managed at upper level)
        if(super.onEvent(_event))
            return;
        
        switch (_event) {
            // Alt select knob events (toolbar panel only)
            // Very large events are not used here
            case "SEL_ALT_SMALL_INC":
            case "SEL_ALT_SMALL_DEC":
            case "SEL_ALT_LARGE_INC":
            case "SEL_ALT_LARGE_DEC":
            case "SEL_ALT_VERY_LARGE_INC":
            case "SEL_ALT_VERY_LARGE_DEC":
                if(!this.externAPGANoUpdatableAltSelector) {
                    if (this.rightBlockCurrDisplay == 1) {
                        this.rightBlockReinitTime = 0;
                        this.rightBlockCurrDisplay = 0;
                    }
                    switch (_event) {
                        case "SEL_ALT_VERY_LARGE_INC":
                            this.sendOrderToAGPA("SEL_ALT_VERY_LARGE_INC");
                            break;
                        case "SEL_ALT_VERY_LARGE_DEC":
                            this.sendOrderToAGPA("SEL_ALT_VERY_LARGE_INC");
                            break;
                        case "SEL_ALT_LARGE_INC":
                            if(!this.mouseInInner)
                                this.sendOrderToAGPA("SEL_ALT_LARGE_INC");
                            break;
                        case "SEL_ALT_LARGE_DEC":
                            if(!this.mouseInInner)
                                this.sendOrderToAGPA("SEL_ALT_LARGE_DEC");
                            break;
                        case "SEL_ALT_SMALL_INC":
                            if(this.mouseInInner)
                                this.sendOrderToAGPA("SEL_ALT_SMALL_INC");
                            break;
                        case "SEL_ALT_SMALL_DEC":
                            if(this.mouseInInner)
                                this.sendOrderToAGPA("SEL_ALT_SMALL_DEC");
                            break;
                    }
                }
                break;

            // VS knob events (not used here)
            case "SEL_VS_INC":
                this.sendOrderToAGPA("SEL_VS_INC");
                break;
            case "SEL_VS_DEC":
                this.sendOrderToAGPA("SEL_VS_DEC");
                break;
                
            // Autopilot button events
            case "AP_AP":
                this.sendOrderToAGPA("AP_AP");
                break;
            case "AP_FD":
                this.sendOrderToAGPA("AP_FD");
                break;
            case "AP_YD":
                this.sendOrderToAGPA("AP_YD");
                break;
            case "AP_CGA":
                this.sendOrderToAGPA("AP_CGA");
                break;
            case "AP_CLVL":
                this.sendOrderToAGPA("AP_CLVL");
                break;
            case "AP_HDG":
                this.sendOrderToAGPA("AP_HDG");
                break;
            case "AP_LVL":
                this.sendOrderToAGPA("AP_LVL");
                break;
            case "AP_NAV":
                this.sendOrderToAGPA("AP_NAV");
                break;
            case "AP_APR":
                this.sendOrderToAGPA("AP_APR");
                break;
            case "AP_REV":
                this.sendOrderToAGPA("AP_REV");
                break;
            case "AP_ALT":
                this.sendOrderToAGPA("AP_ALT");
                break;
            case "AP_VS":
                if (this.verticalMode != verticalMode.vs) {
                    // We are going to enter VS mode
                    // Prepare the display of the VS for 3 seconds
                    setTimeout(()=>{
                        this.rightBlockReinitTime = 3000;
                        this.rightBlockCurrDisplay = 1;
                    }, 500);
                }
                this.sendOrderToAGPA("AP_VS");
                break;
            case "AP_FLC":
                if (this.verticalMode != verticalMode.flc) {
                    // We are going to enter FLC mode
                    // Prepare the display of the airspeed for 3 seconds
                    setTimeout(()=>{
                        this.rightBlockReinitTime = 3000;
                        this.rightBlockCurrDisplay = 1;
                    }, 500);
                }
                this.sendOrderToAGPA("AP_FLC");
                break;
            case "AP_VNAV":
                this.sendOrderToAGPA("AP_VNAV");
                break;
            case "AP_ARM":
                if(SimVar.GetSimVarValue("L:PMS50_APGA_ARM_BUTTON_STATE", "Bool")) {
                    setTimeout(()=>{
                        this.rightBlockReinitTime = 3000;
                        this.rightBlockCurrDisplay = 1;
                    }, 500);
                }
                else {
                    // Reset the display of VS or airspeed
                    setTimeout(()=>{
                        this.rightBlockReinitTime = 0;
                        this.rightBlockCurrDisplay = 0;
                    }, 500);
                }
                this.sendOrderToAGPA("AP_ARM");
                break;
            
            // Up / Down buttons (disabled if not in VS or FLC mode)
            case "AP_UP":
                if(this.verticalMode == verticalMode.alt || this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.pitch) {
                    if(this.verticalMode == verticalMode.pitch || this.verticalMode == verticalMode.alt) {
                        this.sendOrderToAGPA("AP_UP");
                    }
                    else {
                        if (this.rightBlockCurrDisplay != 1) {
                            // We just redisplay the VS or airspeed if it was not already displayed
                            // This allows viewing the current value without modifying it
                            this.rightBlockCurrDisplay = 1;
                        }
                        else {
                            // If already displayed, we send the order
                            this.sendOrderToAGPA("AP_UP");
                        }
                        // Reset the time out to 3 seconds
                        this.rightBlockReinitTime = 3000;
                    }
                }
                break;
            case "AP_DOWN":
                if(this.verticalMode == verticalMode.alt || this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.pitch) {
                    if(this.verticalMode == verticalMode.pitch || this.verticalMode == verticalMode.alt) {
                        this.sendOrderToAGPA("AP_DOWN");
                    }
                    else {
                        if (this.rightBlockCurrDisplay != 1) {
                            // We just redisplay the VS or airspeed if it was not already displayed
                            // THis allows to see the current value without modifying it
                            this.rightBlockCurrDisplay = 1;
                        }
                        else {
                            // If already displayed, we send the order
                            this.sendOrderToAGPA("AP_DOWN");
                        }
                        // Reset the time out to 3 seconds
                        this.rightBlockReinitTime = 3000;
                    }
                }
                break;

            case "AP_ALT_SELECT":
                // Altitude selection button (click on virtual knob center)
                // Display the keyboard to enter the value
                if(!this.externAPGANoUpdatableAltSelector) {
                    this.gps.numericKeyboard.element.setContext("Target altitude", SimVar.GetSimVarValue("L:PMS50_APGA_SELECTED_ALTITUDE", "feet"), 5, 0, NaN, NaN, "FT", this.onEnteredTargetAltitude.bind(this));
                    this.gps.switchToPopUpPage(this.gps.numericKeyboard);
                }
                break;

            case "ALT_PRESEL":
                // Altitude selection change (called from the autopilot menu)
                this.altPreselMode++;
                if(this.altPreselMode > altPreselMode.manual)
                    this.altPreselMode = altPreselMode.none;

                // Do the base class stuff
                this.onAltPreselModeChanged();

                this.sendOrderToAGPA("ALT_PRESEL", "ALTITUDE_PRESEL_MODE", this.altPreselMode);
                break;
            case "PRECISION":
                this.sendOrderToAGPA("PRECISION", "PRECISION", this.highAltPrecision, "boolean");
                break;
        }
    }

    // Send an order to the local or extern APGA
    // With an optional value
    // For local APGA we send a local event
    // For extern APGA we send a H event
    sendOrderToAGPA(_order, _variableName, _value, _valueType = "number") {
        if(_variableName != undefined &&_value != undefined) {
            SimVar.SetSimVarValue("L:PMS50_APGA_" + _variableName, _valueType, _value);
            this.externAPGAInstalled ? postHEvent("PMS50_APGA_" + _order) : this.apga.onEvent(_order);
        }
        else
            this.externAPGAInstalled ? postHEvent("PMS50_APGA_" + _order) : this.apga.onEvent(_order);
    }

    // Get the active lateral mode for display purpose
    getActiveLateralModeForDisplay() {
        if(this.lateralMode == lateralMode.roll)
            return "ROL";
        if(this.lateralMode == lateralMode.hdg)
            return "HDG";
        if(this.lateralMode == lateralMode.gps || this.lateralMode == lateralMode.aprgps)
            return "GPS";
        if(this.lateralMode == lateralMode.vor || this.lateralMode == lateralMode.aprvor)
            return "VOR";
        if(this.lateralMode == lateralMode.loc || this.lateralMode == lateralMode.aprloc)
            return "LOC";
        if(this.lateralMode == lateralMode.bc)
            return "REV";
        if(this.lateralMode == lateralMode.lvl)
            return "LVL";
        if(this.lateralMode == lateralMode.cga)
            return "GA";
        if(this.lateralMode == lateralMode.clvl)
            return "LVL";
        return "";
    }

    // Get the active vertical mode for display purpose
    getActiveVerticalModeForDisplay() {
        if(this.verticalMode == verticalMode.pitch)
            return this.selectedAltitudePhase == selectedAltitudePhase.near ? "ALTS" : "PITCH";
        if(this.verticalMode == verticalMode.vs)
            return this.selectedAltitudePhase == selectedAltitudePhase.near ? "ALTS" : "VS";
        if(this.verticalMode == verticalMode.alt)
            return "ALT";
        if(this.verticalMode == verticalMode.gs)
            return "GS";
        if(this.verticalMode == verticalMode.gp)
            return "GP";
        if(this.verticalMode == verticalMode.flc)
            return this.selectedAltitudePhase == selectedAltitudePhase.near ? "ALTS" : "FLC";
        if(this.verticalMode == verticalMode.vnav)
            return "VNV";
        if(this.verticalMode == verticalMode.cga)
            return this.selectedAltitudePhase == selectedAltitudePhase.near ? "ALTS" : "GA";
        if(this.verticalMode == verticalMode.clvl)
            return "LVL";
        return "";
    }

    // Get the armed lateral mode for display purpose
    getArmedLateralModeForDisplay() {
        if(this.armedLateralMode == lateralMode.bc)
            return "REV";
        if(this.armedLateralMode == lateralMode.gps || this.armedLateralMode == lateralMode.aprgps)
            return "GPS";
        if(this.armedLateralMode == lateralMode.vor || this.armedLateralMode == lateralMode.aprvor)
            return "VOR";
        if(this.armedLateralMode == lateralMode.loc || this.armedLateralMode == lateralMode.aprloc)
            return "LOC";
        return "";
    }

    // Get the armed vertical mode for display purpose
    getArmedVerticalModeForDisplay() {
        if(this.armedVerticalMode == verticalMode.gs)
            return "GS";
        if(this.armedVerticalMode == verticalMode.gp)
            return "GP";
        if(this.armedVerticalMode == verticalMode.vnav)
            return "VNV";
        if(this.selectedAltitudePhase == selectedAltitudePhase.near || this.selectedAltitudePhase == selectedAltitudePhase.ready)
            return "ALT";
        if(this.selectedAltitudePhase == selectedAltitudePhase.engaged || this.selectedAltitudePhase == selectedAltitudePhase.alert)
            return "ALTS";
        return "";
    }

    // Get the selected altitude for display purpose
    getAltitudeSelectedForDisplay() {
        // Sim value if auto arm and internal value for manual arm
        let altitudeSelected = SimVar.GetSimVarValue("L:PMS50_APGA_SELECTED_ALTITUDE", "feet");
        return (fastToFixed(altitudeSelected, 0)).replace(/\d+(?=(\d{3}))/, '$&,');
    }

    // Format the captured altitude for display purpose
    getCapturedAltitudeForDisplay() {
        if(this.capturedAltitude >= 0)
            return (fastToFixed(this.capturedAltitude, 0)).replace(/\d+(?=(\d{3}))/, '$&,');
        else
            return "";
    }

    // Get the selected VS for display purpose
    getVerticalSpeedSelectedForDisplay() {
        return (fastToFixed(SimVar.GetSimVarValue("L:PMS50_APGA_SELECTED_VS", "feet/minute"), 0)).replace(/\d+(?=(\d{3}))/, '$&,');
    }

    // Get the selected airspeed for display purpose
    getAirSpeedSelectedForDisplay() {
        return fastToFixed(SimVar.GetSimVarValue("L:PMS50_APGA_SELECTED_AIRSPEED", "knots"), 0);
    }

    // called when entering the target selected altitude
    onEnteredTargetAltitude(_value) {
        if(!this.externAPGANoUpdatableAltSelector)
            this.sendOrderToAGPA("AP_ALT_SELECT", "ALTITUDE_PRESEL_VALUE", _value, "feet");
    }

    // Get blink state (used for trim display purpose only)
    blinkGetState(_blinkPeriod, _duration) {
        return Math.round(this.blinkCounter / _duration) % (_blinkPeriod / _duration) == 0;
    }

    // Mouse wheel management (toolbar panel only)
    OnMouseWheelInner(_e) {
        if(!this.externAPGANoUpdatableAltSelector) {
            if (_e.deltaY < 0)
                this.onEvent("SEL_ALT_SMALL_INC");
            else if (_e.deltaY > 0)
                this.onEvent("SEL_ALT_SMALL_DEC");
        }
    }
    OnMouseWheelOuter(_e) {
        if(!this.externAPGANoUpdatableAltSelector) {
            if (_e.deltaY < 0)
                this.onEvent("SEL_ALT_LARGE_INC");
            else if (_e.deltaY > 0)
                this.onEvent("SEL_ALT_LARGE_DEC");
        }
    }
    OnMouseWheelUpDown(_e) {
        if(this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.pitch) {
            if (_e.deltaY < 0)
                this.onEvent("AP_DOWN");
            else if (_e.deltaY > 0)
                this.onEvent("AP_UP");
        }
    }
}

class PMS50_APGA_LOCAL_BASE {
    constructor() {
    }
    connectedCallback() {
    }
    onUpdate(_deltaTime) {
    }
}
// APGA_LOCAL class
// Same code as PMS50_APGA except the class declaration
class PMS50_APGA_LOCAL extends PMS50_APGA_LOCAL_BASE {
    get templateID() { return "PMS50_APGA"; }  // ID if the instrument in HTML
    get manageFlightPlan() { return false; }
    constructor() {
        super();
        // Version and provider
        this.version = "1.0.15";     // Change that if you have modified this file
        this.provider = "PMS50";    // Change that if you have modified this file

        // Parameters
        // You can modify these parameters to suit your needs in the MY_APGA. Check the APGA documentation.
        this.updateFrequency = 10;  // Min 1, Max 10
        this.highAltPrecision = false;
        this.altPreselMode = altPreselMode.auto;
        this.noUpdatableAltitudeSelector = false;  // Set it to true if mechanical selector that can't be updated from APGA
        this.vsEngageMode = vsEngageMode.current;
        this.vsEngageModeDefaultValue = 500;
        this.gaVsValue = 700;  // Vs value in feet/mn for ga mode
        this.defaultLateralMode = lateralMode.roll;  // Default lateral mode
        this.manualArmDefaultVerticalMode = verticalMode.vs; // Default vertical mode to engage when arming in manual arm mode (none, vs, flc)
        this.useVsForArmedVerticalCheck = false;  // Use the current vertical speed to check the arming to the selected altitude
        this.vsThresholdForArmedVerticalCheck = 50;  // Vs threshold to use when this.useVsForArmedVerticalCheck is true. Keep it between 10 and 50.
        this.targetAltitudeAlertThreshold = 1000;   // Target altitude alert phase threshold
        this.targetAltitudeNearThreshold = 300;   // Target altitude near phase threshold
        this.targetAltitudeReachedThreshold = 20;   // Target altitude reached phase threshold
        this.targetAltitudeReachedTimeOut = 5;  // Target altitude reached phase timeout in seconds
        this.unarmedAltituteLockValue = 99000;  // Altitude locked value for unarmed state when in manual arm mode
        this.altModeUpDownIncrementValue = 20;  // Up/Down increment when in ALT mode

        // Internal state
        this.neededTrim = 0; // needed trim value
        this.lateralMode = lateralMode.none;  // Current lateral mode
        this.armedLateralMode = lateralMode.none;  // Armed lateral mode
        this.verticalMode = verticalMode.none;    // Current vertical mode
        this.armedVerticalMode = verticalMode.none;  // Armed vertical mode
        this.armedAltitudeMode = verticalMode.none;  // Used to distinguish 2 armed modes
        this.armedApproachMode = verticalMode.none;  // Used to distinguish 2 armed modes
        this.capturedAltitude = -1; // Captured altitude in Alt mode (-1 if not in ALT mode)
        this.internalSelectedAltitude = 0; // Internal selected altitude. Used in manual arm mode only
        this.internalArmed = false; // Internal arming mode. Used in manual arm mode only
        this.cgaMode = false; // Go around mode (internal management)
        this.clvlMode = false; // LVL combined mode (internal management)
        this.checkUpdateCombinedMode = false; // Use to check the change of combined mode
        this.lvarValues = {}; // Used for optimizing performance in writting Lvars
        this.updateSimAltitudeLockVar = true; // Allow to block the altitude lock var reset to this.unarmedAltituteLockValue in manual arm mode
        this.selectedAltitudePhase = selectedAltitudePhase.none;    // Current selected altitude phase
        this.fPApproachType = approachType.none; // Current approach type
        this.isVloc = false; // true if in VLOC mode
        this.WTT = false;   // WTT detected
        this.WTTManageManualArming = false;   // true if the WTT module can manage the manual arming (just for compatibility)
    }
    connectedCallback() {
        // Called when the instrument is connected
        super.connectedCallback();
        this.init();
        // Tells the world that we are here
        SimVar.SetSimVarValue("L:PMS50_APGA_RUNNING", "boolean", true);
        SimVar.SetSimVarValue("L:PMS50_APGA_ALTSEL_NOT_UPDATABLE", "boolean", this.noUpdatableAltitudeSelector);
    }
    init() {
        // Check update frequence max and min ranges
        if(this.updateFrequency > 10)
            this.updateFrequency = 10;
        if(this.updateFrequency < 1)
            this.updateFrequency = 1;
        this._t_update = 0;
    }
    onUpdate(_deltaTime) {
        super.onUpdate(_deltaTime);

        // Check update frequency
        if(this.updateFrequency < 10) {
            this._t_update++;
            if(this._t_update % (11 - this.updateFrequency) != 0)
                return;
            this._t_update = 0;
        }

        // Update WTT mode detection and manual arming management
        this.WTT = SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_INSTALLED", "bool");
        this.hasVnav = this.WTT && SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_HAS_VNAV", "bool");
        this.WTTManageManualArming = this.WTT ? SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_MANAGE_MANUAL_ARMING", "bool") : false;

        // Update internal arm in manual arm mode
        if(this.altPreselMode == altPreselMode.manual) {
            // We reset the manual arm if not in VS or FLC mode
            if(this.verticalMode != verticalMode.vs && this.verticalMode != verticalMode.flc)
                this.internalArmed = false;
        }
        else {
            // No internal arming mode for other preselection modes 
            this.internalArmed = false;
        }

        // Update approachType
        this.fPApproachType = SimVar.GetSimVarValue("GPS APPROACH APPROACH TYPE", "number");

        // Update gps/vloc
        this.isVloc = !SimVar.GetSimVarValue("GPS DRIVES NAV1", "bool");

        // Update the current lateral mode
        this.updateActiveLateralMode();

        // Update the current vertical mode
        this.updateActiveVerticalMode();

        // Update the armed lateral mode
        this.updateArmedLateralMode();

        // Update the armed vertical modes (global, altitude and approach)
        this.updateArmedVerticalModes();

        // Update the target altitude phase
        this.updateSelectedAltitudePhase();

        // Update level mode
        this.checkUpdateLevelMode();

        // Update the captured altitude
        if(this.WTTManageManualArming)
            this.capturedAltitude = this.verticalMode == verticalMode.alt ? 10 * Math.round(SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_CAPTURED_ALTITUDE", "feet") / 10) : -1;
        else
            this.capturedAltitude = this.verticalMode == verticalMode.alt ? 10 * Math.round(SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR:" + Simplane.getAutoPilotAltitudeSlotIndex(), "feet") / 10) : -1;

        // Update the selected altitude from selector if not updatable
        if(this.noUpdatableAltitudeSelector && this.altPreselMode != altPreselMode.none)
            this.setSelectedAltitude(this.getLvar("ALTITUDE_PRESEL_VALUE", "feet"));

        // Set the optional Lvars
        this.setLvars();

        // Manage manual arming
        if(this.WTTManageManualArming) {
            // WT mode can work without setting a high value to AUTOPILOT ALTITUDE LOCK VAR
            if((this.altPreselMode == altPreselMode.none || this.altPreselMode == altPreselMode.manual && !this.internalArmed) && (this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.pitch )) {
                if(SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_ALT_ARMING", "Bool"))
                    postHEvent("PMS50_AUTOPILOT_ALT_ARMING_OFF")
            }
            else {
                if(!SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_ALT_ARMING", "Bool"))
                    postHEvent("PMS50_AUTOPILOT_ALT_ARMING_ON")
            }
        }
        else {
            // Set a high value (with correct sign following VS) to AUTOPILOT ALTITUDE LOCK VAR if no alt preselect mode or manual arm mode
            // And if we are not armed and not in alt mode
            if(this.altPreselMode == altPreselMode.none || (this.updateSimAltitudeLockVar && this.altPreselMode == altPreselMode.manual)) {
                if(!this.internalArmed && this.verticalMode != verticalMode.alt)
                    this.setSimSelectedAltitude(SimVar.GetSimVarValue("VERTICAL SPEED", "feet per minute") >= 0 ? this.unarmedAltituteLockValue : -this.unarmedAltituteLockValue);
            }
        }

        // Always limit the selected altitude to positive values
        if(this.internalSelectedAltitude < 0)
            this.setSelectedAltitude(0);

        // If we are in ALT mode and wing leveler mode
        // We probably come from a combined mode
        // Restore to default mode (usually ROLL)
        if(this.verticalMode == verticalMode.alt && this.lateralMode == lateralMode.lvl && this.defaultLateralMode != lateralMode.lvl) {
            if(this.defaultLateralMode == lateralMode.roll)
                SimVar.SetSimVarValue("K:AP_BANK_HOLD_ON", "number", 0);
        }
    }

    onEvent(_event) {
        
        switch (_event) {
            // Alt select knob events (toolbar panel only)
            case "SEL_ALT_SMALL_INC":
                this.onEventSEL_ALT_SMALL_INC();
                break;
            case "SEL_ALT_SMALL_DEC":
                this.onEventSEL_ALT_SMALL_DEC();
                break;
            case "SEL_ALT_LARGE_INC":
                this.onEventSEL_ALT_LARGE_INC();
                break;
            case "SEL_ALT_LARGE_DEC":
                this.onEventSEL_ALT_LARGE_DEC();
                break;
            case "SEL_ALT_VERY_LARGE_INC":
                this.onEventSEL_ALT_VERY_LARGE_INC();
                break;
            case "SEL_ALT_VERY_LARGE_DEC":
                this.onEventSEL_ALT_VERY_LARGE_DEC();
                break;

            // VS knob events
            case "SEL_VS_SMALL_INC":
                this.onEventSEL_VS_SMALL_INC();
                break;
            case "SEL_VS_SMALL_DEC":
                this.onEventSEL_VS_SMALL_DEC();
                break;
            case "SEL_VS_LARGE_INC":
                this.onEventSEL_VS_LARGE_INC();
                break;
            case "SEL_VS_LARGE_DEC":
                this.onEventSEL_VS_LARGE_DEC();
                break;
                
            // Autopilot button events
            case "AP_AP":
                this.onEventAP();
                break;
            case "AP_FD":
                this.onEventFD();
                break;
            case "AP_YD":
                this.onEventYD();
                break;
            case "AP_HDG":
                this.onEventHDG();
                break;
            case "AP_LVL":
                this.onEventLVL();
                break;
            case "AP_NAV":
                this.onEventNAV();
                break;
            case "AP_APR":
                this.onEventAPR();
                break;
            case "AP_REV":
                this.onEventREV();
                break;
            case "AP_ALT":
                this.onEventALT();
                break;
            case "AP_VS":
                this.onEventVS();
                break;
            case "AP_FLC":
                this.onEventFLC();
                break;
            case "AP_CGA":
                this.onEventCGA();
                break;
            case "AP_VNAV":
                if(this.hasVnav)
                    this.onEventVNAV();
                break;
            case "AP_CLVL":
                this.onEventCLVL();
                break;
            case "AP_ARM":
                this.onEventARM();
                break;
            case "AP_ARM_PRESEL":
                this.onEventARM_PRESEL();
                break;
                
            // Up / Down buttons (disabled if not in VS or FLC mode)
            case "AP_UP":
                this.onEventUP();
                break;
            case "AP_DOWN":
                this.onEventDOWN();
                break;

            // Special events not public (used for GTN750)
            case "AP_ALT_SELECT":
                // Get the entered value and set it as target altitude
                // Use a time out to be sure that the sim variable is ready
                setTimeout(()=>{
                    this.setSelectedAltitude(this.getLvar("ALTITUDE_PRESEL_VALUE", "feet"));
                }, 500);
                break;
            case "ALT_PRESEL":
                // Altitude selection change (called from the autopilot menu)
                // Use a time out to be sure that the sim variable is ready
                setTimeout(()=>{
                    this.altPreselMode = this.getLvar("ALTITUDE_PRESEL_MODE", "number");
                    if(this.altPreselMode > altPreselMode.manual)
                        this.altPreselMode = altPreselMode.none;

                    // resets internal arming in any case when we change the mode
                    this.internalArmed = false;
                    let indicateAltitude = 10 * Math.round(SimVar.GetSimVarValue("INDICATED ALTITUDE", "feet") / 10);
                    indicateAltitude = this.getAltitudeWithPrecision(indicateAltitude);
            
                    if(this.altPreselMode == altPreselMode.none) {
                        // No alt select

                        // Reset FLC mode to ALT mode
                        if(this.verticalMode == verticalMode.flc) {
                            SimVar.SetSimVarValue("K:AP_ALT_HOLD", "number", 0);
                            SimVar.SetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute", 0);
                        }
                    }
                    else if(this.altPreselMode == altPreselMode.auto) {
                        // Alt select with auto arm
                        // Reset the sim selected altitude to the current one
                        this.setSimSelectedAltitude(this.getAltitudeWithPrecision(indicateAltitude));
                    }
                    else {
                        // Alt select with manual arm

                        // Internal selected altitude is the current altitude
                        this.internalSelectedAltitude = indicateAltitude;
                    }
                }, 500);
                break;
            case "PRECISION":
                // Use a time out to be sure that the sim variable is ready
                setTimeout(()=>{
                    this.highAltPrecision = this.getLvar("PRECISION");
                }, 500);
                break;
        }
    }

    // Events
    onEventAP() {
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:AP_MASTER", "number", 0);
    }
    onEventFD() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        setTimeout(()=>{
            // Send the K event. The sim is doing the job behind.
            SimVar.SetSimVarValue("K:TOGGLE_FLIGHT_DIRECTOR", "number", 0);
        }, 500);
    }
    onEventYD() {
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:YAW_DAMPER_TOGGLE", "number", 0);
    }
    onEventHDG() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Send the K event. The sim is doing the job behind.
        if (this.lateralMode == lateralMode.hdg) {
            SimVar.SetSimVarValue("K:AP_HDG_HOLD_OFF", "number", 0);
        }
        else {
            SimVar.SetSimVarValue("K:AP_HDG_HOLD_ON", "number", 0);
        }
    }
    onEventNAV() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:AP_NAV1_HOLD", "number", 0);
    }
    onEventAPR() {
        if(!this.isAutopilotEngaged())
            return;
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:AP_APR_HOLD", "number", 0);
    }
    onEventREV() {
        if(!this.isAutopilotEngaged())
            return;
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:AP_BC_HOLD", "number", 0);
    }
    onEventALT() {
        if(!this.isAutopilotEngaged())
            return;
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Send the K event. The sim is doing the job behind.
        let currentVerticalMode = this.verticalMode;
        SimVar.SetSimVarValue("K:AP_ALT_HOLD", "number", 0);
        if(currentVerticalMode != verticalMode.alt) {
            // When entering the alt mode, we reset the VS sim variable to 0
            // and the altitude lock var to the current altitude
            SimVar.SetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute", 0);
            let indicateAltitude = 10 * Math.round(SimVar.GetSimVarValue("INDICATED ALTITUDE", "feet") / 10);
            indicateAltitude = this.getAltitudeWithPrecision(indicateAltitude);
            this.setSimSelectedAltitude(indicateAltitude, true);
            // The internal selected altitude is also set to locked one (used in manual arm mode only)
            this.internalSelectedAltitude = indicateAltitude;
        }
        else {
            // Leaving ALT mode
            // Reset hold altitude to the big value
            if(!this.WTTManageManualArming && (this.altPreselMode == altPreselMode.none || this.altPreselMode == altPreselMode.manual))
                 this.setSimSelectedAltitude(this.unarmedAltituteLockValue);
        }
    }
    onEventVS() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        this.setVsVerticalMode(this.verticalMode != verticalMode.vs);
    }
    onEventFLC() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Disable ALT mode if set (for stock AP manager)
        if(this.verticalMode == verticalMode.alt) {
            SimVar.SetSimVarValue("K:AP_ALT_HOLD", "number", 0);
            if(!this.WTTManageManualArming && (this.altPreselMode == altPreselMode.none || this.altPreselMode == altPreselMode.manual))
                this.setSimSelectedAltitude(this.unarmedAltituteLockValue);
        }
        // Send the K event. The sim is doing the job behind.
        if(this.verticalMode != verticalMode.flc) {
            // We engage FLC mode
            // We must set the current airspeed as hold value (necessary for stock sim)
            SimVar.SetSimVarValue("K:AP_SPD_VAR_SET", "number", SimVar.GetSimVarValue("AIRSPEED INDICATED", "knots"));
        }
        SimVar.SetSimVarValue("K:FLIGHT_LEVEL_CHANGE", "number", 0);
    }
    onEventVNAV() {
        if(!this.isAutopilotEngaged())
            return;
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Send the H event to WTT1
        postHEvent("PMS50_VNAV_TOGGLE");
    }
    onEventLVL() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:AP_WING_LEVELER", "number", 0);
    }
    onEventCGA() {
        // armedCombinedMode is use for display purpose
        // It blocks some LVars to a 0 value to get a clean display transition
        this.armedCombinedMode = true;
        setTimeout(()=>{
            this.armedCombinedMode = false;
        }, 500);
        this.enableCombinedMode(combinedMode.cga, !this.cgaMode);
    }
    onEventCLVL() {
        // armedCombinedMode is use for display purpose
        // It blocks some LVars to a 0 value to get a clean display transition
        this.armedCombinedMode = true;
        setTimeout(()=>{
            this.armedCombinedMode = false;
        }, 500);
        this.enableCombinedMode(combinedMode.clvl, !this.clvlMode);
    }
    onEventARM() {
        // Do not arm if in clvl mode
        if(this.clvlMode)
            return;
        // No K event here. This is an internal management.
        // Toggle armed mode
        if(this.altPreselMode != altPreselMode.manual) {
            this.internalArmed = false;
            return;
        }
        if(this.internalArmed)
            this.internalArmed = false;
        else {
            // We are going to arm
            if(this.manualArmDefaultVerticalMode == verticalMode.none) {
                // Do nothing if not in the correct mode and not armed in this case
                if(this.verticalMode != verticalMode.vs && this.verticalMode != verticalMode.flc)
                    return;
            }
            // Set the sim selected altitude to the current one
            if(!this.WTTManageManualArming)
                this.setSimSelectedAltitude(this.getAltitudeWithPrecision(this.internalSelectedAltitude), true);
            if(this.manualArmDefaultVerticalMode == verticalMode.vs) {
                if(this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc) {
                    // VS or FLC already engaged, just arm
                    this.internalArmed = true;
                }
                else {
                    // Engage the vs mode
                    this.disableAnyCombinedMode();
                    this.setVsVerticalMode(true);
                    // Let time to vertical mode to engage before arming, otherwise, the arming mode will be reset
                    setTimeout(()=>{
                        this.internalArmed = true;
                    }, 500);
                }
            }
            else if (this.manualArmDefaultVerticalMode == verticalMode.flc) {
                // Engage the flc mode if not already engaged
                if(this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc) {
                    // VS or FLC already engaged, just arm
                    this.internalArmed = true;
                }
                else {
                    // Engage the flc mode
                    this.disableAnyCombinedMode();
                    // We must set the current airspeed as hold value (necessary for stock sim)
                    SimVar.SetSimVarValue("K:AP_SPD_VAR_SET", "number", SimVar.GetSimVarValue("AIRSPEED INDICATED", "knots"));
                    SimVar.SetSimVarValue("K:FLIGHT_LEVEL_CHANGE", "number", 0);
                    // Let time to vertical mode to engage before arming, otherwise, the arming mode will be reset
                    setTimeout(()=>{
                        this.internalArmed = true;
                    }, 500);
                }
            }
            else
                this.internalArmed = !this.internalArmed;
        }
    }
    onEventARM_PRESEL() {
        // Do not arm if in clvl mode
        if(this.clvlMode)
            return;
        // No K event here. This is an internal management.
        // Toggle armed mode
        if(this.altPreselMode != altPreselMode.manual) {
            this.internalArmed = false;
            return;
        }
        // Set the sim selected altitude to the one given by the sim variable PMS50_APGA_ALTITUDE_PRESEL_VALUE
        // Use a time out to be sure that the sim variable is ready
        if(this.internalArmed) {
            // We are armed so just send the ARM event to un-arm
            this.onEvent("AP_ARM");
        }
        else {
            // We are not armed
            // Read the arma value and then send the arm event
            setTimeout(()=>{
                this.setSelectedAltitude(this.getLvar("ALTITUDE_PRESEL_VALUE", "feet"));
                // Send the ARM event
                this.onEvent("AP_ARM");
            }, 500);
        }
    }
    onEventUP() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        if(this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.pitch) {
            // We send the K events to the sim.
            if(this.verticalMode == verticalMode.vs)
                SimVar.SetSimVarValue("K:AP_VS_VAR_INC", "number", 0);
            else if(this.verticalMode == verticalMode.flc)
                SimVar.SetSimVarValue("K:AP_SPD_VAR_DEC", "number", 0);
            else
                SimVar.SetSimVarValue("K:AP_PITCH_REF_INC_UP", "number", 0);
        }
        else if(this.verticalMode == verticalMode.alt) {
            if(this.WTTManageManualArming) {
                SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_CAPTURED_ALTITUDE_SET", "feet", this.capturedAltitude + this.altModeUpDownIncrementValue);
                postHEvent("PMS50_AUTOPILOT_SYNC_CAPTURED_ALTITUDE")
            }
            else {
                SimVar.SetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR:" + Simplane.getAutoPilotAltitudeSlotIndex(), "feet", this.capturedAltitude + this.altModeUpDownIncrementValue);
                if(this.WTT)
                    postHEvent("PMS50_AUTOPILOT_SYNC_SEL_ALTITUDE")
            }
        }
    }
    onEventDOWN() {
        // Disable GA or LVL combined modes
        this.disableAnyCombinedMode();
        if(this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.pitch) {
            // If already displayed, we send the K events to the sim.
            if(this.verticalMode == verticalMode.vs)
                SimVar.SetSimVarValue("K:AP_VS_VAR_DEC", "number", 0);
            else if(this.verticalMode == verticalMode.flc)
                SimVar.SetSimVarValue("K:AP_SPD_VAR_INC", "number", 0);
            else
                SimVar.SetSimVarValue("K:AP_PITCH_REF_INC_DN", "number", 0);
        }
        else if(this.verticalMode == verticalMode.alt) {
            if(this.WTTManageManualArming) {
                SimVar.SetSimVarValue("L:PMS50_AUTOPILOT_CAPTURED_ALTITUDE_SET", "feet", this.capturedAltitude - this.altModeUpDownIncrementValue);
                postHEvent("PMS50_AUTOPILOT_SYNC_CAPTURED_ALTITUDE")
            }
            else {
                SimVar.SetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR:" + Simplane.getAutoPilotAltitudeSlotIndex(), "feet", this.capturedAltitude - this.altModeUpDownIncrementValue);
                if(this.WTT)
                    postHEvent("PMS50_AUTOPILOT_SYNC_SEL_ALTITUDE")
            }
        }
    }
    onEventSEL_ALT_SMALL_INC() {
        let currentAltitudeForSelect = !this.WTTManageManualArming && this.altPreselMode == altPreselMode.manual ? this.internalSelectedAltitude : SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
        currentAltitudeForSelect = this.getAltitudeWithPrecision(currentAltitudeForSelect);
        this.setSelectedAltitude(currentAltitudeForSelect + (this.highAltPrecision ? 10 : 100));
    }
    onEventSEL_ALT_LARGE_INC() {
        let currentAltitudeForSelect = !this.WTTManageManualArming && this.altPreselMode == altPreselMode.manual ? this.internalSelectedAltitude : SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
        currentAltitudeForSelect = this.getAltitudeWithPrecision(currentAltitudeForSelect);
        this.setSelectedAltitude(currentAltitudeForSelect + (this.highAltPrecision ? 100 : 1000));
    }
    onEventSEL_ALT_VERY_LARGE_INC() {
        let currentAltitudeForSelect = !this.WTTManageManualArming && this.altPreselMode == altPreselMode.manual ? this.internalSelectedAltitude : SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
        currentAltitudeForSelect = this.getAltitudeWithPrecision(currentAltitudeForSelect);
        this.setSelectedAltitude(currentAltitudeForSelect + (this.highAltPrecision ? 1000 : 10000));
    }
    onEventSEL_ALT_SMALL_DEC() {
        let currentAltitudeForSelect = !this.WTTManageManualArming && this.altPreselMode == altPreselMode.manual ? this.internalSelectedAltitude : SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
        currentAltitudeForSelect = this.getAltitudeWithPrecision(currentAltitudeForSelect);
        this.setSelectedAltitude(currentAltitudeForSelect - (this.highAltPrecision ? 10 : 100));
    }
    onEventSEL_ALT_LARGE_DEC() {
        let currentAltitudeForSelect = !this.WTTManageManualArming && this.altPreselMode == altPreselMode.manual ? this.internalSelectedAltitude : SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
        currentAltitudeForSelect = this.getAltitudeWithPrecision(currentAltitudeForSelect);
        this.setSelectedAltitude(currentAltitudeForSelect - (this.highAltPrecision ? 100 : 1000));
    }
    onEventSEL_ALT_VERY_LARGE_DEC() {
        let currentAltitudeForSelect = !this.WTTManageManualArming && this.altPreselMode == altPreselMode.manual ? this.internalSelectedAltitude : SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
        currentAltitudeForSelect = this.getAltitudeWithPrecision(currentAltitudeForSelect);
        this.setSelectedAltitude(currentAltitudeForSelect - (this.highAltPrecision ? 1000 : 10000));
    }
    onEventSEL_VS_SMALL_INC() {
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:AP_VS_VAR_INC", "number", 0);
    }
    onEventSEL_VS_SMALL_DEC() {
        // Send the K event. The sim is doing the job behind.
        SimVar.SetSimVarValue("K:AP_VS_VAR_DEC", "number", 0);
    }
    onEventSEL_VS_LARGE_INC() {
        // Send the K event.
        SimVar.SetSimVarValue("K:AP_VS_VAR_SET_ENGLISH", "feet/minute",  Math.round(SimVar.GetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute")) + 1000, "feet/minute");
    }
    onEventSEL_VS_LARGE_DEC() {
        // Send the K event.
        SimVar.SetSimVarValue("K:AP_VS_VAR_SET_ENGLISH", "feet/minute",  Math.round(SimVar.GetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute")) - 1000, "feet/minute");
    }

    // Check if the autopilot is engaged
    isAutopilotEngaged() {
        return SimVar.GetSimVarValue("AUTOPILOT MASTER", "Bool");
    }

    // Check if the flight director is engaged
    isFlightDirectorEngaged() {
        return SimVar.GetSimVarValue("AUTOPILOT FLIGHT DIRECTOR ACTIVE", "Bool");
    }

    // Update the current lateral mode
    updateActiveLateralMode() {
        if(!this.isAutopilotEngaged() && !this.isFlightDirectorEngaged())
            this.lateralMode = lateralMode.none;
        else if(this.cgaMode)
            this.lateralMode = lateralMode.cga;
        else if(this.clvlMode)
            this.lateralMode = lateralMode.clvl;
        else if (SimVar.GetSimVarValue("AUTOPILOT WING LEVELER", "Bool"))
            this.lateralMode = lateralMode.lvl;
        else if (SimVar.GetSimVarValue("AUTOPILOT BANK HOLD", "Bool"))
            this.lateralMode = lateralMode.roll;
        else if (SimVar.GetSimVarValue("AUTOPILOT HEADING LOCK", "Bool"))
            this.lateralMode = lateralMode.hdg;
        else if (SimVar.GetSimVarValue("AUTOPILOT BACKCOURSE HOLD", "Bool"))
            this.lateralMode = lateralMode.bc;
        else if (SimVar.GetSimVarValue("AUTOPILOT APPROACH HOLD", "Bool") || SimVar.GetSimVarValue("AUTOPILOT GLIDESLOPE HOLD", "Bool")) {
            if(this.isVloc)
                this.lateralMode = SimVar.GetSimVarValue("NAV HAS LOCALIZER:" + Simplane.getAutoPilotSelectedNav(), "Bool") ? lateralMode.aprloc : this.lateralMode = lateralMode.aprvor;
            else
                this.lateralMode = lateralMode.aprgps;
        }
        else if (SimVar.GetSimVarValue("AUTOPILOT NAV1 LOCK", "Bool")) {
            if(this.isVloc)
                this.lateralMode = SimVar.GetSimVarValue("NAV HAS LOCALIZER:" + Simplane.getAutoPilotSelectedNav(), "Bool") ? lateralMode.loc : lateralMode.vor;
            else
                this.lateralMode = lateralMode.gps;
        }
        else
            this.lateralMode = lateralMode.none;
    }

    // Update the current vertical mode
    updateActiveVerticalMode() {
        if(!this.isAutopilotEngaged() && !this.isFlightDirectorEngaged())
            this.verticalMode = verticalMode.none;
        else if(this.clvlMode)
            this.verticalMode = verticalMode.clvl;
        else if (this.isVloc && (SimVar.GetSimVarValue("AUTOPILOT APPROACH HOLD", "Bool") || SimVar.GetSimVarValue("AUTOPILOT GLIDESLOPE HOLD", "Bool")) && SimVar.GetSimVarValue("AUTOPILOT GLIDESLOPE ACTIVE", "Boolean"))
            this.verticalMode = verticalMode.gs;
        else if (!this.isVloc && SimVar.GetSimVarValue("AUTOPILOT APPROACH HOLD", "Bool") && this.fPApproachType == approachType.rnav && (SimVar.GetSimVarValue("AUTOPILOT GLIDESLOPE ACTIVE", "Boolean")))
            this.verticalMode = verticalMode.gp;
        else if (SimVar.GetSimVarValue("AUTOPILOT VERTICAL HOLD", "Bool"))
            this.verticalMode = verticalMode.vs;
        else if (SimVar.GetSimVarValue("AUTOPILOT FLIGHT LEVEL CHANGE", "Bool"))
            this.verticalMode = verticalMode.flc;
        else if (SimVar.GetSimVarValue("AUTOPILOT PITCH HOLD", "Bool"))
            this.verticalMode = verticalMode.pitch;
        else if (SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK", "Bool"))
            this.verticalMode = verticalMode.alt;
        else
            this.verticalMode = verticalMode.none;
        if(this.cgaMode && this.checkUpdateCombinedMode) {
            if(SimVar.GetSimVarValue("AUTOPILOT WING LEVELER", "Bool") && this.verticalMode == verticalMode.vs) {
                // Check if we are still in cga mode
                let currentHoldVs = Math.round(SimVar.GetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute"));
                if(this.cgaMode && currentHoldVs == this.gaVsValue)
                    this.verticalMode = verticalMode.cga;
                else {
                    // Reset cga
                    this.cgaMode = false;
                    this.checkUpdateCombinedMode = false;
                }
            }
            else {
                // Reset cga
                this.cgaMode = false;
                this.checkUpdateCombinedMode = false;
            }
        }
        // Get VNAV mode
        if(this.verticalMode == verticalMode.none && this.hasVnav && SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_VNAV_STATE", "number") == 2 && SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_VNAV_PATH_MODE", "number") == 2)
            this.verticalMode = verticalMode.vnav;
    }

    // Update the armed lateral mode
    updateArmedLateralMode() {
        if(!this.isAutopilotEngaged() && !this.isFlightDirectorEngaged())
            this.armedLateralMode = lateralMode.none;
        else if(this.clvlMode)
            this.armedLateralMode = lateralMode.none;
        else if (!SimVar.GetSimVarValue("AUTOPILOT HEADING LOCK", "Bool") && !SimVar.GetSimVarValue("AUTOPILOT WING LEVELER", "Bool") && !SimVar.GetSimVarValue("AUTOPILOT BANK HOLD", "Bool"))
            this.armedLateralMode = lateralMode.none;
        else if (SimVar.GetSimVarValue("AUTOPILOT BACKCOURSE HOLD", "Bool"))
            this.armedLateralMode = lateralMode.bc;
        else if (SimVar.GetSimVarValue("AUTOPILOT APPROACH HOLD", "Bool") || SimVar.GetSimVarValue("AUTOPILOT GLIDESLOPE ARM", "Bool")) {
            if(this.isVloc)
                this.armedLateralMode = SimVar.GetSimVarValue("NAV HAS LOCALIZER:" + Simplane.getAutoPilotSelectedNav(), "Bool") ? lateralMode.aprloc : lateralMode.aprvor;
            else
                this.armedLateralMode = lateralMode.aprgps;
        }
        else if (SimVar.GetSimVarValue("AUTOPILOT NAV1 LOCK", "Bool")) {
            if(this.isVloc)
                this.armedLateralMode = SimVar.GetSimVarValue("NAV HAS LOCALIZER:" + Simplane.getAutoPilotSelectedNav(), "Bool") ? lateralMode.loc : lateralMode.vor;
            else
                this.armedLateralMode = lateralMode.gps;
        }
        else
            this.armedLateralMode = lateralMode.none;
    }

    // Update the armed vertical modes (global, altitude and approach)
    updateArmedVerticalModes() {
        if((!this.isAutopilotEngaged() && !this.isFlightDirectorEngaged()) || this.clvlMode) {
            this.armedVerticalMode = verticalMode.none;
            this.armedAltitudeMode = verticalMode.none;
            this.armedApproachMode = verticalMode.none;
            return;
        }
        // Separate armed modes for altitude and approach
        let GsArmed = SimVar.GetSimVarValue("AUTOPILOT GLIDESLOPE ARM", "Bool");
        if(GsArmed || SimVar.GetSimVarValue("AUTOPILOT APPROACH HOLD", "Bool")) {
            // We check also if not already active
            if(this.verticalMode != verticalMode.gs && this.verticalMode != verticalMode.gp) {
                if(this.isVloc)
                   this.armedApproachMode = GsArmed && SimVar.GetSimVarValue("NAV HAS LOCALIZER:" + Simplane.getAutoPilotSelectedNav(), "Bool") ? verticalMode.gs : verticalMode.none;
                else
                    this.armedApproachMode = this.fPApproachType == approachType.rnav ? verticalMode.gp : verticalMode.none;
            }
            else
                this.armedApproachMode = verticalMode.none;
        }
        else
            this.armedApproachMode = verticalMode.none;

        if((this.altPreselMode == altPreselMode.auto || (this.altPreselMode == altPreselMode.manual && this.internalArmed)) && (this.verticalMode == verticalMode.pitch || this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.cga)) {
            // altitude arm mode
            if(this.useVsForArmedVerticalCheck) {
                // Check the arming mode following VS
                let vs = SimVar.GetSimVarValue("VERTICAL SPEED", "feet per second")*60;
                let currentAltitude = Math.round(SimVar.GetSimVarValue("INDICATED ALTITUDE", "feet"));
                let targetAltitude = SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
                let difference = Math.abs(targetAltitude - currentAltitude);
                if(vs > this.vsThresholdForArmedVerticalCheck && targetAltitude > currentAltitude && difference > this.targetAltitudeReachedThreshold) {
                    // Climb to target altitude
                    this.armedAltitudeMode = verticalMode.alt;
                }
                else if(vs < -this.vsThresholdForArmedVerticalCheck && targetAltitude < currentAltitude && difference > this.targetAltitudeReachedThreshold) {
                    // Descent to target altitude
                    this.armedAltitudeMode = verticalMode.alt;
                }
                else if(SimVar.GetSimVarValue("SIM ON GROUND", "bool") && targetAltitude > currentAltitude + this.targetAltitudeReachedThreshold) {
                    this.armedAltitudeMode = verticalMode.alt;
                }
                else
                    this.armedAltitudeMode = verticalMode.none;
            }
            else
                this.armedAltitudeMode = verticalMode.alt;
        }
        else
            this.armedAltitudeMode = verticalMode.none;

        // Set the global armed mode (priority is gs/alt/gp)
        if(this.armedApproachMode == verticalMode.gs)
            this.armedVerticalMode = verticalMode.gs;
        else if(this.armedAltitudeMode == verticalMode.alt)
            this.armedVerticalMode = verticalMode.alt;
        else if(this.armedApproachMode == verticalMode.gp)
            this.armedVerticalMode = verticalMode.gp;
        else
            this.armedVerticalMode = verticalMode.none;

            // Get VNAV mode
        if(this.armedVerticalMode == verticalMode.none && this.verticalMode == verticalMode.alt && this.hasVnav && SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_VNAV_STATE", "number") == 2 && SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_VNAV_PATH_MODE", "number") == 1)
            this.armedVerticalMode = verticalMode.vnav;
    }

    updateSelectedAltitudePhase() {
        let vs = SimVar.GetSimVarValue("VERTICAL SPEED", "feet per second")*60;
        let currentAltitude = Math.round(SimVar.GetSimVarValue("INDICATED ALTITUDE", "feet"));
        let targetAltitude = SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet");
        if(!this.WTTManageManualArming && targetAltitude == this.unarmedAltituteLockValue && (this.altPreselMode == altPreselMode.none || this.altPreselMode == altPreselMode.manual)) {
            // We are not in ALTS in this case
            this.selectedAltitudePhase = selectedAltitudePhase.none;
            return;
        }
        let climbToTarget = false;
        let descentToTarget = false;
        if(this.useVsForArmedVerticalCheck && this.selectedAltitudePhase != selectedAltitudePhase.ready && this.selectedAltitudePhase != selectedAltitudePhase.reached) {
            if(vs > this.vsThresholdForArmedVerticalCheck && currentAltitude < targetAltitude + 5)
                climbToTarget = true;
            else if(vs < -this.vsThresholdForArmedVerticalCheck && currentAltitude > targetAltitude - 5)
                descentToTarget = true;
        }
        else {
            if(currentAltitude < targetAltitude + 5)
                climbToTarget = true;
            else if(currentAltitude > targetAltitude - 5)
                descentToTarget = true;
        }
        if(!climbToTarget && !descentToTarget) {
            // We are not climbing and not descending
            // So reset the phase to none
            this.selectedAltitudePhase = selectedAltitudePhase.none;
            return;
        }
        let currentPhase = this.selectedAltitudePhase;
        let difference = Math.abs(targetAltitude - currentAltitude);
        if(this.armedAltitudeMode == verticalMode.alt) {
            // We are armed
            if(difference > this.targetAltitudeAlertThreshold)
                this.selectedAltitudePhase = selectedAltitudePhase.engaged;
            else if(difference > this.targetAltitudeNearThreshold)
                this.selectedAltitudePhase = selectedAltitudePhase.alert;
            else
                this.selectedAltitudePhase = selectedAltitudePhase.near;
        }
        else if(this.verticalMode == verticalMode.alt) {
            // We are in alt mode, we always engage ready phase at least
            if(currentPhase == selectedAltitudePhase.alert || currentPhase == selectedAltitudePhase.near) {
                // Switch to ready
                this.selectedAltitudePhase = selectedAltitudePhase.ready;
            }
            else if(currentPhase == selectedAltitudePhase.ready) {
                // Switch to reached
                if(difference > 0 && difference < this.targetAltitudeReachedThreshold) {
                    this.selectedAltitudePhase = selectedAltitudePhase.reached;
                    // Set the reached timeout
                    setTimeout(()=>{
                        this.selectedAltitudePhase = selectedAltitudePhase.none;
                    }, this.targetAltitudeReachedTimeOut * 1000);
                }
            }
            else if(currentPhase != selectedAltitudePhase.reached)
                this.selectedAltitudePhase = selectedAltitudePhase.none;
        }
        else
            this.selectedAltitudePhase = selectedAltitudePhase.none;
    }


    // set the target selected altitude
    setSelectedAltitude(_value) {
        let value = this.getAltitudeWithPrecision(_value);
        // Limit the selected altitude to positive values
        if(value < 0)
            value = 0;
        if(this.altPreselMode == altPreselMode.manual) {
            // In manual arm mode, we set the internal selected altitude
            this.internalSelectedAltitude = value;
            // and the sim selected altitude if armed or for WTT mode
            if(this.internalArmed || this.WTTManageManualArming)
                this.setSimSelectedAltitude(value);
        }
        else if(this.altPreselMode == altPreselMode.auto) {
            // We set the sim selected altitude for the auto arm mode
            this.setSimSelectedAltitude(value);
        }
    }

    // Send the selected altitude to the sim
    // With a time out and a blocking update if the value is this.unarmedAltituteLockValue (this.unarmedAltituteLockValue is used for manual arm mode)
    setSimSelectedAltitude(_value, _blockUpdate = false) {
        if(_blockUpdate) {
            this.updateSimAltitudeLockVar = false;
            SimVar.SetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet", _value);
            setTimeout(()=>{
                this.updateSimAltitudeLockVar = true;
            }, 1000);
        }
        else
            SimVar.SetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet", _value);
    }

    // format an altitude by taking precision into account
    getAltitudeWithPrecision(_value) {
        let precision = this.highAltPrecision ? 10 : 100;
        return Math.round(_value / precision) * precision;
    }

    // Enable / disable combined mode (CLVL, CGA)
    enableCombinedMode(mode, enable) {
        if(mode == combinedMode.cga && this.cgaMode != enable) {
            if(enable) {
                // GA mode is a Wing leveler/VS combined mode with VS set to a predefined value
                // In real world we should use PITCH instead of VS but currently WT do not manage the PITCH HOLD K event
                if(this.altPreselMode == altPreselMode.none || this.altPreselMode == altPreselMode.manual) {
                    this.internalArmed = false;
                    if(!this.WTTManageManualArming)
                        this.setSimSelectedAltitude(this.unarmedAltituteLockValue);
                }
                SimVar.SetSimVarValue("K:AP_WING_LEVELER_ON", "number", 0);
                SimVar.SetSimVarValue("K:AP_PANEL_VS_ON", "number", 0);
                SimVar.SetSimVarValue("K:AP_NAV1_HOLD_OFF", "number", 0);
                SimVar.SetSimVarValue("K:AP_APR_HOLD_OFF", "number", 0);
                setTimeout(()=>{
                    SimVar.SetSimVarValue("K:AP_VS_VAR_SET_ENGLISH", "feet/minute", this.gaVsValue);
                }, 200);
                this.cgaMode = true;
                this.checkUpdateCombinedMode = false;
                setTimeout(()=>{
                    this.checkUpdateCombinedMode = true;
                }, 300);
            }
            else {
                // Release the VS mode and wing leveler
                SimVar.SetSimVarValue("K:AP_PANEL_VS_OFF", "number", 0);
                SimVar.SetSimVarValue("K:AP_WING_LEVELER_OFF", "number", 0);
                // Release the internal cga state when removing GA mode
                this.cgaMode = false;
                this.checkUpdateCombinedMode = false;
            }
            // Disable the clvlMode in any case here
            this.clvlMode = false;
        }
        if(mode == combinedMode.clvl && this.clvlMode != enable) {
            this.clvlMode = enable;
            if(this.clvlMode) {
                SimVar.SetSimVarValue("K:AP_NAV1_HOLD_OFF", "number", 0);
                SimVar.SetSimVarValue("K:AP_APR_HOLD_OFF", "number", 0);
                SimVar.SetSimVarValue("K:AP_WING_LEVELER_ON", "number", 0);
                SimVar.SetSimVarValue("K:AP_PANEL_VS_ON", "number", 0);
                setTimeout(()=>{
                    SimVar.SetSimVarValue("K:AP_VS_VAR_SET_ENGLISH", "feet/minute", 0);
                }, 200);
            }
            else {
                // Release the VS mode
                SimVar.SetSimVarValue("K:AP_PANEL_VS_OFF", "number", 0);
                SimVar.SetSimVarValue("K:AP_WING_LEVELER_OFF", "number", 0);
            }
            // Disable the cgalMode in any case here
            this.cgaMode = false;
        }
    }

    setVsVerticalMode(enable) {
        if (enable && this.verticalMode != verticalMode.vs) {
            // We are going to enter VS mode
            // Set the current hold VS value
            let vsValue = SimVar.GetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute");
            let engageMode = this.vsEngageMode;
            // We force engage mode to current if default requested but no alt preselect
            // because we don't know if we have to climb or descent 
            if(this.altPreselMode == altPreselMode.none && engageMode == vsEngageMode.default)
                engageMode = vsEngageMode.current;
            if(engageMode == vsEngageMode.current) {
                vsValue = SimVar.GetSimVarValue("VERTICAL SPEED", "feet per second")*60;
                vsValue = 100 * Math.round(vsValue / 100);
            }
            else if(engageMode == vsEngageMode.reset) {
                vsValue = 0;
            }
            else if(engageMode == vsEngageMode.default) {
                // If we are here, we are armed to the selected altitude
                // We must check for climb or descent and adjust the default value sign accordingly
                vsValue = this.vsEngageModeDefaultValue;
                if(SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet") < SimVar.GetSimVarValue("INDICATED ALTITUDE", "feet"))
                    vsValue = -vsValue;
            }
            setTimeout(()=>{
                SimVar.SetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute", vsValue);
            }, 500);
            // Send the K event. The sim is doing the job behind.
            SimVar.SetSimVarValue("K:AP_PANEL_VS_ON", "number", 0);
        }
        else if (!enable && this.verticalMode == verticalMode.vs){
            // Send the K event. The sim is doing the job behind.
            SimVar.SetSimVarValue("K:AP_PANEL_VS_OFF", "number", 0);
        }
    }

    // Check and update the combined level mode
    // this mode is a combination of wing leveler on (0 bank angle) and VS mode with VS = 0
    checkUpdateLevelMode() {
        if(this.clvlMode) {
            if(!SimVar.GetSimVarValue("AUTOPILOT WING LEVELER", "Bool") || !SimVar.GetSimVarValue("AUTOPILOT VERTICAL HOLD", "Bool")) {
                SimVar.SetSimVarValue("K:AP_WING_LEVELER_ON", "number", 0);
                SimVar.SetSimVarValue("K:AP_PANEL_VS_ON", "number", 0);
                setTimeout(()=>{
                    SimVar.SetSimVarValue("K:AP_VS_VAR_SET_ENGLISH", "feet/minute", 0);
                }, 200);
            }
            else if(SimVar.GetSimVarValue("VERTICAL SPEED", "feet per second")*60 != 0)
                SimVar.SetSimVarValue("K:AP_VS_VAR_SET_ENGLISH", "feet/minute", 0);
        }
    }

    // Disable any combined mode (CLVL, CGA)
    disableAnyCombinedMode() {
        if(this.cgaMode || this.clvlMode) {
            SimVar.SetSimVarValue("K:AP_PANEL_VS_OFF", "number", 0);
            SimVar.SetSimVarValue("K:AP_WING_LEVELER_OFF", "number", 0);
        }
        this.cgaMode = false;
        this.clvlMode = false;
    }

    // Set optional LVars for reporting the gauge state
    setLvars() {
        // Lvars are used to report the autopilot gauge state for display purpose

        // Modes
        this.setLvar("ALT_SEL_MODE", this.altPreselMode, "number");
        this.setLvar("HIGHT_ALT_PRECISION", this.highAltPrecision);

        // AP, FD and YD states
        this.setLvar("AP", this.isAutopilotEngaged());
        this.setLvar("FD", this.isFlightDirectorEngaged());
        this.setLvar("YD", SimVar.GetSimVarValue("AUTOPILOT YAW DAMPER", "Bool"));

        // Buttons state
        this.setLvar("AP_BUTTON_STATE", this.isAutopilotEngaged());
        this.setLvar("FD_BUTTON_STATE", this.isFlightDirectorEngaged());
        this.setLvar("YD_BUTTON_STATE", SimVar.GetSimVarValue("AUTOPILOT YAW DAMPER", "Bool"));
        this.setLvar("GA_BUTTON_STATE", this.cgaMode);
        this.setLvar("LVL_BUTTON_STATE", this.clvlMode);
        this.setLvar("VNAV_BUTTON_STATE", this.hasVnav && this.isAutopilotEngaged() && SimVar.GetSimVarValue("L:PMS50_AUTOPILOT_VNAV_STATE", "number") == 2);
        this.setLvar("HDG_BUTTON_STATE", this.lateralMode == lateralMode.hdg);
        this.setLvar("NAV_BUTTON_STATE", this.lateralMode == lateralMode.gps || this.lateralMode == lateralMode.vor || this.lateralMode == lateralMode.loc || this.armedLateralMode == lateralMode.gps || this.armedLateralMode == lateralMode.vor || this.armedLateralMode == lateralMode.loc);
        this.setLvar("APR_BUTTON_STATE", this.lateralMode == lateralMode.aprgps || this.lateralMode == lateralMode.aprvor || this.lateralMode == lateralMode.aprloc || this.armedLateralMode == lateralMode.aprgps || this.armedLateralMode == lateralMode.aprvor || this.armedLateralMode == lateralMode.aprloc);
        this.setLvar("REV_BUTTON_STATE", this.lateralMode == lateralMode.bc);
        this.setLvar("ALT_BUTTON_STATE", this.verticalMode == verticalMode.alt);
        this.setLvar("VS_BUTTON_STATE", this.verticalMode == verticalMode.vs && !this.cgaMode && !this.clvlMode);
        this.setLvar("FLC_BUTTON_STATE", this.verticalMode == verticalMode.flc);
        let armButtonState = this.altPreselMode == altPreselMode.manual && this.internalArmed;
        this.setLvar("ARM_BUTTON_STATE", this.altPreselMode == altPreselMode.manual && (this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc) ? armButtonState : false);

        // Can arm
        this.setLvar("CAN_ARM", (this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.manualArmDefaultVerticalMode != verticalMode.none) && !this.clvlMode && this.altPreselMode == altPreselMode.manual);

        // Lateral and vertical modes
        this.setLvar("ACTIVE_LATERAL_MODE", this.armedCombinedMode ? 0 : this.lateralMode, "number");
        this.setLvar("ACTIVE_VERTICAL_MODE", this.armedCombinedMode ? 0 : this.verticalMode, "number");
        this.setLvar("ARMED_LATERAL_MODE", this.armedCombinedMode ? 0 : this.armedLateralMode, "number");
        this.setLvar("ARMED_VERTICAL_MODE", this.armedCombinedMode ? 0 : this.armedVerticalMode, "number");
        this.setLvar("ARMED_ALTITUDE_MODE", this.armedCombinedMode ? 0 : this.armedAltitudeMode, "number");
        this.setLvar("ARMED_APPROACH_MODE", this.armedCombinedMode ? 0 : this.armedApproachMode, "number");

        // Up down button validity
        this.setLvar("UP_DOWN_BUTTON_VALID", (this.verticalMode == verticalMode.alt && this.altModeUpDownIncrementValue > 0) || this.verticalMode == verticalMode.vs || this.verticalMode == verticalMode.flc || this.verticalMode == verticalMode.pitch);

        // Selected altitude, captured altitude, selected VS and selected airspeed
        // Sim value if auto arm and internal value for manual arm
        this.setLvar("SELECTED_ALTITUDE", this.getAltitudeWithPrecision(!this.WTTManageManualArming && this.altPreselMode == altPreselMode.manual ? this.internalSelectedAltitude : SimVar.GetSimVarValue("AUTOPILOT ALTITUDE LOCK VAR", "feet")), "feet");
        if(this.WTTManageManualArming)
            this.setLvar("CAPTURED_ALTITUDE_VALID", this.capturedAltitude >= 0);
        else
            this.setLvar("CAPTURED_ALTITUDE_VALID", this.capturedAltitude >= 0 && this.capturedAltitude < this.unarmedAltituteLockValue);
        this.setLvar("CAPTURED_ALTITUDE", this.capturedAltitude >= 0 ? Math.round(this.capturedAltitude) : 0, "feet");
        this.setLvar("SELECTED_VS", Math.round(SimVar.GetSimVarValue("AUTOPILOT VERTICAL HOLD VAR", "feet/minute")), "feet/minute");
        this.setLvar("SELECTED_AIRSPEED", Math.round(SimVar.GetSimVarValue("AUTOPILOT AIRSPEED HOLD VAR", "knots")), "knots");

        // Altitude phase
        this.setLvar("SELECTED_ALTITUDE_PHASE", this.selectedAltitudePhase, "number");

        // Trim indicator
        let needTrimValue = SimVar.GetSimVarValue("VELOCITY WORLD Y", "feet per minute") - SimVar.GetSimVarValue("VELOCITY WORLD Y", "feet per minute");
        let neededTrimIndicator = (needTrimValue < -100 || needTrimValue > 100) && this.verticalMode != verticalMode.none && this.verticalMode != verticalMode.pitch;
        this.setLvar("NEED_TRIM_INDICATOR", neededTrimIndicator);
        this.setLvar("NEED_TRIM_UP_INDICATOR", neededTrimIndicator && needTrimValue < -100);
        this.setLvar("NEED_TRIM_DOWN_INDICATOR", neededTrimIndicator && needTrimValue > 100);
        this.setLvar("NEED_TRIM_VALUE", needTrimValue, "feet per minute");
    }

    // Set a single Lvar
    setLvar(name, value, type = "boolean") {
        // We don't publish the var if the value is the same as before
        if(!this.lvarValues[name] || this.lvarValues[name] != value) {
            SimVar.SetSimVarValue("L:PMS50_APGA_" + name, type, value);
            this.lvarValues[name] = value;
        }
    }

    // Get a single Lvar
    getLvar(name, type = "boolean") {
        return SimVar.GetSimVarValue("L:PMS50_APGA_" + name, type);
    }
}
