// Generated by CoffeeScript 1.10.0
(function() {
  var INFINITY, Store, Time, arrayOfMaps_To_CSVStyleArray, csvStyleArray_To_ArrayOfMaps, functions, ref, ref1, utils,
    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; };

  ref = require('tztime'), utils = ref.utils, Time = ref.Time;

  functions = require('./functions').functions;

  ref1 = require('./dataTransform'), arrayOfMaps_To_CSVStyleArray = ref1.arrayOfMaps_To_CSVStyleArray, csvStyleArray_To_ArrayOfMaps = ref1.csvStyleArray_To_ArrayOfMaps;

  INFINITY = '9999-01-01T00:00:00.000Z';

  Store = (function() {

    /*
    @class Store
    
    __An efficient, in-memory, datastore for snapshot data.__
    
    Note, this store takes advantage of JavaScript's prototype inheritance to store snapshots in memory. Since the next snapshot might
    only have one field different from the prior one, this saves a ton of space. There is some concern that this will
    slow down certain operations because the JavaScript engine has to search all fields in the current level before bumping up
    to the next. However, there is some evidence that modern JavaScript implementations handle this very efficiently.
    
    However, this choice means that each row in the snapshots array doesn't have all of the fields.
    
    Store keeps track of all of the fields it has seen so you can flatten a row(s) if necessary.
    
    Example:
    
        {Store} = require('../')
    
        snapshotCSVStyleArray = [
          ['RecordID', 'DefectID', 'Created_Date', 'Severity', 'Modified_Date', 'Status'],
          [         1,          1,   '2014-06-16',          5,    '2014-06-16',    'New'],
          [       100,          1,   '2014-06-16',          5,    '2014-07-17',    'In Progress'],
          [      1000,          1,   '2014-06-16',          5,    '2014-08-18',    'Done'],
        ]
    
        defects = require('../').csvStyleArray_To_ArrayOfMaps(snapshotCSVStyleArray)
    
        config =
          uniqueIDField: 'DefectID'
          validFromField: 'Modified_Date'
          idField: 'RecordID'
          defaultValues:
            Severity: 4
    
        store = new Store(config, defects)
    
        console.log(require('../').table.toString(store.snapshots, store.fields))
         * | Modified_Date            | _ValidTo                 | _PreviousValues | DefectID | RecordID | Created_Date | Severity | Status      |
         * | ------------------------ | ------------------------ | --------------- | -------- | -------- | ------------ | -------- | ----------- |
         * | 2014-06-16T00:00:00.000Z | 2014-07-17T00:00:00.000Z | [object Object] | 1        | 1        | 2014-06-16   | 5        | New         |
         * | 2014-07-17T00:00:00.000Z | 2014-08-18T00:00:00.000Z | [object Object] | 1        | 100      | 2014-06-16   | 5        | In Progress |
         * | 2014-08-18T00:00:00.000Z | 9999-01-01T00:00:00.000Z | [object Object] | 1        | 1000     | 2014-06-16   | 5        | Done        |
    
    That's pretty boring. We pretty much got out what we put in. There are a few things to notice though. First,
    Notice how the _ValidTo field is automatically set. Also, notice that it added the _PreviousValues field. This is
    a record of the immediately proceeding values for the fields that changed. In this way, the records not only
    represent the current snapshot; they also represent the state transition that occured to get into this snapshot
    state. That's what stateBoundaryCrossedFilter and other methods key off of.
    
    Also, under the covers, the prototype of each snapshot is the prior snapshot and only the fields that changed
    are actually stored in the next snapshot. So:
    
        console.log(store.snapshots[1] is store.snapshots[2].__proto__)
         * true
    
    The Store also keeps the equivalent of a database index on uniqueIDField and keeps a pointer to the last snapshot
    for each particular uniqueIDField. This provides a convenient way to do per entity analysis.
    
        console.log(store.byUniqueID['1'].snapshots[0].RecordID)
         * 1
    
        console.log(store.byUniqueID['1'].lastSnapshot.RecordID)
         * 1000
     */

    /*
    @property snapshots
    An Array of Objects
    
    The snapshots in compressed (via JavaScript inheritance) format
     */

    /*
    @property fields
    An Array of Strings
    
    The list of all fields that this Store has ever seen. Use to expand each row.
     */

    /*
    @property byUniqueID
    This is the database equivalent of an index by uniqueIDField.
    
    An Object in the form:
    
        {
          '1234': {
            snapshots: [...],
            lastSnapshot: <points to last snapshot for this uniqueID>
          },
          '7890': {
            ...
          },
          ...
        }
     */
    function Store(userConfig, snapshots) {
      this.userConfig = userConfig;

      /*
      @constructor
      
      @param {Object} config See Config options for details.
      @param {Object[]} [snapshots] Optional parameter allowing the population of the Store at instantiation.
      
      @cfg {String} [uniqueIDField = "_EntityID"] Specifies the field that identifies unique entities.
      @cfg {String} [validFromField = "_ValidFrom"]
      @cfg {String} [validToField = "_ValidTo"]
      @cfg {String} [idField = "_id"]
      @cfg {String} [tz = "GMT"]
      @cfg {Object} [defaultValues = {}] In some datastores, null numeric fields may be assumed to be zero and null
        boolean fields may be assumed to be false. Lumenize makes no such assumption and will crash if a field value
        is missing. the defaultValues becomes the root of prototype inheritance hierarchy.
       */
      this.config = utils.clone(this.userConfig);
      if (this.config.uniqueIDField == null) {
        this.config.uniqueIDField = '_EntityID';
      }
      if (this.config.validFromField == null) {
        this.config.validFromField = '_ValidFrom';
      }
      if (this.config.validToField == null) {
        this.config.validToField = '_ValidTo';
      }
      if (this.config.tz == null) {
        this.config.tz = 'GMT';
      }
      if (this.config.defaultValues == null) {
        this.config.defaultValues = {};
      }
      this.config.defaultValues[this.config.validFromField] = new Time(1, Time.MILLISECOND).toString();
      if (this.config.idField == null) {
        this.config.idField = '_id';
      }
      this.snapshots = [];
      this.fields = [this.config.validFromField, this.config.validToField, '_PreviousValues', this.config.uniqueIDField];
      this.lastValidFrom = new Time(1, Time.MILLISECOND).toString();
      this.byUniqueID = {};
      this.addSnapshots(snapshots);
    }

    Store.prototype.addSnapshots = function(snapshots) {

      /*
      @method addSnapshots
        Adds the snapshots to the Store
      @param {Object[]} snapshots
      @chainable
      @return {Store} Returns this
       */
      var dataForUniqueID, i, key, len, newSnapshot, priorSnapshot, s, uniqueID, validFrom, validTo, value;
      snapshots = utils._.sortBy(snapshots, this.config.validFromField);
      for (i = 0, len = snapshots.length; i < len; i++) {
        s = snapshots[i];
        uniqueID = s[this.config.uniqueIDField];
        utils.assert(uniqueID != null, ("Missing " + this.config.uniqueIDField + " field in submitted snapshot: \n") + JSON.stringify(s, null, 2));
        dataForUniqueID = this.byUniqueID[uniqueID];
        if (dataForUniqueID == null) {
          dataForUniqueID = {
            snapshots: [],
            lastSnapshot: this.config.defaultValues
          };
          this.byUniqueID[uniqueID] = dataForUniqueID;
        }
        if (s[this.config.validFromField] < dataForUniqueID.lastSnapshot[this.config.validFromField]) {
          throw new Error("Got a new snapshot for a time earlier than the prior last snapshot for " + this.config.uniqueIDField + " " + uniqueID + ".");
        } else if (s[this.config.validFromField] === dataForUniqueID.lastSnapshot[this.config.validFromField]) {
          for (key in s) {
            value = s[key];
            dataForUniqueID.lastSnapshot[key] = value;
          }
        } else {
          validFrom = s[this.config.validFromField];
          validFrom = new Time(validFrom, null, this.config.tz).getISOStringInTZ(this.config.tz);
          utils.assert(validFrom >= dataForUniqueID.lastSnapshot[this.config.validFromField], "validFromField (" + validFrom + ") must be >= lastValidFrom (" + dataForUniqueID.lastSnapshot[this.config.validFromField] + ") for this entity");
          utils.assert(validFrom >= this.lastValidFrom, "validFromField (" + validFrom + ") must be >= lastValidFrom (" + this.lastValidFrom + ") for the Store");
          validTo = s[this.config.validTo];
          if (validTo != null) {
            validTo = new Time(validTo, null, this.config.tz).getISOStringInTZ(this.config.tz);
          } else {
            validTo = INFINITY;
          }
          priorSnapshot = dataForUniqueID.lastSnapshot;
          newSnapshot = {};
          newSnapshot._PreviousValues = {};
          for (key in s) {
            value = s[key];
            if (key !== this.config.validFromField && key !== this.config.validToField && key !== '_PreviousValues' && key !== this.config.uniqueIDField) {
              if (indexOf.call(this.fields, key) < 0) {
                this.fields.push(key);
              }
              if (value !== priorSnapshot[key]) {
                newSnapshot[key] = value;
                if (key !== this.config.idField) {
                  if (priorSnapshot[key] != null) {
                    newSnapshot._PreviousValues[key] = priorSnapshot[key];
                  } else {
                    newSnapshot._PreviousValues[key] = null;
                  }
                }
              }
            }
          }
          newSnapshot[this.config.uniqueIDField] = uniqueID;
          newSnapshot[this.config.validFromField] = validFrom;
          newSnapshot[this.config.validToField] = validTo;
          if (s._PreviousValues != null) {
            newSnapshot._PreviousValues = s._PreviousValues;
          }
          newSnapshot.__proto__ = priorSnapshot;
          if (priorSnapshot[this.config.validToField] === INFINITY) {
            priorSnapshot[this.config.validToField] = validFrom;
          }
          dataForUniqueID.lastSnapshot = newSnapshot;
          this.lastValidFrom = validFrom;
          this.byUniqueID[uniqueID].snapshots.push(newSnapshot);
          this.snapshots.push(newSnapshot);
        }
      }
      return this;
    };

    Store.prototype.filtered = function(filter) {

      /*
      @method filtered
        Returns the subset of the snapshots that match the filter
      @param {Function} filter
      @return {Object[]} An array of snapshots. Note, they will not be flattened so they have references to their prototypes
       */
      var i, len, ref2, result, s;
      result = [];
      ref2 = this.snapshots;
      for (i = 0, len = ref2.length; i < len; i++) {
        s = ref2[i];
        if (filter(s)) {
          result.push(s);
        }
      }
      return result;
    };

    Store.prototype.stateBoundaryCrossedFiltered = function(field, values, valueToTheRightOfBoundary, forward, assumeNullIsLowest) {
      var filter, index, left, right;
      if (forward == null) {
        forward = true;
      }
      if (assumeNullIsLowest == null) {
        assumeNullIsLowest = true;
      }

      /*
      @method stateBoundaryCrossedFiltered
        Returns the subset of the snapshots where the field transitions from the left of valueToTheRightOfBoundary to
        the right (inclusive)
      @param {String} field
      @param {String[]} values
      @param {String} valueToTheRightOfBoundary
      @param {Boolean} [forward = true] When true (the default), this will return the transitions from left to right
        However, if you set this to false, it will return the transitions right to left.
      @param {Boolean} [assumeNullIsLowest = true] Set to false if you don't want to consider transitions out of null
      @return {Object[]} An array or snapshots. Note, they will not be flattened so they have references to their prototypes
       */
      index = values.indexOf(valueToTheRightOfBoundary);
      utils.assert(index >= 0, "stateToTheRightOfBoundary must be in stateList");
      left = values.slice(0, index);
      if (assumeNullIsLowest) {
        left.unshift(null);
      }
      right = values.slice(index);
      if (forward) {
        filter = function(s) {
          var ref2, ref3;
          return s._PreviousValues.hasOwnProperty(field) && (ref2 = s._PreviousValues[field], indexOf.call(left, ref2) >= 0) && (ref3 = s[field], indexOf.call(right, ref3) >= 0);
        };
      } else {
        filter = function(s) {
          var ref2, ref3;
          return s._PreviousValues.hasOwnProperty(field) && (ref2 = s._PreviousValues[field], indexOf.call(right, ref2) >= 0) && (ref3 = s[field], indexOf.call(left, ref3) >= 0);
        };
      }
      return this.filtered(filter);
    };

    Store.prototype.stateBoundaryCrossedFilteredBothWays = function(field, values, valueToTheRightOfBoundary, assumeNullIsLowest) {
      var backward, forward;
      if (assumeNullIsLowest == null) {
        assumeNullIsLowest = true;
      }

      /*
      @method stateBoundaryCrossedFilteredBothWays
        Shortcut to stateBoundaryCrossedFiltered for when you need both directions
      @param {String} field
      @param {String[]} values
      @param {String} valueToTheRightOfBoundary
      @param {Boolean} [assumeNullIsLowest = true] Set to false if you don't want to consider transitions out of null
      @return {Object} An object with two root keys: 1) forward, 2) backward. The values are the arrays that are returned
        from stateBoundaryCrossedFiltered
       */
      forward = this.stateBoundaryCrossedFiltered(field, values, valueToTheRightOfBoundary, true, assumeNullIsLowest);
      backward = this.stateBoundaryCrossedFiltered(field, values, valueToTheRightOfBoundary, false, assumeNullIsLowest);
      return {
        forward: forward,
        backward: backward
      };
    };

    return Store;

  })();

  exports.Store = Store;

}).call(this);
