text: import { compose } from 'stampit';
  import pickBy from 'lodash/pickBy';
  import { isFunction } from './helpers';
  
  import Entity from './Entity';
  import Pool from './Pool';
  
  /**
   * Centralized place for registering, creating and reusing entities.
   *
   * __For internal use only!__
   *
   * @class EntityStore
   * @param {Object}          opts
   * @param {Entropy}         opts.game
   * @param {ComponentStore}  opts.componentStore
   */
  const EntityStore = compose({
    init({ game }) {
      /**
       * Object with different pool for each entity type.
       * Entity type is a key and {@link Pool} instance is a value.
       *
       * @private
       * @memberOf EntityStore#
       * @name _pools
       * @type Object
       */
      this._pools = {};
  
      /**
       * Object with factories for entity types.
       *
       * @private
       * @memberOf EntityStore#
       * @name _factories
       * @type Object
       */
      this._factories = {};
  
      /**
       * Game instance injected to constructor.
       *
       * @public
       * @memberOf EntityStore#
       * @name game
       * @type Entropy
       */
      this.game = game;
    },
    methods: {
      /**
       * Registers entity. Entity has to be registered before it can be created.
       *
       * @example
       * game.entity.register({
       *   type: 'Ball',
       *   onCreate(x, y, texture) {
       *     const sprite = Sprite(texture);
       *
       *     this.addComponent('Position', x, y);
       *     this.addComponent('Sprite', sprite);
       *
       *     // add sprite to stage for renderer (when using PIXI this has to be done)
       *     this.game.stage.addChild(sprite);
       *   },
       *   onRemove() {
       *     this.game.stage.removeChild(this.components.sprite.sprite);
       *   },
       * });
       *
       * const c = game.entity.create('Ball', 1, 2, 'ball.png');
       *
       * @public
       * @memberof EntityStore#
       * @method register
       * @param {Object}    descriptor            object describing entity
       * @param {String}    descriptor.type       type of entity
       * @param {Function}  descriptor.onCreate   method called when entity is created (may be recycled from pool)
       * @param {Function}  descriptor.onReuse    method called when entity is reused from pool (called before `onCreate`)
       * @param {Function}  descriptor.onRemove   method called when entity is removed (usually it returns to a pool)
       */
      register(descriptor) {
        this._factories[descriptor.type] = compose(Entity, {
          methods: pickBy(descriptor, value => isFunction(value)),
        });
  
        this._pools[descriptor.type] = Pool({
          _new: (...args) => {
            const entity = this._factories[descriptor.type]({
              game: this.game,
              type: descriptor.type,
            });
  
            entity.onCreate(...args);
  
            return entity;
          },
          _reuse: (entity, ...args) => {
            entity.onReuse();
            entity.onCreate(...args);
  
            return entity;
          },
        });
      },
      /**
       * Registeres many entities.
       *
       * @public
       * @memberof EntityStore#
       * @method registerMany
       * @param  {Array} descriptiors array of entity's descriptors (see {@link EntityStore#register})
       */
      registerMany(descriptors) {
        descriptors.forEach(descriptor => this.register(descriptor));
      },
      /**
       * Creates new entity instance or acquires one from pool.
       *
       * @public
       * @memberof EntityStore#
       * @method create
       * @param  {String} type    type of entity to create
       * @param  {...Any} ...args arguments passed to `onCreate` method
       * @return {Entity}         new (or reused) entity ready to be added to engine
       */
      create(type, ...args) {
        return this._pools[type].allocate(...args);
      },
      /**
       * Frees entity. Entity is added to the pool. Marks entity as recyceled.
       *
       * _This method is called internally by the engine, user should not call it._
       *
       * @public
       * @memberof EntityStore#
       * @method free
       * @param  {Entity} entity entity
       */
      free(entity) {
        entity.markAsRecycled();
  
        this._pools[entity.type].free(entity);
      },
    },
  });
  
  export default EntityStore;
  