// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.
define(['jquery'], function() {
    (function($) {
        
        // ### hpUtilizationMeter
        //
        // Displays a graphical utilization meter.
        //
        // Usage:
        //
        //     $('#my-element').hpUtilizationMeter(options);
        //
        // Options:
        //
        //     utilization: {
        //         current: currently used amount (required)
        //         min: minimum amount            (default=0)
        //         max: minimum amount            (default=100)
        //         currentLabel: label for legend (optional)
        //         totalLabel: label for legend   (optional)
        //         units: units for legend        (optional)
        //         errorThreshold: above this amount will be styled as an error (optional)
        //         warningThreshold: above this amount will be styled as a warning (optional)
        //     }
        //
        //     vertical: true|false    (default=false)
        //     large: true|false       (default=false) whether the graphic should be larger
        //     baseLength: length of utilization bar (default=80)
        //     maxLength: maximum length of the allocation bar before it is truncated (default=120)
        //     units: units for legend (optional)
        //     percentage: true|galse  (default=false) whether to add percentage to the legend
        //     normalizeValue: function (value, units) {
        //         // see UnitConversion.normalizeBytesSigFigs()
        //         return {value: N, units: U}
        //     }
        //
        //     An optional allocation meter anotation can be included.
        //     This is used for situations for allowed oversubscription.
        //
        //     allocation: { (optional)
        //         current:        
        //         min:                    (optional)
        //         max:                    (optional)
        //         errorThreshold:         (optional)
        //         warningThreshold:       (optional)
        //         currentLabel:             (optional)
        //         totalLabel:             (optional)
        //         units:                  (optional)
        //         fitToUtilization: true|false  (default=false) useful when the allocation scale is significantly different from the utilization scale.
        //     }
        // 
        // Deprecated Usage:
        //
        //      $('#my-element').hpUtilizationMeter(current, min, max, errorThreshold);

        $.fn.hpUtilizationMeter = function(firstArg, minArg, maxArg, thresholdArg) {
            
            function normalize(args, context) {
                var data = $.extend({}, args);
                var utilizationBar = context.children().addBack().filter('.hp-utilization-meter');
                
                if (data.hasOwnProperty('large')) {
                    context.toggleClass('hp-large', args.large); 
                }
                data.vertical = data.hasOwnProperty('vertical') ? data.vertical : false;
                context.toggleClass('hp-vertical', data.vertical); // so we can get dimensions
                
                if (data.hasOwnProperty('baseLength')) {
                    data.utilization.unscaledLength = data.baseLength;
                } else if (utilizationBar.width() > 0) {
                    data.utilization.unscaledLength = (data.vertical ?
                        utilizationBar.height() : utilizationBar.width());
                } else {
                    // element isn't in the DOM yet, so has no sizing from CSS, hard code to match for now.
                    data.utilization.unscaledLength = 80;
                }
                data.utilization.length = data.utilization.unscaledLength;
                data.utilization.rangeLength = data.utilization.max - data.utilization.min;
                data.utilization.scale =
                    data.utilization.unscaledLength / data.utilization.rangeLength;
                    
                if (data.hasOwnProperty('allocation')) {
                    data.allocation.rangeLength = data.allocation.max - data.allocation.min;
                    if (data.allocation.fitToUtilization) {
                        data.allocation.unscaledLength = data.utilization.unscaledLength;
                        data.allocation.scale =
                            data.allocation.unscaledLength / data.allocation.rangeLength;
                    } else {
                        data.allocation.scale = data.utilization.scale;
                        data.allocation.unscaledLength =
                            data.allocation.rangeLength * data.utilization.scale;
                    }
                    if (data.hasOwnProperty('maxLength')) {
                        data.allocation.length = Math.min(data.maxLength, data.allocation.unscaledLength);
                    } else {
                        data.allocation.length = data.allocation.unscaledLength;
                    }
                }
                return data;
            }
            
            function validData(barData) {
                return (barData.current >= barData.min && barData.current <= barData.max);
            }
            
            function validThreshold(threshold) {
                return typeof threshold === 'number' && threshold != 0;
            }
            
            function valueLength(value, barData, data) {
                return (value - barData.min) * barData.scale;
            }
            
            function updateThreshold(context, barData, valueBar, data) {
                
                var errorThresholdBar = $('> .hp-threshold-error', context);
                var warningThresholdBar = $('> .hp-threshold-warning', context);
                valueBar.removeClass('hp-ok hp-warning hp-error');
                barData.valueClass = '';
                
                if (validThreshold(barData.errorThreshold)) {
                    var above = barData.current >= barData.errorThreshold;
                    errorThresholdBar.toggleClass('hp-below', above);
                    errorThresholdBar.toggleClass('hp-above', ! above);
                    errorThresholdBar.css(data.vertical ? 'height' : 'width',
                        valueLength(barData.errorThreshold, barData, data) + 'px');
                    
                    context.toggleClass('hp-utilization-meter-extend',
                        barData.errorThreshold > barData.max);
                    if (above) {
                        barData.valueClass = 'hp-error';
                    } else {
                        barData.valueClass = 'hp-ok';
                    }
                } else {
                    errorThresholdBar.removeClass('hp-above hp-below');
                    context.removeClass('hp-utilization-meter-extend');
                }
                
                if (validThreshold(barData.warningThreshold)) {
                    above = barData.current >= barData.warningThreshold
                    warningThresholdBar.toggleClass('hp-below', above);
                    warningThresholdBar.toggleClass('hp-above', ! above);
                    warningThresholdBar.css(data.vertical ? 'height' : 'width',
                        valueLength(barData.warningThreshold, barData, data) + 'px');
                    if (! barData.valueClass !== 'error') {
                        if (above) {
                            barData.valueClass = 'hp-warning';
                        } else {
                            barData.valueClass = 'hp-ok';
                        }
                    }
                } else {
                    warningThresholdBar.removeClass('hp-above hp-below');
                }
                
                if (barData.valueClass) {
                    valueBar.addClass(barData.valueClass);
                }
            }
            
            function updateBar(context, barData, data) {
                if (validData(barData)) {
                    
                    var valueBar = $('> .hp-value-bar', context);
                    context.removeClass('hp-unset');
                    valueBar.toggleClass('hp-at-min', barData.current == barData.min);
                    valueBar.toggleClass('hp-at-max', barData.current == barData.max);
                    valueBar.css(data.vertical ? 'height' : 'width',
                        valueLength(barData.current, barData, data) + 'px');
                    
                    updateThreshold(context, barData, valueBar, data);

                    context.css(data.vertical ? 'height' : 'width', barData.length).
                        toggleClass('hp-over', barData.unscaledLength > barData.length);
                } else {
                    context.addClass('hp-unset');
                    context.css(data.vertical ? 'height' : 'width', barData.length);
                }
                if (data.vertical && data.hasOwnProperty('maxLength')) {
                    context.css('margin-top', (data.maxLength - barData.length) + 'px');
                }
            }
            
            function updateLegend(context, data, aspect) {
                var parts = {value: data[aspect].current, units: data[aspect].units || data.units};
                if (data.normalizeValue) {
                    parts = data.normalizeValue(parts.value, parts.units);
                }
                $('> div.hp-legend .hp-' + aspect + '-value .hp-value', context).
                    text(parts.value);
                $('> div.hp-legend .hp-' + aspect + '-value .hp-units', context).
                    text(parts.units);
                $('> div.hp-legend .hp-' + aspect + '-value .hp-color', context).
                    addClass(data[aspect].valueClass);
                    
                parts.value = data[aspect].max;
                if (data.normalizeValue) {
                    parts = data.normalizeValue(parts.value, parts.units);
                }
                $('> div.hp-legend .hp-' + aspect + '-total .hp-value', context).
                    text(parts.value);
                $('> div.hp-legend .hp-' + aspect + '-total .hp-units', context).
                    text(parts.units);
                
                if (data.percentage) {
                    $('> div.hp-legend .hp-' + aspect + '-percent .hp-value', context).
                        text(Math.floor(data[aspect].current * 100.0 / data[aspect].rangeLength));
                }
            }
            
            function buildBar(context, data) {
                if ($('> .hp-value-bar', context).length === 0) {
                    var valueBar = $('<div></div>').addClass('hp-value-bar');
                    valueBar.append($('<div></div>').addClass('hp-value'));
                    context.append(valueBar);
                }
                    
                if (validThreshold(data.errorThreshold)) {
                    if ($('> .hp-threshold-error', context).length === 0) {
                        var thresholdBar = $('<div></div>').addClass('hp-threshold-error');
                        context.append(thresholdBar);
                    }
                } else {
                    $('.hp-threshold-error', context).remove();
                }
                
                if (validThreshold(data.warningThreshold)) {
                    if ($('> .hp-threshold-warning', context).length === 0) {
                        thresholdBar = $('<div></div>').addClass('hp-threshold-warning');
                        context.append(thresholdBar);
                    }
                } else {
                    $('.hp-threshold-warning', context).remove();
                }
            }
            
            function buildLegendEntry(list, className, label, units) {
                row = $('> li.' + className, list);
                if (row.length === 0) {
                    var item = $('<li></li>').addClass(className);
                    if (label) {
                        item.append($('<span></span>').addClass('hp-color').append($('<div></div>')));
                        item.append($('<span></span>').addClass('hp-name').text(label));
                    }
                    item.append($('<span></span>').addClass('hp-value'));
                    if (units) {
                        item.append($('<span></span>').addClass('hp-units').text(units));
                    }
                    list.append(item);
                }
            }
            
            function buildLegend(elem, args) {
                if (args.utilization.hasOwnProperty('totalLabel')) {
                    $('> .hp-legend', elem).remove();
                    var legend = $('<div></div>').addClass('hp-legend');
                    var list = $('<ol></ol>').addClass('hp-key');
                    legend.append(list);
                    elem.append(legend);
                    
                    var item = $('<li></li>').addClass('hp-utilization-key');
                    var itemList = $('<ol></ol>');
                    item.append(itemList);
                    list.append(item);
                    if (args.utilization.valueLabel) {
                        buildLegendEntry(itemList, 'hp-utilization-value',
                            args.utilization.valueLabel,
                            args.utilization.units || args.units);
                    }
                    if (args.utilization.totalLabel) {
                        buildLegendEntry(itemList, 'hp-utilization-total',
                            args.utilization.totalLabel,
                            args.utilization.units || args.units);
                    }
                    if (args.hasOwnProperty('percentage')) {
                        buildLegendEntry(itemList, 'hp-utilization-percent');
                    }
                    if (args.allocation) {
                        item = $('<li></li>').addClass('hp-allocation-key');
                        itemList = $('<ol></ol>');
                        item.append(itemList);
                        list.append(item);
                        if (args.allocation.valueLabel) {
                            buildLegendEntry(itemList, 'hp-allocation-value',
                                args.allocation.valueLabel,
                                args.allocation.units || args.units,
                                args.percentage);
                        }
                        if (args.allocation.totalLabel) {
                            buildLegendEntry(itemList, 'hp-allocation-total',
                                args.allocation.totalLabel,
                                args.allocation.units || args.units,
                                args.percentage);
                        }
                        if (args.hasOwnProperty('percentage')) {
                            buildLegendEntry(itemList, 'hp-allocation-percent');
                        }
                    }
                } else {
                    $('> .hp-legend', elem).remove();
                }
            }

            function build(elem, args) {
                
                if (args.hasOwnProperty('allocation')) {
                    
                    if (! elem.hasClass('hp-utilization-allocation')) {
                        elem.empty();
                        elem.addClass('hp-utilization-allocation');
                    }
                    var multiMeter = $('> .hp-multi-meter', elem);
                    if (multiMeter.length === 0) {
                        multiMeter = $('<div></div>').addClass('hp-multi-meter');
                        elem.append(multiMeter);
                    }
                    var utilizationBar = $('> .hp-utilization-meter', multiMeter);
                    if (utilizationBar.length === 0) {
                        utilizationBar = $('<div></div>').
                            addClass('hp-utilization-meter hp-unset');
                        multiMeter.append(utilizationBar);
                    }
                    buildBar(utilizationBar, args.utilization);
                    
                    var allocationBar = $('> .hp-allocation-meter', multiMeter);
                    if (allocationBar.length === 0) {
                        allocationBar = $('<div></div>').
                            addClass('hp-allocation-meter hp-unset');
                        multiMeter.append(allocationBar);
                    }
                    buildBar(allocationBar, args.allocation);
                    
                } else {
                    
                    if (elem.hasClass('hp-utilization-allocation')) {
                        $(elem).removeClass('hp-utilization-allocation');
                        elem.empty();
                    }
                    
                    if (! elem.hasClass('hp-utilization-meter')) {
                        elem.empty();
                        elem.addClass('hp-utilization-meter hp-unset');
                    }
                    buildBar(elem, args.utilization);
                }
                
                buildLegend(elem, args);
            }
            
            function update(elem, args) {
                
                var context = $(elem);
                build(context, args);
                
                var data = normalize(args, context);
                
                if (data.hasOwnProperty('allocation')) {
                    updateBar($('.hp-utilization-meter', context), data.utilization, data);
                    updateBar($('.hp-allocation-meter', context), data.allocation, data);
                    updateLegend(context, data, 'utilization');
                    updateLegend(context, data, 'allocation');
                } else {
                    updateBar(context, data.utilization, data);
                    updateLegend(context, data, 'utilization');
                }
            }
            
            if (typeof firstArg === 'number' || undefined === firstArg) {
                firstArg = {utilization: {
                    current: firstArg,
                    min: minArg || 0,
                    max: maxArg || 100,
                    errorThreshold: thresholdArg
                }};
            }

            return this.each(function() {
                update($(this), firstArg);
            });
        };
    }(jQuery));
});
