class AlarmModel extends ObservableModel {
    constructor() {
        super();

        this.alarms = new Object();
        this.alarmHistory = new Array();
        this.databaseCategory = "Alarms";

        this.storedLatObj = { value: "" };
        this.storedLongObj = { value: "" };
        this.manuallyChangedAnchorPos = false;
        this.justEnabledAnchorAlarm = false;
        this.justDisabledAnchorAlarm = false;

        this.init();
    }

    get currentAnchorLat() {
        return this.storedLatObj.value;
    }

    set currentAnchorLat(currentAnchorLat) {
        this.storedLatObj.value = currentAnchorLat;
    }

    get currentAnchorLong() {
        return this.storedLongObj.value;
    }

    set currentAnchorLong(currentAnchorLong) {
        this.storedLongObj.value = currentAnchorLong;
    }

    init() {
        var self = this;

        readSection("Alarms").done(function(alarmJSON) {
            var alarmSection = JSON.parse(alarmJSON);
            // Populate alarm list
            if (alarmSection.alarmlist !== undefined) {
                var alarmList = alarmSection.alarmlist.split(',');
                for (var i = 0; i < alarmList.length  - 2; i += 3) {
                    var alarmEnable = false;
                    var alarmLatch = false;
                    var alarmName = alarmList[i];
                    var alarmRelay = false;
                    var alarmValue = 0;
                    if (alarmSection[alarmName.toLowerCase()] !== undefined) {
                        var alarmValues = alarmSection[alarmName.toLowerCase()].split(',');
                        // Alarms|<AlarmName>|<Value>,<Enable>,<Relay>,<Latch>
                        if (alarmValues.length >= 4) {
                            alarmValue = alarmValues[0];
                            alarmEnable = (alarmValues[1] == 'true');
                            alarmRelay = (alarmValues[2] == 'true');
                            alarmLatch = (alarmValues[3] == 'true');
                        }
                    }

                    self.alarms[alarmList[i]] = {
                        enable: alarmEnable,
                        fieldId: alarmList[i+2],
                        latch: alarmLatch,
                        relay: alarmRelay,
                        units: alarmList[i+1],
                        value: alarmValue
                    };
                }
            }

            // Gets stored alarm anchor position
            if (alarmSection["alarmanchorpos"] !== undefined) {
                let latLong = alarmSection["alarmanchorpos"].split(";"); 

                self.currentAnchorLat = latLong[0];
                self.currentAnchorLong = latLong[1];
            }

            // Sort alarm list so that "Anchor alarm" is last
            self.alarms = Object.keys(self.alarms).sort(function(a, b) {
                // There should always be only one "Anchor Alarm" entry.                
                if (a == "Anchor Alarm")
                    return 1;

                if (b == "Anchor Alarm")
                    return -1;

                return 0;
            }).reduce(function(obj, key) {
                obj[key] = self.alarms[key];
                return obj;
            }, {});

            // Populate alarm history
            if (alarmSection.alarmhistory !== undefined) {
                var alarmHistory = alarmSection.alarmhistory.split(';');
                
                for (var i = 0; i < alarmHistory.length - 1; i += 2) {
                    var date = new Date(alarmHistory[i] * 1000);
                    self.alarmHistory.push(
                        { timestring: date.toLocaleString('default', { weekday: "short", year: "numeric",
                            month: "long", day: "numeric", hour12: false, hour: "numeric",
                            minute: "numeric", second: "numeric" }), alarmstring: alarmHistory[i+1] }
                    );
                }
            } 

            self.notifyAll();
        });

        setInterval(self.pollAlarmState.bind(self), 1000);
    }

    readAnchorPositionFromDatabase() {
        var deferred = new $.Deferred();
        let self = this;

        readSingle("Alarms", "AlarmAnchorPos").done(function(alarmsAnchorPos) {
            let latLong = alarmsAnchorPos.split(";"); 
            self.currentAnchorLat = latLong[0];
            self.currentAnchorLong = latLong[1];

            // Reading the anchor position from the database means 
            // that it has not been manually set by the user.
            self.manuallyChangedAnchorPos = false; 
            
            deferred.resolve();
        });

        return deferred.promise();
    }

    persistData() {
        var databaseArray = new Array();
        // Alarms|<AlarmName>|<Value>,<Enable>,<Relay>,<Latch>
        for (var property in this.alarms) {
            if (this.alarms.hasOwnProperty(property)) {
                var alarm = this.alarms[property];
                var valueString = "" + alarm.value + "," + alarm.enable + "," + alarm.relay + "," + alarm.latch;
                
                databaseArray.push(
                    { section: "Alarms", key: property, value: valueString }
                );
            }
        }

        let latLong = this.currentAnchorLat + ";" + this.currentAnchorLong; 
        databaseArray.push(
            { section: "Alarms", key: "AlarmAnchorPos", value: latLong }
        );
        
        return writeToDatabase(databaseArray, this.databaseCategory);
    }

    pollAlarmState() {
        var self = this;

        $.ajax( {
            url: 'atp.fields.php',
            type: 'POST',
            data: {
                'action': 'getAlarmState'
            },
            success: function(JSONresponse) {
                var response = JSON.parse(JSONresponse);
                
                if (response.status) {
                    var activeAlarms = response.response.split(',');
                    
                    for (var property in self.alarms) {
                        if (self.alarms.hasOwnProperty(property)) {
                            var oldActive = (self.alarms[property].active == true);
                            self.alarms[property].active = (activeAlarms.indexOf(property) >= 0);

                            if (oldActive !== self.alarms[property].active) {
                                self.notifyAll();
                            }
                        }
                    }
                }
            }
        });
    }
}

class AlarmController extends BasicController {
    constructor(model) {
        super(model);
    }

    changeCondition(e) {
        var index = $(e.target).parent().index();
        // Just using $(element).text() also returns the text of child nodes, this breaks on the anchor alarm tooltip
        // Filter out the child nodes to return ONLY the parent element text
        var alarmName = $('#alarm-grid').children().eq(index - 1).contents().filter(function() { return this.nodeType === 3 }).text();
        var alarmUnits = $(e.target).parent().find('span').attr('data-unit');
        
        switch (alarmUnits) {
            case 'angle':
                validateRange($(e.target), 1, -180, 180, 0);
                break;
            case 'direction':
                validateRange($(e.target), 1, 0, 360, 0);
                break;
            default:
                validateNumericInput($(e.target), 1);
                break;
        }

        this.model.alarms[alarmName].value = $(e.target).val();
    }

    resetAlarm(e) {
        $.ajax({
			url: 'helpers.php',
			type: 'POST',
			data: {
				'action': 'queryProcessor',
				'query': 'Alarm=Reset'
			}
		});
    }

    openDialog() {
        $('#dialog').dialog('open');
    }

    setDialogTitle(title) {
        $('#dialog').dialog({ title: title });
    }

    setDialogText(text) {
        $('#dialog-text').text(text);
    }    

    closeDialog() {
        $('#dialog').dialog('close');
    }

    setAnchorWatch(e) {
        let self = this;

        self.setDialogText('Processing request...');
        self.setDialogTitle('Set Anchor Position');
        self.openDialog();

        $.ajax( {
			url: 'helpers.php',
			type: 'POST',
			data: {
				'action': 'queryProcessor',
				'query': 'DatabaseUpdated=AnchorWatch'
			},
            timeout: 1500,  // Wait up to 1.5 seconds for a response
            success: function(data, textStatus, jqXHR) {
                setTimeout(function() {
                    self.model.readAnchorPositionFromDatabase().done(function() {
                        
                        // calls AlarmView.updateLatLongContainers(self.model, false)
                        let callback = e.data;
                        callback(self.model, false); 
        
                        self.closeDialog();
                    });                    
                }, 3000); // 3 second delay ensures the processor will have already stored the
                          // anchor position in the database before the webpage attempts to read it.

            },
            error: function(jqXHR, textStatus) {
                self.setDialogTitle('Error!');
                switch(textStatus) {
                    case "error": 
                        self.setDialogText('HTTP Request Failed!');
                        break;
                    case "timeout":
                        self.setDialogText('HTTP Request Timeout!');
                        break;
                }
                    
            }
		});
    }

    testAlarm(e) {
        if ($(e.target).text() == 'Test') {
			$.ajax( {
				url: 'helpers.php',
				type: 'POST',
				data: {
					'action': 'queryProcessor',
					'query': 'Alarm=Start&Type=' + Object.keys(this.model.alarms)[0]
				}
			});
			$(e.target).text('Stop');
		} else {
			$.ajax( {
				url: 'helpers.php',
				type: 'POST',
				data: {
					'action': 'queryProcessor',
					'query': 'Alarm=Stop&Type=' + Object.keys(this.model.alarms)[0]
				}
			});
			$(e.target).text('Test');
		}
    }

    toggleEnable(e) {
        var index = $(e.target).parent().index();
        // Just using $(element).text() also returns the text of child nodes, this breaks on the anchor alarm tooltip
        // Filter out the child nodes to return ONLY the parent element text
        var alarmName = $('#alarm-grid').children().eq(index - 4).contents().filter(function() { return this.nodeType === 3 }).text();
        
        if (alarmName == "Anchor Alarm") {
            let checkboxChecked = $(e.target).is(':checked');

            if (!this.model.alarms[alarmName].enable && checkboxChecked) {
                // Set flag if Anchor Alarm has just been enabled
                this.model.justEnabledAnchorAlarm = true;
            }
            else if (this.model.alarms[alarmName].enable && !checkboxChecked) {
                // Set flag if Anchor Alarm has just been disabled
                this.model.justDisabledAnchorAlarm = true;
            }

            this.model.alarms[alarmName].enable = checkboxChecked;
        }
        else
            this.model.alarms[alarmName].enable = $(e.target).is(':checked');
    }

    toggleLatch(e) {
        var index = $(e.target).parent().index();
        var alarmName = $('#alarm-grid').children().eq(index - 3).contents().filter(function() { return this.nodeType === 3 }).text();
        this.model.alarms[alarmName].latch = $(e.target).is(':checked');
    }

    toggleRelay(e) {
        var index = $(e.target).parent().index();
        var alarmName = $('#alarm-grid').children().eq(index - 2).contents().filter(function() { return this.nodeType === 3 }).text();
        this.model.alarms[alarmName].relay = $(e.target).is(':checked');
    }

    toggleAnchorPosInput(e) {
        $('#anchor-pos-input').slideToggle(100);
        $('#anchor-pos-input-toggle i').toggleClass('arrow-down arrow-up');
    }

    changedAnchorLatInput(e) {
        this.model.manuallyChangedAnchorPos = true;
        // Latitude validation not necessary, since it is done with inputmasks.
        // calls AlarmView.updateLatLongContainers(this.model, false)
        let callback = e.data;
        callback(this.model, false);
    }

    changedAnchorLongInput(e) {
        this.model.manuallyChangedAnchorPos = true;
        // Longitude validation not necessary, since it is done with inputmasks.
        // calls AlarmView.updateLatLongContainers(this.model, false)
        let callback = e.data;
        callback(this.model, false);
    }

    /**
     * @override
     * Override Apply button function to add extra logic for the Anchor Alarm.
     */
    clickApply(e) {
        super.clickApply(e);

        if (this.model.justEnabledAnchorAlarm) {
            // Show dialog and read database if user has just enabled Anchor Watch.
            this.model.justEnabledAnchorAlarm = false;   
            this.setDialogText('Processing request...');
            this.setDialogTitle('Set Anchor Position');
            this.openDialog();

            let self = this;
            setTimeout(function() {
                self.model.readAnchorPositionFromDatabase().done(function() {
                    // Assures the anchor location input text fields 
                    // will appear when "Anchor Alarm" is enabled               
                    self.model.notifyAll();

                    self.closeDialog();
                });                    
            }, 3000); // 3 second delay ensures the processor will have already stored the
                      // anchor position in the database before the webpage attempts to read it.
        }   
        else if (this.model.justDisabledAnchorAlarm) {
            // notifyAll() in case the user has just disabled Anchor Watch, 
            // so the anchor location input text fields can be removed. 
            this.model.justDisabledAnchorAlarm = false;
            this.model.notifyAll(); 
        }
    }
}

class AlarmView extends BasicView {
    constructor(controller) {
        super(controller);
    }

    bindEvents() {
        super.bindEvents();

        $('#alarm-grid').on('change', '[name="alarm-value"]', this.controller.changeCondition.bind(this.controller));
        $('#alarm-grid').on('change', '[name="alarm-relay"]', this.controller.toggleRelay.bind(this.controller));
        $('#alarm-grid').on('change', '[name="alarm-latch"]', this.controller.toggleLatch.bind(this.controller));
        $('#alarm-grid').on('change', '[name="alarm-enable"]', this.controller.toggleEnable.bind(this.controller));
        $('#alarm-grid').on('click', '#alarm-test', this.controller.testAlarm.bind(this.controller));

        $('#alarm-grid').on('click', '#alarm-set', this.updateLatLongContainers, this.controller.setAnchorWatch.bind(this.controller));
        
        $('#alarm-reset').on('click', this.controller.resetAlarm.bind(this.controller));

        $('#alarms').on('change', '#anchor-lat-degrees, #anchor-lat-minutes, #anchor-lat-direction' , this.updateLatLongContainers, this.controller.changedAnchorLatInput.bind(this.controller));
        $('#alarms').on('change', '#anchor-long-degrees, #anchor-long-minutes, #anchor-long-direction' , this.updateLatLongContainers, this.controller.changedAnchorLongInput.bind(this.controller));

        $('#alarms').on('click', '#anchor-pos-input-toggle' , this.controller.toggleAnchorPosInput.bind(this.controller));
    }

    reset() {
        $('#alarm-grid').children().slice(7).remove();
        $('#anchor-pos-setup').remove();
        $('#alarm-history').find('tr').slice(1).remove();
    }

    /**
     * Update latitude and longitude input text fields
     * @param model The model object
     * @param inputsEmpty Should be true when the inputs have just been added to the page; false, otherwise.
     */
    updateLatLongContainers(model, inputsEmpty) {
        
        // If "Anchor Alarm" is disabled, drawing the anchor position input fields is not necessary
        if (!model.alarms["Anchor Alarm"].enable)
            return;

        if (inputsEmpty) {
            $('#anchor-lat-degrees').inputmask({
                alias: "decimal",
                digits: 0,
                allowMinus: false,   
                allowPlus: false,     
                digitsOptional: false,
                autoUnmask: true,
                clearMaskOnLostFocus: false,
                min: "0",
                max: "90"
            });
    
            $('#anchor-long-degrees').inputmask({
                alias: "decimal",
                digits: 0,
                allowMinus: false,   
                allowPlus: false,     
                digitsOptional: false,
                autoUnmask: true,
                clearMaskOnLostFocus: false,
                min: "0",
                max: "180"
            });
    
            $('#anchor-lat-minutes, #anchor-long-minutes').inputmask({
                alias: "decimal",
                digits: 4,
                allowMinus: false,   
                allowPlus: false,     
                digitsOptional: false,
                autoUnmask: true,
                clearMaskOnLostFocus: false,
                max: "59.9999"
            });
        }
        
        // If position has not been manually changed or the inputs are empty, update the input text boxes.
        if (!model.manuallyChangedAnchorPos || inputsEmpty) {
            
            // Latitude
            let abs = Math.abs(model.currentAnchorLat);
            let deg = Math.floor(abs);
            let fraction = (abs - deg) * 60;
            let direction = model.currentAnchorLat >= 0 ? "N" : "S";
            
            $('#anchor-lat-degrees').val(deg);
            $('#anchor-lat-minutes').val(fraction);
            $('#anchor-lat-direction').val(direction == "N" ? "North" : "South").prop('selected', true);

            // Longitude
            abs = Math.abs(model.currentAnchorLong);
            deg = Math.floor(abs);
            fraction = (abs - deg) * 60;
            direction = model.currentAnchorLong >= 0 ? "E" : "W";

            $('#anchor-long-degrees').val(deg);
            $('#anchor-long-minutes').val(fraction);
            $('#anchor-long-direction').val(direction == "E" ? "East" : "West").prop('selected', true);
        }
        else {         
            // If position has been manually changed, update the position variables   
            model.currentAnchorLat = decimalMinutesToDecimalDegrees($('#anchor-lat-degrees').val(),  $('#anchor-lat-minutes').val(), $('#anchor-lat-direction').val() == "North" ? "N" : "S", "Latitude");
            model.currentAnchorLong = decimalMinutesToDecimalDegrees($('#anchor-long-degrees').val(),  $('#anchor-long-minutes').val(), $('#anchor-long-direction').val() == "East" ? "E" : "W", "Longitude");
        }
    }

    update(model) {
        this.reset();

        // Draw alarm grid
        var alarmCount = 0;
        var $container = $('#alarm-grid');
        
        for (var property in model.alarms) {
            if (model.alarms.hasOwnProperty(property)) {
                var alarm = model.alarms[property];
                var alarmUnits = "m";
                // Set correct units
                switch (alarm.units) {
                    case "knots":
                        alarmUnits = "kt";
                        break;
                    case "angle":
                    case "direction":
                        alarmUnits = "&deg;";
                        break;
                    case "volts":
                        alarmUnits = "V";
                        break;
                    case "mb":
                        alarmUnits = "mb";
                        break;
                    case "metres":
                    default:
                        alarmUnits = "m";
                        break;
                }
                // Construct alarm row
                switch (property) {
                    case "Anchor Alarm":
                        $container.append(
                                "<div class='tooltip-wrapper " + (alarm.active ? 'alarm-active' : '') + "'>"
                            +       property
                            +       "<div class='tooltip'>"
                            +           "<p> Set or reset anchor drop point using the 'Set' button, "
                            +               "then adjust the alarm condition (distance from the drop point) as normal."
                            +           "</p>"
                            +       "</div>"
                            +   "</div>"
                            +   "<div class='input-unit'>"
                            +       "<input name='alarm-value' type='number' step=0.1 value='" + alarm.value + "'>"
                            +       "<span data-unit='" + alarm.units + "'>m</span>"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-relay' type='checkbox' " + (alarm.relay ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-latch' type='checkbox' " + (alarm.latch ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-enable' type='checkbox' " + (alarm.enable ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div class='flex-field'>"
                            +       "<div class='field-container' id='alarm-" + alarmCount++ + "' data-field='" + alarm.fieldId + "'"
                            +       "data-sender='ATProcessor' data-instance='0'>&nbsp;</div>"
                            +   "</div>"
                            +   "<div id='anchor-pos-set-button'>"
                            +       "<a class='button button-gray button-medium' id='alarm-set'>Set</a>"
                            +   "</div>"
                        );
                        
                        // Do not add Anchor Location input texts if Anchor Alarm is disabled. Break early.
                        if (!model.alarms["Anchor Alarm"].enable)
                            break;

                        $container.after(
                            // Anchor position
                                "<div id='anchor-pos-setup'>"
                            +       "<div class='dropdown-container'>"
                            +           "<a id='anchor-pos-input-toggle' class='button button-gray button-very-small'>"
                            +               "<i class='arrow arrow-down'></i>"
                            +           "</a>"
                            +       "</div>"
                            +       "<div id='anchor-pos-input' class='flex-field-row box box-stack'>"
                            +           "<div class='tooltip-wrapper'>"
                            +               "<div id='anchor-location-label'>Anchor Location:</div>"
                            +               "<div class='tooltip'>"
                            +                   "<p> The current anchor position is shown in the fields aside. It can be manually adjusted by changing the coordinates and pressing the 'Apply' button."
                            +                       "<br><br>Note: Pressing the 'Set' button will automatically revert the anchor coordinates to the current GNSS position."
                            +                   "</p>"
                            +               "</div>"
                            +           "</div>"
                            +           "<div style='display:flex'>"
                            +               "<input type='text' id='anchor-lat-degrees' class='position-input-degress' value=''>"
                            +               "<input type='text' id='anchor-lat-minutes' class='position-input-minutes' step='0.001' value=''>"
                            +               "<select id='anchor-lat-direction' class='position-input-direction'>"
                            +                   "<option value='North'>N</option>"
                            +                   "<option value='South'>S</option>"
                            +               "</select>"
                            +               "<input type='text' id='anchor-long-degrees' class='position-input-degress' value=''>"
                            +               "<input type='text' id='anchor-long-minutes' class='position-input-minutes' step='0.001' value=''>"
                            +               "<select id='anchor-long-direction' class='position-input-direction'>"
                            +                   "<option value='East'>E</option>"
                            +                   "<option value='West'>W</option>"
                            +               "</select>"
                            +           "</div>"
                            +       "</div>"
                            +   "</div>"
                        );

                        this.updateLatLongContainers(model, true);

                        // Hide input text fields by default.
                        $('#anchor-pos-input').hide();

                        break;

                    case "Fastnet Alarm":
                        $container.append(
                                "<div class='" + (alarm.active ? 'alarm-active' : '') + "'>"
                            +       property 
                            +   "</div>"
                            +   "<div></div>"
                            +   "<div>"
                            +       "<input name='alarm-relay' type='checkbox' " + (alarm.relay ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-latch' type='checkbox' " + (alarm.latch ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-enable' type='checkbox' " + (alarm.enable ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div class='flex-field'>"
                            +       "<div class='field-container' id='alarm-" + alarmCount++ + "' data-field='" + alarm.fieldId + "'"
                            +       "data-sender='ATProcessor' data-instance='0'>&nbsp;</div>"
                            +   "</div>"
                            +   "<div></div>"
                        );
                        break;

                    default:
                        $container.append(
                                "<div class='" + (alarm.active ? 'alarm-active' : '') + "'>"
                            +       property 
                            +   "</div>"
                            +   "<div class='input-unit'>"
                            +       "<input name='alarm-value' type='number' step=0.1 value='" + alarm.value + "'>"
                            +       "<span data-unit='" + alarm.units + "'>" + alarmUnits + "</span>"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-relay' type='checkbox' " + (alarm.relay ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-latch' type='checkbox' " + (alarm.latch ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div>"
                            +       "<input name='alarm-enable' type='checkbox' " + (alarm.enable ? "checked" : "") + ">"
                            +   "</div>"
                            +   "<div class='flex-field'>"
                            +       "<div class='field-container' id='alarm-" + alarmCount++ + "' data-field='" + alarm.fieldId + "'"
                            +       "data-sender='ATProcessor' data-instance='0'>&nbsp;</div>"
                            +   "</div>"
                            +   "<div></div>"
                        );
                        break;
                }
            }
        }
        $(document).trigger('fields-changed');

        // Draw alarm history
        for (var i = 0; i < model.alarmHistory.length; i++) {
            $('#alarm-history').append(
                    "<tr>"
                +       "<td>" + model.alarmHistory[i].timestring + "</td>"
                +       "<td>" + model.alarmHistory[i].alarmstring + "</td>" 
                +   "</tr>"
            );
        }
    }
}