System.register(["src/client/reactive/active-expression/events/event.js"], function (_export, _context) {
  "use strict";

  var EventTypes, Event, PartialLayer, Layer, LayerableObjectTrait, LayerableObject, COPError, PartialLayerComposition, updateMember, Config, log_layer_code, object_id_counter, LayerObjectID, LayerStack, globalContextForNamedLayers, implicitLayers;
  function log(string) {
    if (log_layer_code) console.log(string);
  }

  /* 
   * Private State
   */

  // #HACK #TODO

  /* 
   * Private Methods
   */

  // for debugging ContextJS itself
  function withLogLayerCode(func) {
    try {
      var old = log_layer_code;
      _export("log_layer_code", log_layer_code = true);
      func();
    } finally {
      _export("log_layer_code", log_layer_code = old);
    }
  }
  function getLayerDefinitionForObject(layer, object) {
    // log("cop getLayerDefinitionForObject(" + layer + ", " + object + ")");
    if (!layer || !object) {
      return;
    }
    var result = layer[object[LayerObjectID]];
    // #BUG Maximum call stack size exceeded
    try {
      return result ? result : getLayerDefinitionForObject(layer, object.prototype);
    } catch (e) {
      debugger;
      console.error('getLayerDefinitionForObject', e);
      return;
    }
  }

  /**
   * Stores partial definitions for a single layered object and layer.
   */

  function ensurePartialLayer(layer, object) {
    if (!layer) {
      throw new Error("in ensurePartialLayer: layer is nil");
    }
    if (!object.hasOwnProperty(LayerObjectID)) {
      Object.defineProperty(object, LayerObjectID, {
        value: object_id_counter++,
        enumerable: false,
        configurable: false,
        writable: false
      });
    }
    if (!layer[object[LayerObjectID]]) {
      layer[object[LayerObjectID]] = new PartialLayer(object);
    }
    return layer[object[LayerObjectID]];
  }

  // TODO(mariannet) : Find out if ES6 constructor also has type
  function layerMethod(layer, object, property, func) {
    ensurePartialLayer(layer, object).setLayeredPropertyValue(property, func);
    func.displayName = "layered " + String(layer.name) + " " + (object.constructor ? object.constructor.type + "$" : "") + property;
    makeFunctionLayerAware(object, property, layer.isHidden);

    // Bookkeeping for layer uninstall
    // typeof object.getName === 'function'
    //    && (layer._layeredFunctionsList[object][property] = true);
  }

  function layerGetterMethod(layer, object, property, getter) {
    ensurePartialLayer(layer, object).defineGetter(property, getter);
  }
  function layerSetterMethod(layer, object, property, setter) {
    ensurePartialLayer(layer, object).defineSetter(property, setter);
  }
  function layerProperty(layer, object, property, defs) {
    var defProperty = Object.getOwnPropertyDescriptor(defs, property);
    var getter = defProperty && defProperty.get;
    if (getter) {
      layerGetterMethod(layer, object, property, getter);
    }
    var setter = defProperty && defProperty.set;
    if (setter) {
      layerSetterMethod(layer, object, property, setter);
    }
    if (getter || setter) {
      makePropertyLayerAware(object, property);
    } else {
      layerMethod(layer, object, property, defs[property]);
    }
  }
  function layerPropertyWithShadow(layer, object, property) {
    // shadowing does not work with current implementation
    // see the shadow tests in LayersTest
    // TODO: the tests are green, what is the above comment about?
    var defs = {};
    // var baseValue = object[property];
    const layeredPropSymbol = self.Symbol(property + ' ' + (typeof layer.name === 'string' ? 'for Layer ' + layer.name : 'for anonymous Layer'));
    Object.defineProperty(defs, property, {
      get: function layeredGetter() {
        return this[layeredPropSymbol] === undefined ? proceed() : this[layeredPropSymbol];
      },
      set: function layeredSetter(v) {
        this[layeredPropSymbol] = v;
      },
      configurable: true
    });
    layerProperty(layer, object, property, defs);
  }
  function computeLayersFor(obj) {
    return obj && obj.activeLayers ? obj.activeLayers(activeLayers) : activeLayers();
  }
  function composeLayers(stack) {
    var result = self.GlobalLayers.slice(0); // copy the array, #TODO: use `Array.from`
    // duplicate of dynamicLayers, #TODO: use LayerableObjectTrait.dynamicLayers
    for (var i = 0; i < stack.length; i++) {
      var current = stack[i];
      if (current.withLayers) {
        result = result.filter(l => !current.withLayers.includes(l)).concat(current.withLayers);
      } else if (current.withoutLayers) {
        result = result.filter(l => !current.withoutLayers.includes(l));
      }
    }
    return result;
  }
  function resetLayerStack() {
    _export("LayerStack", LayerStack = [{
      isStatic: true,
      toString: function () {
        return "BaseLayer";
      },
      composition: null
    }]);
    invalidateLayerComposition();
  }
  function currentLayers() {
    return activeLayers
    // if (LayerStack.length == 0) {
    //   throw new Error("The default layer is missing");
    // }
    // // NON OPTIMIZED VERSION FOR STATE BASED LAYER ACTIVATION 
    // // #TODO check if this still hold for #async
    // var current = LayerStack[LayerStack.length - 1];
    // if (!current.composition) {
    //   current.composition = composeLayers(LayerStack) ;
    // }
    // return current.composition.concat(getActiveImplicitLayers());
    ();
  }

  // clear cached layer compositions
  function invalidateLayerComposition() {
    LayerStack.forEach(function (ea) {
      ea.composition = null;
    });
  }
  function lookupLayeredFunctionForObject(self, layer, function_name, methodType, n) {
    if (!layer) {
      return undefined;
    }
    let partialFunction;
    const partialLayerForObject = getLayerDefinitionForObject(layer, self);
    if (partialLayerForObject) {
      // log("  found layer definitions for object");
      if (methodType == 'getter') {
        partialFunction = partialLayerForObject.getterMethod(function_name);
      } else if (methodType == 'setter') {
        partialFunction = partialLayerForObject.setterMethod(function_name);
      } else {
        partialFunction = partialLayerForObject.property(function_name);
      }
    }
    if (!partialFunction && !self.hasOwnProperty(function_name)) {
      // this method was probably added by a layer
      // so try to look it up in the superclass hierachy
      const superclass = Object.getPrototypeOf(self);
      if (superclass) {
        return lookupLayeredFunctionForObject(superclass, layer, function_name, methodType);
      }
    }
    return partialFunction;
  }
  function pvtMakeFunctionOrPropertyLayerAware(obj, slotName, baseValue, type, isHidden) {
    // install in obj[slotName] a cop wrapper that weaves partial methods
    // into real method (baseValue)
    if (baseValue.isLayerAware) {
      return;
    }
    makeSlotLayerAwareWithNormalLookup(obj, slotName, baseValue, type, isHidden);
  }
  function makeSlotLayerAwareWithNormalLookup(obj, slotName, baseValue, type, isHidden) {
    var tmpObj = {};
    if (baseValue && baseValue.isLayerAware) {
      debugger;
    }
    let wrapped_function = function () {
      'use strict';

      var composition = new PartialLayerComposition(this, slotName, baseValue, type);
      self.proceedStack.push(composition);
      return invokeLayeredMethodThenPopProceedStack.apply(this, arguments);
    };
    wrapped_function.displayName = "wrapped_" + slotName;
    wrapped_function.isLayerAware = true;
    // this is more declarative outside of COP context
    wrapped_function.isContextJSWrapper = true;
    if (isHidden) {
      wrapped_function.toString = function () {
        return this.getOriginal().toString();
      };
    }
    // For wrapped_function.getOriginal()
    wrapped_function.originalFunction = baseValue;
    if (type == "getter") {
      Object.defineProperty(obj, slotName, {
        get: wrapped_function
      });
    } else if (type == "setter") {
      Object.defineProperty(obj, slotName, {
        set: wrapped_function
      });
    } else {
      Object.defineProperty(obj, slotName, {
        get() {
          return wrapped_function;
        },
        set(newFunction) {
          if (newFunction.isLayerAware) {
            // when someone reasigns an already layered method.... Example Bug. Dexie modifies Promise.then
            // #Paper #ContextJS 
          } else {
            makeSlotLayerAwareWithNormalLookup(this, slotName, newFunction);
          }
        }
      });
    }
  }
  function invokeLayeredMethodThenPopProceedStack() {
    'use strict';

    try {
      return proceed.apply(void 0 /* undefined */, arguments);
    } finally {
      self.proceedStack.pop();
    }
  }
  function makeFunctionLayerAware(base_obj, function_name, isHidden) {
    if (!base_obj) {
      throw new Error("can't layer an non existent object");
    }
    /* ensure base function */
    var base_function = base_obj[function_name];
    if (!base_function) {
      // console.log("WARNING can't layer an non existent function" + function_name +
      // " , so do nothing")
      // return;
      base_function = () => null;
    }
    pvtMakeFunctionOrPropertyLayerAware(base_obj, function_name, base_function, undefined, isHidden);
  }
  function makePropertyLayerAware(baseObj, property) {
    if (!baseObj) {
      throw new Error("can't layer a non existent object");
    }
    // ensure base getter and setter
    var baseObjProperty = Object.getOwnPropertyDescriptor(baseObj, property);
    var propName = "__layered_" + property + "__";
    var getter = baseObjProperty && baseObjProperty.get;
    if (!getter) {
      // does not work when dealing with classes and instances...
      baseObj[propName] = baseObj[property]; // take over old value
      getter = function () {
        return this[propName];
      };
      Object.defineProperty(baseObj, property, {
        get: getter,
        configurable: true
      });
    }
    var setter = baseObjProperty && baseObjProperty.set;
    if (!setter) {
      setter = function (value) {
        return this[propName] = value;
      };
      Object.defineProperty(baseObj, property, {
        set: setter,
        configurable: true
      });
    }
    pvtMakeFunctionOrPropertyLayerAware(baseObj, property, getter, 'getter');
    pvtMakeFunctionOrPropertyLayerAware(baseObj, property, setter, 'setter');
  }
  function makeFunctionLayerUnaware(base_obj, function_name) {
    if (!base_obj) {
      throw new Error("need object to makeFunctionLayerUnaware");
    }
    var prevFunction;
    var currentFunction = base_obj[function_name];
    if (currentFunction === undefined) {
      return; // nothing to do here
    }

    while (typeof currentFunction.originalFunction == 'function' && !currentFunction.isLayerAware) {
      prevFunction = currentFunction;
      currentFunction = currentFunction.originalFunction;
    }
    if (!currentFunction.isLayerAware) {
      return; // nothing to do here
    }

    var originalFunction = currentFunction.originalFunction;
    if (!(originalFunction instanceof Function)) {
      throw new Error("makeFunctionLayerUnaware Error: no orignal function");
    }
    if (prevFunction instanceof Function) {
      prevFunction.originalFunction = originalFunction;
    } else {
      // need to use defineProperty because the setter keeps the function wrapped
      Object.defineProperty(base_obj, function_name, {
        value: originalFunction,
        configurable: true
      });
    }
  }
  function uninstallLayersInObject(object) {
    Object.getOwnPropertyNames(object).forEach(ea => {
      if (typeof object[ea] === 'function') makeFunctionLayerUnaware(object, ea);
    });
  }

  /* 
   * PUBLIC COP Layer Definition
   */

  // Gloabl Layer Activation
  function enableLayer(layer) {
    if (self.GlobalLayers.indexOf(layer) !== -1) {
      return;
    }
    const wasAlreadyActive = currentLayers().includes(layer);
    self.GlobalLayers.push(layer);
    invalidateLayerComposition();
    if (!wasAlreadyActive) {
      layer._emitActivateCallbacks();
    }
  }
  function disableLayer(layer) {
    var idx = self.GlobalLayers.indexOf(layer);
    if (idx < 0) {
      return;
    }
    layer._emitDeactivateCallbacks();
    self.GlobalLayers.splice(idx, 1);
    invalidateLayerComposition();
  }
  function proceed(...args) {
    // COP Proceed Function
    var composition = self.proceedStack[self.proceedStack.length - 1];
    if (!composition) {
      console.warn('ContextJS: no composition to proceed (stack is empty) ');
      return;
    }
    if (composition.partialMethodIndex == undefined) {
      composition.partialMethodIndex = composition.partialMethods.length - 1;
    }
    var index = composition.partialMethodIndex;
    var partialMethod = composition.partialMethods[index];
    if (!partialMethod) {
      if (!partialMethod) {
        throw new COPError('no partialMethod to proceed');
      }
    } else {
      composition.partialMethodIndex = index - 1;
      try {
        if (self.proceedStack.length > 1000) {
          debugger; // #Debug
        }

        return invokeNextPartialMethod(partialMethod, index, composition, args);
      } catch (e) {
        console.error("proceed " + self.proceedStack.length, e);
        debugger; // #TODO debug endless recursion!
        return;
      }
    }
  }
  function invokeNextPartialMethod(partialMethod, index, composition, args) {
    try {
      return partialMethod.apply(composition.object, args);
    } finally {
      composition.partialMethodIndex = index;
    }
  }

  /* 
   * Layer Class
   */

  function getActiveImplicitLayers() {
    return [...implicitLayers].filter(layer => layer.implicitlyActivated());
  }
  function collectWithLayersIn(layers, result) {
    for (var i = layers.length - 1; i >= 0; i--) {
      var ea = layers[i];
      if (result.withLayers.indexOf(ea) === -1 && result.withoutLayers.indexOf(ea) === -1) {
        result.withLayers.unshift(ea);
      }
    }
  }
  function collectWithoutLayersIn(layers, result) {
    for (var i = 0; i < layers.length; i++) {
      var ea = layers[i];
      if (result.withoutLayers.indexOf(ea) === -1) {
        result.withoutLayers.push(ea);
      }
    }
  }
  function structuralLayers(result, obj) {
    // var allLayers = result.withLayers;
    // var allWithoutLayers = result.withoutLayers;
    // go ownerchain backward and gather all layer activations and deactivations
    while (obj) {
      // don't use accessor methods because of speed... (not measured yet)
      if (obj.withLayers) {
        collectWithLayersIn(obj.withLayers, result);
      }
      if (obj.withoutLayers) {
        collectWithoutLayersIn(obj.withoutLayers, result);
      }
      // recurse, stop if owner is undefined
      obj = obj.owner; // || obj.parentElement || obj.parentNode
    }

    return result;
  }
  function aysncLayers(result) {
    // optimized version, that does not use closures and recursion
    var stack = self.AsyncLayerStack;
    // top down, ignore bottom element
    for (var j = stack.length - 1; j > 0; j--) {
      var current = stack[j];
      if (current.withLayers) {
        collectWithLayersIn(current.withLayers, result);
      }
      if (current.withoutLayers) {
        collectWithoutLayersIn(current.withoutLayers, result);
      }
    }
    return result;
  }
  function globalLayers(result) {
    collectWithLayersIn(self.GlobalLayers, result);
    return result;
  }
  function dynamicLayers(result) {
    // optimized version, that does not use closures and recursion
    var stack = LayerStack;
    // top down, ignore bottom element
    for (var j = stack.length - 1; j > 0; j--) {
      var current = stack[j];
      if (current.withLayers) {
        collectWithLayersIn(current.withLayers, result);
      }
      if (current.withoutLayers) {
        collectWithoutLayersIn(current.withoutLayers, result);
      }
    }
    return result;
  }

  // #TODO #STEFAN rename -> implicitLayers
  function activeImplicitLayers(result) {
    collectWithLayersIn(getActiveImplicitLayers(), result);
    return result;
  }
  function getImplicitLayers(result) {
    [...implicitLayers].forEach(layer => {
      if (layer.implicitlyActivated()) {
        collectWithLayersIn([layer], result);
      } else {
        collectWithoutLayersIn([layer], result);
      }
    });
    return result;
  }
  function allLayersFor(obj, functionName, methodType) {
    const result = [];
    const layers = self.AllLayers;
    for (var i = 0; i < layers.length; i++) {
      var layer = layers[i];
      var partialMethod = lookupLayeredFunctionForObject(obj, layer, functionName, methodType);
      if (partialMethod) {
        result.push({
          partialMethod,
          layer
        });
      }
    }
    return result;
  }
  function allLayers() {
    var result = {
      withLayers: [],
      withoutLayers: []
    };
    dynamicLayers(result);
    getImplicitLayers(result);
    globalLayers(result);
    return result;
  }
  function activeLayers() {
    var result = {
      withLayers: [],
      withoutLayers: []
    };
    // go top to bottom in stack... 
    // so the last (dynamically) (de-)activated layers are first...
    // remember withLayers and withoutLayers in result
    // this is a bit to "clever" to iterate only once...
    dynamicLayers(result);

    // #Idea we can implement structural layer for HTMLElements without having to modify prototype...
    // structuralLayers(result, obj);
    activeImplicitLayers(result);
    globalLayers(result);
    // and the global layers are last
    return result.withLayers;
  }

  /*
   * Example implementation of a layerable object
   */
  _export({
    log: log,
    withLogLayerCode: withLogLayerCode,
    getLayerDefinitionForObject: getLayerDefinitionForObject,
    ensurePartialLayer: ensurePartialLayer,
    layerMethod: layerMethod,
    layerProperty: layerProperty,
    layerPropertyWithShadow: layerPropertyWithShadow,
    computeLayersFor: computeLayersFor,
    composeLayers: composeLayers,
    resetLayerStack: resetLayerStack,
    currentLayers: currentLayers,
    invalidateLayerComposition: invalidateLayerComposition,
    lookupLayeredFunctionForObject: lookupLayeredFunctionForObject,
    uninstallLayersInObject: uninstallLayersInObject,
    enableLayer: enableLayer,
    disableLayer: disableLayer,
    proceed: proceed,
    Layer: void 0,
    collectWithLayersIn: collectWithLayersIn,
    collectWithoutLayersIn: collectWithoutLayersIn,
    structuralLayers: structuralLayers,
    aysncLayers: aysncLayers,
    globalLayers: globalLayers,
    dynamicLayers: dynamicLayers,
    activeImplicitLayers: activeImplicitLayers,
    getImplicitLayers: getImplicitLayers,
    allLayersFor: allLayersFor,
    allLayers: allLayers,
    activeLayers: activeLayers,
    LayerableObjectTrait: void 0,
    LayerableObject: void 0,
    COPError: void 0,
    PartialLayerComposition: void 0,
    LayerStack: void 0
  });
  return {
    setters: [function (_srcClientReactiveActiveExpressionEventsEventJs) {
      EventTypes = _srcClientReactiveActiveExpressionEventsEventJs.EventTypes;
      Event = _srcClientReactiveActiveExpressionEventsEventJs.default;
    }],
    execute: function () {
      if (self.HTMLElement) {
        updateMember = function () {
          console.warn("Layers.js: aexpr updateMember not loaed yet!");
          throw new Error("raise condition? active-expression-rewriting.js not loaded yet");
        };
        System.import(lively4url + '/src/client/reactive/active-expression-rewriting/active-expression-rewriting.js').then(mod => {
          updateMember = mod.updateMember; // replace updateMember
        });
      } else {
        // fallback for worker or nodejs 
        updateMember = function () {
          // there is nothing to do here? 
        };
      }
      _export("Config", Config = {});
      Config.ignoreDeprecatedProceed = true;
      _export("log_layer_code", log_layer_code = false);
      self.proceedStack = [];
      self.AllLayers = [];
      self.GlobalLayers = [];
      self.AsyncLayerStack = [];

      // export const proceedStack = [];
      // export const GlobalLayers = [];

      // hack, to work around absence of identity dictionaries in JavaScript
      // we could perhaps limit ourselfs to layer only those objects that respond to object.id()
      // because working with objects is a serialization problem in itself, perhaps we should
      // restrict ourself in working with classes
      // So classes have names and names can be used as keys in dictionaries :-)
      object_id_counter = 0;
      LayerObjectID = self.Symbol("layerObjectID");
      PartialLayer = class PartialLayer {
        constructor(layeredObject) {
          this.layeredObject = layeredObject;
          this.layeredProperties = {};
        }
        setLayeredPropertyValue(name, value) {
          this.layeredProperties[name] = value;
        }
        defineGetter(propertyName, getter) {
          return Object.defineProperty(this.layeredProperties, propertyName, {
            get: getter,
            configurable: true
          });
        }
        defineSetter(propertyName, setter) {
          return Object.defineProperty(this.layeredProperties, propertyName, {
            set: setter,
            configurable: true
          });
        }
        getterMethod(propertyName) {
          var desc = Object.getOwnPropertyDescriptor(this.layeredProperties, propertyName);
          if (desc) return desc.get;
        }
        setterMethod(propertyName) {
          var desc = Object.getOwnPropertyDescriptor(this.layeredProperties, propertyName);
          if (desc) return desc.set;
        }
        property(propertyName) {
          if (this.layeredProperties.hasOwnProperty(propertyName)) {
            return this.layeredProperties[propertyName];
          }
        }
        reinstall() {
          Object.getOwnPropertyNames(this.layeredProperties).forEach(eachProperty => {
            const property = Object.getOwnPropertyDescriptor(this.layeredProperties, eachProperty);
            if (typeof property.get !== 'undefined' || typeof property.set !== 'undefined') {
              makePropertyLayerAware(this.layeredObject, eachProperty);
            } else {
              makeFunctionLayerAware(this.layeredObject, eachProperty);
            }
          });
        }
      };
      _export("GlobalNamedLayers", globalContextForNamedLayers = {});
      _export("Layer", Layer = class Layer {
        constructor(name, context) {
          this._name = name;
          if (typeof name === 'undefined') {
            this._name = self.Symbol('COP Layer');
          }
          this._context = context;
          // this._layeredFunctionsList = {};

          this._activateCallbacks = [];
          this._deactivateCallbacks = [];
          this.events = [];
          this.logEvent(EventTypes.LAYERCREATED, {
            layer: this
          });
          self.AllLayers.push(this);
        }

        // Accessing
        get name() {
          return this._name;
        }
        fullName() {
          return '' + this._context + '.' + this._name;
        }
        layeredObjects() {
          return Object.getOwnPropertyNames(this).map(ea => this[ea] && this[ea].layeredObject).filter(ea => ea); // filters falsy things
        }

        partialLayers() {
          return Object.getOwnPropertyNames(this).map(propName => this[propName]).filter(prop => prop && prop.layeredObject);
        }
        // TODO: doesn't differentiate between functions and classes - necessary?
        layeredClasses() {
          return this.layeredObjects().map(ea => ea.constructor);
        }

        // Removing
        remove() {
          self.AllLayers.splice(self.AllLayers.indexOf(this), 1);
          // Deletes the LayerClass, but keeps the layered Functions.
          if (this.isGlobal()) {
            this.beNotGlobal();
          }
          implicitLayers.delete(this);
          if (this.AExprForILA) {
            this.AExprForILA.dispose();
          }
          var context = this._context;
          if (typeof context !== 'undefined') delete context[this.name];
        }
        uninstall() {
          // Uninstalls just this Layer.
          // functions that are layered by other Layers will not be reset.
          var layer = this;
          this.layeredObjects().forEach(function (eachLayeredObj) {
            // var layerIdx = typeof eachLayeredObj.activeLayers === 'function'
            //     ? eachLayeredObj.activeLayers().indexOf(layer) : -1;

            // #Special Lively Webwerkstatt code.... General Case? #Jens
            // #TODO if we have of gloabal list of all layers... we can look there

            // Properties.own(layer._layeredFunctionsList[eachLayeredObj]).each(
            //   function(eachLayeredFunc) {
            //     var newerLayer = eachLayeredObj.activeLayers().find(
            //       function(eachOtherLayer) {
            //         var eachOtherLayerIdx
            //             = eachLayeredObj.activeLayers().indexOf(eachOtherLayer);
            //         var isNewer = (eachOtherLayerIdx !== -1)
            //             && (eachOtherLayerIdx < layerIdx);
            //         return isNewer &&
            //             eachOtherLayer._layeredFunctionsList[eachLayeredObj][eachLayeredFunc];
            //       });
            //       if (!newerLayer) {
            //         makeFunctionLayerUnaware(eachLayeredObj, eachLayeredFunc);
            //       }
            //   });
          });
          this.remove();
        }

        // Layer installation
        refineClass(classObject, methods, debugInfo) {
          if (!classObject || !classObject.prototype) {
            throw new Error("ContextJS: can not refine class '" + classObject + "' in " + this);
          }
          this.refineObject(classObject.prototype, methods, debugInfo);
          return this;
        }

        // Layering objects may be a garbage collection problem, because the layers keep strong
        // reference to the objects
        refineObject(object, methods, debugInfo) {
          // log("cop refineObject");

          // Bookkeeping:
          // typeof object.getName === 'function' && (layer._layeredFunctionsList[object] = {});
          Object.getOwnPropertyNames(methods).forEach(function_name => {
            if (debugInfo) {
              // console.log("ContextJS DEBUG INFO ", debugInfo)
              if (methods[function_name] instanceof Function) {
                methods[function_name].code = debugInfo[function_name].code;
                methods[function_name].location = debugInfo[function_name].location;
              } else {
                lively.warn("Unable to add debug info to non-function property");
              }
            }
            // log(" layer property: " + function_name)
            layerProperty(this, object, function_name, methods);
          });
          this.addRefineEvent(object, methods, debugInfo);
          return this;
        }
        unrefineObject(obj) {
          var id = obj[LayerObjectID];
          if (id !== undefined) {
            delete this[id];
          }
        }
        unrefineClass(classObj) {
          this.unrefineObject(classObj.prototype);
        }
        reinstallInClass(constructor) {
          this.reinstallInObject(constructor.prototype);
        }
        reinstallInObject(object) {
          const partialLayer = ensurePartialLayer(this, object);
          partialLayer.reinstall();
        }
        findLast(array, predicate) {
          for (let i = array.length - 1; i >= 0; --i) {
            const x = array[i];
            if (predicate(x)) {
              return x;
            }
          }
        }
        async getILAChangeLocation() {
          if (this.AExprForILA) {
            const lastChangeEvent = this.findLast(this.AExprForILA.events, event => event.type === EventTypes.CHANGED);
            if (!lastChangeEvent) {
              return undefined;
            }
            await lastChangeEvent.ensureResolved();
            if (lastChangeEvent.value && lastChangeEvent.value.triggers && lastChangeEvent.value.triggers.length > 0) {
              return lastChangeEvent.value.triggers[0].location;
            }
          }
          return undefined;
        }

        // Layer activation
        beGlobal() {
          enableLayer(this);
          this.getILAChangeLocation().then(location => {
            if (!location) return;
            for (const PL of this.partialLayers()) {
              Object.getOwnPropertyNames(PL.layeredProperties).forEach(fn => {
                updateMember(PL.layeredObject, fn, location);
              });
            }
          });
          return this;
        }
        beNotGlobal() {
          disableLayer(this);
          this.getILAChangeLocation().then(location => {
            if (!location) return;
            for (const PL of this.partialLayers()) {
              Object.getOwnPropertyNames(PL.layeredProperties).forEach(fn => {
                updateMember(PL.layeredObject, fn, location);
              });
            }
          });
          return this;
        }
        hide() {
          // Hidden Layers do not appear when evaluating the sourcecode of a function
          // TODO: this function has to be called BEFORE the layer refines any class,
          // due to problems in unrefining classes.
          this.isHidden = true;
          return this;
        }

        // Testing
        isLayer() {
          return true;
        }
        isGlobal() {
          return self.GlobalLayers.indexOf(this) !== -1;
        }

        // Debugging
        toString() {
          return String(this.name); // could be a symbol
        }

        // Life-cycle callbacks
        onActivate(callback) {
          this._activateCallbacks.push(callback);
          this._fallbackToReactiveTrackingOfILA();
          return this;
        }
        onDeactivate(callback) {
          this._deactivateCallbacks.push(callback);
          this._fallbackToReactiveTrackingOfILA();
          return this;
        }
        _emitActivateCallbacks() {
          this._activateCallbacks.forEach(cb => cb());
        }
        _emitDeactivateCallbacks() {
          this._deactivateCallbacks.forEach(cb => cb());
        }
        addRefineEvent(obj, functions, debugInfo) {
          this.logEvent(EventTypes.REFINE, {
            obj,
            functions,
            debugInfo
          });
        }
        addUnrefineEvent(obj) {
          this.logEvent(EventTypes.UNREFINE, {
            obj
          });
        }
        logEvent(eventType, obj) {
          if (this.AExprForILA) {
            this.AExprForILA.logEvent(eventType, obj);
          } else {
            this.events.push(new Event(undefined, obj, eventType));
          }
        }
        markTimestamp(reason) {
          this.logEvent(EventTypes.CUSTOM, reason);
        }

        // Implicit Layer Activation
        activeWhile(condition, aexprConstructor) {
          this.implicitlyActivated = condition;
          this.aexprConstructor = aexprConstructor;
          implicitLayers.add(this);
          this._fallbackToReactiveTrackingOfILA();
          return this;
        }
        _fallbackToReactiveTrackingOfILA() {
          if (this._shouldUseReactiveTracking()) {
            this._setupReactiveILA();
          }
        }
        _shouldUseReactiveTracking() {
          return this.implicitlyActivated && this.aexprConstructor && this._activateCallbacks.length + this._deactivateCallbacks.length > 0;
        }
        _setupReactiveILA() {
          implicitLayers.delete(this);
          if (!this.AExprForILA) {
            this.AExprForILA = this.aexprConstructor(this.implicitlyActivated, this).onBecomeTrue(() => this.beGlobal()).onBecomeFalse(() => this.beNotGlobal());
            this.events.forEach(event => {
              event.setAE(this.AExprForILA);
            });
          }
        }
        clearActiveWhile() {
          const mightBeActive = !!this.AExprForILA;
          if (mightBeActive) {
            this.AExprForILA.dispose();
          }
          delete this.aexprConstructor;
          implicitLayers.delete(this);
          delete this.implicitlyActivated;
          if (mightBeActive) {
            this.beNotGlobal();
          }
        }
      });
      implicitLayers = new Set();
      _export("LayerableObjectTrait", LayerableObjectTrait = class LayerableObjectTrait {
        activeLayers(defaultActiveLayersFunc) {
          var result = {
            withLayers: [],
            withoutLayers: []
          };
          // go top to bottom in stack... 
          // so the last (dynamically) (de-)activated layers are first...
          // remember withLayers and withoutLayers in result
          // this is a bit to "clever" to iterate only once...
          dynamicLayers(result);
          structuralLayers(result, this);
          globalLayers(result);
          // and the global layers are last
          return result.withLayers;
        }
        setWithLayers(layers) {
          this.withLayers = layers;
        }
        addWithLayer(layer) {
          var layers = this.getWithLayers();
          if (!layers.includes(layer)) {
            this.setWithLayers(layers.concat([layer]));
          }
        }
        removeWithLayer(layer) {
          var layers = this.getWithLayers();
          if (layers.includes(layer)) {
            this.setWithLayers(layers.filter(l => l !== layer));
          }
        }
        addWithoutLayer(layer) {
          var layers = this.getWithoutLayers();
          if (!layers.include(layer)) {
            this.setWithoutLayers(layers.concat([layer]));
          }
        }
        removeWithoutLayer(layer) {
          var layers = this.getWithoutLayers();
          this.setWithoutLayers(layers.filter(l => l !== layer));
        }
        setWithoutLayers(layers) {
          this.withoutLayers = layers;
        }
        getWithLayers() {
          return this.withLayers || [];
        }
        getWithoutLayer() {
          return this.withoutLayers || [];
        }
      });
      _export("LayerableObject", LayerableObject = class LayerableObject extends LayerableObjectTrait {});
      _export("COPError", COPError = class COPError {
        constructor(message) {
          this._msg = message;
        }
        toString() {
          return "COP ERROR: " + this._msg;
        }
      });
      _export("PartialLayerComposition", PartialLayerComposition = class PartialLayerComposition {
        constructor(obj, functionName, baseFunction, methodType) {
          this._partialMethods = [baseFunction];
          this._layers = [undefined];
          const layers = computeLayersFor(obj);
          for (var i = 0; i < layers.length; i++) {
            var layer = layers[i];
            var partialMethod = lookupLayeredFunctionForObject(obj, layer, functionName, methodType);
            if (partialMethod) {
              this._partialMethods.push(partialMethod);
              this._layers.push(layer);
            }
          }
          this._object = obj;
        }
        get object() {
          return this._object;
        }
        get partialMethods() {
          return this._partialMethods;
        }
        get layers() {
          return this._layers;
        }
      });
      resetLayerStack();

      // vim: sw=2
    }
  };
});