define(["backbone", "underscore", "underscore.string", "jquery"], function(Backbone, _, str, $) {
    var SystemFeedback = Backbone.View.extend({
        options: {
            title: "",
            message: "",
            intent: null,  // "warning", "confirmation", "error", "announcement", "step-required", etc
            type: null, // "alert", "notification", or "prompt": set by subclass
            shown: true,  // is this view currently being shown?
            icon: true,  // should we render an icon related to the message intent?
            closeIcon: true,  // should we render a close button in the top right corner?
            minShown: 0,  // length of time after this view has been shown before it can be hidden (milliseconds)
            maxShown: Infinity  // length of time after this view has been shown before it will be automatically hidden (milliseconds)

            /* Could also have an "actions" hash: here is an example demonstrating
               the expected structure. For each action, by default the framework
               will call preventDefault on the click event before the function is
               run; to make it not do that, just pass `preventDefault: false` in
               the action object.

            actions: {
                primary: {
                    "text": "Save",
                    "class": "action-save",
                    "click": function(view) {
                        // do something when Save is clicked
                    }
                },
                secondary: [
                    {
                        "text": "Cancel",
                        "class": "action-cancel",
                        "click": function(view) {}
                    }, {
                        "text": "Discard Changes",
                        "class": "action-discard",
                        "click": function(view) {}
                    }
                ]
            }
            */
        },
        initialize: function() {
            if(!this.options.type) {
                throw "SystemFeedback: type required (given " +
                    JSON.stringify(this.options) + ")";
            }
            if(!this.options.intent) {
                throw "SystemFeedback: intent required (given " +
                    JSON.stringify(this.options) + ")";
            }
            var tpl = $("#system-feedback-tpl").text();
            if(!tpl) {
                console.error("Couldn't load system-feedback template");
            }
            this.template = _.template(tpl);
            this.setElement($("#page-"+this.options.type));
            // handle single "secondary" action
            if (this.options.actions && this.options.actions.secondary &&
                !_.isArray(this.options.actions.secondary)) {
                this.options.actions.secondary = [this.options.actions.secondary];
            }
            return this;
        },
        // public API: show() and hide()
        show: function() {
            clearTimeout(this.hideTimeout);
            this.options.shown = true;
            this.shownAt = new Date();
            this.render();
            if($.isNumeric(this.options.maxShown)) {
                this.hideTimeout = setTimeout(_.bind(this.hide, this),
                    this.options.maxShown);
            }
            return this;
        },
        hide: function() {
            if(this.shownAt && $.isNumeric(this.options.minShown) &&
               this.options.minShown > new Date() - this.shownAt)
            {
                clearTimeout(this.hideTimeout);
                this.hideTimeout = setTimeout(_.bind(this.hide, this),
                    this.options.minShown - (new Date() - this.shownAt));
            } else {
                this.options.shown = false;
                delete this.shownAt;
                this.render();
            }
            return this;
        },
        // the rest of the API should be considered semi-private
        events: {
            "click .action-close": "hide",
            "click .action-primary": "primaryClick",
            "click .action-secondary": "secondaryClick"
        },
        render: function() {
            // there can be only one active view of a given type at a time: only
            // one alert, only one notification, only one prompt. Therefore, we'll
            // use a singleton approach.
            var singleton = SystemFeedback["active_"+this.options.type];
            if(singleton && singleton !== this) {
                singleton.stopListening();
                singleton.undelegateEvents();
            }
            this.$el.html(this.template(this.options));
            SystemFeedback["active_"+this.options.type] = this;
            return this;
        },
        primaryClick: function(event) {
            var actions = this.options.actions;
            if(!actions) { return; }
            var primary = actions.primary;
            if(!primary) { return; }
            if(primary.preventDefault !== false) {
                event.preventDefault();
            }
            if(primary.click) {
                primary.click.call(event.target, this, event);
            }
        },
        secondaryClick: function(event) {
            var actions = this.options.actions;
            if(!actions) { return; }
            var secondaryList = actions.secondary;
            if(!secondaryList) { return; }
            // which secondary action was clicked?
            var i = 0;  // default to the first secondary action (easier for testing)
            if(event && event.target) {
                i = _.indexOf(this.$(".action-secondary"), event.target);
            }
            var secondary = secondaryList[i];
            if(secondary.preventDefault !== false) {
                event.preventDefault();
            }
            if(secondary.click) {
                secondary.click.call(event.target, this, event);
            }
        }
    });
    return SystemFeedback;
});
