Show:
/**
  @module ember-flexberry-data
*/

import { computed } from '@ember/object';
import { getOwner } from '@ember/application';
import { isArray } from '@ember/array';
import { assert, debug } from '@ember/debug';
import { isNone, isBlank } from '@ember/utils';
import RSVP from 'rsvp';
import DS from 'ember-data';
import OfflineAdapter from '../adapters/offline';
import QueryBuilder from '../query/builder';

/**
  Store that used in offline mode by default.

  @class LocalStore
  @extends <a href="http://emberjs.com/api/data/classes/DS.Store.html">DS.Store</a>
  @private
*/
export default DS.Store.extend({
  /**
    Database name for IndexedDB.

    @property dbName
    @type String
    @default 'ember-flexberry-data'
  */
  dbName: computed({
    get() {
      return this.get('adapter.dbName');
    },
    set(key, value) {
      return this.set('adapter.dbName', value);
    },
  }),

  /**
    Initializing instance.
    [More info](http://emberjs.com/api/data/classes/DS.Store.html#method_init).

    @method init
  */
  init() {
    this._super(...arguments);
    let dbName = this.get('dbName');
    let owner = getOwner(this);
    this.set('adapter', OfflineAdapter.create(owner.ownerInjection(), dbName ? { dbName } : {}));
  },

  /**
   * Returns an instance of the serializer for a given type.
   * Offline serializers should have name with postfix '-offline'.
   *
   * @method serializerFor
   * @param {String} modelName The name of the model type.
   * @public
   */
  serializerFor: function(modelName) {
    let owner = getOwner(this);
    let serializer = owner.lookup(`serializer:${modelName}-offline`);
    if (!serializer) {
      serializer = owner.lookup(`serializer:application-offline`);
      if (!serializer) {
        serializer = this.adapterFor(modelName).defaultSerializer;
      }
    }

    return serializer;
  },

  /**
   * Returns an instance of the adapter for a given type.
   * Offline adapters should have name with postfix '-offline'.
   *
   * @method adapterFor
   * @param {String} modelName The name of the model type.
   * @public
   */
  adapterFor: function(modelName) {
    let owner = getOwner(this);
    let adapter = owner.lookup(`adapter:${modelName}-offline`);
    if (!adapter) {
      adapter = owner.lookup(`adapter:application-offline`);
      if (!adapter) {
        adapter = this.get('adapter');
      }
    }

    return adapter;
  },

  /**
   * Finds the records for the given model type.
   *
   * See {{#crossLink "DS.Store/findAll:method"}}{{/crossLink}} for details.
   *
   * @method findAll
   * @public
   *
   * @param {String} modelName The name of the model type.
   * @param {Object} [options] Options.
   * @param {String} options.projection Projection name.
   * @return {DS.AdapterPopulatedRecordArray} Records promise.
   */
  findAll: function(modelName, options) {
    debug(`Flexberry Local Store::findAll ${modelName}`);

    let builder = new QueryBuilder(this, modelName);
    if (options && options.projection) {
      debug(`Flexberry Local Store::findAll using projection '${options.projection}'`);

      builder.selectByProjection(options.projection);
      return this.query(modelName, builder.build());
    }

    let queryObject = builder.build();

    // Now if projection is not specified then only 'id' field will be selected.
    queryObject.select = [];
    return this.query(modelName, queryObject);
  },

  /**
   * Returns a record for a given type and id combination.
   *
   * See {{#crossLink "DS.Store/findRecord:method"}}{{/crossLink}} for details.
   *
   * @method findRecord
   * @public
   *
   * @param {String} modelName The name of the model type.
   * @param {String|Integer} id Record ID.
   * @param {Object} [options] Options.
   * @param {String} options.projection Projection name.
   * @return {Promise} Record promise.
   */
  findRecord: function(modelName, id, options) {
    // TODO: case of options.reload === false.
    debug(`Flexberry Local Store::findRecord ${modelName}(${id})`);

    let builder = new QueryBuilder(this, modelName).byId(id);
    if (options && options.projection) {
      debug(`Flexberry Local Store::findRecord using projection '${options.projection}'`);

      builder.selectByProjection(options.projection);
      return this.queryRecord(modelName, builder.build());
    }

    let queryObject = builder.build();

    // Now if projection is not specified then only 'id' field will be selected.
    queryObject.select = [];
    return this.queryRecord(modelName, queryObject);
  },

  /**
   * This method delegates a query to the adapter.
   *
   * See {{#crossLink "DS.Store/query:method"}}{{/crossLink}} for details.
   *
   * @method query
   * @public
   *
   * @param {String} modelName The name of the model type.
   * @param {Object} query An opaque query to be used by the adapter.
   * @param {String} [query.projection] Projection name.
   * @return {Promise} A promise, which is resolved with a
   *                   {{#crossLink "DS.RecordArray"}}RecordArray{{/crossLink}}
   *                   once the server returns.
   */
  query: function(modelName, query) {
    debug(`Flexberry Local Store::query ${modelName}`, query);

    let promise = this._super(...arguments);
    return new RSVP.Promise((resolve, reject) => {
      promise.then((results) => {
        if (results && isArray(results)) {
          results.forEach((result) => {
            result.didLoad();
          });
        }

        resolve(results);
      }, reject);
    });
  },

  /**
   * This method delegates a query to the adapter.
   *
   * See {{#crossLink "DS.Store/queryRecord:method"}}{{/crossLink}} for details.
   *
   * @method queryRecord
   * @public
   *
   * @param {String} modelName The name of the model type.
   * @param {Object} query An opaque query to be used by the adapter.
   * @param {String} [query.projection] Projection name.
   * @return {Promise} A promise, which is resolved with a
   *                   {{#crossLink "DS.RecordObject"}}RecordObject{{/crossLink}}
   *                   once the server returns.
   */
  queryRecord: function(modelName, query) {
    debug(`Flexberry Local Store::queryRecord ${modelName}`, query);

    let promise = this._super(...arguments);
    return new RSVP.Promise((resolve, reject) => {
      promise.then((result) => {
        if (result) {
          result.didLoad();
        }

        resolve(result);
      }, reject);
    });
  },

  /**
    Delete all record from the current store.
    @method deleteRecord
    @param {String} modelName modelName
    @param {Object} filter filter
  */
  deleteAllRecords: function(modelName, filter) {
    let adapter = this.adapterFor(modelName);
    if (isNone(adapter.deleteAllRecords)) {
      assert('Method \'deleteAllRecords\' is missing');
    }

    return adapter.deleteAllRecords(adapter.store, modelName, filter);
  },

  /**
    Calls the `save` method on each passed model and returns a promise that is resolved by an array of saved models.

    The array which fulfilled the promise may contain the following values:
    - `same model object` - for created, updated or unaltered records.
    - `null` - for deleted records.

    @method batchUpdate
    @param {DS.Model[]|DS.Model} models Is array of models or single model for batch update.
    @return {Promise} A promise that fulfilled with an array of models in the new state.
  */
  batchUpdate(models) {
    return RSVP.all(isArray(models) ? models.map((model) => {
      if (model.get('dirtyType') === 'deleted') {
        return model.save().then(() => null);
      }

      return model.save();
    }) : [models.save()]);
  },

  /**
    A method to get array of models with batch request.

    @method batchSelect
    @param {Query} queries Array of Flexberry Query objects.
    @return {Promise} A promise that fulfilled with an array of query responses.
  */
  batchSelect(queries) {
    return this.adapterFor('application').batchSelect(this, queries);
  },

  /**
   * Pushes into store the model that exists in backend without a request to it.
   * @param {String} modelName Name of the model to push into store.
   * @param {String} primaryKey Primery key of the model to push into store.
   */
  createExistingRecord(modelName, primaryKey) {
    assert('Model name for store.createExistingRecord() method must not be blank.', !isBlank(modelName));
    assert('Model primary key for store.createExistingRecord() method must not be blank.', !isBlank(primaryKey));

    return this.push({
      data: {
        id: primaryKey,
        type: modelName
      }
    });
  }
});