Source: components/details/details.js

define('application/components/details/details', [
    'application/components/modals/abstract',
    'application/widgets/detail/tablist',
    'application/widgets/detail/series/header',
    'application/widgets/detail/vod/header',
    'application/widgets/detail/header',
    'application/widgets/player/warningbox',
    'rofl/widgets/verticallist',
    'rofl/widgets/label',
    'rofl/widgets/container',
    'rofl/lib/l10n',
    'rofl/analytics/web/google',
    'application/managers/api',
    'rofl/lib/utils',
    'antie/runtimecontext',
    'antie/events/keyevent',
    'rofl/lib/promise'
], function (
    AbstractModal,
    TabList,
    HeaderSeries,
    HeaderVod,
    HeaderEpg,
    WarningBox,
    VerticalList,
    Label,
    Container,
    L10N,
    GoogleAnalytics,
    ApiManager,
    Utils,
    RuntimeContext,
    KeyEvent,
    Promise
) {
    'use strict';

    var api = ApiManager.getKPNAPI(),
        application = RuntimeContext.getCurrentApplication(),
        device = RuntimeContext.getDevice(),
        l10n = L10N.getInstance(),
        GA = GoogleAnalytics.getInstance(),
        DETAIL_TYPES = {
            vod: 'voddetail',
            series: 'seriesdetail',
            epg: 'epgdetail'
        };

    return AbstractModal.extend({

        /**
         * Initialises the component.
         *
         * @param {string} detail - Detail type to create.
         */
        init: function init (detail) {
            var detailType = this._detailType = DETAIL_TYPES[detail];

            init.base.call(this, detailType);

            this._onKeyDownBound = Utils.bind(this._onKeyDown, this);
            this._onSelectBound = Utils.bind(this._onSelect, this);
            this._lockBound = Utils.bind(this._lock, this);
        },

        /**
         * Builds the component.
         *
         * @private
         */
        _createModal: function () {
            var isSeriesDetail = this._detailType === DETAIL_TYPES.series,
                isEpgDetail = this._detailType === DETAIL_TYPES.epg,
                list = this._list = new VerticalList(),
                tabList = this._tabList = new TabList(),
                warningBox = this._warningBox = new WarningBox(),
                headerComponent = HeaderVod,
                header,
                detailslistbox;

            if (isSeriesDetail) {
                headerComponent = HeaderSeries;
            }

            if (isEpgDetail) {
                headerComponent = HeaderEpg;
            }

            header = this._header = new headerComponent();

            list.addClass('detail-content');
            list.appendChildWidget(header);

            if (this._detailType === DETAIL_TYPES.series || this._detailType === DETAIL_TYPES.vod) {
                list.appendChildWidget(tabList);
            }

            if (this._detailType === DETAIL_TYPES.epg) {
                detailslistbox = this._detailslistbox = new VerticalList();
                detailslistbox.addClass('details-listing');

                list.appendChildWidget(detailslistbox);
            }

            this.appendChildWidget(warningBox);
            this.appendChildWidget(list);

            if (this._detailType === DETAIL_TYPES.vod) {
                this._createPurchaseMessage();
            }
        },

        /**
         * BeforeRender event.
         */
        onBeforeRender: function () {
            if (this._onBingeWatchEventBound) {
                application.addEventListener('$bingewatch', this._onBingeWatchEventBound);
            }

            if (this._onNextEpisodeEventBound) {
                application.addEventListener('$nextepisode', this._onNextEpisodeEventBound);
            }

            if (this._onRecordingChangeBound) {
                application.addEventListener('$record', this._onRecordingChangeBound);
            }

            if (this._onSelectedItemChangeBound) {
                this.addEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            }

            if (this._onRecordDeletedBound) {
                this.addEventListener('recorddeleted', this._onRecordDeletedBound);
            }

            application.addEventListener('$locked', this._lockBound);
            this.addEventListener('keydown', this._onKeyDownBound);
            this.addEventListener('select', this._onSelectBound);
        },

        /**
         * BeforeShow event.
         *
         * @param {Object} e - The event data.
         */
        onBeforeShow: function (e) {
            var data = e.args.data,
                isEpgSeries = e.args.series;

            this._isEpg = this._detailType === DETAIL_TYPES.epg;
            this._isEpgSeries = this._isEpg && isEpgSeries;

            this.parentWidget.setStyleTo('display', 'block');

            e.stopPropagation();
            e.preventDefault();

            // Clear Details.
            this.clear();

            this._callback = e.args.callback || null;
            this._callingPage = e.args.callingPage || null;
            this._userCanWatch = false;
            this.setDataItem(data);

            if (this._warningBox && this._warningBox.isRendered()) {
                this._warningBox.hide({skipAnim: true});
            }

            switch (this._detailType) {
                case DETAIL_TYPES.vod:
                    this._getDetails(data)
                        .then(Utils.bind(this._getUserData, this, data))
                        .then(Utils.bind(this._onDataReady, this));
                    break;
                case DETAIL_TYPES.series:
                    this._getDetails(data)
                        .then(Utils.bind(this._onDataReady, this));
                    break;
                case DETAIL_TYPES.epg:
                    if (isEpgSeries) {
                        this._getDetails(data.getItems()[0])
                            .then(Utils.bind(this._onDataReady, this));
                    } else {
                        this._getDetails(data)
                            .then(Utils.bind(this._loadRelatedContent, this))
                            .then(Utils.bind(this._onDataReady, this));
                    }

                    GA.onPageView('detail');
                    break;

                    // No default.
            }

            application.showLoader();
        },

        /**
         * Requests userdata for the given item.
         *
         * @param {Object} data - Content to get it's detail.
         * @returns {Promise} - Content detail promise.
         * @private
         */
        _getDetails: function (data) {
            var detailsAction = data.getDetailsAction();

            return api
                .read('detail', {
                    params: {
                        endpoint: detailsAction
                    },
                    withCredentials: true
                })
                .then(Utils.bind(this._setContentDetail, this, data))
                ['catch'](Utils.bind(function () {

                // No content detail data, reject promise.
                this._onError(Utils.bind(this._onBack, this));

                return Promise.reject();
            }, this));
        },

        /**
         * Requests userdata for the given item.
         *
         * @param {Object} data - Content to get it's userdata.
         * @param {Object} contentDetail - The data's content detail result.
         * @returns {Promise} - Userdata promise.
         * @private
         */
        _getUserData: function (data, contentDetail) {
            return api.read('userdata', {
                params: {
                    data: data
                },
                withCredentials: true
            })
                .then(Utils.bind(this._processUserData, this, contentDetail[0]));
        },

        /**
         * Sets content detail.
         *
         * @param {Object} data - The content data.
         * @param {Object} contentDetail - The data's content detail result.
         * @returns {Array} - The content detail.
         * @private
         */
        _setContentDetail: function (data, contentDetail) {
            var dataItems;

            if (this._isEpgSeries) {
                dataItems = this.getDataItem().getItems();

                Utils.each(dataItems, function (item) {
                    item._description = contentDetail.getDescription();
                });

                return [contentDetail, dataItems];
            }

            return [contentDetail];
        },

        /**
         * Loads the related content.
         *
         * @param {Object} detail - The detail data.
         * @returns {Array} - Returns the content detail and/or relatedContent.
         * @private
         */
        _loadRelatedContent: function (detail) {
            var detailData = detail[0] || {},
                related = detailData.getRelatedContent && detailData.getRelatedContent();

            if (related) {

                return api.read(related)
                    .then(function (relatedContent) {
                        return [detailData, relatedContent];
                    })
                    ['catch'](function () {
                    return detailData;
                });
            }

            return [detailData];
        },

        /**
         * BeforeHide event.
         */
        onBeforeHide: function () {
            if (this._onBingeWatchEventBound) {
                application.removeEventListener('$bingewatch', this._onBingeWatchEventBound);
            }

            if (this._onNextEpisodeEventBound) {
                application.removeEventListener('$nextepisode', this._onNextEpisodeEventBound);
            }

            if (this._onRecordingChangeBound) {
                application.removeEventListener('$record', this._onRecordingChangeBound);
            }

            if (this._onSelectedItemChangeBound) {
                this.removeEventListener('selecteditemchange', this._onSelectedItemChangeBound);
            }

            if (this._onRecordDeletedBound) {
                this.removeEventListener('recorddeleted', this._onRecordDeletedBound);
            }

            application.removeEventListener('$locked', this._lockBound);
            this.removeEventListener('keydown', this._onKeyDownBound);
            this.removeEventListener('select', this._onSelectBound);
        },

        /**
         * Creates purchase message.
         *
         * @private
         */
        _createPurchaseMessage: function () {
            var messageContainer = this._purchaseMessage = new Container('purchase-message'),
                purchaseMessage = new Label({ text: l10n.get('movies.puchase_message')}),
                icon = new Label({ text: '', classNames: ['icon-info', 'icon'] });

            messageContainer.appendChildWidget(icon);
            messageContainer.appendChildWidget(purchaseMessage);
            this._list.appendChildWidget(messageContainer);
        },

        /**
         * DataReady.
         *
         * @param {Array} results - Data results for details and userdata.
         * @private
         */
        _onDataReady: function (results) {
            var contentDetail = results[0],
                epgSeriesEpisodes = results[1] || [],
                data = {
                    data: this.getDataItem(),
                    contentDetail: contentDetail,
                    epgSeriesEpisodes: epgSeriesEpisodes
                };

            this._contentDetail = contentDetail;

            application.hideLoader();

            this._setHeaderData(data);
            this._setDetailData(data);

            this.show();
        },

        /**
         * Processes user data.
         *
         * @param {Object} contentDetail - The data's content detail result.
         * @param {Object} userData - User data.
         * @returns {Array} - Returns the previously requested content detail.
         * @private
         */
        _processUserData: function (contentDetail, userData) {
            var containers = Utils.getNested(userData, 'resultObj', 'containers'),
                assets,
                asset,
                packages,
                i;

            if (!containers.length) {
                return [contentDetail];
            }

            assets = Utils.getNested(containers[0], 'entitlement', 'assets');

            if (!assets.length) {
                return [contentDetail];
            }

            for (i = 0; i < assets.length; i++) {
                if (assets[i].assetType === 'MASTER') {
                    asset = assets[i];
                    break;
                }
            }

            if (asset) {
                if (Utils.getNested(asset, 'rights') === 'watch') {

                    this._userCanWatch = true;
                } else {

                    packages = Utils.getNested(asset, 'commercialPackages');

                    if (packages && packages.length) {
                        this._price = Utils.getNested(packages[0], 'notRecurring', 'price');
                    }

                }
            }

            return [contentDetail];
        },

        /**
         * KeyDown Event.
         *
         * @param {Object} e - The event data.
         * @private
         */
        _onKeyDown: function (e) {
            var samsungChannelKey = this._handleChannelKey(e);

            if (!samsungChannelKey) {
                e.preventDefault();
                e.stopPropagation();

                switch (e.keyCode) {
                    case KeyEvent.VK_BACK:
                        this._onBack();
                        break;
                }
            }
        },

        /**
         * Determines if Samsung's channel key will be handled by the main app or the component.
         *
         * @param {Object} e - The keyEvent data.
         * @returns {boolean} - True if the channel key will be handled by main app instead of component.
         */
        _handleChannelKey: function (e) {
            var deviceBrand = device.getBrand();

            return (e.keyCode === KeyEvent.VK_CHANNEL_UP || e.keyCode === KeyEvent.VK_CHANNEL_DOWN) &&
                deviceBrand === 'samsung';
        },

        /**
         * Routes to the dropdown.
         *
         * @param {string} seasonTitle - The season title.
         * @private
         */
        _routeToDropdown: function (seasonTitle) {
            var options = [],
                values = [];

            Utils.each(this._contentDetail.getSeasons(), function (season) {
                options.push(season.getTitle());
                values.push({
                    name: season.getTitle(),
                    season: season
                });
            });

            application.route('dropdown', {
                dataOptions: options,
                dataValues: values,
                prevValue: seasonTitle,
                filter: 'genre',
                showGoBackButton: true,
                callback: Utils.bind(this._onDropdownCallback, this)
            });
        },

        /**
         * The Dropdown Callback.
         *
         * @param {Object} e - The callback event.
         * @private
         */
        _onDropdownCallback: function (e) {
            var season = e.target.getItemValueObject().season;

            season.prepare()
                .then(Utils.bind(function (data) {
                    this._season = data;
                    this._tabList.updateTabData(data);
                    this._tabList.getTab(0).alignCarousel();
                }, this));
            this.focus();
        },

        /**
         * Show the player on top of this component.
         *
         * @private
         */
        _showPlayer: function () {
            var main = application.getComponent('main'),
                player = application.getComponent('player');

            main.setStyleTo('display', 'none');
            player.setStyleTo('display', 'block');
            player.getChildWidget('player').setStyleTo('display', 'block');
            this.parentWidget.setStyleTo('display', 'none');
        },

        /**
         * Bingewatch event.
         *
         * @param {Object} e - The bingewatch event data.
         * @private
         */
        _onBingeWatchEvent: function (e) {
            var episode = e.episode,
                index = this._season.getItems().indexOf(episode);

            if (index >= 0) {
                this._tabList.getTab(0).alignCarousel(index, true);
            }
        },

        /**
         * NextEpisode event.
         *
         * @param {Object} e - The next episode event data.
         * @private
         */
        _onNextEpisodeEvent: function (e) {

            // Use the same process for bingewatch.
            this._onBingeWatchEvent(e);
        },

        /**
         * Clears the old elements.
         */
        clear: function () {
            if (this._header) {
                this._header.dispose();
            }

            if (this._detailsList) {
                this._detailsList.dispose();
            }
        },

        /**
         * Locks the widget again.
         *
         * @private
         */
        _lock: function () {

            if (this.parentWidget && this.parentWidget.outputElement && this.parentWidget.outputElement.style.display === 'block') {

                this._onBack();
            } else {
                this._header.clear();
                this.parentWidget.back();
            }
        },

        /**
         * Go back.
         *
         * @private
         */
        _onBack: function () {
            this._header.clear();
            this._list.unsetActiveChildWidget();
            this.parentWidget.back();

            application.getComponent('main').focus();

            if (this._callback) {
                this._callback();
            }
        }
    });
});