Source: widgets/player/miniepg/carousel.js

define('application/widgets/player/miniepg/carousel', [
    'rofl/widgets/container',
    'rofl/widgets/carousel',
    'antie/widgets/carousel/keyhandlers/activatefirsthandler',
    'application/widgets/carousel/strips/culling',
    'application/widgets/carousel/horizontalcarouselaligner',
    'antie/runtimecontext',
    'rofl/lib/utils',
    'application/managers/epg',
    'application/managers/channel',
    'application/widgets/pointerfocusablebutton',
    'application/formatters/homeepgasset',
    'application/widgets/player/miniepg/nextprogram',
    'application/widgets/pointerbutton',
    'rofl/events/keyevent'
], function (
    Container,
    Carousel,
    KeyHandler,
    CullingStrip,
    Aligner,
    RuntimeContext,
    Utils,
    EPGManager,
    ChannelManager,
    Button,
    CurrentProgram,
    NextProgram,
    PointerButton,
    KeyEvent
) {
    'use strict';

    var application = RuntimeContext.getCurrentApplication(),
        layout = application.getLayout().miniepg,
        loadmoreThreshold = 5,
        channelManager = ChannelManager.getInstance(),
        epgManager = EPGManager.getInstance();

    return Container.extend({

        /**
         * Initialises the widget.
         */
        init: function init () {
            var keyHandler = new KeyHandler(),
                aligner,
                carousel;

            init.base.call(this);

            carousel = this._carousel = new Carousel('miniepg-carousel', Carousel.orientations.HORIZONTAL);
            aligner = new Aligner(carousel.getMask());
            carousel.setWidgetStrip(CullingStrip);
            keyHandler.setAnimationOptions({
                duration: 300,
                easing: 'easeInOut',
                fps: 60,
                skipAnim: false
            });

            carousel.setMaskLength(layout.width);
            aligner.setNumberOfItemsVisibleOnScreen(4);
            carousel.setWidgetLengths(layout.itemWidth);
            carousel.setAligner(aligner);
            keyHandler.attach(carousel);

            this.appendChildWidget(carousel);

            this._leftPointer = this.appendChildWidget(new PointerButton(PointerButton.DIRECTIONS.LEFT));
            this._rightPointer = this.appendChildWidget(new PointerButton(PointerButton.DIRECTIONS.RIGHT));
            this._currentProgramFormatter = new CurrentProgram();
            this.setListeners();
        },

        /**
         * Sets the listeners.
         */
        setListeners: function () {
            this._onSelectedItemChangeBound = Utils.bind(this._onSelectedItemChange, this);
            this._onSelectBound = Utils.bind(this._onSelect, this);
            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);

            this.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            this.addEventListener('select', this._onSelectBound);
            this.addEventListener('keydown', this._onKeyDownBound);
        },

        /**
         * SelectedItemChange event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onSelectedItemChange: function (e) {
            var index = e.index;

            if (index === 0) {

                this._leftPointer.disable();
            } else if (index - loadmoreThreshold <= 0 && !this._isLoadingMore) {
                this.loadMore(true);
            } else if (index + loadmoreThreshold >= this._carousel.getChildWidgetCount() - 1 && !this._isLoadingMore) {
                this.loadMore(false);
            } else if (index === this._carousel.getChildWidgetCount() - 1) {
                this._rightPointer.disable();
            } else {
                this._leftPointer.enable();
                this._rightPointer.enable();
            }
        },

        /**
         * Select event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onSelect: function (e) {

            if (e.target instanceof PointerButton) {

                switch (e.target.getDirection()) {
                    case PointerButton.DIRECTIONS.LEFT:
                        this._carousel.alignPrevious();
                        break;
                    case PointerButton.DIRECTIONS.RIGHT:
                        this._carousel.alignNext();
                        break;
                }
            }
        },

        /**
         * Keydown event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onKeyDown: function (e) {
            var carousel = this._carousel;

            if (this._isLoadingInitial) {
                e.preventDefault();
                e.stopPropagation();
                return;
            }

            switch (e.keyCode) {
                case KeyEvent.VK_LEFT:
                    this.setChannel(channelManager.getChannelByNumber(999).getId())
                        .then(Utils.bind(function () {
                            carousel.alignToIndex(carousel.getChildWidgetCount() - 1, {
                                skipAnim: true,
                                onComplete: Utils.bind(function () {
                                    setTimeout(Utils.bind(function () {

                                        carousel.setActiveChildWidget(carousel.getChildWidgetByIndex(carousel.getChildWidgetCount() - 1));
                                    }, this), 100);
                                }, this)
                            });
                        }, this));
                    e.preventDefault();
                    e.stopPropagation();
                    break;

                case KeyEvent.VK_RIGHT:
                    this.setChannel(channelManager.getChannelByNumber(0).getId());
                    e.preventDefault();
                    e.stopPropagation();
                    break;

                // No default.
            }
        },

        /**
         * Loads more data.
         *
         * @param {boolean} prepend - True if the data should be prepended.
         */
        loadMore: function (prepend) {
            var start,
                end,
                channels;

            this._isLoadingMore = true;

            if (prepend) {
                end = channelManager.getChannelIndex(this._lastPrependedIndex);

                if (end === 0) {
                    this._isLoadingMore = false;
                    return;
                }

                start = end - 12;
                channels = channelManager.getChannelMap(start, end).slice().reverse();

                // Last to prepend is now at the end due to reversing array.
                this._lastPrependedIndex = channels[channels.length - 1];
            } else {

                start = channelManager.getChannelIndex(this._lastAppendedIndex) + 1;
                end = start + 12;
                channels = channelManager.getChannelMap(start, end).slice();

                if (channels.length) {
                    this._lastAppendedIndex = channels[channels.length - 1];
                }
            }

            if (channels && channels.length) {
                epgManager.getNowNext(channels)
                    .then(Utils.bind(function (r) {
                        this._appendData(r, prepend, channels);
                        this._isLoadingMore = false;
                    }, this));
            } else {
                this._isLoadingMore = false;
            }
        },

        /**
         * Sets the current channel.
         *
         * @param {number} channel - The current channel.
         * @returns {Object} - Promise.
         */
        setChannel: function (channel) {
            this._isLoadingInitial = true;
            this._lastAppendedIndex = null;
            this._lastPrependedIndex = null;
            this._isLoadingMore = false;
            this._carousel.completeAlignment();
            this._carousel.removeChildWidgets();
            this._carousel._aligner.reset();
            this._carousel.getMask()._lastAlignIndex = null;
            return this._loadData(channel)
                .then(Utils.bind(function () {
                    this._isLoadingInitial = false;
                }, this));
        },

        /**
         * Loads the data for a given channel.
         *
         * @param {number} channel - The channel number.
         * @returns {Object} - Promise.
         * @private
         */
        _loadData: function (channel) {
            var index = channelManager.getChannelIndex(channel),
                start = index - 6,
                end = index + 6,
                alignIndex,
                channels;

            if (start < 0) {
                end -= start;
                start = 0;
            }

            channels = channelManager.getChannelMap(start, end).slice();
            this._lastPrependedIndex = channels[0];
            this._lastAppendedIndex = channels[channels.length - 1];

            return epgManager.getNowNext(channels)
                .then(Utils.bind(function (r) {
                    this._appendData(r, false, channels);

                    alignIndex = channels.indexOf(channel);
                    alignIndex -= 2;

                    if (alignIndex < 0) {
                        alignIndex = 0;
                    }

                    this._carousel.alignToIndex(alignIndex, {
                        skipAnim: true
                    });
                }, this));
        },

        /**
         * Adds the data.
         *
         * @param {Object} data - The data to be added.
         * @param {boolean} prepend - True if the data should be prepended.
         * @param {Array} channels - The channels order in which the data should be added.
         * @private
         */
        _appendData: function (data, prepend, channels) {
            var self = this;

            Utils.each(channels, function (channel) {
                var item = data[channel];

                if (prepend) {

                    self._carousel.insert(0, self._createItem(item.now, item.next), layout.itemWidth);
                } else {
                    self._carousel.append(self._createItem(item.now, item.next), layout.itemWidth);
                }
            });

            if (prepend) {
                this._carousel.alignToIndex(this._carousel.getActiveChildIndex(), {
                    skipAnim: true
                });
            }

            this._updateProgressbars();
        },

        /**
         * Creates a carousel item.
         *
         * @param {Object} now - The current program data.
         * @param {Object} next - The next program data.
         * @returns {Object} - The carousel item.
         * @private
         */
        _createItem: function (now, next) {
            var button = new Button();

            button.setDataItem(now.getChannel().getId());
            button.appendChildWidget(this._currentProgramFormatter.format(now));
            button.appendChildWidget(new NextProgram(next));
            button.loadNext = Utils.bind(this._loadNext, this, button);

            return button;
        },

        /**
         * Loads next item for the given carousel item.
         *
         * @param {Object} button - The carousel item.
         * @private
         */
        _loadNext: function (button) {

            epgManager.getNowNext(button.getDataItem())
                .then(Utils.bind(function (r) {
                    r = r[button.getDataItem()];
                    this._currentProgramFormatter.format(r.now, button.getChildWidgetByIndex(0));
                    button.getChildWidgetByIndex(1).setData(r.next);
                }, this));
        },

        /**
         * Starts the progressbars update interval.
         */
        _updateProgressbars: function () {
            var carouselAssets,
                asset,
                currentProgramFinished,
                now = application.getDate(),
                dataItem;

            if (this._progressInterval) {
                clearInterval(this._progressInterval);

                carouselAssets = this._carousel.getChildWidgets();
                Utils.each(carouselAssets, Utils.bind(function (button) {
                    asset = button.getChildWidgetByIndex(0);
                    dataItem = asset.getDataItem();
                    currentProgramFinished = now >= dataItem.endTime;

                    asset.updateProgressBar();

                    if (currentProgramFinished) {

                        // Nasty check to avoid items that are removed from updating.
                        if (asset.parentWidget && asset.parentWidget.parentWidget) {
                            asset.parentWidget.loadNext();
                        }
                    }
                }, this));
            }

            this._progressInterval = setInterval(Utils.bind(this._updateProgressbars, this), 5000);
        },

        /**
         * Sets focus to the carousel.
         */
        focus: function () {
            this._carousel.focus();
        },

        /**
         * Set miniEPG as focusable or not.
         *
         * @param {boolean} focusable - True to set miniEPG focusable, false not focusable.
         */
        setFocusable: function (focusable) {
            this._isFocusable = focusable;
        },

        /**
         * Determines if the widget is focusable.
         *
         * @returns {boolean} - True if the widget is focusable.
         */
        isFocusable: function isFocusable () {
            return this._isFocusable;
        }
    });
});