System.register(['../utils/annotations.js', '../utils/event-target.js', '../utils/equality.js', 'utils'], function (_export, _context) {
  "use strict";

  var Annotations, EventTarget, shallowEqualsArray, shallowEqualsSet, shallowEqualsMap, shallowEquals, deepEquals, isString, clone, cloneDeep;
  return {
    setters: [function (_utilsAnnotationsJs) {
      Annotations = _utilsAnnotationsJs.default;
    }, function (_utilsEventTargetJs) {
      EventTarget = _utilsEventTargetJs.default;
    }, function (_utilsEqualityJs) {
      shallowEqualsArray = _utilsEqualityJs.shallowEqualsArray;
      shallowEqualsSet = _utilsEqualityJs.shallowEqualsSet;
      shallowEqualsMap = _utilsEqualityJs.shallowEqualsMap;
      shallowEquals = _utilsEqualityJs.shallowEquals;
      deepEquals = _utilsEqualityJs.deepEquals;
    }, function (_utils) {
      isString = _utils.isString;
      clone = _utils.clone;
      cloneDeep = _utils.cloneDeep;
    }],
    execute: function () {
      const __SystemJSRewritingHack = {};
      _recorder_._src_client_reactive_active_expression_active_expression_js = _recorder_._src_client_reactive_active_expression_active_expression_js || {};
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'aexpr', {
        get() {
          return aexpr;
        },

        set(thisIsVererySecretVariableName) {
          _export('aexpr', aexpr = thisIsVererySecretVariableName);

          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'Annotations', {
        get() {
          return Annotations;
        },

        set(thisIsVererySecretVariableName) {
          Annotations = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'EventTarget', {
        get() {
          return EventTarget;
        },

        set(thisIsVererySecretVariableName) {
          EventTarget = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'deepEquals', {
        get() {
          return deepEquals;
        },

        set(thisIsVererySecretVariableName) {
          deepEquals = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'shallowEquals', {
        get() {
          return shallowEquals;
        },

        set(thisIsVererySecretVariableName) {
          shallowEquals = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'shallowEqualsMap', {
        get() {
          return shallowEqualsMap;
        },

        set(thisIsVererySecretVariableName) {
          shallowEqualsMap = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'shallowEqualsSet', {
        get() {
          return shallowEqualsSet;
        },

        set(thisIsVererySecretVariableName) {
          shallowEqualsSet = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'shallowEqualsArray', {
        get() {
          return shallowEqualsArray;
        },

        set(thisIsVererySecretVariableName) {
          shallowEqualsArray = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'cloneDeep', {
        get() {
          return cloneDeep;
        },

        set(thisIsVererySecretVariableName) {
          cloneDeep = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'clone', {
        get() {
          return clone;
        },

        set(thisIsVererySecretVariableName) {
          clone = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'isString', {
        get() {
          return isString;
        },

        set(thisIsVererySecretVariableName) {
          isString = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });


      // #TODO: this is use to keep SystemJS from messing up scoping
      // (BaseActiveExpression would not be defined in aexpr)
      const HACK = {};

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'HACK', {
        get() {
          return HACK;
        },

        set(thisIsVererySecretVariableName) {
          HACK = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      window.__compareAExprResults__ = false;

      /*MD ## Registry of Active Expressions MD*/
      const AExprRegistry = {

        _eventTarget: new EventTarget(),
        _aexprs: new Set(),
        _idCounters: new Map(),

        /**
         * Handling membership
         */
        addAExpr(aexpr) {
          this._aexprs.add(aexpr);
          this.buildIdFor(aexpr);
          this._eventTarget.dispatchEvent('add', aexpr);
        },
        removeAExpr(aexpr) {
          const deleted = this._aexprs.delete(aexpr);
          if (deleted) {
            this._eventTarget.dispatchEvent('remove', aexpr);
          }
        },
        updateAExpr(aexpr) {
          this._eventTarget.dispatchEvent('update', aexpr);
        },

        on(type, callback) {
          return this._eventTarget.addEventListener(type, callback);
        },
        off(type, callback) {
          return this._eventTarget.removeEventListener(type, callback);
        },

        buildIdFor(ae) {
          let locationId;
          if (ae.meta().has('location')) {
            let location = ae.meta().get('location');
            let file = location.file.replace(lively4url + '/', '');
            locationId = file + '@' + location.start.line + ':' + location.start.column;
          } else {
            locationId = 'unknown_location';
          }
          this._idCounters.set(locationId, this._idCounters.get(locationId) + 1 || 0);
          ae.meta({ id: locationId + '#' + this._idCounters.get(locationId) });
        },

        /**
         * For Development purpose if the registry gets into inconsistent state
         */
        purge() {
          for (let each of this._aexprs) each._isDisposed = true;
          this._eventTarget.callbacks.clear();
          this._aexprs.clear();
          this._idCounters.clear();
        },

        /**
         * Access
         */
        allAsArray() {
          return Array.from(this._aexprs);
        }
      };

      /*MD # Equality Matchers MD*/

      _export('AExprRegistry', AExprRegistry);

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'AExprRegistry', {
        get() {
          return AExprRegistry;
        },

        set(thisIsVererySecretVariableName) {
          _export('AExprRegistry', AExprRegistry = thisIsVererySecretVariableName);

          return true;
        },

        enumerable: true,
        configurable: true
      });
      class DefaultMatcher {
        static compare(lastResult, newResult) {
          // array
          if (Array.isArray(lastResult) && Array.isArray(newResult)) {
            return shallowEqualsArray(lastResult, newResult);
          }

          // set
          if (lastResult instanceof Set && newResult instanceof Set) {
            return shallowEqualsSet(lastResult, newResult);
          }

          // map
          if (lastResult instanceof Map && newResult instanceof Map) {
            return shallowEqualsMap(lastResult, newResult);
          }

          return lastResult === newResult;
        }

        static store(result) {
          // array
          if (Array.isArray(result)) {
            return Array.prototype.slice.call(result);
          }

          // set
          if (result instanceof Set) {
            return new Set(result);
          }

          // map
          if (result instanceof Map) {
            return new Map(result);
          }

          return result;
        }
      }

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'DefaultMatcher', {
        get() {
          return DefaultMatcher;
        },

        set(thisIsVererySecretVariableName) {
          DefaultMatcher = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      class IdentityMatcher {
        static compare(lastResult, newResult) {
          return lastResult === newResult;
        }

        static store(result) {
          return result;
        }
      }

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'IdentityMatcher', {
        get() {
          return IdentityMatcher;
        },

        set(thisIsVererySecretVariableName) {
          IdentityMatcher = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      class ShallowMatcher {
        static compare(lastResult, newResult) {
          // array
          if (Array.isArray(lastResult) && Array.isArray(newResult)) {
            return shallowEqualsArray(lastResult, newResult);
          }

          // set
          if (lastResult instanceof Set && newResult instanceof Set) {
            return shallowEqualsSet(lastResult, newResult);
          }

          // map
          if (lastResult instanceof Map && newResult instanceof Map) {
            return shallowEqualsMap(lastResult, newResult);
          }

          return shallowEquals(lastResult, newResult);
        }

        static store(result) {
          return clone.call(result);
        }
      }

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'ShallowMatcher', {
        get() {
          return ShallowMatcher;
        },

        set(thisIsVererySecretVariableName) {
          ShallowMatcher = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      class DeepMatcher {
        static compare(lastResult, newResult) {
          return deepEquals(lastResult, newResult);
        }

        static store(result) {
          return cloneDeep.call(result);
        }
      }

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'DeepMatcher', {
        get() {
          return DeepMatcher;
        },

        set(thisIsVererySecretVariableName) {
          DeepMatcher = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const MATCHER_MAP = new Map([['default', DefaultMatcher], ['identity', IdentityMatcher], ['shallow', ShallowMatcher], ['deep', DeepMatcher]]);

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'MATCHER_MAP', {
        get() {
          return MATCHER_MAP;
        },

        set(thisIsVererySecretVariableName) {
          MATCHER_MAP = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      const NO_VALUE_YET = Symbol('No value yet');

      /*MD # ACTIVE EXPRESSIONS MD*/
      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'NO_VALUE_YET', {
        get() {
          return NO_VALUE_YET;
        },

        set(thisIsVererySecretVariableName) {
          NO_VALUE_YET = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      class BaseActiveExpression {

        /**
         *
         * @param func (Function) the expression to be observed
         * #TODO: incorrect parameter list, how to specify spread arguments in jsdoc?
         * @param ...params (Objects) the instances bound as parameters to the expression
         */
        constructor(func, { params = [], match, errorMode = 'silent', location } = {}) {
          this._eventTarget = new EventTarget(), this.func = func;
          this.params = params;
          this.errorMode = errorMode;
          this.setupMatcher(match);
          this._initLastValue();
          this.callbacks = [];
          // this.allCallbacks = new Map();
          this._isDisposed = false;
          this._shouldDisposeOnLastCallbackDetached = false;

          this._annotations = new Annotations();
          if (location) {
            this.meta({ location });
          };
          this.initializeEvents();
          this.logEvent('created');

          if (new.target === BaseActiveExpression) {
            this.addToRegistry();
          }
        }

        _initLastValue() {
          const { value, isError } = this.evaluateToCurrentValue();
          if (!isError) {
            this.storeResult(value);
          } else {
            this.lastValue = NO_VALUE_YET;
          }
        }

        addToRegistry() {
          AExprRegistry.addAExpr(this);
        }

        hasCallbacks() {
          return this.callbacks.length !== 0;
        }

        /**
         * Executes the encapsulated expression with the given parameters.
         * aliases with 'now' (#TODO: caution, consider ambigous terminology: 'now' as in 'give me the value' or as in 'dataflow'?)
         * @private
         * @returns {*} the current value of the expression
         */
        getCurrentValue() {
          return this.func(...this.params);
        }

        /**
         * Safely executes the encapsulated expression with the given parameters.
         * @public
         * @returns {{ value: *, isError: Boolean}} the current value of the expression, or the thrown error if any
         */
        evaluateToCurrentValue() {
          try {
            const result = this.getCurrentValue();
            return { value: result, isError: false };
          } catch (e) {
            return { value: e, isError: true };
          }
        }

        /*MD ## EventTarget Interface MD*/
        on(type, callback) {
          this._eventTarget.addEventListener(type, callback);
          return this;
        }

        off(type, callback) {
          this._eventTarget.removeEventListener(type, callback);
          return this;
        }

        emit(type, ...params) {
          this._eventTarget.dispatchEvent(type, ...params);
        }

        getEventListeners(type) {
          return this._eventTarget.getEventListeners(type);
        }

        /*MD ## --- MD*/
        /**
         * @public
         * @param callback
         * @returns {BaseActiveExpression} this very active expression (for chaining)
         */
        onChange(callback) {
          this.callbacks.push(callback);
          this.logEvent('dependencies changed', 'Added: ' + callback);
          AExprRegistry.updateAExpr(this);
          return this;
        }
        /**
         * @public
         * @param callback
         * @returns {BaseActiveExpression} this very active expression (for chaining)
         */
        // #TODO: should this remove all occurences of the callback?
        offChange(callback) {
          const index = this.callbacks.indexOf(callback);
          if (index > -1) {
            this.callbacks.splice(index, 1);
            this.logEvent('dependencies changed', 'Removed: ' + callback);
            AExprRegistry.updateAExpr(this);
          }
          if (this._shouldDisposeOnLastCallbackDetached && this.callbacks.length === 0) {
            this.dispose();
          }

          return this;
        }

        /**
         * Signals the active expression that a state change might have happened.
         * Mainly for implementation strategies.
         * @public
         */
        checkAndNotify() {
          const { value, isError } = this.evaluateToCurrentValue();
          if (isError || this.compareResults(this.lastValue, value)) {
            return;
          }
          const lastValue = this.lastValue;
          this.storeResult(value);

          this.logEvent('changed value', value);

          this.notify(value, {
            lastValue,
            expr: this.func,
            aexpr: this
          });
        }

        setupMatcher(matchConfig) {
          // configure using existing matcher
          if (matchConfig && isString.call(matchConfig)) {
            if (!MATCHER_MAP.has(matchConfig)) {
              throw new Error(`No matcher of type '${matchConfig}' registered.`);
            }
            this.matcher = MATCHER_MAP.get(matchConfig);
            return;
          }

          // configure using a custom matcher
          if (typeof matchConfig === 'object') {
            if (matchConfig.hasOwnProperty('compare') && matchConfig.hasOwnProperty('store')) {
              this.matcher = matchConfig;
              return;
            }
            throw new Error(`Given matcher object does not provide 'compare' and 'store' methods.`);
          }

          // use smart default matcher
          this.matcher = DefaultMatcher;
        }

        // #TODO: extract into CompareAndStore classes
        compareResults(lastResult, newResult) {
          try {
            window.__compareAExprResults__ = true;
            return this.matcher.compare(lastResult, newResult);
          } finally {
            window.__compareAExprResults__ = false;
          }
        }

        storeResult(result) {
          try {
            window.__compareAExprResults__ = true;
            this.lastValue = this.matcher.store(result);
          } finally {
            window.__compareAExprResults__ = false;
          }
        }

        notify(...args) {
          this.callbacks.forEach(callback => callback(...args));
          AExprRegistry.updateAExpr(this);
        }

        onBecomeTrue(callback) {
          // setup dependency
          this.onChange(bool => {
            if (bool) {
              callback();
            }
          });

          // check initial state
          const { value, isError } = this.evaluateToCurrentValue();
          if (!isError && value) {
            callback();
          }

          return this;
        }

        onBecomeFalse(callback) {
          // setup dependency
          this.onChange(bool => {
            if (!bool) {
              callback();
            }
          });

          // check initial state
          const { value, isError } = this.evaluateToCurrentValue();
          if (!isError && !value) {
            callback();
          }

          return this;
        }

        dataflow(callback) {
          // setup dependency
          this.onChange(callback);

          // call immediately
          // #TODO: duplicated code: we should extract this call
          const { value, isError } = this.evaluateToCurrentValue();
          if (!isError) {
            callback(value, {});
          }

          return this;
        }

        /*MD ## Disposing MD*/
        dispose() {
          if (!this._isDisposed) {
            this._isDisposed = true;
            AExprRegistry.removeAExpr(this);
            this.emit('dispose');
            this.logEvent('disposed');
          }
        }

        isDisposed() {
          return this._isDisposed;
        }

        /**
         * #TODO: implement
         * disposeOnLastCallbackDetached
         * chainable
         * for some auto-cleanup
         * (only triggers, when a callback is detached; not initially, if there are no callbacks)
         */
        disposeOnLastCallbackDetached() {
          this._shouldDisposeOnLastCallbackDetached = true;
          return this;
        }

        /*MD ## Reflection Information MD*/
        meta(annotation) {
          if (annotation) {
            this._annotations.add(annotation);
            return this;
          } else {
            return this._annotations;
          }
        }

        supportsDependencies() {
          return false;
        }

        initializeEvents() {
          this.meta({ events: new Array() });
        }

        logEvent(type, value) {
          if (this.isMeta()) return;
          //if(!this.meta().has('events'))this.meta({events : new Array()});
          let events = this.meta().get('events');
          events.push({ timestamp: new Date(), type, value });
          if (events.length > 5000) events.shift();
        }

        isMeta(value) {
          if (value !== undefined) this.meta({ isMeta: value });else return this.meta().has('isMeta') && this.meta().get('isMeta');
        }
      }

      _export('BaseActiveExpression', BaseActiveExpression);

      Object.defineProperty(_recorder_._src_client_reactive_active_expression_active_expression_js, 'BaseActiveExpression', {
        get() {
          return BaseActiveExpression;
        },

        set(thisIsVererySecretVariableName) {
          _export('BaseActiveExpression', BaseActiveExpression = thisIsVererySecretVariableName);

          return true;
        },

        enumerable: true,
        configurable: true
      });
      function aexpr(func, ...args) {
        return new BaseActiveExpression(func, ...args);
      }

      _export('aexpr', aexpr);

      _export('default', BaseActiveExpression);
    }
  };
});