/**
 * Course export-related js.
 */
define([
    'jquery', 'underscore', 'gettext', 'moment', 'common/js/components/views/feedback_prompt',
    'edx-ui-toolkit/js/utils/html-utils', 'jquery.cookie'
], function($, _, gettext, moment, PromptView, HtmlUtils) {
    'use strict';

    /** ******** Private properties ****************************************/

    var COOKIE_NAME = 'lastexport';

    var STAGE = {
        PREPARING: 0,
        EXPORTING: 1,
        COMPRESSING: 2,
        SUCCESS: 3
    };

    var STATE = {
        READY: 1,
        IN_PROGRESS: 2,
        SUCCESS: 3,
        ERROR: 4
    };

    var courselikeHomeUrl;
    var current = {stage: 0, state: STATE.READY, downloadUrl: null};
    var deferred = null;
    var isLibrary = false;
    var statusUrl = null;
    var successUnixDate = null;
    var timeout = {id: null, delay: 1000};
    var $dom = {
        downloadLink: $('#download-exported-button'),
        stages: $('ol.status-progress').children(),
        successStage: $('.item-progresspoint-success'),
        wrapper: $('div.wrapper-status')
    };

    /** ******** Private functions *****************************************/

    /**
     * Makes Export feedback status list visible
     *
     */
    var displayFeedbackList = function() {
        $dom.wrapper.removeClass('is-hidden');
    };

    /**
     * Updates the Export feedback status list
     *
     * @param {string} [currStageMsg=''] The message to show on the
     *   current stage (for now only in case of error)
     */
    var updateFeedbackList = function(currStageMsg) {
        var $checkmark, $curr, $prev, $next;
        var date, stageMsg, time;

        $checkmark = $dom.successStage.find('.icon');
        stageMsg = currStageMsg || '';

        function completeStage(stage) {
            $(stage)
                .removeClass('is-not-started is-started')
                .addClass('is-complete');
        }

        function errorStage(stage) {
            if (!$(stage).hasClass('has-error')) {
                stageMsg = HtmlUtils.joinHtml(
                    HtmlUtils.HTML('<p class="copy error">'),
                    stageMsg,
                    HtmlUtils.HTML('</p>')
                );
                $(stage)
                    .removeClass('is-started')
                    .addClass('has-error')
                    .find('p.copy')
                    .hide()
                    .after(HtmlUtils.ensureHtml(stageMsg).toString());
            }
        }

        function resetStage(stage) {
            $(stage)
                .removeClass('is-complete is-started has-error')
                .addClass('is-not-started')
                .find('p.error')
                .remove()
                .end()
                .find('p.copy')
                .show();
        }

        switch (current.state) {
        case STATE.READY:
            _.map($dom.stages, resetStage);

            break;

        case STATE.IN_PROGRESS:
            $prev = $dom.stages.slice(0, current.stage);
            $curr = $dom.stages.eq(current.stage);

            _.map($prev, completeStage);
            $curr.removeClass('is-not-started').addClass('is-started');

            break;

        case STATE.SUCCESS:
            date = moment(successUnixDate).utc().format('MM/DD/YYYY');
            time = moment(successUnixDate).utc().format('HH:mm');

            _.map($dom.stages, completeStage);

            $dom.successStage
                    .find('.item-progresspoint-success-date')
                    .text('(' + date + ' at ' + time + ' UTC)');

            break;

        case STATE.ERROR:
            // Make all stages up to, and including, the error stage 'complete'.
            $prev = $dom.stages.slice(0, current.stage + 1);
            $curr = $dom.stages.eq(current.stage);
            $next = $dom.stages.slice(current.stage + 1);

            _.map($prev, completeStage);
            _.map($next, resetStage);
            errorStage($curr);

            break;

        default:
            // Invalid state, don't change anything
            return;
        }

        if (current.state === STATE.SUCCESS) {
            $checkmark.removeClass('fa-square-o').addClass('fa-check-square-o');
            $dom.downloadLink.attr('href', current.downloadUrl);
        } else {
            $checkmark.removeClass('fa-check-square-o').addClass('fa-square-o');
            $dom.downloadLink.attr('href', '#');
        }
    };

    /**
     * Sets the Export in the "error" status.
     *
     * Immediately stops any further polling from the server.
     * Displays the error message at the list element that corresponds
     * to the stage where the error occurred.
     *
     * @param {string} msg Error message to display.
     * @param {int} [stage=current.stage] Stage of export process at which error occurred.
     */
    var error = function(msg, stage) {
        current.stage = Math.abs(stage || current.stage); // Could be negative
        current.state = STATE.ERROR;

        clearTimeout(timeout.id);
        updateFeedbackList(msg);

        deferred.resolve();
    };

    /**
     * Stores in a cookie the current export data
     *
     * @param {boolean} [completed=false] If the export has been completed or not
     */
    var storeExport = function(completed) {
        $.cookie(COOKIE_NAME, JSON.stringify({
            statusUrl: statusUrl,
            date: moment().valueOf(),
            completed: completed || false
        }), {path: window.location.pathname});
    };

    /** ******** Public functions ******************************************/

    var CourseExport = {
        /**
         * Fetches the previous stored export
         *
         * @param {string} contentHomeUrl the full URL to the course or library being exported
         * @return {JSON} the data of the previous export
         */
        storedExport: function(contentHomeUrl) {
            var storedData = JSON.parse($.cookie(COOKIE_NAME));
            if (storedData) {
                successUnixDate = storedData.date;
            }
            if (contentHomeUrl) {
                courselikeHomeUrl = contentHomeUrl;
            }
            return storedData;
        },

        /**
         * Sets the Export on the "success" status
         *
         * If it wasn't already, marks the stored export as "completed",
         * and updates its date timestamp
         */
        success: function() {
            current.state = STATE.SUCCESS;

            if (this.storedExport().completed !== true) {
                storeExport(true);
            }

            updateFeedbackList();

            deferred.resolve();
        },

        /**
         * Entry point for server feedback
         *
         * Checks for export status updates every `timeout` milliseconds,
         * and updates the page accordingly.
         *
         * @param {int} [stage=0] Starting stage.
         */
        pollStatus: function(data) {
            var editUnitUrl = null,
                msg = data;
            if (current.state !== STATE.IN_PROGRESS) {
                return;
            }

            current.stage = data.ExportStatus || STAGE.PREPARING;

            if (current.stage === STAGE.SUCCESS) {
                current.downloadUrl = data.ExportOutput;
                this.success();
            } else if (current.stage < STAGE.PREPARING) { // Failed
                if (data.ExportError) {
                    msg = data.ExportError;
                }
                if (msg.raw_error_msg) {
                    editUnitUrl = msg.edit_unit_url;
                    msg = msg.raw_error_msg;
                }
                error(msg);
                this.showError(editUnitUrl, msg);
            } else { // In progress
                updateFeedbackList();

                $.getJSON(statusUrl, function(result) {
                    timeout.id = setTimeout(function() {
                        this.pollStatus(result);
                    }.bind(this), timeout.delay);
                }.bind(this));
            }
        },

        /**
         * Resets the Export internally and visually
         *
         */
        reset: function(library) {
            current.stage = STAGE.PREPARING;
            current.state = STATE.READY;
            current.downloadUrl = null;
            isLibrary = library;

            clearTimeout(timeout.id);
            updateFeedbackList();
        },

        /**
         * Show last export status from server and start sending requests
         * to the server for status updates
         *
         * @return {jQuery promise}
         */
        resume: function(library) {
            deferred = $.Deferred();
            isLibrary = library;
            statusUrl = this.storedExport().statusUrl;

            $.getJSON(statusUrl, function(data) {
                current.stage = data.ExportStatus;
                current.downloadUrl = data.ExportOutput;

                displayFeedbackList();
                current.state = STATE.IN_PROGRESS;
                this.pollStatus(data);
            }.bind(this));

            return deferred.promise();
        },

        /**
         * Show a dialog giving further information about the details of an export error.
         *
         * @param {string} editUnitUrl URL of the unit in which the error occurred, if known
         * @param {string} errMsg Detailed error message
         */
        showError: function(editUnitUrl, errMsg) {
            var action,
                dialog,
                msg = '';
            if (editUnitUrl) {
                dialog = new PromptView({
                    title: gettext('There has been an error while exporting.'),
                    message: gettext('There has been a failure to export to XML at least one component. ' +
                        'It is recommended that you go to the edit page and repair the error before attempting ' +
                        'another export. Please check that all components on the page are valid and do not display ' +
                        'any error messages.'),
                    intent: 'error',
                    actions: {
                        primary: {
                            text: gettext('Correct failed component'),
                            click: function(view) {
                                view.hide();
                                document.location = editUnitUrl;
                            }
                        },
                        secondary: {
                            text: gettext('Return to Export'),
                            click: function(view) {
                                view.hide();
                            }
                        }
                    }
                });
            } else {
                if (isLibrary) {
                    msg += gettext('Your library could not be exported to XML. There is not enough information to ' +
                        'identify the failed component. Inspect your library to identify any problematic components ' +
                        'and try again.');
                    action = gettext('Take me to the main library page');
                } else {
                    msg += gettext('Your course could not be exported to XML. There is not enough information to ' +
                        'identify the failed component. Inspect your course to identify any problematic components ' +
                        'and try again.');
                    action = gettext('Take me to the main course page');
                }
                msg += ' ' + gettext('The raw error message is:') + ' ' + errMsg;
                dialog = new PromptView({
                    title: gettext('There has been an error with your export.'),
                    message: msg,
                    intent: 'error',
                    actions: {
                        primary: {
                            text: action,
                            click: function(view) {
                                view.hide();
                                document.location = courselikeHomeUrl;
                            }
                        },
                        secondary: {
                            text: gettext('Cancel'),
                            click: function(view) {
                                view.hide();
                            }
                        }
                    }
                });
            }

            // The CSS animation for the dialog relies on the 'js' class
            // being on the body. This happens after this JavaScript is executed,
            // causing a 'bouncing' of the dialog after it is initially shown.
            // As a workaround, add this class first.
            $('body').addClass('js');
            dialog.show();
        },

        /**
         * Starts the exporting process.
         * Makes status list visible and starts showing export progress.
         *
         * @param {string} url The full URL to use to query the server
         *     about the export status
         * @return {jQuery promise}
         */
        start: function(url) {
            current.state = STATE.IN_PROGRESS;
            deferred = $.Deferred();

            statusUrl = url;

            storeExport();
            displayFeedbackList();
            updateFeedbackList();

            return deferred.promise();
        }
    };

    return CourseExport;
});
