// (C) Copyright 2020 Hewlett-Packard Enterprise Company, L.P.

define(['hp/core/Style', 'jquery', 'lib/excanvas'],
function(style) { "use strict";
  
    /**
     * Draws connection lines for a logical switch.
     * There are four basic sections of code:
     * 1) layout - aligns sizes and determine when to be hp-small
     * 2) cache  - inspect the DOM and stores coordinates to make drawing faster
     * 3) draw   - draw the connection lines based on the cached coordinates
     * 4) mouse  - react to mouse events to update the DOM and cache
     */

    var GraphicRack = (function() {
      
        var RACK_CONTEXT = 'hp-rack-context';
        var RACK = '.hp-rack';
        var CANVAS = 'canvas';
        var PDU = '.hp-power-device';
        var SLOT = '.hp-rack-devices > li';
        var HIGHLIGHT = 'hp-highlight';
        var HAS_HIGHLIGHT = 'hp-has-highlight';
        
        /**
         * Constructor
         */
        function GraphicRack() {
          
            var container = null;
            var rackContext = null;
            var scrollContext = null;
            
            var resizeTimer = null;
            // Cache of style.map()
            var mapStyle;
            
            // cached state for drawing
            var context;
            var pdus = {}; // id: {coords: C2, slots: []}
            var slots = []; // {coords: C2, pdus: []}
            var channelWidth;
            var channelDelta;
            var leftChannels = 0;
            var rightChannels = 0;
            
            // temporary state for drawing
            var hasHighlight = false;
            
            // DRAWING functions
            
            function beginDrawing(highlighted) {
                context.beginPath();
                if (highlighted) {
                    context.lineWidth = mapStyle.highlight.width;
                    context.strokeStyle = mapStyle.highlight.color;
                } else {
                    context.lineWidth = mapStyle.primary.width;
                    context.strokeStyle = mapStyle.primary.color;
                }
            }
            
            function drawPduSlot(channel, slot) {
                var coords = slot.coords;
                
                context.moveTo(channel.x, coords.centerY);
                if (channel.x > coords.right) {
                    context.lineTo(coords.right, coords.centerY);
                } else {
                    context.lineTo(coords.left, coords.centerY);
                }
            }
            
            function drawPdu(pdu) {
                var coords = pdu.coords;
                var channel = pdu.channel;
                var length = pdu.slots.length;
                
                if (length > 0) {
                    beginDrawing(pdu.highlight);
                    
                    if (channel.x > coords.right) {
                        context.moveTo(coords.right, coords.centerY);
                    } else {
                        context.moveTo(coords.left, coords.centerY);
                    }
                    context.lineTo(channel.x, coords.centerY);
                    context.moveTo(channel.x, channel.top);
                    context.lineTo(channel.x, channel.bottom);
                
                    for (var i=0; i<length; i++) {
                        drawPduSlot(pdu.channel, pdu.slots[i]);
                    }
                
                    context.lineCap = 'square';
                    context.stroke();
                }
            }
          
            function draw() {
                var canvas = $(CANVAS, container)[0];
                var highlightedPdus = [];
                var length, pdu;
                
                if (canvas.getContext) {
                    context = canvas.getContext('2d');
                    context.clearRect(0, 0, canvas.width, canvas.height);
                    hasHighlight = rackContext.hasClass(HAS_HIGHLIGHT);
                    
                    // draw connections, first unhighlighted, then highlighted
                    // this is so highlighted ones overlay non highlighted ones
                    $.each(pdus, function (index, pdu) {
                        if (pdu.highlight) {
                            highlightedPdus.push(pdu);
                        } else {
                            drawPdu(pdu);	
                        }
                    });
                    
                    length = highlightedPdus.length;
                    for (i=0; i<length; i++) {
                        pdu = highlightedPdus[i];
                        drawPdu(pdu);
                    }
                    
                    context = null;
                }
            }
            
            // CACHING functions
            
            function itemCoords(item) {
                var containerOffset = rackContext.offset();
                var itemOffset = item.offset();
                var left = itemOffset.left - containerOffset.left;
                var top = itemOffset.top - containerOffset.top;
                var bottom = top + item.outerHeight();
                var right = left + item.outerWidth();
                var centerX = Math.ceil(left + ((right - left) / 2));
                var centerY = Math.ceil(top + ((bottom - top) / 2));
                
                return {left: left, top: top, bottom: bottom, right: right,
                    centerX: centerX, centerY: centerY};
            }
            
            // DEMARCATION
            
            function addLeftChannel(channel, slotCoords) {
                leftChannels += 1;
                var offset = channelDelta * leftChannels;
                channel.x = slotCoords.left - offset;
            }
            
            function addRightChannel(channel, slotCoords) {
                rightChannels += 1;
                var offset = channelDelta * rightChannels;
                channel.x = slotCoords.right + offset;
            }
            
            function demarcateChannel(channel, pduCoords, slotCoords) {
                channel.top = Math.min(channel.top, slotCoords.centerY);
                channel.bottom = Math.max(channel.bottom, slotCoords.centerY);
                if (pduCoords.left > slotCoords.right) {
                    // pdu is on right
                    if (-1 === channel.x) {
                        addRightChannel(channel, slotCoords);
                    }
                } else if (pduCoords.right < slotCoords.left) {
                    // pdu is on left
                    if (-1 === channel.x) {
                        addLeftChannel(channel, slotCoords);
                    }
                } else {
                    // pdu is in rack
                    if (-1 === channel.x) {
                        // put on the side with more room
                        if (leftChannels > rightChannels) {
                            addRightChannel(channel, slotCoords);
                        } else {
                            addLeftChannel(channel, slotCoords);
                        }
                    }
                }
            }
            
            function demarcate() {
                var length = slots.length;
                for (var i=0; i<length; i++) {
                    var slot = slots[i];
                    var length2 = slot.pdus.length;
                    for (var j=0; j<length2; j++) {
                        var pdu = slot.pdus[j];
                        demarcateChannel(pdu.channel, pdu.coords, slot.coords);
                    }
                }
            }
            
            function sizeChannels(pduCoords, slotCoords) {
                if (-1 === channelWidth) {
                    if (slotCoords.left > pduCoords.right) {
                        channelWidth = slotCoords.left - pduCoords.right;
                        channelDelta = Math.ceil(channelWidth / 4);
                    } else if (slotCoords.right < pduCoords.left) {
                        channelWidth = pduCoords.left - slotCoords.right;
                        channelDelta = Math.ceil(channelWidth / 4);
                    }
                }
            }
            
            // CACHE OBJECTS
            
            function createSlot(slotElem) {
                var coords = itemCoords(slotElem);
                var slot = {
                    coords: coords,
                    pdus: [], // for connections
                    // needed for selection highlighting
                    slotElem: slotElem
                };
                slots.push(slot);
                
                // connect power
                var idString = $(slotElem).attr('data-power-id');
                if (idString) {
                    var ids = idString.split(' ');
                    var length = ids.length;
                    for (var i=0; i<length; i++) {
                        var pdu = pdus[ids[i]];
                        if (pdu) {
                            slot.pdus.push(pdu);
                            pdu.slots.push(slot);
                            
                            sizeChannels(pdu.coords, slot.coords);
                        }
                    }
                }
                
                return slot;
            }
            
            function createPdu(pduElem) {
                var coords = itemCoords(pduElem);
                var pdu;
                
                pdu = {
                    id: pduElem.attr('id'),
                    coords: coords,
                    slots: [], // for connections
                    channel: {top: coords.centerY,
                      bottom: coords.centerY,
                      x: -1},
                    // for port selection highlighting
                    pduElem: pduElem,
                    highlight: pduElem.hasClass(HIGHLIGHT)
                };
                pdus[pdu.id] = pdu;
                
                return pdu;
            }
            
            // we cache coordinates to make drawing faster
            function resetCache() {
                pdus = {};
                slots = [];
                leftChannels = 0;
                rightChannels = 0;
                channelWidth = -1;
                
                $(PDU + ':visible', container).each(
                    function (index, pduElem) {
                        createPdu($(pduElem));
                    });
                $(SLOT + ':visible', container).each(
                    function (index, slotElem) {
                        createSlot($(slotElem));
                    });
                    
                demarcate();
            }
            
            // LAYOUT functions
            
            function resetSmall() {
                if (scrollContext && scrollContext.length > 0) {
                    rackContext.removeClass('hp-small');
                    var rack = $(RACK, container).removeClass('hp-small');
                    rack.css('visibility', 'hidden');
                    $(CANVAS, container).hide();
                    if ((scrollContext.outerHeight() + 10) < scrollContext[0].scrollHeight) {
                        rack.addClass('hp-small');
                        rackContext.addClass('hp-small');
                    }
                    rack.css('visibility', '');
                    $(CANVAS, container).show();
                }
            }
          
            function layout() {
                var canvas = $(CANVAS, rackContext)[0];
                
                // IE canvas setup
                if (!canvas.getContext && window.G_vmlCanvasManager) {
                    window.G_vmlCanvasManager.initElement(canvas);
                }

                resetSmall();
                
                $(CANVAS, container).attr('width', rackContext.width()-2).
                    attr('height', rackContext.height()-2);
            }
            
            function reset(full) {
                layout();
                resetCache();
                draw();
            }
            
            // MOUSE EVENTS
            
            function dehighlight() {
                $.each(pdus, function (index, pdu) {
                    pdu.highlight = false;
                });
                
                $(SLOT, container).removeClass(HIGHLIGHT);
                $(PDU, container).removeClass(HIGHLIGHT);
                rackContext.removeClass(HAS_HIGHLIGHT);
                
                draw();
            }
            
            function highlightPdu(pdu) {
                var slot;
                var length = pdu.slots.length;
                pdu.pduElem.addClass(HIGHLIGHT);
                pdu.highlight = true;
                for (var i=0; i<length; i++) {
                    slot = pdu.slots[i];
                    slot.slotElem.addClass(HIGHLIGHT);
                }
                // if this PDU is in the rack, highlight its slot too
                if (pdu.pduElem.parents('ol.hp-rack-devices').length > 0) {
                    pdu.pduElem.parent().addClass(HIGHLIGHT);
                }
                rackContext.addClass(HAS_HIGHLIGHT);
            }
            
            function dehighlightPdu(pdu) {
                var slot;
                var length = pdu.slots.length;
                pdu.pduElem.removeClass(HIGHLIGHT);
                pdu.highlight = false;
                for (var i=0; i<length; i++) {
                    slot = pdu.slots[i];
                    slot.slotElem.removeClass(HIGHLIGHT);
                }
                // if this PDU is in the rack, highlight its slot too
                if (pdu.pduElem.parents('ol.hp-rack-devices').length > 0) {
                    pdu.pduElem.parent().removeClass(HIGHLIGHT);
                }
                rackContext.removeClass(HAS_HIGHLIGHT);
            }
            
            function onEnterPdu(event) {
                var pduElem = $(event.currentTarget);
                var id = pduElem.attr('id');
                var pdu = pdus[id];
                if (pdu) {
                    highlightPdu(pdu);
                    draw();
                }
            }
            
            function onLeavePdu(event) {
                var pduElem = $(event.currentTarget);
                var id = pduElem.attr('id');
                var pdu = pdus[id];
                if (pdu) {
                    dehighlightPdu(pdu);
                    draw();
                }
            }
            
            function pdusForSlotElem(slotElem) {
                var result = [];
                var powerId = slotElem.attr('data-power-id');
                if (powerId) {
                    var pduIds = powerId.split(' ');
                    var length = pduIds.length;
                    for (var i=0; i<length; i++) {
                        result.push(pdus[pduIds[i]]);
                    }
                }
                return result;
            }
            
            function onEnterSlot(event) {
                var slotElem = $(event.currentTarget);
                var slotPdus = pdusForSlotElem(slotElem);
                var length = slotPdus.length;
                for (var i=0; i<length; i++) {
                    var pdu = slotPdus[i];
                    if (pdu) {
                        highlightPdu(pdu);
                        draw();
                    }
                }
            }
            
            function onLeaveSlot(event) {
                var slotElem = $(event.currentTarget);
                var slotPdus = pdusForSlotElem(slotElem);
                var length = slotPdus.length;
                for (var i=0; i<length; i++) {
                    var pdu = slotPdus[i];
                    if (pdu) {
                        dehighlightPdu(pdu);
                        draw();
                    }
                }
            }
            
            function onResize(event) {
                clearTimeout(resizeTimer);
                resizeTimer = setTimeout(function () {
                    reset(false);
                }, 50);
            }
            
            function disableInteractivity() {
                $(container).off({'mouseenter': onEnterSlot,
                    'mouseleave': onLeaveSlot},
                    SLOT);
                $(container).off({'mouseenter': onEnterPdu,
                    'mouseleave': onLeavePdu},
                    PDU);
            }
            
            function enableInteractivity() {
                disableInteractivity(); // always turn off
                $(container).on({'mouseenter': onEnterSlot,
                    'mouseleave': onLeaveSlot},
                    SLOT);
                $(container).on({'mouseenter': onEnterPdu,
                    'mouseleave': onLeavePdu},
                    PDU);
            }
            
            function registerListeners() {
                $(window).on('resize', onResize);
                container.parents('.hp-page').on('relayout', reset);
            }
            
            function unregisterListeners() {
                $(window).off('resize', onResize);
                container.parents('.hp-page').off('relayout', reset);
            }
            
            /**
             * @public
             */
            this.init = function(containerArg, scrollContextArg) {
                mapStyle = style.map();
                container = $(containerArg);
                scrollContext = $(scrollContextArg);
                
                if (container.hasClass(RACK_CONTEXT)) {
                    rackContext = container;
                } else {
                    rackContext = $('.' + RACK_CONTEXT, container);
                }
                rackContext.prepend($('<canvas></canvas>'));
                
                enableInteractivity();
                registerListeners();
                
                reset(true);
            };
            
            this.pause = function () {
                disableInteractivity();
                unregisterListeners();
            };
            
            this.resume = function () {
                enableInteractivity();
                registerListeners();
                reset(true);
            };
            
            this.reset = function () {
                reset(true);
            };
        }

        return GraphicRack;
    }());
    
    return GraphicRack;
});
