/**
 * Class definitions for Fastnet 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');

// Default Analogue Configuration
let cDefaultAnalogue = "0,,false,0";
//Default Depth Transducer configuration.
let cDefaultTransducer = "0,,false";
// Default Input Configuration
let cDefaultInput = {
    "gps" : "5,256,false",
    "heading" : "16,73,false",
    "rudder" : "0,11,false",
    "boatspeed": "1,65,false"
} 
// Default Output Configuration
let cDefaultOutput = {
    "compasstype" : "magnetic",
    "gps" : "5,,false",
    "heading" : "5,,false,1",
    "heeltrim": ",,true",
    "rudder" : "0,,false,0",
    "cog" : "5,,false,0",
    "depthsource0" : "Primary,,true",
    "sendmenus" : "1"
}

const N2KCyclopsLoadcellStationNames = [
    "Forestay",
    "Backstay",
    "Boom Vang",
    "Inner Forestay",
    "Inner Forestay Halyard",
    "Jib Halyard",
    "Outhaul",
    "Code Zero",
    "Bobstay",
    "J1",
    "J2",
    "J3",
    "Mast Base",
    "Mainsheet",
    "D0 Port",
    "D0 Starboard",
    "Runner Port",
    "Runner Starboard",
    "Foil Port",
    "Foil Starboard",
    "Sailtack Port",
    "Sailtack Starboard",
    "Deflect Port",
    "Deflect Starboard",
    "Rudder Port",
    "Rudder Starboard",
    "D1 Port",
    "D1 Starboard",
    "V0 Port",
    "V0 Starboard",
    "V1 Port",
    "V1 Starboard",
    "Reacher",
    "Blade",
    "Staysail",
    "Tack",
    "J4",
    "Solent",
    "Tack Port",
    "Tack Starboard",
    "Deflect Upper",
    "Deflect Lower",
    "Winch Port",
    "Winch Starboard",
    "Spin Halyard Port",
    "Spin Halyard Stbd",
    "Main Halyard",
    "Load 1",
    "Load 2",
    "Mast Base 2",
    "","","","","","","","","","",
    "Mainsail Head",
    "Mainsail Clew",
    "Mainsail Tack",
    "J1 Head",
    "J1 Clew",
    "J1 Tack",
    "J2 Head",
    "J2 Clew",
    "J2 Tack",
    "J3 Head",
    "J3 Clew",
    "J3 Tack",
    "Code Zero Head",
    "Code Zero Clew",
    "Code Zero Tack"
];

const n2kCyclopsLoadcellPrefix = "cyclops loadcells:";

 /**
 * Defines Fastnet I/O configuration
 */
class FastnetModel extends ObservableModel {
    constructor() {
        super();

        this.analogueChannels = [];
        this.loadcellChannels = [];
        this.loadcells = {};
        this.sources = [];
        this.primaryTransducer = [];
        this.transducers = [];
    }
    
    get analogueSources() {
        return this.sources.filter(x => x.interfaceType == eInterfaceType.MainInterface
            || x.interfaceType == eInterfaceType.Analogue || x.interfaceType == eInterfaceType.DLA2);
    }

    get bearingMode() {
        return this.outputData.compasstype;
    }

    get cogOutput() {
        return this.outputData.cog;
    }

    get headingInput() {
        return this.inputData.heading;
    }
    get headingOutput() {
        return this.outputData.heading;
    }

    get heelTrimOutput()
    {
        return this.outputData.heeltrim;
    }

    get positionInput() {
        return this.inputData.gps;
    }
    get positionOutput() {
        return this.outputData.gps;
    }

    get rudderInput() {
        return this.inputData.rudder;
    }
    get rudderOutput() {
        return this.outputData.rudder;
    }

    get sendMenus() {
        return !this.outputData.disablemenus;
    }

    get speedInput()
    {
        return this.inputData.boatspeed;
    }

    init() {
        let self = this;

        $.when(readSection("FastnetInput"), readSection("FastnetOutput"), readSection("Boards"),
            readSection("Display"), readSingle("Depth", "SourceCount"), readSection("N2kLoadcells"), getServerMillis(), interfacesLoaded).done(
                function(inputJSON, outputJSON, boardJSON, displayJSON, depthCount, n2kLoadcellsJSON, milliseconds) {

            self.inputData = $.extend({}, cDefaultInput, JSON.parse(inputJSON[0]));
            self.outputData = $.extend({}, cDefaultOutput, JSON.parse(outputJSON[0]));
            self.depthCount = parseInt(depthCount[0]) || 0;

            let async = [];
            let boards = JSON.parse(boardJSON[0]);
            let displays = JSON.parse(displayJSON[0]);
            let n2kLoadcellsSection = JSON.parse(n2kLoadcellsJSON[0]);
            let interfaceFactory = new InterfaceFactory();
            let millis = JSON.parse(milliseconds[0]);

            // Create an interface for each active board
            $.each(boards, function(board, timestamp)
            {
                if (board.includes("dcell")) {
                    if (validateTimestamp(timestamp) && (millis - timestamp) < maxInactiveTime_ms) {
                        self.loadcells[board.split(':')[0]] = { 'internalName': board, 'timestamp': timestamp };
                    }

                    return;
                }

                if (validateTimestamp(timestamp) && (millis - timestamp) < maxInactiveTime_ms) {
                    async.push(interfaceFactory.getInterface(board).done(function(concreteInterface) {
                        self.sources.push(concreteInterface);
                    }));
                }
            });

            // Store active loadcells
            $.each(boards, function(device, timestamp)
            {
                if (device.toLowerCase().includes("dla2"))
                {
                    if (validateTimestamp(timestamp) && (millis - timestamp) < maxInactiveTime_ms) 
                    {
                        // This device is valid, but we want the serial number and IP info from the display section
                        // We find this based on the device instance
                        let instance = device.split(':')[1];

                        // Ideally there would be a single loadcell with the correct instance
                        // In reality old devices may linger in the database
                        // We select the one with the most up-to-date timestamp
                        let largest_timestamp = 0;
                        let serial;
                        $.each(displays, function(key, value)
                        {
                            let displayInstance = value.split(',')[1];

                            if (key.toLowerCase().includes("dla") && displayInstance == instance)
                            {
                                let displayTimestamp = value.split(',')[2];
                                if (displayTimestamp >= largest_timestamp)
                                {
                                    largest_timestamp = displayTimestamp;
                                    serial = key;
                                }
                            }
                        });
                        self.loadcells[serial] = { 'internalName': "dla2 board:" + instance, 'timestamp' : largest_timestamp };
                    }
                }
                else if (device.toLowerCase().includes("dla3"))
                {
                    if (validateTimestamp(timestamp) && (millis - timestamp) < maxInactiveTime_ms) 
                    {
                        async.push($.when(readSingle("loadcell", device), readSingle(device, "channelnames")).done(function(loadcellResponse, deviceResponse)
                        {
                            const deviceJSON = JSON.parse(deviceResponse[0] || "{}");
                            const channels = (loadcellResponse[0] || "").split(',');

                            for (let channel in channels)
                            {
                                const id = device + ":" + channel;
                                let name = device + ":Ch" + channel;
                                if (deviceJSON.hasOwnProperty(channel))
                                {
                                    name = deviceJSON[channel];
                                }

                                self.loadcells[id] = { 'internalName' : id, 'displayName' : name };
                            }
                        }));
                    }
                }
            })

            // Stores active N2k loadcells
            if (n2kLoadcellsSection.hasOwnProperty("stations")) {
                let stationlist = n2kLoadcellsSection["stations"].split(":").filter(function(i) { return i; });
                    
                $.each(stationlist, function(stationIndex, station) {
                    self.loadcells[n2kCyclopsLoadcellPrefix + station] = { 'internalName': n2kCyclopsLoadcellPrefix + station, 'timestamp': 0 };
                });
            }

            // Read in analogue channel list
            let analogueArray = self.outputData.fastnetoutputchannels.split(',');
            for (let i = 0; i < analogueArray.length; i += 2) {
                self.analogueChannels.push({
                    'label' : analogueArray[i + 1].trim(),
                    'value' : parseInt(analogueArray[i].trim()) || 0
                })
            }

            // Read in loadcell channel list
            let loadcellArray = self.inputData.loadcellchannels.split(',');
            for (let i = 0; i < loadcellArray.length; i += 2) {
                self.loadcellChannels.push({
                    'label' : loadcellArray[i + 1].trim(),
                    'value' : parseInt(loadcellArray[i].trim()) || 0
                })
            }

            // store depth sources
            self.transducers.push(
                {
                    "index" : "primary",
                    "internalName" : "depthsourceprimary",
                    "displayName" : "Primary Depth",
                    "friendlyName" : "Primary",
                    "configuration" : self.outputData.depthsourceprimary
                }
            );

            for (let i = 0; i < self.depthCount; i++) {
                let internalName = "depthsource" + i;

                async.push(readSingle(internalName, "Name").done(function(name) {
                    self.transducers.push(
                        {
                            "index" : i,
                            "internalName" : internalName,
                            "displayName" : "Depth Input " + (i + 1),    // 1-indexed for users
                            "friendlyName" : name,
                            "configuration" : self.outputData[internalName]
                        }
                    );
                }));
            }

            $.when.apply($, async).then(function() {
                // Sort depth transducers
                self.transducers.sort(function(a, b) { return a.index > b.index });

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

    persistData() {
        let databaseArray = [];

        $.each(this.inputData, function(key, value) {
            databaseArray.push({
                'section': 'FastnetInput',
                'key' : key,
                'value' : value
            });
        });

        $.each(this.outputData, function(key, value) {
            databaseArray.push({
                'section': 'FastnetOutput',
                'key' : key,
                'value' : value
            });
        });
        return writeToDatabase(databaseArray, 'Fastnet');
    }
}

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

    changeAnalogueOutputDP(e) {
        let channel = $(e.target).closest('.output-row').attr('data-channel');

        let config = (this.model.outputData[channel] || cDefaultAnalogue).split(',');
        config[3] = $(e.target).find('option:selected').val();
        this.model.outputData[channel] = config.join(',');
    }

    changeAnalogueOutputChannel(e) {
        validateRange($(e.target), 0, 0, 255, 64);

        let channel = $(e.target).closest('.output-row').attr('data-channel');

        if ($(e.target).val() === "29" || $(e.target).val() === "31")
        {
            $('#dialog-text').text('Air/Sea Temperature is already sent to Fastnet by the ATP. Source selection can be found under Other Data > Environment.')
            $('#dialog').dialog( {
                title: "Warning: Duplicate Temperature",
            }).dialog('open');
        }

        let config = (this.model.outputData[channel] || cDefaultAnalogue).split(',');
        config[0] = $(e.target).val();
        this.model.outputData[channel] = config.join(',');

        // Set value to autocomplete label
        channel = $(e.target).val();
        let analogueChannel = this.model.analogueChannels.find(x => x.value == channel);
        if (analogueChannel !== undefined) {
            $(e.target).val(analogueChannel.label);
        }
    }

    changeAnalogueOutputName(e) {
        let channel = $(e.target).closest('.output-row').attr('data-channel');

        let config = (this.model.outputData[channel] || cDefaultAnalogue).split(',');
        config[1] = $(e.target).val();
        this.model.outputData[channel] = config.join(',');
    }

    changeBearingMode(e) {
        this.model.outputData.compasstype = $(e.target).val();
    }

    changeCOGOutputDP(e) {
        let config = this.model.outputData.cog.split(',');
        config[3] = $(e.target).find('option:selected').val();
        this.model.outputData.cog = config.join(',');
    }

    changeCOGOutputName(e) {
        let config = this.model.outputData.cog.split(',');
        config[1] = $(e.target).val();
        this.model.outputData.cog = config.join(',');
    }

    changeCOGOutputNode(e) {
        validateRange($(e.target), 0, 0, 255, 5);

        let config = this.model.outputData.cog.split(',');
        config[0] = $(e.target).val();
        this.model.outputData.cog = config.join(',');
    }

    changeDepthOutputChannel(e) {
        if ($(e.target).val() == "") {
            $(e.target).css('border-color', '');
        }
        else if (!($(e.target).val().toLowerCase() == "primary" || $(e.target).val().toLowerCase() == "aft")) {
            validateRange($(e.target), 0, 0, 255, 73);
        } else {
            let depthChannels = $('#depth-output [name="depth-channel"]').filter(function() {
                return $(this).val() == $(e.target).val();
            });
            if (depthChannels.length > 1) {
                $(e.target).val($(e.target).attr('value')).css('border-color', 'red');
                $('#dialog-text').text('Only one depth transducer may be set as Primary or Aft respectively.')
                $('#dialog').dialog( {
                    title: "Invalid Depth Channel",
                }).dialog('open');
                
                return false;
            } else {
                $(e.target).css('border-color', '');
            }
        }

        let source = $(e.target).closest('.output-row').attr('data-channel');
        let config = (this.model.outputData[source] || cDefaultTransducer).split(',');
        config[0] = $(e.target).val();
        this.model.outputData[source] = config.join(',');
    }

    changeDepthOutputName(e) {
        let source = $(e.target).closest('.output-row').attr('data-channel');
        let config = (this.model.outputData[source] || cDefaultTransducer).split(',');
        config[1] = $(e.target).val();
        this.model.outputData[source] = config.join(',');
    }

    changeHeadingInputChannel(e) {
        validateRange($(e.target), 0, 0, 255, 73);

        let config = this.model.inputData.heading.split(',');
        config[1] = $(e.target).val();
        this.model.inputData.heading = config.join(',');
    }

    changeHeadingInputNode(e) {
        validateRange($(e.target), 0, 0, 255, 16);

        let config = this.model.inputData.heading.split(',');
        config[0] = $(e.target).val();
        this.model.inputData.heading = config.join(',');
    }

    changeHeadingOutputDP(e) {
        let config = this.model.outputData.heading.split(',');
        config[3] = $(e.target).find('option:selected').val();
        this.model.outputData.heading = config.join(',');
    }

    changeHeadingOutputName(e) {
        let config = this.model.outputData.heading.split(',');
        config[1] = $(e.target).val();
        this.model.outputData.heading = config.join(',');
    }

    changeHeadingOutputNode(e) {
        validateRange($(e.target), 0, 0, 255, 5);

        let config = this.model.outputData.heading.split(',');
        config[0] = $(e.target).val();
        this.model.outputData.heading = config.join(',');
    }

    changeLoadcellOutputChannel(e) {
        validateRange($(e.target), 0, 0, 255, 64);

        let loadcell = $(e.target).closest('.output-row').attr('data-channel');
        let config = (this.model.outputData[loadcell] || cDefaultAnalogue).split(',');
        config[0] = $(e.target).val();
        this.model.outputData[loadcell] = config.join(',');

        // Set value to autocomplete label
        let channel = $(e.target).val();
        let loadcellChannel = this.model.loadcellChannels.find(x => x.value == channel);
        if (loadcellChannel !== undefined) {
            $(e.target).val(loadcellChannel.label);
        }
    }

    changeLoadcellOutputDP(e) {
        let loadcell = $(e.target).closest('.output-row').attr('data-channel');
        let config = (this.model.outputData[loadcell] || cDefaultAnalogue).split(',');
        config[3] = $(e.target).find('option:selected').val();
        this.model.outputData[loadcell] = config.join(',');
    }

    changeLoadcellOutputName(e) {
        let loadcell = $(e.target).closest('.output-row').attr('data-channel');
        let config = (this.model.outputData[loadcell] || cDefaultAnalogue).split(',');
        config[1] = $(e.target).val();
        this.model.outputData[loadcell] = config.join(',');
    }

    changePositionInputNode(e) {
        validateRange($(e.target), 0, 0, 255, 5);

        let config = this.model.inputData.gps.split(',');
        config[0] = $(e.target).val();
        this.model.inputData.gps = config.join(',');
    }

    changePositionOutputName(e) {
        let config = this.model.outputData.gps.split(',');
        config[1] = $(e.target).val();
        this.model.outputData.gps = config.join(',');
    }

    changePositionOutputNode(e) {
        validateRange($(e.target), 0, 0, 255, 5);

        let config = this.model.outputData.gps.split(',');
        config[0] = $(e.target).val();
        this.model.outputData.gps = config.join(',');
    }

    changeRudderInputChannel(e) {
        validateRange($(e.target), 0, 0, 255, 11);

        let config = this.model.inputData.rudder.split(',');
        config[1] = $(e.target).val();
        this.model.inputData.rudder = config.join(',');
    }

    changeRudderInputNode(e) {
        validateRange($(e.target), 0, 0, 255, 0);

        let config = this.model.inputData.rudder.split(',');
        config[0] = $(e.target).val();
        this.model.inputData.rudder = config.join(',');
    }

    changeRudderOutputDP(e) {
        let config = this.model.outputData.rudder.split(',');
        config[3] = $(e.target).find('option:selected').val();
        this.model.outputData.rudder = config.join(',');
    }

    changeRudderOutputName(e) {
        let config = this.model.outputData.rudder.split(',');
        config[1] = $(e.target).val();
        this.model.outputData.rudder = config.join(',');
    }

    changeRudderOutputNode(e) {
        validateRange($(e.target), 0, 0, 255, 5);

        let config = this.model.outputData.rudder.split(',');
        config[0] = $(e.target).val();
        this.model.outputData.rudder = config.join(',');
    }

    changeSpeedInputNode(e)
    {
        validateRange($(e.target), 0, 0, 255, 1);

        let config = this.model.inputData.boatspeed.split(',');
        config[0] = $(e.target).val();
        this.model.inputData.boatspeed = config.join(',');
    }

    toggleAnalogueOutput(e) {
        let channel = $(e.target).closest('.output-row').attr('data-channel');

        let config = (this.model.outputData[channel] || cDefaultAnalogue).split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData[channel] = config.join(',');
    }

    toggleCOGOutput(e) {
        let config = this.model.outputData.cog.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData.cog = config.join(',');
    }

    toggleDepthOutput(e) {
        let source = $(e.target).closest('.output-row').attr('data-channel');
        let config = (this.model.outputData[source] || cDefaultTransducer).split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData[source] = config.join(',');
    }

    toggleFastnetMenus(e) {
        this.model.outputData.disablemenus = $(e.target).is(':checked');
    }

    toggleHeadingInput(e) {
        let config = this.model.inputData.heading.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.inputData.heading = config.join(',');
    }

    toggleHeadingOutput(e) {
        let config = this.model.outputData.heading.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData.heading = config.join(',');
    }

    toggleHeelTrimOutput(e)
    {
        let config = this.model.outputData.heeltrim.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData.heeltrim = config.join(',');
    }

    toggleLoadcellOutput(e) {
        let loadcell = $(e.target).closest('.output-row').attr('data-channel');
        let config = (this.model.outputData[loadcell] || cDefaultAnalogue).split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData[loadcell] = config.join(',');
    }

    togglePositionInput(e) {
        let config = this.model.inputData.gps.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.inputData.gps = config.join(',');
    }

    togglePositionOutput(e) {
        let config = this.model.outputData.gps.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData.gps = config.join(',');
    }

    toggleRudderInput(e) {
        let config = this.model.inputData.rudder.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.inputData.rudder = config.join(',');
    }

    toggleRudderOutput(e) {
        let config = this.model.outputData.rudder.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.outputData.rudder = config.join(',');
    }

    toggleSpeedInput(e)
    {
        let config = this.model.inputData.boatspeed.split(',');
        config[2] = $(e.target).is(':checked');
        this.model.inputData.boatspeed = config.join(',');
    }
}

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

        this.controller.model.init();
    }

    bindEvents() {
        super.bindEvents();

        $('#position-input-node').on('change', this.controller.changePositionInputNode.bind(this.controller));
        $('#position-input-enable').on('change', this.controller.togglePositionInput.bind(this.controller));
        $('#heading-input-node').on('change', this.controller.changeHeadingInputNode.bind(this.controller));
        $('#heading-input-channel').on('change', this.controller.changeHeadingInputChannel.bind(this.controller));
        $('#heading-input-enable').on('change', this.controller.toggleHeadingInput.bind(this.controller));
        $('#rudder-input-node').on('change', this.controller.changeRudderInputNode.bind(this.controller));
        $('#rudder-input-channel').on('change', this.controller.changeRudderInputChannel.bind(this.controller));
        $('#rudder-input-enable').on('change', this.controller.toggleRudderInput.bind(this.controller));
        $('#speed-input-node').on('change', this.controller.changeSpeedInputNode.bind(this.controller));
        $('#speed-input-enable').on('change', this.controller.toggleSpeedInput.bind(this.controller));

        $('#position-output-node').on('change', this.controller.changePositionOutputNode.bind(this.controller));
        $('#position-output-name').on('change', this.controller.changePositionOutputName.bind(this.controller));
        $('#position-output-enable').on('change', this.controller.togglePositionOutput.bind(this.controller));
        $('#heading-output-node').on('change', this.controller.changeHeadingOutputNode.bind(this.controller));
        $('#heading-output-name').on('change', this.controller.changeHeadingOutputName.bind(this.controller));
        $('#heading-output-enable').on('change', this.controller.toggleHeadingOutput.bind(this.controller));
        $('#heel-output-enable').on('change', this.controller.toggleHeelTrimOutput.bind(this.controller));
        $('#rudder-output-node').on('change', this.controller.changeRudderOutputNode.bind(this.controller));
        $('#rudder-output-name').on('change', this.controller.changeRudderOutputName.bind(this.controller));
        $('#rudder-output-dp').on('change', this.controller.changeRudderOutputDP.bind(this.controller));
        $('#rudder-output-enable').on('change', this.controller.toggleRudderOutput.bind(this.controller));
        $('#cog-output-node').on('change', this.controller.changeCOGOutputNode.bind(this.controller));
        $('#cog-output-name').on('change', this.controller.changeCOGOutputName.bind(this.controller));
        $('#cog-output-dp').on('change', this.controller.changeCOGOutputDP.bind(this.controller));
        $('#cog-output-enable').on('change', this.controller.toggleCOGOutput.bind(this.controller));

        $('#analogue-output').on('autocompletechange', '[name="analogue-channel"]', this.controller.changeAnalogueOutputChannel.bind(this.controller));
        $('#analogue-output').on('change', '[name="analogue-dp"]', this.controller.changeAnalogueOutputDP.bind(this.controller));
        $('#analogue-output').on('change', '[name="analogue-enable"]', this.controller.toggleAnalogueOutput.bind(this.controller));
        $('#analogue-output').on('change', '[name="analogue-name"]', this.controller.changeAnalogueOutputName.bind(this.controller));

        $('#bearing-container').on('change', '[name="bearing-mode"]', this.controller.changeBearingMode.bind(this.controller));

        $('#depth-output').on('autocompletechange', '[name="depth-channel"]', this.controller.changeDepthOutputChannel.bind(this.controller));
        $('#depth-output').on('change', '[name="depth-enable"]', this.controller.toggleDepthOutput.bind(this.controller));
        $('#depth-output').on('change', '[name="depth-name"]', this.controller.changeDepthOutputName.bind(this.controller));

        $('#fastnet-menus').on('change', this.controller.toggleFastnetMenus.bind(this.controller));

        $('#loadcell-output').on('autocompletechange', '[name="loadcell-channel"]', this.controller.changeLoadcellOutputChannel.bind(this.controller));
        $('#loadcell-output').on('change', '[name="loadcell-dp"]', this.controller.changeLoadcellOutputDP.bind(this.controller));
        $('#loadcell-output').on('change', '[name="loadcell-enable"]', this.controller.toggleLoadcellOutput.bind(this.controller));
        $('#loadcell-output').on('change', '[name="loadcell-name"]', this.controller.changeLoadcellOutputName.bind(this.controller));

        $('#analogue-toggle').on('click', function(e) {
            $('#analogue-output').slideToggle(100);
            $('#analogue-toggle i').toggleClass('arrow-down arrow-up');
        });
        $('#depth-toggle').on('click', function(e) {
            $('#depth-output').slideToggle(100);
            $('#depth-toggle i').toggleClass('arrow-down arrow-up');
        });
        $('#loadcell-toggle').on('click', function(e) {
            $('#loadcell-output').slideToggle(100);
            $('#loadcell-toggle i').toggleClass('arrow-down arrow-up');
        });
    }

    drawAnalogueOutputs(model) {
        $('#analogue-output').empty();

        if (model.analogueSources.length > 0) {
            model.analogueSources.forEach(function(analogueSource) {
                analogueSource.analogueInputs.forEach(function(channel) {
                    if (channel.data.enabled) {
                        let analogueConfig = (model.outputData[channel.internalName]|| cDefaultAnalogue).split(',');

                        $('#analogue-output').append(
                                '<div class="output-row" data-channel="' + channel.internalName + '">'
                            +       '<div class="tooltip-wrapper">'
                            +           '<h2>' + (channel.data.name || toTitleCase(channel.internalName)) + '</h2>'
                            +           '<div class="tooltip">'
                            +               '<p>' + channel.internalName + '</p>'
                            +           '</div>'
                            +       '</div>'
                            +       '<input type="text" name="analogue-channel" value=' + analogueConfig[0] + '>'
                            +       '<input type="text" name="analogue-name" value="' + analogueConfig[1] + '">'
                            +       '<select name="analogue-dp">'
                            +           '<option value="0" ' + (analogueConfig[3] == "0" ? "selected" : "") + '>0</option>'
                            +           '<option value="1" ' + (analogueConfig[3] == "1" ? "selected" : "") + '>1</option>'
                            +           '<option value="2" ' + (analogueConfig[3] == "2" ? "selected" : "") + '>2</option>'
                            +       '</select>'
                            +       '<input name="analogue-enable" type="checkbox" ' + (analogueConfig[2] == "true" ? "checked" : "") + '>'
                            +   '</div>'
                        );
                    }
                })
            });

            // Setup autocomplete inputs
            $('[name="analogue-channel"]').autocomplete({
                minLength: 0,
                position: { collision: 'flip' },
                source: function(request, response) {
                    response(model.analogueChannels);
                }
            }).focus(function() {
                // Open dropdown on focus
                $(this).data("uiAutocomplete").search($(this).val());
            });

            // Set autocomplete labels from stored values
            $('[name="analogue-channel"]').each(function() {
                var channel = $(this).val();
                var analogueChannel = model.analogueChannels.find(x => x.value == channel);
                if (analogueChannel !== undefined) {
                    $(this).val(analogueChannel.label);
                }
            });
        } else {
            $('#analogue-output').append('<div class="box loading-box"><div>No Analogue Devices Found</div></div>');
        }
    }

    drawDepthPrimaryOutput(config) {
        let html =
            '<div class="primary-source output-row" data-channel="depthsourceprimary">' +
            '   <div class="tooltip-wrapper">' +
            '       <h2>Primary Depth</h2>' +
            '       <div class="tooltip">' +
            '           <p>Primary Depth Source</p>' + 
            '       </div>' +
            '   </div>' + 
            '   <input type="text" name="depth-channel" value="' + config[0] + '" disabled="disabled">' +
            '   <input type="text" name="depth-name" value="' + config[1] + '">' + 
            '   <div name="depth-placeholder"></div>' + 
            '   <input name="depth-enable" type="checkbox" ' + (config[2] == "true" ? "checked" : "") +' >' +
            '</div>';
        
        $('#depth-output').append(html);
    }

    drawDepthOutputs(model) {
        $('#depth-output').empty();
        let self = this;
        if (model.transducers.length > 0) {
            model.transducers.forEach(function(transducer) {
                let depthConfig = (transducer.configuration || ",,false").split(',');

                if (transducer.index == "primary") {
                    self.drawDepthPrimaryOutput(depthConfig);
                } else {
                    $('#depth-output').append(
                        '<div class="output-row" data-channel="' + transducer.internalName + '">'
                    +       '<div class="tooltip-wrapper">'
                    +           '<h2>' + (transducer.friendlyName || transducer.displayName) + '</h2>'
                    +           '<div class="tooltip">'
                    +               '<p>' + transducer.displayName + '</p>'
                    +           '</div>'
                    +       '</div>'
                    +       '<input type="text" name="depth-channel" value="' + depthConfig[0] + '">'
                    +       '<input type="text" name="depth-name" value="' + depthConfig[1] + '">'
                    +       '<div name="depth-placeholder"></div>'
                    +       '<input name="depth-enable" type="checkbox" ' + (depthConfig[2] == "true" ? "checked" : "") + '>'
                    +   '</div>'
                    );
                }
            });

            // Setup autocomplete inputs
            $('[name="depth-channel"]').autocomplete({
                minLength: 0,
                source: function(request, response) {
                    response([
		                {label: "Aft", value: 'Aft'}
                    ]);
                }
            }).focus(function() {
                // Open dropdown on focus
                $(this).data("uiAutocomplete").search($(this).val());
            });
        } else {
            $('#depth-output').append('<div class="box loading-box"><div>No Depth Transducers Configured</div></div>');
        }
    }

    drawLoadcellOutputs(model) 
    {
        if (Object.keys(model.loadcells).length > 0) 
        {
            $.each(model.loadcells, function(key, value) 
            {
                let loadcellConfig = (model.outputData[value.internalName] || "64,,false,0").split(',');
                
                let title = value.internalName.slice();             // Creates a copy of the name that can be altered

                // Handle Cyclops Marine loadcells differently
                let prefixPosition = title.lastIndexOf(n2kCyclopsLoadcellPrefix);
                if (prefixPosition != -1)
                {
                    // Extract the station number
                    let stationNumber = parseInt(title.substr(prefixPosition + n2kCyclopsLoadcellPrefix.length));
                    // Set appropriate station name
                    title = N2KCyclopsLoadcellStationNames[stationNumber] || "Unknown Station";       
                }
                else if (value.hasOwnProperty("displayName")) title = value["displayName"];

                $('#loadcell-output').append(
                        '<div class="output-row" data-channel="' + value.internalName + '">'
                    +       '<h2>' + title + '</h2>'
                    +       '<input type="text" name="loadcell-channel" value=' + loadcellConfig[0] + '>'
                    +       '<input type="text" name="loadcell-name" value="' + loadcellConfig[1] + '">'
                    +       '<select name="loadcell-dp">'
                    +           '<option value="0" ' + (loadcellConfig[3] == "0" ? "selected" : "") + '>0</option>'
                    +           '<option value="1" ' + (loadcellConfig[3] == "1" ? "selected" : "") + '>1</option>'
                    +           '<option value="2" ' + (loadcellConfig[3] == "2" ? "selected" : "") + '>2</option>'
                    +       '</select>'
                    +       '<input name="loadcell-enable" type="checkbox" ' + (loadcellConfig[2] == "true" ? "checked" : "") + '>'
                    +   '</div>'
                );
            });

            // Setup autocomplete inputs
            $('[name="loadcell-channel"]').autocomplete({
                minLength: 0,
                position: { collision: 'flip' },
                source: function(request, response) {
                    response(model.loadcellChannels);
                }
            }).focus(function() {
                // Open dropdown on focus
                $(this).data("uiAutocomplete").search($(this).val());
            });

            // Set autocomplete labels from stored values
            $('[name="loadcell-channel"]').each(function() {
                var channel = $(this).val();
                var loadcellChannel = model.loadcellChannels.find(x => x.value == channel);
                if (loadcellChannel !== undefined) {
                    $(this).val(loadcellChannel.label);
                }
            });
        } else {
            $('#loadcell-output').append('<div class="box loading-box"><div>No Loadcell Amplifiers Found</div></div>');
        }
    }

    populateFixedConfig(model) {
        $('#fastnet-menus').prop('checked', !model.sendMenus);
        $('[name="bearing-mode"][value="' + model.bearingMode + '"]').prop('checked', true);

        // Populate Fastnet input config
        let positionData = model.positionInput.split(',');
        $('#position-input-node').val(positionData[0]);
        $('#position-input-enable').prop('checked', positionData[2] == "true");

        let headingData = model.headingInput.split(',');
        $('#heading-input-node').val(headingData[0]);
        $('#heading-input-channel').val(headingData[1]);
        $('#heading-input-enable').prop('checked', headingData[2] == "true");

        let speedData = model.speedInput.split(',');
        $('#speed-input-node').val(speedData[0]);
        $('#speed-input-enable').prop('checked', speedData[2] == "true");

        let rudderData = model.rudderInput.split(',');
        $('#rudder-input-node').val(rudderData[0]);
        $('#rudder-input-channel').val(rudderData[1]);
        $('#rudder-input-enable').prop('checked', rudderData[2] == "true");

        // Populate Fastnet output config
        positionData = model.positionOutput.split(',');
        $('#position-output-node').val(positionData[0]);
        $('#position-output-name').val(positionData[1]);
        $('#position-output-enable').prop('checked', positionData[2] == "true");
        $('#position-output-dp').find('option[value="' + positionData[3] + '"]').prop('selected', true);

        headingData = model.headingOutput.split(',');
        $('#heading-output-node').val(headingData[0]);
        $('#heading-output-name').val(headingData[1]);
        $('#heading-output-enable').prop('checked', headingData[2] == "true");

        let heelData = model.heelTrimOutput.split(',');
        $('#heel-output-enable').prop('checked', heelData[2] == "true");

        rudderData = model.rudderOutput.split(',');
        $('#rudder-output-node').val(rudderData[0]);
        $('#rudder-output-name').val(rudderData[1]);
        $('#rudder-output-enable').prop('checked', rudderData[2] == "true");
        $('#rudder-output-dp').find('option[value="' + rudderData[3] + '"]').prop('selected', true);

        let cogData = model.cogOutput.split(',');
        $('#cog-output-node').val(cogData[0]);
        $('#cog-output-name').val(cogData[1]);
        $('#cog-output-enable').prop('checked', cogData[2] == "true");
        $('#cog-output-dp').find('option[value="' + cogData[3] + '"]').prop('selected', true);
    }

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

        this.populateFixedConfig(model);
        this.drawAnalogueOutputs(model);
        this.drawDepthOutputs(model);
        this.drawLoadcellOutputs(model);
    }
}