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

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

/**
 * Defines available environmental sensor inputs
 */
class EnvironmentSensorModel extends ObservableModel {
    constructor() {
        super();

        this.airTempSource = null;
        this.seaTempSource = null;
        this.canbusSources = [];
        this.sources = [];
        this.nmeaAirTempSources = [];
        this.nmeaSeaTempSources = [];
    
        this.init();
    }

    // Air temp sources are analogue inputs set as 'Air Temp'
    // Return an array of analogue inputs
    get airTempSources() {
        let airTempSources = [];

        this.sources.forEach(function(value) {
            if (value.interfaceType == eInterfaceType.Analogue ||
                value.interfaceType == eInterfaceType.MainInterface) {
                // Check if any of the analogue inputs is an AirTemp input
                value.analogueInputs.forEach(function(analogueInput) {
                    if (analogueInput.analogueType == eAnalogueType.AirTemp) {
                        airTempSources.push(analogueInput);
                    }
                });
            }
        });

        return airTempSources;
    }

    // Sea temp sources are Speed/SpeedDepth boards or analogue inputs set as 'Sea Temp'
    get seaTempSources() {
        let seaTempSources = this.sources.filter(x => (x.interfaceType == eInterfaceType.Speed ||
            x.interfaceType == eInterfaceType.SpeedDepth));

        this.sources.forEach(function(value) {
            if (value.interfaceType == eInterfaceType.Analogue ||
                value.interfaceType == eInterfaceType.MainInterface) {
                // Check if any of the analogue inputs is an SeaTemp input
                value.analogueInputs.forEach(function(analogueInput) {
                    if (analogueInput.analogueType == eAnalogueType.SeaTemp) {
                        seaTempSources.push(analogueInput);
                    }
                });
            }
        });

        return seaTempSources;
    }

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

        $.when(readSection("Boards"), getServerMillis(), readSection("Environment"),
            readSingle("CanSource", "Environment"), readSection("NMEA"), interfacesLoaded).done(function(
                boardJson, milliseconds, environmentJson, canSource, NMEAJSON) {

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

            // 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);
                    }));
                }
            });
            // Add the CANbus devices
            if (canSource[0])
            {
                let canSources = canSource[0].split(';');
                for (let i = 0; i < canSources.length; i++)
                {
                    let sourceData = {};
                    sourceData.id = canSources[i];
                    async.push(readSection("CanDevices:" + sourceData.id).done(function(deviceJSON)
                    {
                        let device = JSON.parse(deviceJSON);
                        sourceData.address = device["address"];
                        sourceData.name = device["model"];
                        sourceData.serial = device["serialnumber"];
                        sourceData.displayName = sourceData.name + " [" + sourceData.serial + "]";
                        self.canbusSources.push(sourceData);
                    }));
                }
            }
            // Find any NMEA ports configured to receive temperature
            for (let key in nmea)
            {
                if (key.includes("mearxfilter"))
                {
                    let instance = key.slice(-1);
                    let sentences = nmea[key].split(',');
                    if (sentences.includes("MTA"))
                        self.nmeaAirTempSources.push(instance);
                    else if (sentences.includes("MTW"))
                        self.nmeaSeaTempSources.push(instance);
                    else if (sentences.includes("XDR-a") || sentences.includes("XDR-b"))
                    {
                        self.nmeaAirTempSources.push(instance);
                        self.nmeaSeaTempSources.push(instance);
                    }
                }
            }

            self.airTempSource = environment.airtempsource;
            self.seaTempSource = environment.seatempsource;

            $.when.apply($, async).then(function() {
                if (self.airTempSource == null || self.airTempSource == "") {
                    if (self.airTempSources.length > 0)
                        self.airTempSource = self.airTempSources[0].internalName;
                }
                if (self.seaTempSource == null || self.seaTempSource == "") {
                    if (self.seaTempSources.length > 0)
                        self.seaTempSource = self.seaTempSources[0].internalName;
                }

                self.notifyAll();
                deferred.resolve();
            });
        });

        return deferred.promise();
    }

    persistData() {
        var databaseArray = new Array();

        $.merge(this.seaTempSources, this.airTempSources).forEach(function(source) {
            databaseArray.push(
                { 'section': source.internalName, 'key': 'tempoffset', 'value': source.data.tempoffset }
            );
        });

        databaseArray.push(
            { 'section': 'Environment', 'key': 'AirTempSource', value: (this.airTempSource || "") }
        );
        databaseArray.push(
            { 'section': 'Environment', 'key': 'SeaTempSource', value: (this.seaTempSource || "") }
        );
        
        return writeToDatabase(databaseArray, 'Environment');
    }

    // Each source will check if it is the target of the update, then update if so
    setOffset(sensor, value) {
        this.sources.forEach(function(source) {
            source.setProperty(sensor, 'tempoffset', value);
        });
    }
}

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

    changeAirTempSource(e) {
        this.model.airTempSource = $(e.target).val();
    }

    changeOffset(e) {
        validateNumericInput($(e.target), 1);

        var sensor = $(e.target).attr('data-source');
        this.model.setOffset(sensor, $(e.target).val());
    }

    changeSeaTempSource(e) {
        this.model.seaTempSource = $(e.target).val();
    }
}

/**
 * Handle display for Environment Variables page
 */
class EnvironmentSensorView extends BasicView {
    constructor(controller) {
        super(controller);

        this.fieldDaemon.callback = this.temperatureCallback.bind(this);
    }

    bindEvents() {
        super.bindEvents();

        $('#air-temperature-container').on('change', '.sensor-grid .sensor-offset input', this.controller.changeOffset.bind(this.controller));
        $('#air-temperature-container').on('change', '.sensor-grid .sensor-select input', this.controller.changeAirTempSource.bind(this.controller));
        $('#sea-temperature-container').on('change', '.sensor-grid .sensor-offset input', this.controller.changeOffset.bind(this.controller));
        $('#sea-temperature-container').on('change', '.sensor-grid .sensor-select input', this.controller.changeSeaTempSource.bind(this.controller));
    }

    // Populate the air temperature grid with available air temperature sources
    drawAirTemp(model) {
        let self = this;
        let sources = model.airTempSources;

        if (sources.length > 0 || model.canbusSources.length > 0 || model.nmeaAirTempSources.length > 0) {
            let $grid = $('#air-temperature-container .sensor-grid');
            $grid.parent().find('.no-source-row').remove();

            self.drawTitleRow($grid);

            sources.forEach(function(sensor) {
                self.drawSensor($grid, sensor, 'air');
            });
            model.nmeaAirTempSources.forEach(function(instance) {
                self.drawNMEASensor($grid, instance, 'air');
            });
            model.canbusSources.forEach(function(canbusSource) {
                self.drawCanSensor($grid, canbusSource, 'air');
            });
            $grid.find('input[value="' + model.airTempSource + '"]').prop('checked', true);
        }
    }

    // Populate the sea temperature grid with available sea temperature sources
    drawSeaTemp(model) {
        let self = this;
        let sources = model.seaTempSources;
        
        if (sources.length > 0 || model.canbusSources.length > 0 || model.nmeaSeaTempSources.length > 0) {
            let $grid = $('#sea-temperature-container .sensor-grid');
            $grid.parent().find('.no-source-row').remove();

            self.drawTitleRow($grid);

            sources.forEach(function(sensor) {
                self.drawSensor($grid, sensor, 'sea');
            });
            model.nmeaSeaTempSources.forEach(function(instance) {
                self.drawNMEASensor($grid, instance, 'sea');
            });
            model.canbusSources.forEach(function(canbusSource) {
                self.drawCanSensor($grid, canbusSource, 'sea');
            });
            $grid.find('input[value="' + model.seaTempSource + '"]').prop('checked', true);
        }
    }

    drawCanSensor($grid, sensor, sensorType) {
        // Draw sensor name
        $grid.append('<div class="sensor-name">'
                        + '<div class="tooltip">' 
                        + '<p>Environment data source selected on CANbus page (' + sensor.serial + ')</p>'
                        + '</div>'
                        + '<h2>' + sensor.displayName + '</h2>'
                        + '</div>');
        // No Sensor Value or Offset for CANbus source
        $grid.append('<div></div><div></div>');
        // CANbus source uses Env_Temp_C field
        $grid.append('<div class="sensor-value-offset"><div class="field-container" title="Temperature value."'
            + 'data-field="' + (sensorType == 'air' ? "0x0700" : "0x0702") + '"'
            + 'data-sender="canbus" id="canbus-' + sensorType + '-' + sensor.id + '"'
            + 'data-instance="' + sensor.address + '">'
            + '&nbsp;</div></div>');
        // Input selection radio
        $grid.append('<div class="sensor-select">'
                +       '<input type="radio" name="' + sensorType + '-input-select" value="canbus:' + sensor.id + '">'
                +    '</div>'
        );
    }

    drawNMEASensor($grid, instance, sensorType) {
        $grid.append('<div class="sensor-name nmea-sensor">'
            + '<h2>NMEA Port ' + instance + '</h2>'
            + '</div>');
        // No Sensor Value or Offset for NMEA source
        $grid.append('<div></div><div></div>');
        // NMEA source uses Env_Temp_C field
        $grid.append('<div class="sensor-value-offset"><div class="field-container" title="Temperature value."'
            + 'data-field="' + (sensorType == 'air' ? "0x0700" : "0x0702") + '"'
            + 'data-sender="nmea0183" id="nmea-' + instance + '-' + sensorType + '"'
            + 'data-instance="' + instance + '">'
            + '&nbsp;</div></div>');
        // Input selection radio
        $grid.append('<div class="sensor-select">'
                +    '<input type="radio" name="' + sensorType + '-input-select" value="nmea0183:' + instance + '">'
                +    '</div>'
        );
    }

    // Add a given sensor to a given grid
    drawSensor($grid, sensor, sensorType) {

        // Construct unique id as <board initials><board number><channelnumber> e.g. mib0ch2
        var id = sensor.internalName.split(' ').reduce((response, word)=> response+=word.slice(0,1),'');
        id += sensor.internalName.split(':')[1] + (sensor.internalName.split(':')[2] ? sensor.internalName.split(':')[2] : '');

        // Draw sensor name
        if (sensor.displayName == 'Input Name' || sensor.displayName == '') {
            $grid.append('<div class="sensor-name"><h2>' + toTitleCase(sensor.internalName) + '</h2></div>');
        } else {
            $grid.append('<div class="sensor-name">'
                        + '<div class="tooltip">' 
                        + '<p>' + toTitleCase(sensor.internalName) + '</p>'
                        + '</div>'
                        + '<h2>' + sensor.displayName + '</h2>'
                        + '</div>');
        }

        // Draw raw sensor value field
        let rawDataField = "";
        if (sensor.hasOwnProperty('interfaceType'))
            rawDataField = "0x0210";
        else
            rawDataField = "0x180" + sensor.channel;

        $grid.append('<div class="sensor-value-raw"><div class="field-container" title="Raw sensor value."'
                        + 'data-field="' + rawDataField + '" id="' + id + 'raw"'
                        + 'data-sender="' + sensor.internalName.split(':')[0] + '"'
                        + 'data-instance="' + sensor.internalName.split(':')[1] + '">'
                        + '&nbsp;</div></div>');

        // Draw offset
        $grid.append('<div class="sensor-offset"><div class="input-group"><input step=0.1 type="number" data-source="' + sensor.internalName
                 + '" value="' + sensor.data.tempoffset + '"><span>&deg;</span></div>');

        // Draw offset sensor value field
        let calcDataField = "";
        if (sensor.hasOwnProperty('interfaceType'))
            calcDataField = "0x1702";
        else
            calcDataField = '0x' + (parseInt('0x1825', 16) + parseInt(sensor.channel)).toString(16);

        $grid.append('<div class="sensor-value-offset"><div class="field-container" title="Offset temperature value."'
                        + 'data-field="' + calcDataField + '"'
                        + 'data-sender="' + sensor.internalName.split(':')[0] + '" id="' + id + 'calc"'
                        + 'data-instance="' + sensor.internalName.split(':')[1] + '">'
                        + '&nbsp;</div></div>');

        // Input selection radio
        $grid.append('<div class="sensor-select">'
                +       '<input type="radio" name="' + sensorType + '-input-select" value="' + sensor.internalName + '">'
                +    '</div>'
        );
    }

    // Draw headings for grid
    drawTitleRow($grid) {
        $grid.append('<div></div>');
        $grid.append('<div class="sensor-heading"><h2>&nbsp;</h2></div>');
        $grid.append('<div class="sensor-heading"><h2>Sensor Value</h2></div>');
        $grid.append('<div class="sensor-heading"><h2>Offset</h2></div>');
        $grid.append('<div class="sensor-heading"><h2>Temperature</h2></div>');
        $grid.append(
                '<div class="sensor-heading tooltip-wrapper">'
            +       '<h2>Use</h2>'
            +       '<div class="tooltip" tooltip-position="left">'
            +           '<p>Use this input as the system default.</p>'
            +       '</div>'
            +   '</div>'
        );
    }

    // Append missing degree symbols to fields
    temperatureCallback($container, valueReference) {
        let fieldId = parseInt($container.attr('data-field'), 16);

        if (fieldId >= 6182 && fieldId <= 6186) {
            // Calculated Analogue fields
            if (!valueReference.valueString.endsWith('°C')) {
                let value = parseFloat(valueReference.valueString);
                if (!isNaN(value))
                    valueReference.valueString = value.toFixed(1) + '°C';
            }
        }
    }

    update(model) 
    {
        console.log(model);

        this.drawAirTemp(model);
        this.drawSeaTemp(model);
    }
}
