Source: Versionizable/impl.js

define('Versionizable/impl',[
  './namespace','./Version','./Transaction','./History',
  'Hookable','Hookable/Descriptor',
  'Equals','Events'
],function (Versionizable,Version,Transaction,History,
            Hookable,Descriptor,
            Equals,Events) {
  /**
   * @function getHistoryLength
   * @memberOf BeautifulProperties.Versionizable
   *
   * @param {object} object
   * @param {string} key
   * @returns {number}
   */
  Versionizable.getHistoryLength = function getHistoryLength(object,key) {
    var history = History.retrieve(object,key);
    return history.length;
  };
  var aNullVersion = new Version();
  (function (version) {
    Object.defineProperty(version,'isNull',{
      value:true,
      writable:false
    });
    Object.defineProperty(version,'value',{
      value:undefined,
      writable:false
    });
  })(aNullVersion);
  /**
   * @function getVersions
   * @memberOf BeautifulProperties.Versionizable
   *
   * @param {object} object
   * @param {string} key
   * @returns {Array.<BeautifulProperties.Versionizable.Version>}
   */
  Versionizable.getVersions = function getVersions(object,key) {
    var history = History.retrieve(object,key);
    return history.slice();
  };
  /**
   * @function getVersion
   * @memberOf BeautifulProperties.Versionizable
   *
   * @param {object} object
   * @param {string} key
   * @param {number} index
   * @returns {BeautifulProperties.Versionizable.Version}
   */
  Versionizable.getVersion = function getVersion(object,key,index) {
    var history = History.retrieve(object,key);
    return history[index] || aNullVersion;
  };
  /**
   * @function undo
   * @memberOf BeautifulProperties.Versionizable
   *
   * @param {object} object
   * @param {string} key
   * @param {BeautifulProperties.Versionizable.Version} version
   */
  Versionizable.undo = function undo(object,key,version) {
    // Only for data property.
    this.transaction(object,key,function (versions){
      var t = this;
      var targetIndex = versions.indexOf(version);
      if (versions.length <= 1 || targetIndex === -1) {
        return;
      }
      versions.filter(function(version,index){
        return index < targetIndex;
      }).forEach(function(version){
        t.remove(version);
      });
    },function done(currentVersion,versions,currentVersionBeforeTransaction,versionsBeforeTransaction){
      if (currentVersion !== currentVersionBeforeTransaction) {
        Events.trigger(object,'undo:'+key,currentVersion.value,currentVersionBeforeTransaction.value);
      }
    });
  };
  /**
   * @callback BeautifulProperties.Versionizable~transactionCallback
   * @this BeautifulProperties.Versionizable.Transaction
   * @param {Array.<BeautifulProperties.Versionizable.Version>} versions
   */
  /**
   * @callback BeautifulProperties.Versionizable~doneCallback
   * @param {BeautifulProperties.Versionizable.Version} currentVersion
   * @param {Array.<BeautifulProperties.Versionizable.Version>} versions
   * @param {BeautifulProperties.Versionizable.Version} currentVersionBeforeTransaction
   * @param {Array.<BeautifulProperties.Versionizable.Version>} versionsBeforeTransaction
   */
  /**
   * @function transaction
   * @memberOf BeautifulProperties.Versionizable
   *
   * @param {object} object
   * @param {string} key
   * @param {BeautifulProperties.Versionizable~transactionCallback} callback
   * @param {BeautifulProperties.Versionizable~doneCallback=} doneCallback
   * @description The method modify property's history.<br/>
   * It's experimental API.
   */
  Versionizable.transaction = function transaction(object,key,callback,doneCallback){
    var currentVersionBeforeTransaction = this.getVersion(object,key,0);
    var versionsBeforeTransaction = this.getVersions(object,key);
    callback.call(new (Transaction)(object,key),versionsBeforeTransaction);
    var currentVersion = this.getVersion(object,key,0);
    var versions = this.getVersions(object,key);
    if ((currentVersion.isNull && !currentVersionBeforeTransaction.isNull)
    || (!currentVersion.isNull && currentVersionBeforeTransaction.isNull)
    || !Equals.equals(this,key,currentVersion.value,currentVersionBeforeTransaction.value)) {
      Hookable.setRaw(object,key,currentVersion.value);
    }
    if (doneCallback) {
      doneCallback(currentVersion,versions,currentVersionBeforeTransaction,versionsBeforeTransaction);
    }
  };
  /**
   * @function getPreviousValue
   * @memberOf BeautifulProperties.Versionizable
   *
   * @param {object} object
   * @param {string} key
   * @returns {*}
   */
  Versionizable.getPreviousValue = function getPreviousValue(object,key) {
    var history = History.retrieve(object,key);
    return (history[1] || aNullVersion).value;
  };
  /**
   * @function define
   * @memberOf BeautifulProperties.Versionizable
   * @see BeautifulProperties.Equals.equals
   *
   * @param {object} object
   * @param {string} key
   * @param {{length:number=}=} options length's default value is 2.
   * @description This method can be use after Hookable.define.
   */
  Versionizable.define = function define(object,key,options) {
    options = options || Object.create(null);
    if (options.length === undefined) {
      options.length = 2;
    }
    // Versionizable property depends on Hookable.
    if (!Hookable.hasHooks(object,key)) {
      Hookable.define(object,key);
    }
    var descriptor = Descriptor.retrieve(object,key);
    function enqueue(key,val) {
      var history = History.retrieve(this,key);
      var version = new Version;
      version.value = val;
      version.timestamp = Date.now();
      history.unshift(version);
      // truncate
      if (history.length > options.length){
        history.length = options.length;
      }
    }
    function enqueueWhenChangeExists(val,previousVal) {
      if (!Equals.equals(this,key,val,previousVal)) {
        enqueue.call(this,key,val)
      }
    }
    function enqueueWhenInit(val) {
      enqueue.call(this,key,val)
    }
    var hookType = descriptor.get ? 'refresh' : 'afterSet';
    Hookable.addHook(object,key,hookType,enqueueWhenChangeExists,10000);
    Hookable.addHook(object,key,'afterInit',enqueueWhenInit,10000);
  };
});