/**
 * @module meteoJS/synview/resourceCollection
 */
import Collection from './Collection.js';
import Resource from './Resource.js';

/**
 * Collection of resource objects.
 * Extends meteoJS.synview.collection with storage of time objects.
 * This collection contains either a list of resources assigned with time or
 * one resource with no time (or both). With the methods from
 * meteoJS.synview.collection all resources (with or without time) will be
 * retrieved. Id of the resource without time is '' (empty string).
 * 
 * @extends module:meteoJS/synview/collection.Collection
 */
export class ResourceCollection extends Collection {
  
  constructor() {
    super();
    
    /**
     * List of the datetime objects of the resources (sorted upwardly).
     * @member {Date[]}
     * @private
     */
    this.times = [];
  }
  
  /**
   * Returns all resources assigned with time (ordered temporal upwardly).
   * 
   * @return {module:meteoJS/synview/resource.Resource[]} Resources.
   */
  getResources() {
    return this.times.map(function (time) {
      return this.items[time.valueOf()];
    }, this);
  }
  
  /**
   * Returns times (ordered temporal upwardly).
   * 
   * @return {Date[]} Times (no invalid times).
   */
  getTimes() {
    return this.times;
  }
  
  /**
   * @override
   * @param {mixed} id ID.
   * @return {module:meteoJS/synview/resource.Resource} Resource.
   */
  getItemById(id) {
    var res = super.getItemById(id);
    return (res === undefined) ? new Resource() : res;
  }
  
  /**
   * Returns resource valid at passed datetime (could be an invalid datetime).
   * If resource doesn't exist, an empty object is returned.
   * 
   * @param {Date} time Datetime.
   * @return {module:meteoJS/synview/resource.Resource} Resource.
   */
  getResourceByTime(time) {
    return this.getItemById(isNaN(time) ? '' : time.valueOf());
  }
  
  /**
   * Returns if a resource with passed time exists. Time could be invalid.
   * 
   * @param {Date} Time.
   * @return {boolean} If exists.
   */
  containsTime(time) {
    return this.getIndexById(isNaN(time) ? '' : time.valueOf()) > -1;
  }
  
  /**
   * Returns index of the time inside the times array. Time could be invalid.
   * -1 if not existant.
   * 
   * @param {Date} time Time.
   * @return {integer} Index.
   */
  getIndexByTime(time) {
    var result = -1;
    if (!isNaN(time))
      this.times.forEach(function (t, i) {
        if (t.valueOf() == time.valueOf())
          result = i;
      });
    return result;
  }
  
  /**
   * Returns if a resource with ID exists in this collection.
   * 
   * @return {module:meteoJS/synview/resource.Resource}
   */
  getNewestResource() {
    if (this.times.length < 1)
      return new Resource();
    return this.getResourceByTime(this.times[this.times.length-1]);
  }
  
  /**
   * Append a resource to the collection.
   * 
   * @override
   * @param {module:meteoJS/synview/resource.Resource} resource Resource.
   * @return {module:meteoJS/synview/resourceCollection.ResourceCollection} This.
   */
  append(resource) {
    this._append(resource);
    this._sortTimes();
    return this;
  }
  
  /**
   * Removes a resource from the collection.
   * 
   * @override
   * @param {Date} time Resource's time.
   * @return {module:meteoJS/synview/resourceCollection.ResourceCollection} This.
   */
  remove(time) {
    this._remove(isNaN(time) ? '' : time.valueOf());
    this._sortTimes();
    return this;
  }
  
  /**
   * Exchanges the collection content with a list of resource.
   * 
   * @param {module:meteoJS/synview/resource.Resource[]} resources Resources.
   * @fires module:meteoJS/synview/collection#add:item
   * @fires module:meteoJS/synview/collection#replace:item
   * @fires module:meteoJS/synview/collection#remove:item
   * @return {module:meteoJS/synview/resourceCollection.ResourceCollection} This.
   */
  setResources(resources) {
    resources.forEach(function (resource) {
      this._append(resource);
    }, this);
    this._filterTimesByResources(resources);
    this._sortTimes();
    return this;
  }
  
  /**
   * Append a resource to the collection without reordering times-array.
   * 
   * @private
   * @param {module:meteoJS/synview/resource.Resource} resource Resource.
   */
  _append(resource) {
    var time = resource.getDatetime();
    var id = (time === undefined) ? '' : time.valueOf();
    if (this.containsId(id)) {
      this.trigger('replace:item', resource, this.getItemById(id));
      this.items[id] = resource;
    }
    else {
      this.itemIds.push(id);
      this.items[id] = resource;
      if (time !== undefined && !isNaN(time))
        this.times.push(time);
      this.trigger('add:item', resource);
    }
  }
  
  /**
   * Removes a resource fromt the collection without reordering times-array.
   * 
   * @private
   * @param {mixed} id Resource ID.
   */
  _remove(id) {
    var index = this.getIndexById(id);
    if (index > -1) {
      var resource = this.items[id];
      delete this.items[id];
      this.itemIds.splice(index, 1);
      if (id !== undefined && id !== '' && !isNaN(id)) {
        var tIndex = this.times.findIndex(function (t) {
          return t.valueOf() == id;
        });
        if (tIndex > -1)
          this.times.splice(tIndex, 1);
      }
      this.trigger('remove:item', resource);
    }
  }
  
  /**
   * Removes all resources whose times doesn't exist in the collection.
   * 
   * @private
   * @param {module:meteoJS/synview/resource.Resource[]}
   */
  _filterTimesByResources(resources) {
    var containsStaticResource = false;
    this.times = this.times.filter(function (t) {
      var filter = false;
      var i = resources.findIndex(function (resource) {
        var match = false;
        var time = resource.getDatetime();
        if (time !== undefined &&
            t.valueOf() == time.valueOf()) {
          match = true;
        }
        else if (time === undefined)
          containsStaticResource = true;
        return match;
      }, this);
      if (i < 0) {
        this.remove(t.valueOf());
        filter = true;
      }
      return !filter;
    }, this);
    if (containsStaticResource)
      this.remove('');
  }
  
  /**
   * Sortiert den Zeit-Array this.times der Reihe nach.
   * @private
   */
  _sortTimes() {
    this.times.sort(function (a, b) {
      return a.valueOf()-b.valueOf();
    });
  }
  
}
export default ResourceCollection;