var BFDFileFormat = /BFD_(.+)_(.+)E\.zip$/; // Character 'E' refers to Ethernet Version
var QBDFileFormat = /QBD_(.+)_(.+)E\.zip$/; // Character 'E' refers to Ethernet Version
var BoardFileFormat = /^BD_(.+)_(.+)\.zip$/;
var DisplayFileFormat = /DS_(.+)_(.+)\.zip$/;

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

        this.bfds = new Array();
        this.qbds = new Array();
        this.boards = new Array();
        this.displays = new Array();

        this.bfdFile;
        this.qbdFile;
        this.boardFile;
        this.displayFile;

        this.upgradeSequence = 1;

        this.init();
    }

    init() {
        var async = new Array();
        var self = this;

        async.push($.when( readSection("Boards"), readSection("Display"), getServerMillis() ).done(function(boardJSON, displayJSON, milliseconds) {
            var boards = JSON.parse(boardJSON[0]);
            var displays = JSON.parse(displayJSON[0]);
            var millis = JSON.parse(milliseconds[0]);

            $.each(boards, function(key, value) {
                if (key.toLowerCase() == "main interface board:0" || (validateTimestamp(value) && (millis - value) < maxInactiveTime_ms)) {
                    if (key.toLowerCase().indexOf("dcell") >= 0) {
                        // DCells appear in board section so that they can be displayed on the Loadcell page, but cannot bt updated
                        return;
                    }
                    else if (key.toLowerCase().indexOf("dla2 board") >= 0)
                    {
                        self.boards.push({ device: key.toLowerCase(), instance: key.split(':')[1], type: 'board' });
                        return;
                    }
                    else if (key.toLowerCase().indexOf("dla3") >= 0)
                    {
                        self.boards.push({device: key.toUpperCase(), instance: key.split(':')[1], type: 'board'});
                        return;
                    }

                    //Boards use instance number as instance
                    self.boards.push( {device: key, instance: key.split(':')[1], type: 'board'} );
                }
            });
            $.each(displays, function(key, value) {
                var values = value.split(',');
                if (validateTimestamp(values[2]) && (millis - values[2]) < maxInactiveTime_ms) {
                    if (key.toLowerCase().indexOf("bfd") >= 0) {
                        self.bfds.push( {device: key.toUpperCase(), instance: values[1], type: 'BFD', ipaddress: values[0]} )
                    } else if (key.toLowerCase().indexOf("qbd") >= 0) {
                        self.qbds.push( {device: key.toUpperCase(), instance: values[1], type: 'QBD', ipaddress: values[0]} )
                    } else if (key.toLowerCase().indexOf("atdla2") >= 0) {
                        // Put DLAs in the 'Boards' section, seems to fit better
                        let index;
                        if ((index = self.boards.findIndex(e => (e.device.toLowerCase().indexOf("dla2 board") >= 0 && e.instance == values[1]))) >= 0) {
                            self.boards[index] = { device: key.toUpperCase(), instance: values[1], type: 'board', ipaddress: values[0] };
                        } else {
                            self.boards.push( {device: key.toUpperCase(), instance: values[1], type: 'board', ipaddress: values[0]} );
                        }
                    } 
                    else if (key.toLowerCase().indexOf("atdla3") >= 0)
                    {
                        // DLA3 follows a similar pattern
                        const index = self.boards.findIndex(e => (e.device.toLowerCase().indexOf("dla3") >= 0 && e.instance == values[1]));
                        if (index >= 0)
                        {
                            self.boards[index] = { device: key.toUpperCase(), instance: values[1], type: 'board', ipaddress: values[0] };
                        }
                        else self.boards.push({ device: key.toUpperCase(), instance: values[1], type: 'board', ipaddress: values[0] });
                    }
                    else 
                    {
                        self.displays.push( {device: key.toUpperCase(), instance: values[1], type: 'display', ipaddress: values[0]} );
                    }
                }
                else if (key.toLowerCase().indexOf("atdla2") >= 0)
                {
                    // Also update DLA2s if they have been added from the 'boards' list
                    // This follows a change in November 2021 where the registration message (which updates the 'display' timestamp)
                    // is no longer sent every second. Instead rely on the status message (which updates the 'boards' timestamp).
                    let index;
                    if ((index = self.boards.findIndex(e => (e.device.toLowerCase().indexOf("dla2 board") >= 0 && e.instance == values[1]))) >= 0) {
                        // This board is sending status messages, set its name and IP Address
                        self.boards[index] = { device: key.toUpperCase(), instance: values[1], type: 'board', ipaddress: values[0] };
                    }
                }
            });

            self.boards.sort(function(a, b) {
                return a.device.localeCompare(b.device);
            });
            self.displays.sort(function(a, b) {
                return a.device.localeCompare(b.device);
            });
        }));

        async.push($.ajax({
            type: 'POST',
            url: 'upload.php',
            data: {
                action: 'GetFiles'
            },
            success: function( fileJSON ) {
                var files = JSON.parse(fileJSON);
                var match = null;
                if ((match = files.filter(file => BoardFileFormat.test(file))).length > 0) {
                    self.boardFile = match[0];
                } 
                if ((match = files.filter(file => DisplayFileFormat.test(file))).length > 0) {
                    self.displayFile = match[0];
                }
                if ((match = files.filter(file => BFDFileFormat.test(file))).length > 0) {
                    self.bfdFile = match[0];
                }
                if ((match = files.filter(file => QBDFileFormat.test(file))).length > 0) {
                    self.qbdFile = match[0];
                }
            }
        }));

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

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

    changeFixedDevice(e) {
        $('#fixed-ip-upgrade').attr('data-device', $(e.target).val());
    }

    changeFixedIP(e) {
        $('#fixed-ip-upgrade').attr('data-ipaddress', $(e.target).val());
        $('#fixed-ip-upgrade').attr('data-instance', $(e.target).val().split('.')[3]);
    }

    pollStatus(seq, $button, $progressbar) {
        $.ajax({
            type: 'POST',
            url: '/helpers.php',
            data: {
                action: 'queryProcessor',
                query: '?FieldId=0x5130&SenderId=Loader&SenderInstance=' + seq
            },
            success: function(result) {
                // Parse response as XML
                var xml = result, xmlDoc = $.parseXML( xml ), $xml = $( xmlDoc ), $body = $xml.find( "BODY" );
                var exp = RegExp('0x5130=(.*)');
                var index = $button.index();
                if ( exp.test( $body.text() ) ) {
                    let match = exp.exec($body.text());
                    let status = parseInt(match[1]);
                    console.log('Status=' + status);
                    if ( status >= 0 && status < 101 ) {
                        $progressbar.progressbar("value", status);
                    } else if ( status == 101 ) {
                        $progressbar.progressbar().progressbar("destroy");

                        $button.text('Failed').css('color', 'red');
                        $button.parent().children(':eq(' + (index - 1) + ')').remove();
                        $button.parent().children(':eq(' + (index - 2) + ')').children().unwrap();
    
                        clearInterval($button.attr('data-interval'));
                    } else if ( status == 102 ) {
                        $progressbar.progressbar().progressbar("destroy");

                        $button.text('Success').css('color', 'green');
                        $button.parent().children(':eq(' + (index - 1) + ')').remove();
                        $button.parent().children(':eq(' + (index - 2) + ')').children().unwrap();
                        
                        clearInterval($button.attr('data-interval'));
                    }
                } else {
                    $progressContainer.progressbar().progressbar("destroy");

                    $button.text('Failed').css('color', 'red');
                    $button.parent().children(':eq(' + (index - 1) + ')').remove();
                    $button.parent().children(':eq(' + (index - 2) + ')').children().unwrap();

                    clearInterval($button.attr('data-interval'));
                }
            }
        })
    }

    upgradeBFD(e) {
        var $button = $(e.target);
        var index = $button.index();
        var self = this;

        if (BFDFileFormat.test(self.model.bfdFile))
        {
            // Prompt processor
            $.ajax({
                type: 'POST',
                url: '/helpers.php',
                data: {
                    action: 'queryProcessor',
                    query: 'BFD=UA&display=' + $button.attr('data-device') + '&ipaddress=' + $button.attr('data-ipaddress')
                }
            });

            // Display user message
            $('#dialog-text').text("Update command sent for BFD " + $button.attr('data-device')
                +   ". BFD will restart shortly.");
            $('#dialog').dialog( {
                title: "Update Started",
                buttons: {
                    Ok : function() {
                        $(this).dialog("close");
                    }
                }
            }).dialog( "open" );
        }
        else
        {
            // Display user message
            $('#dialog-text').text("Firmware format is wrong. Firmware file for an Ethernet BFD  " + 
                                   "should have the format BFD_<ver>_<build>E.zip");
            $('#dialog').dialog( {
                title: "Cannot upgrade BFD!",
                buttons: {
                    Ok : function() {
                        $(this).dialog("close");
                    }
                }
            }).dialog( "open" );
        }        
    }

    upgradeQBD(e) {
        var $button = $(e.target);
        var index = $button.index();
        var self = this;

        if (QBDFileFormat.test(self.model.qbdFile))
        {
            // Prompt processor
            $.ajax({
                type: 'POST',
                url: '/helpers.php',
                data: {
                    action: 'queryProcessor',
                    query: 'QBD=UA&display=' + $button.attr('data-device') + '&ipaddress=' + $button.attr('data-ipaddress')
                }
            });

            // Display user message
            $('#dialog-text').text("Update command sent for QBD " + $button.attr('data-device')
                +   ". QBD will restart shortly.");
            $('#dialog').dialog( {
                title: "Update Started",
                buttons: {
                    Ok : function() {
                        $(this).dialog("close");
                    }
                }
            }).dialog( "open" );
        }
        else
        {
            // Display user message
            $('#dialog-text').text("Firmware format is wrong. Firmware file for an Ethernet QBD  " + 
                                   "should have the format QBD_<ver>_<build>E.zip");
            $('#dialog').dialog( {
                title: "Cannot upgrade QBD!",
                buttons: {
                    Ok : function() {
                        $(this).dialog("close");
                    }
                }
            }).dialog( "open" );
        }        
    }

    upgradeDevice(e) {
        var $button = $(e.target);
        var index = $button.index();
        var self = this;

        if ( $button.text().trim() == 'Upgrade' ) {
            var seq = self.model.upgradeSequence++;

            var $progress = $('<div class="firmware-progress"></div>')

            // Select the sibling fields, wrap them in a div and hide. Insert progress bar in their place.
            // $fieldContainer now stores the original fields (wrapper in a div) ready for replacement
            var $fieldContainer = $button.parent().children(':eq(' + (index - 1) + '), :eq(' + (index - 2) + '),'
                + ' :eq(' + (index - 3) + ')').not('.device-name').wrapAll('<div/>').parent().css('display', 'none');
            $progress.insertAfter($fieldContainer);
            // Initialise progress bar
            $progress.progressbar({
                value: 0
            }).show();

            // Prompt processor
            $.ajax({
                type: 'POST',
                url: '/helpers.php',
                data: {
                    action: 'queryProcessor',
                    query: '?Loader=' + $button.attr('data-device')
                        + '&Sequence=' + seq
                        + '&mode=start'
                        + '&SenderInstance=' + $button.attr('data-instance')
                        + ($button.attr('data-type') == 'board' ? '' : ('&ipaddress=' + $button.attr('data-ipaddress')))
                }
            });
            $button.attr('data-sequence', seq);
            $button.text('Cancel');

            // Poll upgrade status
            var interval = setInterval(self.pollStatus.bind(self, seq, $button, $progress), 1000);
            $button.attr('data-interval', interval);
        } else if ( $button.text().trim() == 'Cancel' ) {
            // Prompt processor
            $.ajax({
                type: 'POST',
                url: '/helpers.php',
                data: {
                    action: 'queryProcessor',
                    query: '?Loader=' + $button.attr('data-device')
                        + '&Sequence=' + $button.attr('data-sequence')
                        + '&mode=stop'
                }
            });
            $button.parent().children(':eq(' + (index - 1) + ')').progressbar().progressbar("destroy")
            $button.parent().children(':eq(' + (index - 1) + ')').remove();
            $button.parent().children(':eq(' + (index - 2) + ')').children().unwrap();
            $button.text('Upgrade');
            // Stop polling
            clearInterval($button.attr('data-interval'));
        } else {
            $button.text( 'Upgrade' ).css('color', '');
        }
    }

    uploadFile(e) {
        e.preventDefault();

        // Check file has been selected
        if ( $('#file-to-upload').prop('files').length == 0 ) {
            $('#dialog-text').text('Please select a firmware file to upload.');
            $('#dialog').dialog( {
                title: "No File",
                buttons: {
                    Ok : function() {
                        $(this).dialog("close");
                    }
                }
            }).dialog( "open" );
        } else {
            // Validate file
            var fileName = $('#file-to-upload').prop('files')[0]['name'];
            var fileType = null;

            if ( BoardFileFormat.test(fileName) ) {
                fileType = "Board";
                this.model.boardFile = fileName;
            } else if ( DisplayFileFormat.test(fileName) ) {
                fileType = "Display";
                this.model.displayFile = fileName;
            } else if ( BFDFileFormat.test(fileName) ) {
                fileType = "BFD";
                this.model.bfdFile = fileName;
            } else if ( QBDFileFormat.test(fileName) ) {
                fileType = "QBD";
                this.model.qbdFile = fileName;
            } else {
                $('#dialog-text').text("Upload must be a board firmware file with format BD_<ver>_<build>.zip"
                    + " or a display firmware file with format DS_<ver>_<build>.zip or an Ethernet BFD or QBD firmware file"
                    + " with format BFD_<ver>_<build>E.zip or QBD_<ver>_<build>E.zip");
                $('#dialog').dialog( {
                    title: "Invalid File",
                    buttons: {
                        Ok : function() {
                            $(this).dialog("close");
                        }
                    }
                }).dialog( "open" );
                
                return;
            }

            // Upload to processor
            var formData = new FormData();
            formData.append('fileToUpload', $('#file-to-upload').prop('files')[0]);
            formData.append('action', 'Software');

            $.ajax( {
                url: 'upload.php',
                type: 'POST',
                data: formData,
                processData: false,
                contentType: false,
                cache: false
            }).done(function() {
                if ( fileType == "Board" ) {
                    // Show file available
                    $('#file-check-board').show();
                    $('#file-container-board').text(fileName);
                    $('#board-container').find('a[name="upgrade-button"][data-type="board"]').css('visibility', 'visible');
                    writeSingle("Board Update", "UpdateFile", fileName, 'Firmware');
                } else if ( fileType == "Display" ) {
                    $('#file-check-display').show();
                    $('#file-container-display').text(fileName);
                    $('#display-container').find('a[name="upgrade-button"][data-type="display"]').css('visibility', 'visible');
                    writeSingle("Display Update", "UpdateFile", fileName, 'Firmware');
                } else if ( fileType == "BFD" ) {
                    $('#file-check-bfd').show();
                    $('#file-container-bfd').text(fileName);
                    $('#display-container').find('a[name="bfd-upgrade-button"][data-type="BFD"]').css('visibility', 'visible');
                    writeSingle("BFD Update", "UpdateFile", fileName, 'Firmware');
                } else if ( fileType == "QBD" ) {
                    $('#file-check-qbd').show();
                    $('#file-container-qbd').text(fileName);
                    $('#display-container').find('a[name="qvd-upgrade-button"][data-type="QBD"]').css('visibility', 'visible');
                    writeSingle("QBD Update", "UpdateFile", fileName, 'Firmware');
                }

                // Inform processor of new firmware file
                $.ajax({
                    type: 'POST',
                    url: 'helpers.php',
                    data: {
                        action: 'queryProcessor',
                        query: '?Firmware=' + fileName
                    }
                });
            });
        }
    }
}

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

    bindEvents() {
        super.bindEvents();

        $('#upload-form').on('submit', this.controller.uploadFile.bind(this.controller));
        $(document).on('click', '[name="upgrade-button"]', this.controller.upgradeDevice.bind(this.controller));
        $(document).on('click', '[name="bfd-upgrade-button"]', this.controller.upgradeBFD.bind(this.controller));
        $(document).on('click', '[name="qbd-upgrade-button"]', this.controller.upgradeQBD.bind(this.controller));
        $('#display-container').on('change', '#device-select', this.controller.changeFixedDevice.bind(this.controller));
        $('#display-container').on('change', '#ip-input', this.controller.changeFixedIP.bind(this.controller));
    }

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

        // Draw boards
        var $boardContainer = $('#board-container');
        $boardContainer.empty();
        $.each(model.boards, function(index, value) {
            $boardContainer.append(
                    '<div class="device-name">' + value.device + '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="board' + index + '-status" data-field="0x5131"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="board' + index + '-version" data-field="0xff00"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="board' + index + '-bootloader" data-field="0x5132"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<a class="button button-medium button-gray" name="upgrade-button" '
                +       'data-type="' + value.type + '" data-device="' + value.device + '" '
                +       'data-instance="' + value.instance + '">Upgrade</a>'
            );
        });

        var $displayContainer = $('#display-container');
        $displayContainer.empty();
        // Fixed IP Row
        $displayContainer.append(
                '<div class="tooltip-wrapper device-name">'
            +       'Fixed IP Address'
            +       '<div class="tooltip">'
            +           '<p>Used to upgrade a display on a specific IP Address.'
            +           'This is inteded as a recovery tool and should not be needed in general</p>'
            +       '</div>'
            +   '</div>'
            +   '<select id="device-select">'
            +       '<option value="AT3020XXX">3020</option>'
            +       '<option value="ATANA1XXX">Analogue</option>'
            +       '<option value="ATMFD1XXX" selected>MFD</option>'
            +       '<option value="ATDLA2XXX">DLA2</option>'
            +       '<option value="ATPHD1XXX">PHD</option>'
            +   '</select>'
            +   '<input id="ip-input" value="172.16.32.1" data-inputmask="&#39;alias&#39; : &#39;ip&#39;">'
            +   '<a class="button button-medium button-gray" name="upgrade-button" id="fixed-ip-upgrade" '
            +       'data-type="display" data-device="ATMFD1XXX" data-instance="1" '
            +       'data-ipaddress="172.16.32.1">Upgrade</a>'
        );
        $('#ip-input').inputmask({ rightAlign: true });

        $.each(model.displays, function(index, value) {
            $displayContainer.append(
                    '<div class="device-name">' + value.device + '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="display' + index + '-status" data-field="0x5131"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="display' + index + '-version" data-field="0xff00"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="display' + index + '-bootloader" data-field="0x5132"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<a class="button button-medium button-gray" name="upgrade-button" '
                +       'data-type="' + value.type + '" data-device="' + value.device + '" '
                +       'data-instance="' + value.instance + '" data-ipaddress="' + value.ipaddress + '">Upgrade</a>'
            );
        });
        $.each(model.bfds, function(index, value) {
            $displayContainer.append(
                    '<div class="device-name">' + value.device + '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="bfd' + index + '-status" data-field="0x5131"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="bfd' + index + '-version" data-field="0xff00"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="bfd' + index + '-bootloader" data-field="0x5132"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<a class="button button-medium button-gray" name="bfd-upgrade-button" '
                +       'data-type="' + value.type + '" data-device="' + value.device + '" '
                +       'data-instance="' + value.instance + '" data-ipaddress="' + value.ipaddress + '">Upgrade</a>'
            );
        });
        $.each(model.qbds, function(index, value) {
            $displayContainer.append(
                    '<div class="device-name">' + value.device + '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="qbd' + index + '-status" data-field="0x5131"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="qbd' + index + '-version" data-field="0xff00"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<div class="flex-field">'
                +       '<div class="field-container" id="qbd' + index + '-bootloader" data-field="0x5132"'
                +           'data-sender="' + value.device + '" data-instance="' + value.instance + '">'
                +           '&nbsp;'
                +       '</div>'
                +   '</div>'
                +   '<a class="button button-medium button-gray" name="qbd-upgrade-button" '
                +       'data-type="' + value.type + '" data-device="' + value.device + '" '
                +       'data-instance="' + value.instance + '" data-ipaddress="' + value.ipaddress + '">Upgrade</a>'
            );
        });

        if (model.bfdFile !== undefined) {
            $('#file-container-bfd').text(model.bfdFile);
            $('#display-container').find('a[name="bfd-upgrade-button"][data-type="BFD"]').css('visibility', 'visible');
        }
        if (model.qbdFile !== undefined) {
            $('#file-container-qbd').text(model.qbdFile);
            $('#display-container').find('a[name="qbd-upgrade-button"][data-type="QBD"]').css('visibility', 'visible');
        }
        if (model.boardFile !== undefined) {
            $('#file-container-board').text(model.boardFile);
            $('#board-container').find('a[name="upgrade-button"][data-type="board"]').css('visibility', 'visible');
        }
        if (model.displayFile !== undefined) {
            $('#file-container-display').text(model.displayFile);
            $('#display-container').find('a[name="upgrade-button"][data-type="display"]').css('visibility', 'visible');
        }
    }
}