define([], function () {

    /**
     * Undo/Save object
     * @class Undo
     * @constructor
     * @property {Array} history - array of actions performed since last save.
     */
    var Undo = function () {
        'use strict';
        this.model = new Undo.Model();
        this.view = new Undo.View();
        this.model.setView(this.view);
        this.view.setModel(this.model);
    };

    Undo.Model = function () {
        'use strict';
        this.view = null;
        /**
         * Array of actions
         * @private
         * @type {Array}
         */
        this.history = [];

        this.init();
    };

    Undo.Model.prototype = (function () {
        'use strict';
        /**
         * Undo right change
         * @private
         * @memberOf Undo
         * @param {Object} revert - Undo history object
         * @returns {Object} Undo
         */
        var undoRightChange = function (revert) {
            var group = Instance.Groups.collection.getByGroupId(revert.groupId);
            _.each(group.model.rights, function (rightCollection) {
                if (rightCollection.collection.ruleId === revert.ruleId) {
                    var right = rightCollection.collection.getByBitmaskAndMode(revert.bitmask, revert.right);
                    right.model.right = revert.original;
                }
            });
        };

        /**
         * Undo a change of a whole row (for a certain mode)
         * @param {Object} revert - Undo history object
         * @memberOf Undo
         */
        var undoAllChange = function(revert){
            //pop off and reverse individual cell changes
            for(var i = 0; i<revert.depth; ++i)
                Instance.Undo.model.innerUndo();
            //find and change back "all" cell
            var rightSets = Instance.Groups.collection.getByGroupId(revert.groupId).model.rights;
            _.each(rightSets, function(rights){
                if(parseInt(rights.collection.ruleId, 10) === parseInt(revert.ruleId, 10)){
                    var allCell = rights.collection.getByBitmaskAndMode(-1, revert.right);
                    allCell.model.right = revert.original;
                }
            });
        };

        /**
         * Undo re-ordering of rules
         * @private
         * @memberOf Undo
         * @param {Object} revert - Undo history object
         * @returns {Object} Undo
         */
        var undoMove = function (revert) {
            //in these next few lines, we get the group model, find the rows that changed,
            //and then update it accordingly (the is updated automatically)
            var currentPlace = revert.current.indexOf(revert.ruleId);
            var originalPlace = revert.original.indexOf(revert.ruleId);
            var group = Instance.Groups.collection.getByGroupId(revert.groupId);
            var right = group.model.rights[currentPlace];
            group.model.rights.splice(currentPlace, 1);
            group.model.rights.splice(originalPlace, 0, right);
        };

        /**
         * Undo rule delete
         * @private
         * @memberOf Undo
         * @param {Object} revert - Undo history object
         */
        var undoDeleteRule = function (revert) {
            var group = Instance.Groups.collection.getByGroupId(revert.groupId);
            var rule = Instance.Rules.collection.getByRuleId(revert.ruleId);
            //remove the row from the model -- this automatically updates the view
            group.model.rights.splice(revert.rightIndex, 0, revert.rights);
        };

        /**
         * Undo rule add
         * @private
         * @memberOf Undo
         * @param {Object} revert - Undo history object
         */
        var undoAddRule = function (revert) {
            var group = Instance.Groups.collection.getByGroupId(revert.groupId);
            var ruleIndex = _.findIndex(group.model.rights, function (right) {
                return parseInt(right.collection.ruleId, 10) === parseInt(revert.ruleId, 10);
            });
            group.model.rights.splice(ruleIndex, 1);
        };

        /**
         * Undo edit rule
         * @private
         * @memberOf Undo
         * @param {Object} revert - Undo history object
         */
        var undoEditRule = function (revert) {
            var group = Instance.Groups.collection.getByGroupId(revert.groupId);
            var ruleIndex = _.findIndex(group.model.rights, function (right) {
                return parseInt(right.collection.ruleId, 10) === parseInt(revert.ruleId, 10);
            });
            group.model.rights.splice(ruleIndex, 1, revert.fieldRights);
        };

        /**
         * Undo rights/rules copy
         * @private
         * @memberOf Undo
         * @param {Object} revert - Undo history object
         */
        var undoGroupCopy = function (revert) {
            var group = Instance.Groups.collection.getByGroupId(revert.targetGroupId);
            group.model.rights = revert.targetRights;
        };

        /**
         * Revert the last item in the history array
         * @private
         * @memberOf Undo
         * @returns {Object} Undo
         */
        var addHistory = function (obj) {
            this.history.push(obj);
            this.view.toggleVisibility();
            return this;
        };

        /**
         * Undo last actioon in history
         * @private
         * @memberOf Undo
         */
        var undo = function () {
            //actuall undo the action
            var needsRecalc = innerUndo();
            Instance.Undo.view.toggleVisibility();
            //update "all" role (bitmask -1)
            if(needsRecalc) {
                _.each(Instance.Groups.collection.groups, function (group) {
                    _.each(group.model.rights, function (rights) {
                        var allSame = {};
                        var rowRight = {};
                        _.each(rights.collection.rights, function (right) {
                            if (right.model.bitmask != -1) {
                                if (allSame[right.model.mode] === undefined) {
                                    allSame[right.model.mode] = true;
                                    rowRight[right.model.mode] = parseInt(right.model.right, 10);
                                } else if (parseInt(right.model.right, 10) !== rowRight[right.model.mode]) {
                                    allSame[right.model.mode] = false;
                                }
                            }
                        });
                        for (var key in allSame) {
                            if (!allSame[key]) {
                                rowRight[key] = 3;
                            }
                        }
                        for (var key in rowRight) {
                            var allRight = rights.collection.getByBitmaskAndMode(-1, key);
                            allRight.model.right = rowRight[key];
                        }
                    });
                });
            }
            Instance.Groups.view.render();
        };

        /**
         * Actually undo an individual action. This is here so we can undo the individual
         * actions that make up an "all-change" command without recalculating the status
         * of the all role every time. The return value signals whether the all status
         * needs to be updated.
         * @return bool
         */
        var innerUndo = function(){
            if (Instance.Undo.model.history.length > 0) {
                var revert = Instance.Undo.model.history.pop();
                switch (revert.action) {
                    case 'right-change' :
                        undoRightChange(revert);
                        return true;
                        break;
                    case 'move' :
                        undoMove(revert);
                        return false;
                        break;
                    case 'delete-rule' :
                        undoDeleteRule(revert);
                        return false;
                        break;
                    case 'add-rule' :
                        undoAddRule(revert);
                        return false;
                        break;
                    case 'edit-rule' :
                        undoEditRule(revert);
                        return false;
                        break;
                    case 'group-copy' :
                        undoGroupCopy(revert);
                        return false;
                        break;
                    case 'all-change' :
                        undoAllChange(revert);
                        return false;
                        break;
                }
            }
        };

        /**
         * Save all actions in history.
         * This function works by sending an XAJAX command to save the next item on the stack
         * When the XAJAX returns, it calls save again, popping the next item, etc...
         * If there are no items left on the stack when save() is called, it re-enables the button
         * and displays a success message.
         * @private
         * @memberOf Undo.Model
         */
        var save = function () {
            $(this).hide();
            $('#saving').show();
            $('#save').hide();
            $('#undo').hide();
            if (Instance.Undo.model.history.length > 0) {
                $('#page_content').find('a').addClass('disabled');
                var sync = Instance.Undo.model.history.shift();
                switch (sync.action) {
                    case 'right-change' :
                        xajax_right_change(sync.ruleId, sync.groupId, sync.current, sync.bitmask, sync.right, window.projectId);
                        break;
                    case 'move' :
                        xajax_rule_move(sync.groupId, sync.current);
                        break;
                    case 'delete-rule' :
                        xajax_rule_delete(sync.ruleId, sync.groupId);
                        break;
                    case 'edit-rule' :
                        xajax_rule_edit(sync.originalRuleId, sync.currentRuleId, sync.groupId);
                        break;
                    case 'add-rule' :
                        xajax_rule_insert(window.projectId, sync.ruleId, sync.groupId);
                        break;
                    case 'group-copy' :
                        var sourceRights = [];
                        _.each(sync.sourceRights, function (rights) {
                            var right = [];
                            _.each(rights.collection.rights, function (sourceRight) {
                                right.push({
                                    ruleId: parseInt(rights.collection.ruleId, 10),
                                    bitmask: parseInt(sourceRight.model.bitmask, 10),
                                    mode: sourceRight.model.mode,
                                    right: parseInt(sourceRight.model.right, 10)
                                });
                            });
                            sourceRights.push(right);
                        });
                        xajax_copy_group_rights(sync.sourceGroupId, sourceRights, sync.targetGroupId);
                        break;
                    case 'all-change' :
                        Instance.Undo.model.save();
                        break;
                }
            } else {
                cla.showMessage(lmsg('infocapture.admin.general.changes_saved'), '', false);
                Instance.Undo.view.toggleVisibility();
                _.debounce($('#page_content').find('a').removeClass('disabled'), 1000);
            }
        };

        var setView = function (view) {
            this.view = view;
        };

        return {
            setView: setView,
            /**
             * Initialise object
             * @public
             * @memberOf Undo
             */
            init: function () {
                //$('#undo').on('click', this.undo);
                //$('#save').on('click', this.save);
                $('#undo').on('click', function (event) {
                    event.preventDefault();
                    undo();
                });
                $('#save').on('click', function (event) {
                    event.preventDefault();
                    save();
                });
            },

            /**
             * Add an action to the history array
             * @public
             * @memberOf Undo
             */
            addHistory: addHistory,

            /**
             * Undo the last action
             * @public
             * @memberOf Undo
             */
            undo: undo,

            innerUndo: innerUndo,

            /**
             * Save all actions using XAJAX
             * @public
             * @memberOf Undo
             */
            save: save
        };
    })();

    Undo.View = function () {
        'use strict';
        this.model = null;
    };

    Undo.View.prototype = (function () {
        'use strict';
        /**
         * Toggle the save and undo buttons
         * @private
         * @memberOf Undo
         * @returns {Object} Undo
         */
        var toggleVisibility = function () {
            var undoButton = $('#undo');
            var saveButton = $('#save');
            var savingButton = $('#saving');
            if (Instance.Undo.model.history.length > 0) {
                undoButton.fadeIn();
                saveButton.fadeIn();
            } else {
                undoButton.fadeOut();
                saveButton.fadeOut();
                savingButton.fadeOut();
            }
            return this;
        };

        var setModel = function (model) {
            this.model = model;
        };

        return {
            setModel: setModel,

            toggleVisibility: toggleVisibility
        };
    })();

    return Undo;
});
