/*======================================================================
 *  History
 *
 *  This is a singleton that keeps track of undoable user actions and
 *  performs undos and redos in response to the browser's Back and
 *  Forward buttons.
 *
 *  Call addAction(action) to register an undoable user action. action
 *  must have 4 fields:
 *
 *      perform: an argument-less function that carries out the action
 *      undo:    an argument-less function that undos the action
 *      label:   a short, user-friendly string describing the action
 *      uiLayer: the UI layer on which the action takes place
 *
 *  By default, the history keeps track of upto 10 actions. You can
 *  configure this behavior by setting
 *      SimileAjax.History.maxHistoryLength
 *  to a different number.
 *
 *  An iframe is inserted into the document's body element to track
 *  onload events.
 *======================================================================
 */

SimileAjax.History = {
    maxHistoryLength:       10,
    historyFile:            "__history__.html",
    enabled:               true,

    _initialized:           false,
    _listeners:             new SimileAjax.ListenerQueue(),

    _actions:               [],
    _baseIndex:             0,
    _currentIndex:          0,

    _plainDocumentTitle:    document.title
};

SimileAjax.History.formatHistoryEntryTitle = function(actionLabel) {
    return SimileAjax.History._plainDocumentTitle + " {" + actionLabel + "}";
};

SimileAjax.History.initialize = function() {
    if (SimileAjax.History._initialized) {
        return;
    }

    if (SimileAjax.History.enabled) {
        var iframe = document.createElement("iframe");
        iframe.id = "simile-ajax-history";
        iframe.style.position = "absolute";
        iframe.style.width = "10px";
        iframe.style.height = "10px";
        iframe.style.top = "0px";
        iframe.style.left = "0px";
        iframe.style.visibility = "hidden";
        iframe.src = SimileAjax.History.historyFile + "?0";

        document.body.appendChild(iframe);
        SimileAjax.DOM.registerEvent(iframe, "load", SimileAjax.History._handleIFrameOnLoad);

        SimileAjax.History._iframe = iframe;
    }
    SimileAjax.History._initialized = true;
};

SimileAjax.History.addListener = function(listener) {
    SimileAjax.History.initialize();

    SimileAjax.History._listeners.add(listener);
};

SimileAjax.History.removeListener = function(listener) {
    SimileAjax.History.initialize();

    SimileAjax.History._listeners.remove(listener);
};

SimileAjax.History.addAction = function(action) {
    SimileAjax.History.initialize();

    SimileAjax.History._listeners.fire("onBeforePerform", [ action ]);
    window.setTimeout(function() {
        try {
            action.perform();
            SimileAjax.History._listeners.fire("onAfterPerform", [ action ]);

            if (SimileAjax.History.enabled) {
                SimileAjax.History._actions = SimileAjax.History._actions.slice(
                    0, SimileAjax.History._currentIndex - SimileAjax.History._baseIndex);

                SimileAjax.History._actions.push(action);
                SimileAjax.History._currentIndex++;

                var diff = SimileAjax.History._actions.length - SimileAjax.History.maxHistoryLength;
                if (diff > 0) {
                    SimileAjax.History._actions = SimileAjax.History._actions.slice(diff);
                    SimileAjax.History._baseIndex += diff;
                }

                try {
                    SimileAjax.History._iframe.contentWindow.location.search =
                        "?" + SimileAjax.History._currentIndex;
                } catch (e) {
                    /*
                     *  We can't modify location.search most probably because it's a file:// url.
                     *  We'll just going to modify the document's title.
                     */
                    var title = SimileAjax.History.formatHistoryEntryTitle(action.label);
                    document.title = title;
                }
            }
        } catch (e) {
            SimileAjax.Debug.exception(e, "Error adding action {" + action.label + "} to history");
        }
    }, 0);
};

SimileAjax.History.addLengthyAction = function(perform, undo, label) {
    SimileAjax.History.addAction({
        perform:    perform,
        undo:       undo,
        label:      label,
        uiLayer:    SimileAjax.WindowManager.getBaseLayer(),
        lengthy:    true
    });
};

SimileAjax.History._handleIFrameOnLoad = function() {
    /*
     *  This function is invoked when the user herself
     *  navigates backward or forward. We need to adjust
     *  the application's state accordingly.
     */

    try {
        var q = SimileAjax.History._iframe.contentWindow.location.search;
        var c = (q.length == 0) ? 0 : Math.max(0, parseInt(q.substr(1)));

        var finishUp = function() {
            var diff = c - SimileAjax.History._currentIndex;
            SimileAjax.History._currentIndex += diff;
            SimileAjax.History._baseIndex += diff;

            SimileAjax.History._iframe.contentWindow.location.search = "?" + c;
        };

        if (c < SimileAjax.History._currentIndex) { // need to undo
            SimileAjax.History._listeners.fire("onBeforeUndoSeveral", []);
            window.setTimeout(function() {
                while (SimileAjax.History._currentIndex > c &&
                       SimileAjax.History._currentIndex > SimileAjax.History._baseIndex) {

                    SimileAjax.History._currentIndex--;

                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];

                    try {
                        action.undo();
                    } catch (e) {
                        SimileAjax.Debug.exception(e, "History: Failed to undo action {" + action.label + "}");
                    }
                }

                SimileAjax.History._listeners.fire("onAfterUndoSeveral", []);
                finishUp();
            }, 0);
        } else if (c > SimileAjax.History._currentIndex) { // need to redo
            SimileAjax.History._listeners.fire("onBeforeRedoSeveral", []);
            window.setTimeout(function() {
                while (SimileAjax.History._currentIndex < c &&
                       SimileAjax.History._currentIndex - SimileAjax.History._baseIndex < SimileAjax.History._actions.length) {

                    var action = SimileAjax.History._actions[SimileAjax.History._currentIndex - SimileAjax.History._baseIndex];

                    try {
                        action.perform();
                    } catch (e) {
                        SimileAjax.Debug.exception(e, "History: Failed to redo action {" + action.label + "}");
                    }

                    SimileAjax.History._currentIndex++;
                }

                SimileAjax.History._listeners.fire("onAfterRedoSeveral", []);
                finishUp();
            }, 0);
        } else {
            var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
            var title = (index >= 0 && index < SimileAjax.History._actions.length) ?
                SimileAjax.History.formatHistoryEntryTitle(SimileAjax.History._actions[index].label) :
                SimileAjax.History._plainDocumentTitle;

            SimileAjax.History._iframe.contentWindow.document.title = title;
            document.title = title;
        }
    } catch (e) {
        // silent
    }
};

SimileAjax.History.getNextUndoAction = function() {
    try {
        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex - 1;
        return SimileAjax.History._actions[index];
    } catch (e) {
        return null;
    }
};

SimileAjax.History.getNextRedoAction = function() {
    try {
        var index = SimileAjax.History._currentIndex - SimileAjax.History._baseIndex;
        return SimileAjax.History._actions[index];
    } catch (e) {
        return null;
    }
};
