/**
 * Class definitions for Analogue page MVC.
 * 
 * @category Webserver
 * @package ATProcessor
 * @author Sam Meredith <sam@aandtinstruments.com>
 * @version 0.1
 * 
 */

// Require interface defs
let interfacesLoaded = $.getScript('/js/atp.interfaces.js');

/**
 * Default values for analogue inputs
 **/
linearTypes = {
    "": { // Default
        'function' : "",
        'minimumvalue': 0.0,
		'maximumvalue': 5
    },
    0: { // User Defined
        'function' : "0",
		'minimumvalue': 0.0,
		'maximumvalue': 5.0
	},
    1: { // Rudder
        'function' : "1",
		'minimumvalue': -50.0,
		'maximumvalue': 50.0
	},
    2: { // Mast Rotation
        'function' : "2",
		'minimumvalue': -55.0,
		'maximumvalue': 55.0
	},
    3: { // Heel Angle
        'function' : "3",
		'minimumvalue': -45.0,
		'maximumvalue': 45.0
	},
    4: { // Trim
        'function' : "4",
		'minimumvalue': -60.0,
		'maximumvalue': 60.0
	},
    5: { // Air Temp
        'function' : "5",
		'minimumvalue': -40.0,
		'maximumvalue': 80.0
	},
    6: { // Sea Temp
        'function' : "6",
		'minimumvalue': -5.0,
		'maximumvalue': 40.0
    },
    7: { // MOB
        'function' : "7",
        'minimumvalue': 0.0,
		'maximumvalue': 5.0
    },
    8: { // Keel Angle
        'function' : "8",
        'minimumvalue': -45.0,
		'maximumvalue': 45.0
    },
    9: { // Canard Angle
        'function' : "9",
        'minimumvalue': -45.0,
		'maximumvalue': 45.0
    },
    10: { // Daggerboard Position
        'function' : "10",
        'minimumvalue': 0.0,
		'maximumvalue': 4.0
    },
    11: { // Boom Position
        'function' : "11",
        'minimumvalue': -30.0,
		'maximumvalue': 30.0
    }
}

// Channel type constants
let cAnalogueTypes = 
        '<option value="" selected>Select Function</option>'
    +   '<option value="0">User Defined</option>'
    +   '<option value="1">Rudder</option>'
    +   '<option value="2">Mast Rotation</option>'
    +   '<option value="3">Heel Angle</option>'
    +   '<option value="4">Trim</option>'
    +   '<option value="5">Air Temp</option>'
    +   '<option value="6">Sea Temp</option>'
    +   '<option value="8">Keel Angle</option>'
    +   '<option value="9">Canard Angle</option>'
    +   '<option value="10">Daggerboard Position</option>'
    +   '<option value="11">Boom Position</option>';

let cMainInterfaceTypes = cAnalogueTypes
    +   '<option value="7">Man-Overboard</option>';

 /**
 * Defines analogue configurations
 */
class AnalogueModel extends ObservableModel {
    constructor() {
        super();

        this.sources = [];
    }

    init() {
        let self = this;

        $.when(readSection("Boards"), getServerMillis(), readSingle("Hardware", "SupportsExtendedAnalogue"),
            interfacesLoaded).done(function(boardJson, milliseconds, isSupported) {

            let async = [];
            let boards = JSON.parse(boardJson[0]);
            let interfaceFactory = new InterfaceFactory();
            let millis = JSON.parse(milliseconds[0]);

            try {
                self.supportsExtendedAnalogue = JSON.parse(isSupported[0]);
            } catch(e) {
                console.log(e);
                self.supportsExtendedAnalogue = false;
            }

            // Create an interface for each active board
            $.each(boards, function(board, timestamp) {
                if (validateTimestamp(timestamp) && (millis - timestamp) < maxInactiveTime_ms) {
                    async.push(interfaceFactory.getInterface(board).done(function(concreteInterface) {
                        self.sources.push(concreteInterface);
                    }));
                }
            });

            $.when.apply($, async).then(function() {
                // Main Interface Board always first, otherwise sort alphabetically
                self.sources.sort(function(a, b) {
                    if (a.interfaceType == eInterfaceType.MainInterface && b.interfaceType != eInterfaceType.MainInterface)
                        return -1;
                    else if (a.interfaceType != eInterfaceType.MainInterface && b.interfaceType == eInterfaceType.MainInterface)
                        return 1;
                    else return a.internalName > b.internalName;
                });

                self.notifyAll();
            });
        });
    }

    persistData() {
        var async = [];
        var deferred = new $.Deferred();
        // Have each analogue channel write its own configuration to the database
        this.sources.forEach(function(source) {
            async.push(source.persistChannelData('Analogue'));
        });

        $.when.apply($, async).then(function() {
            deferred.resolve();
        })
        
        return deferred.promise();
    }

    setName(internalName, name) {
        this.sources.forEach(function(source) {
            source.setName(internalName, name);
        })
    }

    setProperty(internalName, key, value) {
        this.sources.forEach(function(source) {
            source.setProperty(internalName, key, value);
        })
    }
}

/**
 * View -> Model interaction for Analogue page
 */
class AnalogueController extends BasicController {
    constructor(model) {
        super(model);
    }

    changeChannelName(e) {
        let board= $(e.target).closest('fieldset').attr('data-board');
        let channel = $(e.target).closest('.analogue-channel').attr('data-channel');

        let internalName = board + ":ch" + channel;
        
        this.model.setProperty(internalName, "name", $(e.target).val());
    }

    changeDisplayName(e) {
        let board= $(e.target).closest('fieldset').attr('data-board');

        this.model.setName(board, $(e.target).val());
    }

    changeMaxValue(e) {
        let internalName = $(e.target).closest('fieldset').attr('data-board');

        validateNumericInput( $(e.target), 2);

        this.model.setProperty(internalName, "maximumvalue", $(e.target).val());
    }

    changeMaxVoltage(e) {
        let internalName = $(e.target).closest('fieldset').attr('data-board');

        validateNumericInput( $(e.target), 2);

        this.model.setProperty(internalName, "maximumvoltage", $(e.target).val());
    }

    changeMinValue(e) {
        let internalName = $(e.target).closest('fieldset').attr('data-board');

        validateNumericInput( $(e.target), 2);

        this.model.setProperty(internalName, "minimumvalue", $(e.target).val());
    }

    changeMinVoltage(e) {
        let internalName = $(e.target).closest('fieldset').attr('data-board');

        validateNumericInput( $(e.target), 2);

        this.model.setProperty(internalName, "minimumvoltage", $(e.target).val());
    }

    changeReference(e) {
        let internalName = $(e.target).closest('fieldset').attr('data-board');

        this.model.setProperty(internalName, "referencevoltage", $(e.target).val());
    }

    changeTempCoeff(e) {
        let internalName = $(e.target).closest('fieldset').attr('data-board');

        validateNumericInput( $(e.target), 0);
        
        this.model.setProperty(internalName, "tempcoeff", $(e.target).val());
    }

    changeType(e) {
        let analogueType = parseInt($(e.target).find('option:selected').val());
        let board= $(e.target).closest('fieldset').attr('data-board');
        let channel = $(e.target).closest('.analogue-channel').attr('data-channel');

        let internalName = board + ":ch" + channel;

        // Default analogue type
        if (isNaN(analogueType)) analogueType = "";

        // If this channel is being used as the system Heel/Trim source - warn the user
        if (internalName == this.model.heelSource || internalName == this.model.trimSource) {
            let reverted = false;

            $('#dialog-text').text('This channel is set as the external heel/trim source. Changing this channel will cause \
                the heel calculation to stop working. Do you wish to continue?')
            $('#dialog').dialog( {
                title: "Warning: Heel/Trim analogue in use.",
                buttons: {
                    Ok: function() {
                        $('#dialog').dialog('close');
                    },
                    Cancel: function() {
                        // Revert to previous analogue function
                        reverted = true;
                        let currentFunction = $(e.target).attr('data-current');
                        $(e.target).find('option[value="' + currentFunction + '"]').prop('selected', true);
                        $('#dialog').dialog('close');
                    }
                }
            }).dialog('open');

            if (reverted) return;
        }

        this.model.sources.forEach(function(source) {
            source.setData(internalName, linearTypes[analogueType]);
        });

        // If the channel name is one of the defaults, change it
        let channelNameInput = $(e.target).closest('.analogue-channel').find('[name="channel-name"]');
        if (Object.keys(eAnalogueType).includes(channelNameInput.val())) {
            let name = Object.keys(eAnalogueType).find(x => eAnalogueType[x] === analogueType);
            channelNameInput.val(name);
            this.model.setProperty(internalName, "name", name);
        }

        // If the type is MOB - hide the advanced button
        if (analogueType === eAnalogueType["Man-Overboard"])
        {
            $(e.target).closest('.analogue-channel').find('.button-advanced').hide();
        } 
        else 
        {
            $(e.target).closest('.analogue-channel').find('.button-advanced').show();
        }

        // Store function as an attribute in case we want to revert
        $(e.target).attr('data-current', analogueType);
    }

    copyMaximumValue(e) {
        let liveValue = parseFloat($('#live-value').text());
        
        if (!isNaN(liveValue))
            $('#maximum-value').val(liveValue).change();
    }

    copyMaximumVoltage(e) {
        let liveValue = parseFloat($('#live-voltage').text());
        
        if (!isNaN(liveValue))
            $('#maximum-voltage').val(liveValue).change();
    }

    copyMinimumValue(e) {
        let liveValue = parseFloat($('#live-value').text());
        
        if (!isNaN(liveValue))
            $('#minimum-value').val(liveValue).change();
    }

    copyMinimumVoltage(e) {
        let liveValue = parseFloat($('#live-voltage').text());

        if (!isNaN(liveValue))
            $('#minimum-voltage').val(liveValue).change();
    }

    // Get the configuration for a specific analogue channel
    getConfig(board, channel) {
        let boardData = this.model.sources.find(x => x.internalName == board);
        
        return boardData.analogueInputs.find(x => x.channel == channel);
    }

    toggleEnable(e) 
    {
        let board= $(e.target).closest('fieldset').attr('data-board');
        let channel = $(e.target).closest('.analogue-channel').attr('data-channel');

        let internalName = board + ":ch" + channel;

        this.model.setProperty(internalName, "enabled", $(e.target).is(':checked'));
    }
}

/**
 * Handle display for Analogue page
 */
class AnalogueView extends BasicView {
    constructor(controller) {
        super(controller);

        this.controller.model.init();
    }

    bindEvents() {
        super.bindEvents();

        $('#analogue-container').on('click', '.button-advanced', this.drawModal.bind(this));
        $('#analogue-container').on('change', '[name="channel-enable"]', this.controller.toggleEnable.bind(this.controller));
        $('#analogue-container').on('change', '[name="channel-name"]', this.controller.changeChannelName.bind(this.controller));
        $('#analogue-container').on('change', '[name="type-select"]', this.controller.changeType.bind(this.controller));
        $('#analogue-container').on('change', '[name="display-name"]', this.controller.changeDisplayName.bind(this.controller));

        $('#analogue-modal').on('change', '#temperature-coeff', this.controller.changeTempCoeff.bind(this.controller));
        $('#analogue-modal').on('change', '#minimum-voltage', this.controller.changeMinVoltage.bind(this.controller));
        $('#analogue-modal').on('change', '#maximum-voltage', this.controller.changeMaxVoltage.bind(this.controller));
        $('#analogue-modal').on('change', '#minimum-value', this.controller.changeMinValue.bind(this.controller));
        $('#analogue-modal').on('change', '#maximum-value', this.controller.changeMaxValue.bind(this.controller));
        $('#analogue-modal').on('change', '[name="reference-voltage"]', this.controller.changeReference.bind(this.controller));

        $('#analogue-modal').on('click', '#set-maximum-value', this.controller.copyMaximumValue);
        $('#analogue-modal').on('click', '#set-maximum-voltage', this.controller.copyMaximumVoltage);
        $('#analogue-modal').on('click', '#set-minimum-value', this.controller.copyMinimumValue);
        $('#analogue-modal').on('click', '#set-minimum-voltage', this.controller.copyMinimumVoltage);
    }

    drawAnalogue(board) {
        // Draw outer container
        if (board.analogueInputs !== undefined) {

            let boardNameLegend = board.internalName;
            
            // Default DLA name comes out as Dla2 Board
            // Neither 'capitalize' or 'uppercase' do the right thing, so we handle this case explicitly
            if (board.interfaceType == eInterfaceType.DLA2)
                boardNameLegend = boardNameLegend.toLowerCase().replace("dla2 board", "DLA2 Board");

            let container = $(
                    '<div class="box top-wrapper">'
                +       '<fieldset data-board="' + board.internalName + '">'
                +           '<legend>'
                +               '<h2>' + boardNameLegend + ' - </h2>'
                +               '<input type="text" name="display-name" value="' + board.displayName + '">'
                +           '</legend>'
                +       '</fieldset>'
                +   '</div>'
            );
            // Draw each analogue channel
            let analogueTypes = (board.interfaceType == eInterfaceType.MainInterface) ? cMainInterfaceTypes :
                cAnalogueTypes;

            $.each(board.analogueInputs, function(idx, val) {
                let row = $(
                        '<div class="analogue-channel" data-channel="' + val.channel + '">'
                    +       '<div>Channel ' + val.channel + ':</div>'
                    +       '<input type="text" name="channel-name" value="' + val.data.name + '">'
                    +       '<select name="type-select">' + analogueTypes + '</select>'
                    +       '<div>'
                    +           '<a class="button button-gray button-medium button-advanced" '
                    +               '>Advanced</a>'
                    +       '</div>'
                    +       '<div>'
                    +           '<input type="checkbox" ' + (val.data.enabled ? "checked" : "") + ' name="channel-enable">'
                    +           '<label>Enable</label>'
                    +   '</div>'
                );
                // Set the analogue type
                row.find('select option[value="' + val.data.function + '"]').prop('selected', true);
                row.find('select').attr('data-current', val.data.function);
                container.find('fieldset').append(row);
            });

            $('#analogue-container').append(container);
        }
    }

    // Populate the configuration modal
    drawModal(e) 
    {
        let board = $(e.target).closest('fieldset').attr('data-board');
        let channel = $(e.target).closest('.analogue-channel').attr('data-channel');
        let internalName = board + ':ch' + channel;

        let config = this.controller.getConfig(board, channel);

        let analogueType = $(e.target).closest('.analogue-channel').find('select option:selected').val();
        let analogueConfig = $("<div class='box' id='config-box'></div>");

        let boardId = board.split(':')[0];
        let boardInstance = board.split(':')[1];
        let voltageField = "0x183" + channel;
        let valueField = "0x180" + channel; 

        $('#analogue-modal .modal-title h1').text(board + ':Ch' + channel);
        
        if (analogueType == eAnalogueType.AirTemp || analogueType == eAnalogueType.SeaTemp) {
            analogueConfig.append(
                    '<fieldset data-board="' + internalName + '">'
                +       '<legend><h2>Temperature Configuration</h2></legend>'
                +       '<div class="temperature-grid">'
                +           '<div class="tooltip-wrapper">'
                +               '<h2>Coefficient</h2>'
                +               '<div class="tooltip">'
                +                   '<p>'
                +                       'The temperature gradient/&beta;-value of the '
                +                       'connected temperature sensor. '
                +                       'Provided by the sensor manufacturer.'
                +                   '</p>'
                +               '</div>'
                +           '</div>'
                +           '<h2>Raw Temp.</h2>'
                +           '<p id="temperature-description">'
                +               'Temperature offets, data sources etc. can be configured '
                +               'on the <a href="/environmentVariables.php">Environment page</a>.'
                +           '</p>'
                +           '<div>'
                +               '<input type="number" id="temperature-coeff" value=' + config.data.tempcoeff + '>'
                +           '</div>'
                +           '<div class="flex-field">'
                +               '<div class="field-container" data-field="' + valueField + '" id="temperature-field" '
                +                   'data-sender="' + boardId + '" data-instance="' + boardInstance + '">'
                +                   '&nbsp;'
                +               '</div>'
                +           '</div>'
                +       '</div>' 
                +   '</fieldset>'
            );
        } else if (analogueType == eAnalogueType["Man-Overboard"]) {
            // There is no modal for Man-Overboard
        } else {
            let containerTitle = (channel == 5 ? "Current Reference" : 
                "Voltage to Output Calibration");
            let columnTitle = (channel == 5 ? "mA" : "V");

            analogueConfig.append(
                    '<fieldset data-board="' + internalName + '">'
                +       '<legend><h2>' + containerTitle + '</h2></legend>'
                +       '<div class="voltage-grid">'
                +           '<h2>' + columnTitle + '<sub>1</sub></h2>'
                +           '<h2>Live</h2>'
                +           '<h2>' + columnTitle + '<sub>2</sub></h2>'
                +           '<div class="input-unit">'
                +               '<input type="number" id="minimum-voltage" step=0.01 ' 
                +                   'value=' + config.data.minimumvoltage + '>'
                +               '<span>' + columnTitle + '</span>'
                +           '</div>'
                +           '<div>'
                +               '<a class="button button-gray button-small" id="set-minimum-voltage" '
                +                   'title="Copy live voltage">&lt;</a>'
                +           '</div>'
                +           '<div class="flex-field">'
                +               '<div class="field-container" data-field="' + voltageField + '" id="live-voltage" '
                +                   'data-sender="' + boardId + '" data-instance="' + boardInstance + '">'
                +                   '&nbsp;'
                +               '</div>'
                +           '</div>'
                +           '<div>'
                +               '<a class="button button-gray button-small" id="set-maximum-voltage" '
                +                   'title="Copy live voltage">&gt;</a>'
                +           '</div>'
                +           '<div class="input-unit">'
                +               '<input type="number" id="maximum-voltage"  step=0.01 '
                +                   'value=' + config.data.maximumvoltage + '>'
                +               '<span>' + columnTitle + '</span>'
                +           '</div>'
                +           '<input type="number" id="minimum-value"  step=0.01 '
                +               'value=' + config.data.minimumvalue + '>'
                +           '<div>'
                +               '<a class="button button-gray button-small" id="set-minimum-value" '
                +                   'title="Copy live value">&lt;</a>'
                +           '</div>'
                +           '<div class="flex-field">'
                +               '<div class="field-container" data-field="' + valueField + '" id="live-value" '
                +                   'data-sender="' + boardId + '" data-instance="' + boardInstance + '">'
                +                   '&nbsp;'
                +               '</div>'
                +           '</div>'
                +           '<div>'
                +               '<a class="button button-gray button-small" id="set-maximum-value" '
                +                   'title="Copy live value">&gt;</a>'
                +           '</div>'
                +           '<input type="number" id="maximum-value" step=0.01 '
                +               'value=' + config.data.maximumvalue + '>'
                +       '</div>' 
                +   '</fieldset>'
            );
            // Add voltage reference for voltage containers
            if (channel != 5 && this.controller.model.supportsExtendedAnalogue) {
                analogueConfig.append(
                        '<fieldset id="reference-container" data-board="' + internalName + '">'
                    +       '<legend><h2>Reference</h2></legend>'
                    +       '<div class="reference-grid">'
                    +           '<div></div><div></div>'
                    +           '<input type="radio" name="reference-voltage" id="reference-5v" value="5V" '
                    +               (config.data.referencevoltage == "5V" ? "checked" : "") + '>'
                    +           '<div class="tooltip-wrapper">'
                    +               '<h2>0-5V</h2>'
                    +               '<div class="tooltip" tooltip-position="left">'
                    +                   '<p>'
                    +                       '0-5 Volt Ratiometric input, referenced to internal 5 Volt output. '
                    +                       'Used for potentiometers etc.'
                    +                   '</p>'
                    +               '</div>'
                    +           '</div>'
                    +           '<input type="radio" name="reference-voltage" id="reference-12v" value="12V" '
                    +                (config.data.referencevoltage == "12V" ? "checked" : "") + '>'
                    +           '<div class="tooltip-wrapper">'
                    +               '<h2>0-16V</h2>'
                    +               '<div class="tooltip" tooltip-position="left">'
                    +                   '<p>'
                    +                       '0-16 Volt Absolute input, using internal precision reference.'
                    +                   '</p>'
                    +               '</div>'
                    +           '</div>'
                    +       '</div>'
                    +   '</fieldset>'
                );
            }
        }
        
        $('#analogue-modal form').empty();
        $('#analogue-modal form').prepend(analogueConfig);

        $(document).trigger('fields-changed');
    }

    update(model) {
        let self = this;

        // Populate boards
        $('#analogue-container').empty();

        if (model.sources.length > 0)
        {
            $.each(model.sources, function(idx, val) {
                // Draw all boards with analogue inputs
                if (val.interfaceType == eInterfaceType.MainInterface ||
                    val.interfaceType == eInterfaceType.Analogue ||
                    val.interfaceType == eInterfaceType.DLA2) {
    
                    self.drawAnalogue(val);
                }            
            });
        } 
        else
        {
            $('#analogue-container').append('<div class="box top-wrapper loading-box"><div>No Analogue Interfaces Detected</div></div>');
        }
        
    }
}
