(function (define) {
'use strict';
// VideoContextMenu module.
define(
'video/095_video_context_menu.js',
['video/00_component.js'],
function (Component) {
    var AbstractItem, AbstractMenu, Menu, Overlay, Submenu, MenuItem;

    AbstractItem = Component.extend({
        initialize: function (options) {
            this.options = $.extend(true, {
                label: '',
                prefix: 'edx-',
                dataAttrs: {menu: this},
                attrs: {},
                items: [],
                callback: $.noop,
                initialize: $.noop
            }, options);

            this.id = _.uniqueId();
            this.element = this.createElement();
            this.element.attr(this.options.attrs).data(this.options.dataAttrs);
            this.children = [];
            this.delegateEvents();
            this.options.initialize.call(this, this);
        },
        destroy: function () {
            _.invoke(this.getChildren(), 'destroy');
            this.undelegateEvents();
            this.getElement().remove();
        },
        open: function () {
            this.getElement().addClass('is-opened');
            return this;
        },
        close: function () { },
        closeSiblings: function () {
            _.invoke(this.getSiblings(), 'close');
            return this;
        },
        getElement: function () {
            return this.element;
        },
        addChild: function (child) {
            var firstChild = null, lastChild = null;
            if (this.hasChildren()) {
                lastChild = this.getLastChild();
                lastChild.next = child;
                firstChild = this.getFirstChild();
                firstChild.prev = child;
            }
            child.parent = this;
            child.next = firstChild;
            child.prev = lastChild;
            this.children.push(child);
            return this;
        },
        getChildren: function () {
            // Returns the copy.
            return this.children.concat();
        },
        hasChildren: function () {
            return this.getChildren().length > 0;
        },
        getFirstChild: function () {
            return _.first(this.children);
        },
        getLastChild: function () {
            return _.last(this.children);
        },
        bindEvent: function (element, events, handler) {
            $(element).on(this.addNamespace(events), handler);
            return this;
        },
        getNext: function () {
            var item = this.next;
            while (item.isHidden() && this.id !== item.id) { item = item.next; }
            return item;
        },
        getPrev: function () {
            var item = this.prev;
            while (item.isHidden() && this.id !== item.id) { item = item.prev; }
            return item;
        },
        createElement: function () {
            return null;
        },
        getRoot: function () {
            var item = this;
            while (item.parent) { item = item.parent; }
            return item;
        },
        populateElement: function () { },
        focus: function () {
            this.getElement().focus();
            this.closeSiblings();
            return this;
        },
        isHidden: function () {
            return this.getElement().is(':hidden');
        },
        getSiblings: function () {
            var items = [],
                item = this;
            while (item.next && item.next.id !== this.id) {
                item = item.next;
                items.push(item);
            }
            return items;
        },
        select: function () { },
        unselect: function () { },
        setLabel: function () { },
        itemHandler: function () { },
        keyDownHandler: function () { },
        delegateEvents: function () { },
        undelegateEvents: function () {
            this.getElement().off('.' + this.id);
        },
        addNamespace: function (events) {
            return _.map(events.split(/\s+/), function (event) {
                return event + '.' + this.id;
            }, this).join(' ');
        }
    });

    AbstractMenu = AbstractItem.extend({
        delegateEvents: function () {
            this.bindEvent(this.getElement(), 'keydown mouseleave mouseover', this.itemHandler.bind(this))
                .bindEvent(this.getElement(), 'contextmenu', function (event) { event.preventDefault(); });
            return this;
        },

        populateElement: function () {
            var fragment = document.createDocumentFragment();

            _.each(this.getChildren(), function (child) {
                fragment.appendChild(child.populateElement()[0]);
            }, this);

            this.appendContent([fragment]);
            this.isRendered = true;
            return this.getElement();
        },

        close: function () {
            this.closeChildren();
            this.getElement().removeClass('is-opened');
            return this;
        },

        closeChildren: function () {
            _.invoke(this.getChildren(), 'close');
            return this;
        },

        itemHandler: function (event) {
            event.preventDefault();
            var item = $(event.target).data('menu');
            switch(event.type) {
                case 'keydown':
                    this.keyDownHandler.call(this, event, item);
                    break;
                case 'mouseover':
                    this.mouseOverHandler.call(this, event, item);
                    break;
                case 'mouseleave':
                    this.mouseLeaveHandler.call(this, event, item);
                    break;
            }
        },

        keyDownHandler: function () { },
        mouseOverHandler: function () { },
        mouseLeaveHandler: function () { }
    });

    Menu = AbstractMenu.extend({
        initialize: function (options, contextmenuElement, container) {
            this.contextmenuElement = $(contextmenuElement);
            this.container = $(container);
            this.overlay = this.getOverlay();
            AbstractMenu.prototype.initialize.apply(this, arguments);
            this.build(this, this.options.items);
        },

        createElement: function () {
            return $('<ol />', {
                'class': ['contextmenu', this.options.prefix + 'contextmenu'].join(' '),
                'role': 'menu',
                'tabindex': -1
            });
        },

        delegateEvents: function () {
            AbstractMenu.prototype.delegateEvents.call(this);
            this.bindEvent(this.contextmenuElement, 'contextmenu', this.contextmenuHandler.bind(this))
                .bindEvent(window, 'resize', _.debounce(this.close.bind(this), 100));
            return this;
        },

        destroy: function () {
            AbstractMenu.prototype.destroy.call(this);
            this.overlay.destroy();
            this.contextmenuElement.removeData('contextmenu');
            return this;
        },

        undelegateEvents: function () {
            AbstractMenu.prototype.undelegateEvents.call(this);
            this.contextmenuElement.off(this.addNamespace('contextmenu'));
            this.overlay.undelegateEvents();
            return this;
        },

        appendContent: function (content) {
            this.getElement().append(content);
            return this;
        },

        addChild: function () {
            AbstractMenu.prototype.addChild.apply(this, arguments);
            this.next = this.getFirstChild();
            this.prev = this.getLastChild();
            return this;
        },

        build: function (container, items) {
            _.each(items, function(item) {
                var child;
                if (_.has(item, 'items')) {
                    child = this.build((new Submenu(item, this.contextmenuElement)), item.items);
                } else {
                    child = new MenuItem(item);
                }
                container.addChild(child);
            }, this);
            return container;
        },

        focus: function () {
            this.getElement().focus();
            return this;
        },

        open: function () {
            var menu = (this.isRendered) ? this.getElement() : this.populateElement();
            this.container.append(menu);
            AbstractItem.prototype.open.call(this);
            this.overlay.show(this.container);
            return this;
        },

        close: function () {
            AbstractMenu.prototype.close.call(this);
            this.getElement().detach();
            this.overlay.hide();
            return this;
        },

        position: function(event) {
            this.getElement().position({
                my: 'left top',
                of: event,
                collision: 'flipfit flipfit',
                within: this.contextmenuElement
            });

            return this;
        },

        pointInContainerBox: function (x, y) {
            var containerOffset = this.contextmenuElement.offset(),
                containerBox = {
                    x0: containerOffset.left,
                    y0: containerOffset.top,
                    x1: containerOffset.left + this.contextmenuElement.outerWidth(),
                    y1: containerOffset.top + this.contextmenuElement.outerHeight()
                };
            return containerBox.x0 <= x && x <= containerBox.x1 && containerBox.y0 <= y && y <= containerBox.y1;
        },

        getOverlay: function () {
            return new Overlay(
                this.close.bind(this),
                function (event) {
                    event.preventDefault();
                    if (this.pointInContainerBox(event.pageX, event.pageY)) {
                        this.position(event).focus();
                        this.closeChildren();
                    } else {
                        this.close();
                    }

                }.bind(this)
            );
        },

        contextmenuHandler: function (event) {
            event.preventDefault();
            event.stopPropagation();
            this.open().position(event).focus();
        },

        keyDownHandler: function (event, item) {
            var KEY = $.ui.keyCode,
                keyCode = event.keyCode;

            switch (keyCode) {
                case KEY.UP:
                    item.getPrev().focus();
                    event.stopPropagation();
                    break;
                case KEY.DOWN:
                    item.getNext().focus();
                    event.stopPropagation();
                    break;
                case KEY.TAB:
                    event.stopPropagation();
                    break;
                case KEY.ESCAPE:
                    this.close();
                    break;
            }

            return false;
        }
    });

    Overlay = Component.extend({
        ns: '.overlay',
        initialize: function (clickHandler, contextmenuHandler) {
            this.element = $('<div />', {
                'class': 'overlay'
            });
            this.clickHandler = clickHandler;
            this.contextmenuHandler = contextmenuHandler;
        },

        destroy: function () {
            this.getElement().remove();
            this.undelegateEvents();
        },

        getElement: function () {
            return this.element;
        },

        hide: function () {
            this.getElement().detach();
            this.undelegateEvents();
            return this;
        },

        show: function (container) {
            $(container).append(this.getElement());
            this.delegateEvents();
            return this;
        },

        delegateEvents: function () {
            var self = this;
            $(document)
                .on('click' + this.ns, function () {
                    if (_.isFunction(self.clickHandler)) {
                        self.clickHandler.apply(this, arguments);
                    }
                    self.hide();
                })
                .on('contextmenu' + this.ns, function () {
                    if (_.isFunction(self.contextmenuHandler)) {
                        self.contextmenuHandler.apply(this, arguments);
                    }
                });
            return this;
        },

        undelegateEvents: function () {
            $(document).off(this.ns);
            return this;
        }
    });

    Submenu = AbstractMenu.extend({
        initialize: function (options, contextmenuElement) {
            this.contextmenuElement = contextmenuElement;
            AbstractMenu.prototype.initialize.apply(this, arguments);
        },

        createElement: function () {
            var element = $('<li />', {
                'class': ['submenu-item','menu-item', this.options.prefix + 'submenu-item'].join(' '),
                'aria-expanded': 'false',
                'aria-haspopup': 'true',
                'aria-labelledby': 'submenu-item-label-' + this.id,
                'role': 'menuitem',
                'tabindex': -1
            });

            this.label = $('<span />', {
                'id': 'submenu-item-label-' + this.id,
                'text': this.options.label
            }).appendTo(element);

            this.list = $('<ol />', {
                'class': ['submenu', this.options.prefix + 'submenu'].join(' '),
                'role': 'menu'
            }).appendTo(element);

            return element;
        },

        appendContent: function (content) {
            this.list.append(content);
            return this;
        },

        setLabel: function (label) {
            this.label.text(label);
            return this;
        },

        openKeyboard: function () {
            if (this.hasChildren()) {
                this.open();
                this.getFirstChild().focus();
            }
            return this;
        },

        keyDownHandler: function (event) {
            var KEY = $.ui.keyCode,
                keyCode = event.keyCode;

            switch (keyCode) {
                case KEY.LEFT:
                    this.close().focus();
                    event.stopPropagation();
                    break;
                case KEY.RIGHT:
                case KEY.ENTER:
                case KEY.SPACE:
                    this.openKeyboard();
                    event.stopPropagation();
                    break;
            }

            return false;
        },

        open: function () {
            AbstractMenu.prototype.open.call(this);
            this.getElement().attr({'aria-expanded': 'true'});
            this.position();
            return this;
        },

        close: function () {
            AbstractMenu.prototype.close.call(this);
            this.getElement().attr({'aria-expanded': 'false'});
            return this;
        },

        position: function () {
            this.list.position({
                my: 'left top',
                at: 'right top',
                of: this.getElement(),
                collision: 'flipfit flipfit',
                within: this.contextmenuElement
            });
            return this;
        },

        mouseOverHandler: function () {
            clearTimeout(this.timer);
            this.timer = setTimeout(this.open.bind(this), 200);
            this.focus();
        },

        mouseLeaveHandler: function () {
            clearTimeout(this.timer);
            this.timer = setTimeout(this.close.bind(this), 200);
            this.focus();
        }
    });

    MenuItem = AbstractItem.extend({
        createElement: function () {
            var classNames = [
                'menu-item', this.options.prefix + 'menu-item',
                this.options.isSelected ? 'is-selected' : ''
            ].join(' ');

            return $('<li />', {
                'class': classNames,
                'aria-selected': this.options.isSelected ? 'true' : 'false',
                'role': 'menuitem',
                'tabindex': -1,
                'text': this.options.label
            });
        },

        populateElement: function () {
            return this.getElement();
        },

        delegateEvents: function () {
            this.bindEvent(this.getElement(), 'click keydown contextmenu mouseover', this.itemHandler.bind(this));
            return this;
        },

        setLabel: function (label) {
            this.getElement().text(label);
            return this;
        },

        select: function (event) {
            this.options.callback.call(this, event, this, this.options);
            this.getElement()
                .addClass('is-selected')
                .attr({'aria-selected': 'true'});
            _.invoke(this.getSiblings(), 'unselect');
            // Hide the menu.
            this.getRoot().close();
            return this;
        },

        unselect: function () {
            this.getElement()
                .removeClass('is-selected')
                .attr({'aria-selected': 'false'});
            return this;
        },

        itemHandler: function (event) {
            event.preventDefault();
            switch(event.type) {
                case 'contextmenu':
                case 'click':
                    this.select();
                    break;
                case 'mouseover':
                    this.focus();
                    event.stopPropagation();
                    break;
                case 'keydown':
                    this.keyDownHandler.call(this, event, this);
                    break;
            }
        },

        keyDownHandler: function (event) {
            var KEY = $.ui.keyCode,
                keyCode = event.keyCode;

            switch (keyCode) {
                case KEY.RIGHT:
                    event.stopPropagation();
                    break;
                case KEY.ENTER:
                case KEY.SPACE:
                    this.select();
                    event.stopPropagation();
                    break;
            }

            return false;
        }
    });

    // VideoContextMenu() function - what this module 'exports'.
    return function (state, i18n) {

        var speedCallback = function (event, menuitem, options) {
                var speed = parseFloat(options.label);
                state.videoCommands.execute('speed', speed);
            },
            options = {
                items: [{
                    label: i18n['Play'], // jshint ignore:line
                    callback: function () {
                        state.videoCommands.execute('togglePlayback');
                    },
                    initialize: function (menuitem) {
                        state.el.on({
                            'play': function () {
                                menuitem.setLabel(i18n['Pause']); // jshint ignore:line
                            },
                            'pause': function () {
                                menuitem.setLabel(i18n['Play']); // jshint ignore:line
                            }
                        });
                    }
                }, {
                    label: state.videoVolumeControl.getMuteStatus() ? i18n['Unmute'] : i18n['Mute'], // jshint ignore:line
                    callback: function () {
                        state.videoCommands.execute('toggleMute');
                    },
                    initialize: function (menuitem) {
                        state.el.on({
                            'volumechange': function () {
                                if (state.videoVolumeControl.getMuteStatus()) {
                                    menuitem.setLabel(i18n['Unmute']); // jshint ignore:line
                                } else {
                                    menuitem.setLabel(i18n['Mute']); // jshint ignore:line
                                }
                            }
                        });
                    }
                }, {
                    label: i18n['Fill browser'],
                    callback: function () {
                        state.videoCommands.execute('toggleFullScreen');
                    },
                    initialize: function (menuitem) {
                        state.el.on({
                            'fullscreen': function (event, isFullscreen) {
                                if (isFullscreen) {
                                    menuitem.setLabel(i18n['Exit full browser']);
                                } else {
                                    menuitem.setLabel(i18n['Fill browser']);
                                }
                            }
                        });
                    }
                }, {
                    label: i18n['Speed'], // jshint ignore:line
                    items: _.map(state.speeds, function (speed) {
                        var isSelected = speed === state.speed;
                        return {label: speed + 'x', callback: speedCallback, speed: speed, isSelected: isSelected};
                    }),
                    initialize: function (menuitem) {
                        state.el.on({
                            'speedchange': function (event, speed) {
                                var item = menuitem.getChildren().filter(function (item) {
                                    return item.options.speed === speed;
                                })[0];
                                if (item) {
                                    item.select();
                                }
                            }
                        });
                    }
                }
            ]
        };

        $.fn.contextmenu = function (container, options) {
            return this.each(function() {
                $(this).data('contextmenu', new Menu(options, this, container));
            });
        };

        if (!state.isYoutubeType()) {
            state.el.find('video').contextmenu(state.el, options);
        }

        return $.Deferred().resolve().promise();
    };
});

}(RequireJS.define));
