/**
 * Class definitions for Loadcell page.
 * 
 * @category Webserver
 * @package ATProcessor
 * @author Sam Meredith <sam@aandtinstruments.com>
 * @version 0.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"
];

 /**
 * Defines available loadcell inputs
 */
class LoadcellModel extends ObservableModel
{
    constructor()
    {
        super();

        this.loadcellCalibrationData = new Object();
        this.loadcellChannels = [];
        this.numFastnetLoadcells = 8;

        this.CyclopsInputs = {};
        this.DLA3Inputs = {};
    }

    get DCellInputs() {
        return this.loadcellCalibrationData.DCell;
    }

    get DLA2Inputs() {
        return this.loadcellCalibrationData.Loadcell;
    }

    get FastnetInputs() {
        return this.loadcellCalibrationData.FastnetInput;
    }
    
    get N2kBAndG() {
        return this.loadcellCalibrationData.N2kBAndG;
    }

    set N2kBAndG(N2kBAndG) {
        this.loadcellCalibrationData.N2kBAndG = N2kBAndG;
    }

    init() 
    {
        let async = [];

        let defaultData = 
        {
            'FastnetInput' : 
            {
                'loadcell1' : '70,64,false',
                'loadcell2' : '71,42,false',
                'loadcell3' : '72,43,false',
                'loadcell4' : '73,44,false',
                'loadcell5' : '74,45,false',
                'loadcell6' : '75,61,false',
                'loadcell7' : '76,62,false',
                'loadcell8' : '77,63,false'
            },
            'Loadcell' : 
            {

            },
            'DCell' : 
            {

            },
            'N2kBAndG' : []
        };
        // DCell config has form <ZeroValue>,<mV/V>,<EquivalentLoad>
        let defaultDCellConfig = 
        {
            'calibration': '0.0,1.0,1.0',
            'instance': 0
        };
        // DLA2 config has form <CalibrationValue>,<CalibrationReference>,<ZeroValue>
        let defaultDLA2Config = 
        {
            'calibration': '125.00,122240,0',
            'instance': 0
        };
        const defaultCyclopsDLAConfig =
        {
            'instance': 0,
            'displayName': "Board Name",
            'serial': "Unknown",
            'calibration': '',
            'channelNames': "{}"
        };
        let self = this;

        this.loadcellCalibrationData = defaultData;

        // Read in Fastnet Input configuration
        async.push(readSection("FastnetInput").done(function(sectionJSON)
        {
            let section = JSON.parse(sectionJSON);
            
            for (let i = 1; i <= self.numFastnetLoadcells; i++) 
            {
                let loadcell = "loadcell" + i;

                let loadcellConfig = section[loadcell];
                if (loadcellConfig !== undefined && loadcellConfig != "" &&
                     loadcellConfig.split(',').length == 3) 
                {
                    self.FastnetInputs[loadcell] = loadcellConfig;
                } 
            }
        }));

        // Read in Loadcell channel list
        async.push(readSingle("FastnetInput", "LoadcellChannels").done(function(channelString)
        {
            let loadcellChannels = channelString.split(',');

            for (let i = 0; i < loadcellChannels.length; i += 2)
            {
                self.loadcellChannels.push( { label : loadcellChannels[i+1], value : loadcellChannels[i] } );
            }
        }));

        // Deferred used to ensure DLA names are read before the page loads
        let deferred = new $.Deferred();
        async.push(deferred.promise());

        // Read in the set of connected DLA2s and their configurations
        async.push($.when(readSection("Display"), readSection("Boards"), readSection("Loadcell"),
            readSection("DCell"), readSection("N2kLoadcells"), getServerMillis()).done(
            function(displayJSON, boardJSON, loadcellJSON, DCellJSON, n2kLoadcellsJSON, milliJSON) 
        {
            let displaySection = JSON.parse(displayJSON[0]);
            let boardSection = JSON.parse(boardJSON[0]);
            let loadcellSection = JSON.parse(loadcellJSON[0]);
            let dcellSection = JSON.parse(DCellJSON[0]);
            let n2kLoadcellsSection = JSON.parse(n2kLoadcellsJSON[0]);
            let milliseconds = JSON.parse(milliJSON[0]);

            // The visible DLA2s are those whose timestamp has been recently updated in the 'boards' section
            let visibleDLA2 = Object.keys(boardSection).filter(function(key) 
            {
                let lastActiveTime = boardSection[key];
                
                return (key.toLowerCase().includes("dla2")) && (milliseconds - lastActiveTime < 600000);
            }); 
            // Similarly for DLA3s
            let visibleDLA3 = Object.keys(boardSection).filter(function(key)
            {
                let lastActiveTime = boardSection[key];

                return (key.toLowerCase().includes("dla3") && !key.toLowerCase().includes("cyclops")) && (milliseconds - lastActiveTime < 600000);
            });
            // And Cyclops DLA3s
            let visibleCyclops = Object.keys(boardSection).filter(function(key)
            {
                let lastActiveTime = boardSection[key];

                return (key.toLowerCase().includes("dla3 cyclops"))&& (milliseconds - lastActiveTime < 600000);
            })

            let asyncNames = []; // Use a second async to ensure all DLA names are read before the page loads
            visibleDLA2.forEach(function(amplifier)
            {
                // Find the serial number corresponding to the loadcells instance
                let instance = amplifier.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(displaySection, 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;
                        }
                    }
                });

                // Add the input from the loadcell section
                // 2022/03/24 Loadcell configurations are now stored against <SenderId:SenderInstance>
                self.DLA2Inputs[serial] = $.extend({}, defaultDLA2Config, { 'calibration' : loadcellSection[amplifier.toLowerCase()] });
                self.DLA2Inputs[serial].instance = instance;

                // Load the friendly name, default to "Board Name"
                self.DLA2Inputs[serial].displayName = "Board Name";
                asyncNames.push(readSingle(amplifier, "name").done(function(name)
                {
                    if (name) self.DLA2Inputs[serial].displayName = name;
                }));
            });

            visibleDLA3.forEach(function(amplifier)
            {
                // DLA3 are nicer and don't rely on serial number
                self.DLA3Inputs[amplifier] = $.extend({}, defaultDLA2Config, { 'calibration' : loadcellSection[amplifier.toLowerCase()] });

                let instance = amplifier.split(':')[1];
                self.DLA3Inputs[amplifier].instance = instance;
                self.DLA3Inputs[amplifier].displayName = "Board Name";
                self.DLA3Inputs[amplifier].serial = amplifier;
                asyncNames.push(readSection(amplifier).done(function(response)
                {
                    const json = JSON.parse(response);

                    self.DLA3Inputs[amplifier].displayName = json.name || "Board Name";
                    self.DLA3Inputs[amplifier].serial = json.serialnumber || amplifier;
                    self.DLA3Inputs[amplifier].channelNames = JSON.parse(json.channelnames || "{}");
                }));
            });

            visibleCyclops.forEach(function(amplifier)
            {
                let instance = amplifier.split(':')[1];

                self.CyclopsInputs[instance] = $.extend({}, defaultCyclopsDLAConfig, { 'calibration' : loadcellSection[amplifier.toLowerCase()] });

                self.CyclopsInputs[instance].displayName = "Board Name";
                self.CyclopsInputs[instance].serial = amplifier;
                asyncNames.push(readSection(amplifier).done(function(response)
                {
                    const json = JSON.parse(response);

                    self.CyclopsInputs[instance].displayName = json.name || "Board Name";
                    self.CyclopsInputs[instance].serial = json.serialnumber || amplifier;
                    self.CyclopsInputs[instance].channelNames = JSON.parse(json.channelnames || "{}");
                }));
            });

            // Populate DCELL input structure
            let dcellAmplifiers = Object.keys(boardSection).filter(function(key) 
            {
                let lastActiveTime = boardSection[key];

                return (key.includes("dcell")) && (milliseconds - lastActiveTime < 600000);
            });

            dcellAmplifiers.forEach(function(amplifier) 
            {
                let instance = amplifier.split(":")[1];

                self.DCellInputs[amplifier] = $.extend({}, defaultDCellConfig, { 'calibration' : dcellSection[instance] });
                self.DCellInputs[amplifier].instance = amplifier.split(':')[1];
            });

            if (n2kLoadcellsSection.hasOwnProperty("stations")) 
            {
                let stationlist = n2kLoadcellsSection["stations"].split(":").filter(function(i) { return i; });
                self.N2kBAndG = stationlist;
            }

            // This needs to happen AFTER names are populated
            $.when.apply($, asyncNames).then(function() 
            {
                deferred.resolve();
            });
        }));

        $.when.apply($, async).then(function() 
        {
            self.notifyAll();
        })
    }

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

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

        $.each(this.DLA2Inputs, function(key, value) {
            // 2022/03/24 Loadcell configurations are now stored against <SenderId:SenderInstance>
            let databaseKeyAndSection = "dla2 board:" + value.instance;

            databaseArray.push( { 'section' : 'Loadcell', 'key' : databaseKeyAndSection, 'value' : value.calibration } );
            databaseArray.push( { 'section' : databaseKeyAndSection, 'key' : 'name', 'value' : value.displayName }); 
        });

        $.each(this.DLA3Inputs, function(key, value) {
            let databaseKeyAndSection = "DLA3:" + value.instance;
            databaseArray.push( { 'section' : 'Loadcell', 'key' : databaseKeyAndSection, 'value' : value.calibration } );
            databaseArray.push( { 'section' : databaseKeyAndSection, 'key' : 'name', 'value' : value.displayName }); 
        });

        $.each(this.CyclopsInputs, function(instance, value) 
        {
            let databaseKeyAndSection = "dla3 cyclops:" + instance;

            databaseArray.push( { 'section' : databaseKeyAndSection, 'key' : 'name', 'value' : value.displayName });
            databaseArray.push( { 'section' : databaseKeyAndSection, 'key' : 'channelnames', 'value' : JSON.stringify(value.channelNames) });
        });

        $.each(this.DCellInputs, function(key, value) {
            databaseArray.push( { 'section' : 'DCell', 'key' : value.instance, 'value' : value.calibration } );
        });

        return writeToDatabase(databaseArray, 'Loadcell');
    }
}

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

    changeChannelName(e)
    {
        let instance = $(e.target).closest('[name="loadcell-container"]').attr('data-instance');
        let channel = $(e.target).attr('data-channel');

        this.model.CyclopsInputs[instance].channelNames[channel] = $(e.target).val();
    }

    changeDisplayName(e)
    {
        const type = $(e.target).closest('[name="loadcell-container"]').attr('data-type');
        if (type == "dla2")
        {
            let loadcell = $(e.target).closest('[name="loadcell-container"]').attr('data-device');
            
            this.model.DLA2Inputs[loadcell].displayName = $(e.target).val();
        }
        else if (type == "dla3")
        {
            let loadcell = $(e.target).closest('[name="loadcell-container"]').attr('data-device');
            
            this.model.DLA3Inputs[loadcell].displayName = $(e.target).val();
        }
        else if (type == "cyclops")
        {
            let instance = $(e.target).closest('[name="loadcell-container"]').attr('data-instance');

            this.model.CyclopsInputs[instance].displayName = $(e.target).val();
        }
    }

    changeCalibration(e) {
        let loadcell = $(e.target).closest('[name="loadcell-container"]').attr('data-device');

        validateRange($(e.target), 3, 0, 650, 125);

        let configData = this.model.DLA2Inputs[loadcell].calibration.split(',');
        configData[0] = $(e.target).val();
        this.model.DLA2Inputs[loadcell].calibration = configData.join(',');
    }

    changeCalibrationReference(e) {
        let loadcell = $(e.target).closest('[name="loadcell-container"]').attr('data-device');

        validateRange($(e.target), 0, 0, 2098000, 122250);

        let configData = this.model.DLA2Inputs[loadcell].calibration.split(',');
        configData[1] = $(e.target).val();
        this.model.DLA2Inputs[loadcell].calibration = configData.join(',');
    }

    changeDCellCalibration(e) {
        let loadcell = $(e.target).closest('[name="dcell-container"]').attr('data-device');

        validateRange($(e.target), 3, 0, 650, 1);
        let configData = this.model.DCellInputs[loadcell].calibration.split(',');
        configData[2] = $(e.target).val();
        this.model.DCellInputs[loadcell].calibration = configData.join(',');
    }

    changeDCellCalibrationReference(e) {
        let loadcell = $(e.target).closest('[name="dcell-container"]').attr('data-device');

        validateRange($(e.target), 3, 0, 650, 1);
        let configData = this.model.DCellInputs[loadcell].calibration.split(',');
        configData[1] = $(e.target).val();
        this.model.DCellInputs[loadcell].calibration = configData.join(',');
    }

    changeDCellZero(e) {
        let loadcell = $(e.target).closest('[name="dcell-container"]').attr('data-device');

        validateRange($(e.target), 4, -320, 320, 0);
        let configData = this.model.DCellInputs[loadcell].calibration.split(',');
        configData[0] = $(e.target).val();
        this.model.DCellInputs[loadcell].calibration = configData.join(',');
    }

    changeInputChannel(e) {
        let loadcell = $(e.target).attr('data-key');
        // Default channel to 0
        validateRange($(e.target), 0, 0, 255, 0);

        let configData = this.model.FastnetInputs[loadcell].split(',');
        configData[1] = $(e.target).val();
        this.model.FastnetInputs[loadcell] = configData.join(',');
    }

    changeInputNode(e) {
        let loadcell = $(e.target).attr('data-key');
        // Default nodes to 70 -> 77
        validateRange($(e.target), 0, 0, 255, 70 + (loadcell.substr(-1) - 1));

        let configData = this.model.FastnetInputs[loadcell].split(',');
        configData[0] = $(e.target).val();
        this.model.FastnetInputs[loadcell] = configData.join(',');
    }

    changeZero(e) {
        let loadcell = $(e.target).closest('[name="loadcell-container"]').attr('data-device');

        validateRange($(e.target), 3, -320, 320, 0);

        let configData = this.model.DLA2Inputs[loadcell].calibration.split(',');
        configData[2] = $(e.target).val() * 100.0;
        this.model.DLA2Inputs[loadcell].calibration = configData.join(',');
    }

    redirectCyclopsDLA3(e)
    {
        console.log("clicked");

        const type = $(e.target).closest('[name="loadcell-container"]').attr('data-type');
        if (type != "cyclops") return;

        const instance = $(e.target).closest('[name="loadcell-container"]').attr('data-instance');

        Object.assign(document.createElement('a'), 
        {
            target: '_blank',
            rel: 'noopener noreferrer',
            href: "dla/36/" + instance + "/"
            
        }).click();
    }

    setDCellZero(e) {
        let self = this;
        let device = $(e.target).closest('[name="dcell-container"]').attr('data-device');

        $('#dialog-text').text('Remove all load from cell before setting zero. For best accuracy only zero \
            when amplifier and cell have been powered up for at least 30 minutes.')
        $('#dialog').dialog( {
            title: "Set Zero Confirmation",
            buttons: {
				Ok: function() {
                    let zeroOffset = $(e.target).closest('[name="dcell-container"]').find('[name="zero-value"]');
                    let rawInput = $(e.target).closest('[name="dcell-container"]').find('[name="raw-input"]');
                    zeroOffset.val(parseFloat(rawInput.text()));
                    zeroOffset.change();
                    $('#dialog').dialog('close');
				},
				Cancel: function() {
					$('#dialog').dialog('close');
                }
            }
        }).dialog('open');
    }

    setZero(e) {
        let self = this;

        let type = $(e.target).closest('[name="loadcell-container"]').attr('data-type');
        let sender = "DLA2 Board";
        if (type == "dla3") sender = "DLA3";
        else if (type == "cyclops") sender = "DLA3 Cyclops";

        let instance = $(e.target).closest('[name="loadcell-container"]').attr('data-instance');

        $('#dialog-text').text('Remove all load from cell before setting zero. Only set zero \
            when amplifier and cell have been powered up for at least 30 minutes. Press "Ok" when you \
            have removed the load and are ready to proceed. Calibration will take a few seconds.')
        $('#dialog').dialog( {
            title: "Set Zero Confirmation",
            buttons: {
				Ok: function() {
					$.ajax({
                        url: 'atp.protocol.php',
                        type: 'POST',
                        data: {
                            'action': 'queryProcessor',
                            'query': 'loadcellsetzero=' + sender + '&senderinstance=' + instance
                        }
                    });
                    $('#dialog-text').text('Calibrating...');
                    $('#dialog').dialog("option", "buttons", {});
                    // Wait for DLA2 to calibrate
                    setTimeout(function() {
                        $('#dialog').dialog('close');
                    }, 3000);
				},
				Cancel: function() {
					$('#dialog').dialog('close');
                }
            }
        }).dialog('open');
    }

    toggleInput(e) {
        let loadcell = $(e.target).attr('data-key');

        let configData = this.model.FastnetInputs[loadcell].split(',');
        configData[2] = $(e.target).is(':checked');
        this.model.FastnetInputs[loadcell] = configData.join(',');
    }
}

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

        this.controller.model.init();
    }

    bindEvents() {
        super.bindEvents();

        $('#dla-container').on('change', '[name="display-name"]', this.controller.changeDisplayName.bind(this.controller));
        $('#dla-container').on('change', '[name="calibration-value"]', this.controller.changeCalibration.bind(this.controller));
        $('#dla-container').on('change', '[name="calibration-reference"]', this.controller.changeCalibrationReference.bind(this.controller));
        $('#dla-container').on('change', '[name="zero-value"]', this.controller.changeZero.bind(this.controller));
        $('#dla-container').on('change', '[name="channel-name"]', this.controller.changeChannelName.bind(this.controller));
        $('#dla-container').on('click', '[name="set-zero"]', this.controller.setZero.bind(this.controller));
        $('#dla-container').on('click', '.loadcell-advanced', this.toggleAdvanced.bind(this));

        $('#dcell-container').on('change', '[name="calibration-value"]', this.controller.changeDCellCalibration.bind(this.controller));
        $('#dcell-container').on('change', '[name="calibration-reference"]', this.controller.changeDCellCalibrationReference.bind(this.controller));
        $('#dcell-container').on('change', '[name="zero-value"]', this.controller.changeDCellZero.bind(this.controller));
        $('#dcell-container').on('click', '[name="set-zero"]', this.controller.setDCellZero.bind(this.controller));

        $('#fastnet-loadcell-container').on('change autocompletechange', '[name="channel"]', this.controller.changeInputChannel.bind(this.controller));
        $('#fastnet-loadcell-container').on('change', '[name="enable"]', this.controller.toggleInput.bind(this.controller));
        $('#fastnet-loadcell-container').on('change', '[name="node"]', this.controller.changeInputNode.bind(this.controller));
        $('#loadcell-toggle').on('click', this.toggleLoadcell.bind(this));

        $('#dla-container').on('click', '.dla3-redirect', this.controller.redirectCyclopsDLA3.bind(this.controller));
    }

    toggleAdvanced(e) {
        $(e.target).find('.loadcell-hidden').slideToggle('500', function() {
            $(e.target).find('.advanced-toggle i').toggleClass('arrow-down arrow-up');
        });
    }

    // Show/Hide the config options for the page
    toggleLoadcell(e) {
        $('#fastnet-loadcell-container').slideToggle('500', function() {
            $('#loadcell-toggle').find('i').toggleClass('arrow-down arrow-up');
        });
    }

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

        // Fastnet Loadcell Input Autocomplete
        $('#loadcell-grid input[name="channel"]').autocomplete({
            minLength: 0,
            source: function(request, response) {
                response(model.loadcellChannels);
            }
        }).focus(function() {
            // Open dropdown on focus
            $(this).data("uiAutocomplete").search($(this).val());
        });

        // Ethernet DLA Inputs
        $('#dla-container').empty();
        if (Object.keys(model.DLA2Inputs).length == 0 && Object.keys(model.DLA3Inputs).length == 0 && Object.keys(model.CyclopsInputs).length == 0)
        {
            $('#dla-container').append('<div class="box top-wrapper loading-box"><div>No Ethernet DLA Detected</div></div>');
        }

        if (Object.keys(model.DLA2Inputs).length > 0)
        {
            $.each(model.DLA2Inputs, function(amplifier, properties) 
            {
                let calibrationValues = properties.calibration.split(',');
                $('#dla-container').append(
                        '<div class="box top-wrapper">'
                    +       '<fieldset name="loadcell-container" data-type="dla2" data-device="' + amplifier + '" data-instance="' + properties.instance + '">'
                    +           '<legend>'
                    +               '<h2>' + amplifier + '</h2>'
                    +           '</legend>'
                    +           '<legend class="loadcell-name">'
                    +               '<input type="text" name="display-name" value="' + properties.displayName + '">'
                    +           '</legend>'
                    +           '<div class="flex-row">'
                    +               '<div class="tooltip-wrapper">'
                    +                   'Calibration Value:'
                    +                   '<div class="tooltip">'
                    +                       '<p>Calibration Value or Equivalent Load provided by the load cell manufacturer. '
                    +                       'This is the value that should be output when a fixed calibration '
                    +                       'load is placed on the cell.</p>'
                    +                   '</div>'
                    +               '</div>'
                    +               '<input type="number" name="calibration-value" step=0.001 value=' + calibrationValues[0] + '>'
                    +           '</div>'
                    +           '<div class="flex-row">'
                    +               '<div>'
                    +                   'Calculated Load'
                    +               '</div>'
                    +               '<div class="flex-field">'
                    +                   '<div class="field-container" data-field=0x197F data-sender="DLA2 Board" '
                    +                       'data-instance=' + parseInt(properties.instance) + ' id="' + amplifier + '">'
                    +                       '&nbsp;'
                    +                   '</div>'
                    +               '</div>'
                    +               '<a class="button button-gray button-medium" name="set-zero" data-instance='
                    +                   properties.instance + '>Set Zero</a>'
                    +           '</div>'
                    +           '<fieldset class="loadcell-advanced">'
                    +               '<legend>Advanced</legend>'
                    +               '<div class="advanced-toggle"><i class="arrow arrow-down"></i></div>'
                    +               '<div class="loadcell-hidden" hidden>'
                    +                   '<div class="flex-row">'
                    +                       '<div class="tooltip-wrapper">'
                    +                           'Zero Offset:'
                    +                           '<div class="tooltip">'
                    +                               '<p>Load cell offset at zero load. This will be set by the &apos;Set Zero&apos; function.</p>' 
                    +                           '</div>'
                    +                       '</div>'
                    +                       '<input type="number" name="zero-value" step=0.01 value=' + (calibrationValues[2] / 100.0) + '>'
                    +                   '</div>'
                    +                   '<div class="flex-row">'
                    +                       '<div class="tooltip-wrapper">'
                    +                           'Calibration Reference:'
                    +                           '<div class="tooltip">'
                    +                               '<p>The value read from the cell when under a fixed calibration load. '
                    +                               'This will be set by the &apos;Set Zero&apos; function.</p>'
                    +                           '</div>'
                    +                       '</div>'
                    +                       '<input type="number" name="calibration-reference" value=' + calibrationValues[1] + '>'
                    +                   '</div>'
                    +               '</div>'
                    +           '</fieldset>'
                    +       '</fieldset>'
                    +   '</div>'
                );
            });
        }

        if (Object.keys(model.DLA3Inputs).length > 0)
        {
            $.each(model.DLA3Inputs, function(amplifier, properties) 
            {
                let calibrationValues = properties.calibration.split(',');
                $('#dla-container').append(
                        '<div class="box top-wrapper">'
                    +       '<fieldset name="loadcell-container" data-type="dla3" data-device="' + amplifier + '" data-instance="' + properties.instance + '">'
                    +           '<legend>'
                    +               '<h2>' + properties.serial + '</h2>'
                    +           '</legend>'
                    +           '<legend class="loadcell-name">'
                    +               '<input type="text" name="display-name" value="' + properties.displayName + '">'
                    +           '</legend>'
                    +           '<div class="flex-row">'
                    +               '<div class="tooltip-wrapper">'
                    +                   'Calibration Value:'
                    +                   '<div class="tooltip">'
                    +                       '<p>Calibration Value or Equivalent Load provided by the load cell manufacturer. '
                    +                       'This is the value that should be output when a fixed calibration '
                    +                       'load is placed on the cell.</p>'
                    +                   '</div>'
                    +               '</div>'
                    +               '<input type="number" name="calibration-value" step=0.001 value=' + calibrationValues[0] + '>'
                    +           '</div>'
                    +           '<div class="flex-row">'
                    +               '<div>'
                    +                   'Calculated Load'
                    +               '</div>'
                    +               '<div class="flex-field">'
                    +                   '<div class="field-container" data-field=0x197f data-sender="DLA3" '
                    +                       'data-instance=' + parseInt(properties.instance) + ' id="dla3-' + properties.instance + '">'
                    +                       '&nbsp;'
                    +                   '</div>'
                    +               '</div>'
                    +               '<a class="button button-gray button-medium" name="set-zero" data-instance='
                    +                   properties.instance + '>Set Zero</a>'
                    +           '</div>'
                    +           '<fieldset class="loadcell-advanced">'
                    +               '<legend>Advanced</legend>'
                    +               '<div class="advanced-toggle"><i class="arrow arrow-down"></i></div>'
                    +               '<div class="loadcell-hidden" hidden>'
                    +                   '<div class="flex-row">'
                    +                       '<div class="tooltip-wrapper">'
                    +                           'Zero Offset:'
                    +                           '<div class="tooltip">'
                    +                               '<p>Load cell offset at zero load. This will be set by the &apos;Set Zero&apos; function.</p>' 
                    +                           '</div>'
                    +                       '</div>'
                    +                       '<input type="number" name="zero-value" step=0.01 value=' + (calibrationValues[2] / 100.0) + '>'
                    +                   '</div>'
                    +                   '<div class="flex-row">'
                    +                       '<div class="tooltip-wrapper">'
                    +                           'Calibration Reference:'
                    +                           '<div class="tooltip">'
                    +                               '<p>The value read from the cell when under a fixed calibration load. '
                    +                               'This will be set by the &apos;Set Zero&apos; function.</p>'
                    +                           '</div>'
                    +                       '</div>'
                    +                       '<input type="number" name="calibration-reference" value=' + calibrationValues[1] + '>'
                    +                   '</div>'
                    +               '</div>'
                    +           '</fieldset>'
                    +       '</fieldset>'
                    +   '</div>'
                );
            });
        }

        if (Object.keys(model.CyclopsInputs).length > 0)
        {
            $.each(model.CyclopsInputs, function(instance, properties) 
            {
                let channels = properties.calibration.split(',');
                let channelGrid = "";
                if (channels.length > 0)
                {
                    channelGrid = '<div class="dla3-grid">';
                    channels.forEach(function(channel, index)
                    {
                        const field = 0x0980 + parseInt(channel);

                        let channelName = "Channel " + channel;
                        if (properties.channelNames.hasOwnProperty(channel))
                        {
                            channelName = properties.channelNames[channel];
                        }

                        channelGrid += '<input type="text" value="' + channelName + '" name="channel-name" data-channel="' + channel + '">'
                            + '<div><div class="field-container" id="dla3' + instance + 'ch' + channel + '" data-field="0x' + field.toString(16) + '" data-sender="DLA3" data-instance="' + instance + '">&nbsp;</div></div>';
                    });
                    channelGrid += '</div>';
                }

                $('#dla-container').append(
                        '<div class="box top-wrapper">'
                    +       '<fieldset name="loadcell-container" data-type="cyclops" data-device="' + properties.serial + '" data-instance="' + instance + '">'
                    +           '<legend>'
                    +               '<h2>' + properties.serial + '</h2>'
                    +           '</legend>'
                    +           '<legend class="loadcell-name">'
                    +               '<input type="text" name="display-name" value="' + properties.displayName + '">'
                    +           '</legend>'
                    +           channelGrid
                    +           '<div class="flex-row redirect-container">'
                    +              '<a class="button button-gray button-medium dla3-redirect">Setup</a>'
                    +           '</div>'
                    +       '</fieldset>'
                    +   '</div>'
                );
            });
        }

        // DCell Amplifiers
        $('#dcell-container').empty();
        if (Object.keys(model.DCellInputs).length > 0)
        {
            $.each(model.DCellInputs, function(amplifier, properties) {
                let calibrationValues = properties.calibration.split(',');
                $('#dcell-container').append(
                        '<div class="box top-wrapper">'
                    +       '<fieldset name="dcell-container" data-device="' + amplifier + '">'
                    +           '<legend>'
                    +               '<h2>' + amplifier.split(':')[0] + '</h2>'
                    +           '</legend>'
                    +           '<div class="flex-row">'
                    +               '<div>'
                    +                   'Raw Input'
                    +               '</div>'
                    +               '<div class="flex-field">'
                    +                   '<div class="field-container" data-field="0x09A0" data-sender="DCell" name="raw-input" '
                    +                       'data-instance=' + parseInt(properties.instance) + ' id="dcell-raw">'
                    +                       '&nbsp;'
                    +                   '</div>'
                    +               '</div>'
                    +               '<a class="button button-gray button-medium" name="set-zero" data-instance='
                    +                   properties.instance + '>Set Zero</a>'
                    +           '</div>'
                    +           '<div class="flex-row">'
                    +               '<div class="tooltip-wrapper">'
                    +                   'Zero Offset:'
                    +                   '<div class="tooltip">'
                    +                       '<p>Load cell offset at zero load. This will be set by the &apos;Set Zero&apos; function.</p>'
                    +                   '</div>'
                    +               '</div>'
                    +               '<input type="number" name="zero-value" step=0.00001 value=' + calibrationValues[0] + '>'
                    +           '</div>'
                    +           '<div class="flex-row">'
                    +               '<div class="tooltip-wrapper">'
                    +                   'mV/V:'
                    +                   '<div class="tooltip">'
                    +                       '<p>milliVolts (from the load pin) per Volt (output from the DCell amplifier).'
                    +                       ' Provided by the load cell manufacturer.</p>'
                    +                   '</div>'
                    +               '</div>'
                    +               '<input type="number" name="calibration-reference" step=0.001 value=' + calibrationValues[1] + '>'
                    +           '</div>'
                    +           '<div class="flex-row">'
                    +               '<div class="tooltip-wrapper">'
                    +                   'Equivalent Load:'
                    +                   '<div class="tooltip">'
                    +                       '<p>Calibration Value or Equivalent Load provided by the load cell manufacturer. '
                    +                       'This is the value that should be output when a fixed calibration '
                    +                       'load is placed on the cell.</p>'
                    +                   '</div>'
                    +               '</div>'
                    +               '<input type="number" name="calibration-value" step=0.001 value=' + calibrationValues[2] + '>'
                    +           '</div>'
                    +           '<div class="flex-row">'
                    +               '<div>'
                    +                   'Calculated Load'
                    +               '</div>'
                    +               '<div class="flex-field">'
                    +                   '<div class="field-container" data-field="0x19A0" data-sender="DCell" '
                    +                       'data-instance=' + parseInt(properties.instance) + ' id="dcell">'
                    +                       '&nbsp;'
                    +                   '</div>'
                    +               '</div>'
                    +           '</div>'
                    +       '</fieldset>'
                    +   '</div>'
                );
            });

            $('#dcell-container').show();
        }
        else
            $('#dcell-container').hide();

        // BAndG N2K Loadcells
        $('#bandg-container').empty();
        if (model.N2kBAndG.length > 0) {
            $('#bandg-container').append(
                '<div class="box top-wrapper">'
            +       '<fieldset name="loadcell-container" id="N2kLoadcellsFieldset">'
            +           '<legend>'
            +               '<h2>N2K Loadcells</h2>'
            +           '</legend>'
            +       '</fieldset>'
            +   '</div>'
            );

            $.each(model.N2kBAndG, function(indexInArray, value) {
                $('#N2kLoadcellsFieldset').append(
                    '<div class="flex-row">'
                +       '<div>'
                +          N2KCyclopsLoadcellStationNames[value]
                +       '</div>'
                +       '<div class="flex-field" style="justify-content: right">'
                +          '<div class="field-container" data-field=0x0981 data-sender="loadcell" '
                +               'data-instance=' + value + ' id="n2k-loadcell-' + indexInArray + '">'
                +               '&nbsp;'
                +           '</div>'
                +       '</div>'
                +   '</div>'
                );
            });
        } else 
            $('#bandg-container').hide();
    }
}