Source: components/channellist.js

define('application/components/channellist', [
    'rofl/widgets/component',
    'rofl/widgets/label',
    'rofl/lib/l10n',
    'application/widgets/channellist/grid',
    'rofl/widgets/verticallist',
    'rofl/lib/utils',
    'rofl/events/keyevent',
    'antie/runtimecontext',
    'application/managers/epg',
    'application/models/configuration',
    'application/widgets/clock',
    'rofl/widgets/horizontallist',
    'application/widgets/infoblock',
    'rofl/analytics/web/google',
    'rofl/widgets/container',
    'application/managers/channel',
    'application/widgets/guide/directdial',
    'application/models/epg/item',
    'application/managers/halo',
    'application/constants'
], function (
    Component,
    Label,
    L10N,
    Grid,
    VerticalList,
    Utils,
    KeyEvent,
    RuntimeContext,
    EPGManager,
    ApiConfig,
    Clock,
    HorizontalList,
    InfoBlock,
    GoogleAnalytics,
    Container,
    ChannelManager,
    DirectDial,
    EPGItem,
    HaloManager,
    Constants
) {
    'use strict';

    var l10n = L10N.getInstance(),
        application = RuntimeContext.getCurrentApplication(),
        configuration = application.getConfiguration(),
        epgManager = EPGManager.getInstance(),
        GA = GoogleAnalytics.getInstance(),
        device = RuntimeContext.getDevice(),
        layout = application.getLayout(),
        channelManager = ChannelManager.getInstance(),
        infoblockConfig = {
            id: 'back-on-top-block',
            text: l10n.get('infoblock'),
            classname: ['icon', 'icon-back-v2'],
            position: 'right'
        },
        ChannelList;

    ChannelList = Component.extend({

        /**
         * Initialises the component.
         */
        init: function init () {
            init.base.call(this, 'channel-list');
            this._build();

            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
            this._onSelectedItemChangeBound = Utils.bind(this._onSelectedItemChange, this);
            this._onVisibilityChangeBound = Utils.bind(this._onVisibilityChange, this);
            this._onSelectBound = Utils.bind(this._onSelect, this);
            this._unlockAssetsBound = Utils.bind(this._unlockAssets, this);
            this._lockAssetsBound = Utils.bind(this._lockAssets, this);

            this._enablePrependData = false;
        },

        /**
         * Builds the component.
         *
         * @private
         */
        _build: function () {
            this._buildBackButton();
            this._buildGradient();
            this._buildTitle();
            this._buildSubtitle();
            this._buildClock();
            this._buildList();
            this._buildGrid();
            this._buildInfoBlock();
            this._buildDirectDial();
        },

        /**
         * Builds the top bar list.
         *
         * @private
         */
        _buildBackButton: function () {
            var topBar = this._topBar = new HorizontalList(),
                branding = this._branding = new Container();

            branding.addClass('page-branding');
            branding.addClass('page-title');
            topBar.addClass('topbar');
            topBar.appendChildWidget(branding);
            this.appendChildWidget(topBar);
        },

        /**
         * Builds the top bar list.
         *
         * @private
         */
        _buildGradient: function () {
            var gradient = this._gradient = new Container();

            gradient.addClass('gradient');

            this._topBar.appendChildWidget(gradient);
        },

        /**
         * Builds the direct dial.
         *
         * @private
         */
        _buildDirectDial: function () {
            var directDial = this._directDial = new DirectDial();

            this.appendChildWidget(directDial);
        },

        /**
         * Builds the title.
         *
         * @private
         */
        _buildTitle: function () {
            var title = this._title = new Label({ text: l10n.get('channellist.title'), classNames: ['top-title'] });

            this._branding.appendChildWidget(title);
        },

        /**
         * Builds the subtitle.
         *
         * @private
         */
        _buildSubtitle: function () {
            var subtitle = this._subtitle = new Label({ text: l10n.get('channellist.subtitle'), classNames: ['main-subtitle'] });

            this._topBar.appendChildWidget(subtitle);
        },

        /**
         * Builds the list.
         *
         * @private
         */
        _buildList: function () {
            var list = this._list = new VerticalList();

            this.appendChildWidget(list);
        },

        /**
         * Builds the grid.
         *
         * @private
         */
        _buildGrid: function () {
            var opts = {
                    loadmoreCallback: Utils.bind(this._getChannels, this)
                },
                grid = this._grid = new Grid(opts);

            this._list.appendChildWidget(grid);
        },

        /**
         * Builds the clock.
         *
         * @private
         */
        _buildClock: function () {
            var clock = this._clock = new Clock();

            clock.addClass('header-clock');
            this._topBar.appendChildWidget(clock);
        },

        /**
         * Builds the infoblock.
         *
         * @private
         */
        _buildInfoBlock: function () {
            var infoblock = this._infoblock = new InfoBlock(infoblockConfig);

            infoblock.addClass('info-block');

            this.appendChildWidget(infoblock);
        },

        /**
         * BeforeRender event.
         */
        onBeforeRender: function () {
            this._loadingTime = application.getDate();
        },

        /**
         * Before show event.
         *
         * @param {Event} e - Event.
         */
        onBeforeShow: function (e) {
            var args = e.args || null,
                channelNumber;

            this._endIndex = configuration.loading.channelList.maxChannels;
            this._startChannelsPage = application.getDate();
            this._enablePrependData = false;
            this._channelsLoading = false;
            this._grid.resetStartEndIndex();
            this._grid.resetLoadmoreIndex();

            GA.onPageView('channellist');

            application.hideLoader();

            this._infoblock.hide({skipAnim: true});
            this._topBar.removeClass('active-gradient');
            this._directDial.hide({skipAnim: true});

            this.resetFocus();

            if (!args.lastChannelId) {
                this._getChannels(0, this._endIndex);
            }

            this._grid.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            this.addEventListener('keydown', this._onKeyDownBound);
            this.addEventListener('select', this._onSelectBound);
            document.addEventListener('visibilitychange', Utils.bind(this._onVisibilityChangeBound, this));
            application.addEventListener('$unlocked', this._unlockAssetsBound);
            application.addEventListener('$locked', this._lockAssetsBound);

            if (args.lastChannelId) {
                channelNumber = channelManager.getChannelById(args.lastChannelId).getNumber();
                this._onDirectDial(channelNumber);
            } else {
                this._grid.alignToFirstItem();
            }

            if (Utils.isFunction(args.callback)) {
                args.callback();
            }

            this._updateProgressbars();

            HaloManager.getInstance()
                .getServiceMessage()
                .then(function (r) {
                    if (r) {
                        application.route('service');
                    }
                });
        },

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

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

                gridAssets = this._grid.getCarouselItems();
                Utils.each(gridAssets, Utils.bind(function (gridAsset) {
                    gridAsset.updateProgressBar();
                    dataItem = gridAsset.getDataItem();

                    currentProgramFinished = now >= dataItem.endTime;

                    if (currentProgramFinished) {
                        channelId = dataItem.item.getChannelId();
                        this._setNextProgram(channelId, gridAsset);
                    }
                }, this));
            }

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

        /**
         * Sets the new program for the given channel id.
         *
         * @param {number} channelId - Channel id.
         * @param {Object} currentItem - Current broadcasting program.
         * @private
         */
        _setNextProgram: function (channelId, currentItem) {
            var resultsArray, data;

            epgManager.getCurrentItem(channelId)
                .then(Utils.bind(function (result) {

                    // Create epgData when missing.
                    Utils.each(result, function (val, key) {
                        result[key] = val || this.createInfoNotAvailable({}, key);
                    });
                    resultsArray = Utils.values(result);
                    data = resultsArray[0];

                    if (currentItem.getId() !== (data && data.getId())) {
                        this._grid.updateAsset(data, currentItem);
                    }
                }, this));
        },

        /**
         * Reset grid focus.
         */
        resetFocus: function () {
            this._grid.focus();
            this._grid.setStyleTo('top', '0');
        },

        /**
         * Before hide event.
         */
        onBeforeHide: function () {
            var duration = ((application.getDate() - this._startChannelsPage) / 60000).toFixed(2),
                grid = this._grid;

            grid.removeEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            grid.resetLoadmoreIndex();
            grid.resetStartEndIndex();

            this.removeEventListener('keydown', this._onKeyDownBound);
            this.removeEventListener('select', this._onSelectBound);
            application.removeEventListener('$unlocked', this._unlockAssetsBound);
            application.removeEventListener('$locked', this._lockAssetsBound);

            this._clearProgressInterval();

            GA.onEvent('page', 'time', {
                eventLabel: 'channellist',
                eventValue: duration
            });
        },

        /**
         * Handle KeyEvent.
         *
         * @param {KeyEvent} e - KeyEvent instance.
         * @private
         */
        _onKeyDown: function (e) {
            var activeRowNumber = this._grid.getActiveChildIndex() + 1;

            switch (e.keyCode) {
                case KeyEvent.VK_LEFT:
                    e.preventDefault();
                    e.stopPropagation();
                    application.focusMenu('main');
                    break;
                case KeyEvent.VK_RIGHT:
                    e.preventDefault();
                    e.stopPropagation();
                    break;
                case KeyEvent.VK_BACK:
                    if (activeRowNumber !== 1) {
                        this._grid.resetLoadmoreIndex();
                        this._grid.resetStartEndIndex();
                        this._getChannels(0, this._endIndex);
                        this._grid.alignToFirstItem();
                    } else {
                        application.focusMenu('main');
                    }
                    break;
                case KeyEvent.VK_0:
                case KeyEvent.VK_1:
                case KeyEvent.VK_2:
                case KeyEvent.VK_3:
                case KeyEvent.VK_4:
                case KeyEvent.VK_5:
                case KeyEvent.VK_6:
                case KeyEvent.VK_7:
                case KeyEvent.VK_8:
                case KeyEvent.VK_9:
                    this._onDirectDialInput(e.keyChar);
                    break;
            }
        },

        /**
         * OnDirectDialInput.
         *
         * @param {string} char - The input character.
         * @private
         */
        _onDirectDialInput: function (char) {
            var directDial = this._directDial;

            clearTimeout(this._timer);

            directDial.setKeyInput(char);

            if (!directDial.isVisible()) {
                directDial.show({duration: 200});
            }

            this._clearDirectDialTimeout();
            this._directDialTimeout = setTimeout(Utils.bind(this._onDirectDial, this), 3000);
        },

        /**
         * Gets executed when the direct dial finalises.
         *
         * @param {Number|null} number - The channel number.
         * @private
         */
        _onDirectDial: function (number) {
            var maxChannels = configuration.loading.channelList.maxChannels,
                endIndex = maxChannels,
                grid = this._grid,
                rowIndex = 3,
                itemsBefore = 12,
                itemsAfter = 4,
                reset = true,
                channelIndex,
                startIndex,
                channel,
                inRowIndex;

            number = number || parseInt(this._directDial.getCurrentInput());

            this._enablePrependData = false;

            if (!isNaN(number)) {
                channel = channelManager.getChannelByNumber(number);

                if (channel) {
                    channelIndex = channelManager.getChannelIndex(channel.getId());

                    inRowIndex = channelIndex % 4;

                    startIndex = channelIndex - itemsBefore - inRowIndex;
                    endIndex = startIndex + maxChannels + itemsAfter;

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

                    if (startIndex === 0) {
                        if (channelIndex < 4) {
                            rowIndex = 0;
                        } else if (channelIndex >= 4 && channelIndex < 8) {
                            rowIndex = 1;
                        } else if (channelIndex >= 8 && channelIndex < 12) {
                            rowIndex = 2;
                        }
                    }

                    grid.setRowIndex(rowIndex);
                    grid.setInRowIndex(inRowIndex);
                    grid.setStartIndex(startIndex);
                    grid.setEndIndex(endIndex);

                    grid.setReachedEnd(false);
                    grid.resetLoadmoreIndex();

                    this.resetFocus();
                    this._getChannels(startIndex, endIndex, reset);

                    this._directDial.hide({duration: 200});
                    this._directDial.reset();
                }

            }
        },

        /**
         * Clears the direct dial timeout.
         *
         * @private
         */
        _clearDirectDialTimeout: function () {
            clearTimeout(this._directDialTimeout);
        },

        /**
         * Get data from API.
         *
         * @param {number} start - Start index.
         * @param {number} end - End index.
         * @param {boolean} reset - True if grid should load data as new list with clearing the previous data.
         * @param {boolean} prependData - True if items should prepending to the grid.
         *
         * @private
         */
        _getChannels: function (start, end, reset, prependData) {
            var maxIndex = channelManager.getChannelMap().length - 1,
                grid = this._grid,
                maxChannels = configuration.loading.guide.maxChannels,
                rowIndex = grid.getRowIndex(),
                channels,
                resultsArray,
                lastActiveRow,
                lastActiveRowItem,
                self = this;

            this._channelsLoading = true;

            reset = reset || false;
            prependData = prependData || false;

            application.showLoader(true);

            if (start === 0) {
                grid.setStartIndex(start);
                grid.setEndIndex(end);
            }

            if (start <= 0) {
                start = 0;
                this._enablePrependData = false;
            }

            channels = channelManager.getChannelMap(start, end);

            epgManager.getCurrentItem(channels)
                .then(Utils.bind(function (result) {
                    var value = ((application.getDate() - this._loadingTime) / 1000).toFixed(2);

                    Utils.each(result, function (val, key) {

                        // Create epgData when missing.
                        if (!val) {
                            result[key] = self.createInfoNotAvailable({}, key);
                        }
                    });
                    resultsArray = Utils.values(result);
                    resultsArray.sort(function (a, b) {
                        return a.getChannel().getNumber() > b.getChannel().getNumber() ? 1 : -1;
                    });

                    if (!grid.isVisible()) {
                        grid.show();
                    }

                    if ((start === 0 && !prependData) || reset) {

                        grid.setDataItem({
                            items: resultsArray
                        }, true);

                        if (reset) {
                            if (start + maxChannels >= maxIndex) {
                                grid.setReachedEnd(true);
                            }

                            if (start !== 0) {
                                this._enablePrependData = true;
                            }

                            grid.getList().alignToIndex(rowIndex);
                            grid.getList().getActiveChildWidget().setActiveChildIndex(grid.getInRowIndex());
                        }

                        this._channelsLoading = false;

                    } else {
                        if (prependData) {
                            lastActiveRow = grid.getActiveRow();
                            lastActiveRowItem = lastActiveRow.getActiveChildWidget();

                            grid.prependData(resultsArray, false, false);

                            grid.setActiveChildWidget(lastActiveRow);
                            grid.getActiveChildWidget().setActiveChildWidget(lastActiveRowItem);

                            grid.getList().alignToIndex(grid.getList().getActiveChildIndex());
                            lastActiveRowItem.focus();
                        } else {
                            grid.appendData(resultsArray, false, false);
                        }

                        this._channelsLoading = false;
                    }

                    application.hideLoader();

                    GA.onEvent('page', 'load', {
                        eventLabel: 'channellist',
                        eventValue: value
                    });
                }, this))
                ['catch'](function () {
                application.route('error', {
                    type: 'fullscreen',
                    title: L10N.getInstance().get('errors.cannot_load_page'),
                    button: {id: 'error-close-button', label: L10N.getInstance().get('errors.close')},
                    imgUrl: 'src/assets/images/error-icon.png'
                });
            });
        },

        /**
         * Creates epgData when missing.
         *
         * @param {Object} data - The data.
         * @param {string} channelId - The channel ID.
         * @returns {EpgItem} - New EPGItem.
         */
        createInfoNotAvailable: function (data, channelId) {
            return new EPGItem(data, channelManager.getChannelById(channelId));
        },

        /**
         * Opens player and plays channel.
         *
         * @param {string} data - The data item to attempt to play.
         * @private
         */
        _playAsset: function (data) {

            application.route('liveplayer', {
                data: data,
                type: Constants.LIVE_VIDEO_TYPE,
                channelId: null,
                callingPage: 'channelList'
            });

            GA.onEvent('player', 'started', {eventLabel: 'channellist'});
        },

        /**
         * OnSelect event.
         *
         * @param {Object} e - Event.
         * @private
         */
        _onSelect: function (e) {
            var dataItem = e.target.getDataItem().item;

            if (e.target.id === 'back-on-top-block') {
                this._grid.alignToFirstItem();
                return;
            }

            if (e.target.isLocked()) {
                application.route('parentalpin', {
                    successCallback:
                        Utils.bind(function () {
                            this._playAsset(dataItem);
                            this._unlockAssets();
                        }, this),
                    escapeCallback: Utils.bind(this.focus, this)
                });
                return;
            }

            this._playAsset(dataItem);
        },

        /**
         * Unlock the assets.
         *
         * @private
         */
        _unlockAssets: function () {
            var rows = this._list.getChildWidgetByIndex(0).getList().getChildWidgets(),
                assets;

            Utils.each(rows, function (row) {
                assets = row.getChildWidgets();

                Utils.each(assets, function (asset) {
                    asset.unlock();
                });
            });
        },

        /**
         * Unlock the assets.
         *
         * @private
         */
        _lockAssets: function () {
            var rows = this._list.getChildWidgetByIndex(0).getList().getChildWidgets(),
                assets;

            Utils.each(rows, function (row) {
                assets = row.getChildWidgets();

                Utils.each(assets, function (asset) {
                    asset.lock();
                });
            });
        },

        /**
         * Change elements positon on selected item change.
         *
         * @param {Object} e - The selected item change event.
         * @private
         */
        _onSelectedItemChange: function (e) {
            var activeRowNumber = this._grid.getActiveChildIndex() + 1,
                shouldPrepend = false;

            if (activeRowNumber === 3 && this._enablePrependData) {
                shouldPrepend = true;
            }

            if (activeRowNumber > 1) {
                this._infoblock.show();
            } else {
                this._infoblock.hide();
            }

            if (activeRowNumber > 1) {
                this._gradient.setStyleTo('display', 'block');
            } else {
                this._gradient.setStyleTo('display', 'none');
            }

            if (!this._channelsLoading) {
                this._grid.checkLoadMore(shouldPrepend);
            }
        },

        /**
         * Visibility change event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onVisibilityChange: function (e) {
            if (this.isFocussed() && (document.visibilityState === 'visible' || e === 'visible')) {
                this._updateProgressbars();
            } else {
                this._clearProgressInterval();
            }
        },

        /**
         * Clears any progress interval update.
         *
         * @private
         */
        _clearProgressInterval: function () {
            clearInterval(this._progressInterval);
            this._progressInterval = null;
        }
    });

    return ChannelList;
});