(function() {
  var BufferPatch, Checkpoint, History, Serializable, Transaction, TransactionAborted, last,
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

  Serializable = require('serializable');

  Transaction = require('./transaction');

  BufferPatch = require('./buffer-patch');

  Checkpoint = require('./checkpoint');

  last = require('underscore-plus').last;

  TransactionAborted = new Error("Transaction Aborted");

  module.exports = History = (function(_super) {
    __extends(History, _super);

    History.version = 1;

    History.registerDeserializers(Transaction, BufferPatch);

    History.prototype.currentTransaction = null;

    History.prototype.transactionDepth = 0;

    History.prototype.transactCallDepth = 0;

    function History(buffer, undoStack, redoStack) {
      this.buffer = buffer;
      this.undoStack = undoStack != null ? undoStack : [];
      this.redoStack = redoStack != null ? redoStack : [];
    }

    History.prototype.serializeParams = function() {
      return {
        undoStack: this.undoStack.map(function(patch) {
          return patch.serialize();
        }),
        redoStack: this.redoStack.map(function(patch) {
          return patch.serialize();
        })
      };
    };

    History.prototype.deserializeParams = function(params) {
      params.undoStack = params.undoStack.map((function(_this) {
        return function(patchState) {
          return _this.constructor.deserialize(patchState);
        };
      })(this));
      params.redoStack = params.redoStack.map((function(_this) {
        return function(patchState) {
          return _this.constructor.deserialize(patchState);
        };
      })(this));
      return params;
    };

    History.prototype.recordNewPatch = function(patch) {
      if (this.currentTransaction != null) {
        this.currentTransaction.push(patch);
        if (patch instanceof BufferPatch) {
          return this.clearRedoStack();
        }
      } else {
        this.beginTransaction();
        this.currentTransaction.push(patch);
        this.commitTransaction();
        return this.clearRedoStack();
      }
    };

    History.prototype.undo = function() {
      var inverse, patch;
      if (this.currentTransaction != null) {
        throw new Error("Can't undo with an open transaction");
      }
      if (last(this.undoStack) instanceof Checkpoint) {
        if (!(this.undoStack.length > 1)) {
          return;
        }
        this.redoStack.push(this.undoStack.pop());
      }
      if (patch = this.undoStack.pop()) {
        inverse = patch.invert(this.buffer);
        this.redoStack.push(inverse);
        return inverse.applyTo(this.buffer);
      }
    };

    History.prototype.redo = function() {
      var inverse, patch;
      if (this.currentTransaction != null) {
        throw new Error("Can't redo with an open transaction");
      }
      if (patch = this.redoStack.pop()) {
        inverse = patch.invert(this.buffer);
        this.undoStack.push(inverse);
        inverse.applyTo(this.buffer);
        if (last(this.redoStack) instanceof Checkpoint) {
          return this.undoStack.push(this.redoStack.pop());
        }
      }
    };

    History.prototype.transact = function(groupingInterval, fn) {
      var error, result;
      if (fn == null) {
        fn = groupingInterval;
        groupingInterval = void 0;
      }
      this.beginTransaction(groupingInterval);
      try {
        ++this.transactCallDepth;
        result = fn();
        --this.transactCallDepth;
        this.commitTransaction();
        return result;
      } catch (_error) {
        error = _error;
        if (--this.transactCallDepth === 0) {
          this.abortTransaction();
          if (error !== TransactionAborted) {
            throw error;
          }
        } else {
          throw error;
        }
      }
    };

    History.prototype.beginTransaction = function(groupingInterval) {
      if (++this.transactionDepth === 1) {
        return this.currentTransaction = new Transaction([], groupingInterval, this.buffer.markers.buildSnapshot());
      }
    };

    History.prototype.commitTransaction = function() {
      var lastTransaction, _base;
      if (!(this.transactionDepth > 0)) {
        throw new Error("No transaction is open");
      }
      if (--this.transactionDepth === 0) {
        if (this.currentTransaction.hasBufferPatches()) {
          lastTransaction = last(this.undoStack);
          if ((typeof (_base = this.currentTransaction).isOpenForGrouping === "function" ? _base.isOpenForGrouping() : void 0) && (lastTransaction != null ? typeof lastTransaction.isOpenForGrouping === "function" ? lastTransaction.isOpenForGrouping() : void 0 : void 0)) {
            lastTransaction.merge(this.currentTransaction);
          } else {
            this.undoStack.push(this.currentTransaction);
          }
        }
        return this.currentTransaction = null;
      }
    };

    History.prototype.abortTransaction = function() {
      var inverse;
      if (!(this.transactionDepth > 0)) {
        throw new Error("No transaction is open");
      }
      if (this.transactCallDepth === 0) {
        inverse = this.currentTransaction.invert(this.buffer);
        this.currentTransaction = null;
        this.transactionDepth = 0;
        return inverse.applyTo(this.buffer);
      } else {
        throw TransactionAborted;
      }
    };

    History.prototype.createCheckpoint = function() {
      var checkpoint;
      if (this.isTransacting()) {
        throw new Error("Cannot create a checkpoint inside of a transaction");
      }
      if (last(this.undoStack) instanceof Checkpoint) {
        return last(this.undoStack);
      } else {
        checkpoint = new Checkpoint;
        this.undoStack.push(checkpoint);
        return checkpoint;
      }
    };

    History.prototype.revertToCheckpoint = function(checkpoint) {
      if (__indexOf.call(this.undoStack, checkpoint) >= 0) {
        while (last(this.undoStack) !== checkpoint) {
          this.undo();
        }
        this.clearRedoStack();
        return true;
      } else {
        return false;
      }
    };

    History.prototype.groupChangesSinceCheckpoint = function(checkpoint) {
      var changesSinceCheckpoint, groupedTransaction, index, patch, _i, _len;
      index = this.undoStack.indexOf(checkpoint) + 1;
      if (index === 0) {
        return false;
      }
      if (index === this.undoStack.length) {
        return false;
      }
      changesSinceCheckpoint = this.undoStack.splice(index, this.undoStack.length - index);
      groupedTransaction = changesSinceCheckpoint.shift();
      for (_i = 0, _len = changesSinceCheckpoint.length; _i < _len; _i++) {
        patch = changesSinceCheckpoint[_i];
        if (!(patch instanceof Checkpoint)) {
          groupedTransaction.merge(patch);
        }
      }
      this.undoStack.push(groupedTransaction);
      return true;
    };

    History.prototype.isTransacting = function() {
      return this.currentTransaction != null;
    };

    History.prototype.clearUndoStack = function() {
      return this.undoStack.length = 0;
    };

    History.prototype.clearRedoStack = function() {
      return this.redoStack.length = 0;
    };

    return History;

  })(Serializable);

}).call(this);
