text: const ComponentRefs = require('./componentrefs');
  const UUID = require('uuid/v1');
  const CoreProperties = new Set([
    'ecs', 'entity', 'type', '_values', '_ready', 'id',
    'updated', 'constructor', 'stringify', 'clone', 'getObject'
  ]);
  
  /**ECS base class of component
   * @class BaseComponent
   */
  class BaseComponent {
  
    constructor(ecs, entity, initialValues) {
  
      Object.defineProperty(this, 'ecs', { enumerable: false, value: ecs });
      Object.defineProperty(this, 'entity', { enumerable: true, value: entity });
      Object.defineProperty(this, 'type', { enumerable: false, value: this.constructor.name });
      Object.defineProperty(this, '_values', { enumerable: false, value: {} });
      Object.defineProperty(this, '_refs', { enumerable: false, value: {} });
      Object.defineProperty(this, '_ready', { writable: true, enumerable: false, value: false });
      Object.defineProperty(this, 'id', { enumerable: true, value: initialValues.id || UUID() });
      Object.defineProperty(this, 'updated', { enumerable: false, writable: true, value: this.ecs.ticks });
  
      //loop through inheritance by way of prototypes
      //avoiding constructor->super() boilerplate for every component
      //also avoiding proxies just for a simple setter on properties
      const definitions = [];
      for (var c = this.constructor; c !== null; c = Object.getPrototypeOf(c)) {
        if (!c.definition) continue;
        definitions.push(c.definition);
      }
      //we want to inherit deep prototype defintions first
      definitions.reverse();
  
      for (let idx = 0, l = definitions.length; idx < l; idx++) {
  
        const definition = definitions[idx];
        // set component properties from Component.properties
        if (!definition.properties) {
          continue;
        }
        const properties = definition.properties;
        const keys = Object.keys(properties);
        for (let idx = 0, l = keys.length; idx < l; idx++) {
          const property = keys[idx];
          if (CoreProperties.has(property)) {
            throw new Error(`Cannot override property in Component definition: ${property}`);
          }
          const value = properties[property];
          if (this._values.hasOwnProperty(property)) {
            this[property] = value;
            continue;
          }
          switch (value) {
            case '<EntitySet>':
              Object.defineProperty(this, property, {
                //writable: true,
                enumerable: true,
                set: (value) => {
                  Reflect.set(this._values, property, ComponentRefs.EntitySet(value, this, property));
                },
                get: () => {
                  return Reflect.get(this._values, property);
                }
              });
              //this._refs[property] = this[property];
              this[property] = [];
              break;
            case '<EntityObject>':
              Object.defineProperty(this, property, {
                writable: false,
                enumerable: true,
                value: ComponentRefs.EntityObject({}, this, property)
              });
              this._refs[property] = this[property];
              break;
            case '<Entity>':
              Object.defineProperty(this, property, {
                enumerable: true,
                writeable: true,
                set: (value) => {
  
                  if (value && value.id) {
                    value = value.id;
                  }
                  const old = Reflect.get(this._values, property);
                  if (old && old !== value) {
                    this.ecs.deleteRef(old, this.entity.id, this.id, property);
                  }
                  if (value && value !== old) {
                    this.ecs.addRef(value, this.entity.id, this.id, property);
                  }
                  const result = Reflect.set(this._values, property, value);
                  this.ecs._sendChange(this, 'setEntity', property, old, value);
                  return result;
                },
                get: () => {
  
                  return this.ecs.getEntity(this._values[property]);
                }
              });
              this._values[property] = null;
              break;
            case '<ComponentObject>':
              Object.defineProperty(this, property, {
                writable: false,
                enumerable: true,
                value: ComponentRefs.ComponentObject({}, this)
              });
              this._refs[property] = this[property];
              break;
            case '<ComponentSet>':
              Object.defineProperty(this, property, {
                //writable: true,
                enumerable: true,
                set: (value) => {
                  Reflect.set(this._values, property, ComponentRefs.ComponentSet(value, this, property));
                },
                get: () => {
                  return Reflect.get(this._values, property);
                }
              });
              //this._refs[property] = this[property];
              this[property] = [];
              break;
            case '<Component>':
              Object.defineProperty(this, property, {
                enumerable: true,
                writeable: true,
                set: (value) => {
  
                  if (typeof value === 'object') {
                    value = value.id;
                  }
                  const old = Reflect.get(this._values, property);
                  const result = Reflect.set(this._values, property, value);
                  this.ecs._sendChange(this, 'setComponent', property, old, value);
                  return result;
                },
                get: () => {
  
                  return this.entity.componentMap[this._values[property]];
                }
              });
              this._values[property] = null;
              break;
            default:
              let reflect = null;
              if (typeof value === 'string' && value.startsWith('<Pointer ')) {
                reflect = value.substring(9, value.length - 1).trim().split('.')
              }
              Object.defineProperty(this, property, {
                enumerable: true,
                writeable: true,
                set: (value) => {
  
                  const old = Reflect.get(this._values, property, value);
                  const result = Reflect.set(this._values, property, value);
                  if (reflect) {
                    let node = this;
                    let fail = false;
                    for (let i = 0; i < reflect.length - 1; i++) {
                      const subprop = reflect[i];
                      /* $lab:coverage:off$ */
                      if (typeof node === 'object' && node !== null && node.hasOwnProperty(subprop)) {
                      /* $lab:coverage:on */
                        node = node[subprop];
                      } else {
                        fail = true;
                      }
                    }
                    if (!fail) {
                      Reflect.set(node, reflect[reflect.length - 1], value);
                      node = value;
                    }
                  }
                  this.ecs._sendChange(this, 'set', property, old, value);
                  return result;
                },
                get: () => {
                  if (!reflect) {
                    return Reflect.get(this._values, property);
                  }
                  let node = this;
                  let fail = false;
                  for (let i = 0; i < reflect.length - 1; i++) {
                    const subprop = reflect[i];
                    /* $lab:coverage:off$ */
                    if (typeof node === 'object' && node !== null && node.hasOwnProperty(subprop)) {
                    /* $lab:coverage:on */
                      node = node[subprop];
                    } else {
                      fail = true;
                    }
                  }
                  if (!fail) {
                    return Reflect.get(node, reflect[reflect.length - 1]);
                  } else {
                    return Reflect.get(this._values, property);
                  }
                }
              });
              this._values[property] = value;
              break;
          }
        }
      }
  
      // don't allow new properties
      Object.seal(this);
      Object.seal(this._values);
      const values = { ...initialValues };
      delete values.type;
      delete values.entity;
      delete values.id;
      Object.assign(this, values);
      this.ecs._sendChange(this, 'addComponent');
      this._ready = true;
    }
  
  
    stringify() {
  
      return JSON.stringify(this.getObject());
    }
  
    getObject() {
  
      const serialize  = this.constructor.definition.serialize;
      let values = this._values;
      if (serialize) {
        /* $lab:coverage:off$ */
        if (serialize.skip) return undefined;
        /* $lab:coverage:on$ */
        if (serialize.ignore.length > 0) {
          values = {}
          const props = new Set([...serialize.ignore]);
          for (const prop of Object.keys(this._values).filter(prop => !props.has(prop))) {
            values[prop] = this._values[prop];
          }
        }
      }
      return Object.assign({ id: this.id, type: this.type }, values, this._refs);
    }
  
  }
  
  BaseComponent.definition = {
    properties: {
    },
    multiset: false,
    serialize: {
      skip: false,
      ignore: [],
    }
  };
  
  module.exports = BaseComponent;
  