/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* global loop:true */

var loop = loop || {};
loop.store = loop.store || {};

loop.store.createStore = (function() {
  "use strict";

  var baseStorePrototype = {
    __registerActions: function(actions) {
      // check that store methods are implemented
      actions.forEach(function(handler) {
        if (typeof this[handler] !== "function") {
          throw new Error("Store should implement an action handler for " +
                          handler);
        }
      }, this);
      this.dispatcher.register(this, actions);
    },

    /**
     * Proxy helper for dispatching an action from this store.
     *
     * @param  {sharedAction.Action} action The action to dispatch.
     */
    dispatchAction: function(action) {
      this.dispatcher.dispatch(action);
    },

    /**
     * Returns current store state. You can request a given state property by
     * providing the `key` argument.
     *
     * @param  {String|undefined} key An optional state property name.
     * @return {Mixed}
     */
    getStoreState: function(key) {
      return key ? this._storeState[key] : this._storeState;
    },

    /**
     * Updates store state and trigger a global "change" event, plus one for
     * each provided newState property.
     *
     * @param {Object} newState The new store state object.
     */
    setStoreState: function(newState) {
      for (var key in newState) {
        this._storeState[key] = newState[key];
        this.trigger("change:" + key);
      }
      this.trigger("change");
    },

    /**
     * Resets the store state to the initially defined state.
     */
    resetStoreState: function() {
      if (typeof this.getInitialStoreState === "function") {
        this._storeState = this.getInitialStoreState();
      } else {
        this._storeState = {};
      }
    }
  };

  /**
   * Creates a new Store constructor.
   *
   * @param  {Object}   storeProto The store prototype.
   * @return {Function}            A store constructor.
   */
  function createStore(storeProto) {
    var BaseStore = function(dispatcher, options) {
      options = options || {};

      if (!dispatcher) {
        throw new Error("Missing required dispatcher");
      }
      this.dispatcher = dispatcher;
      if (Array.isArray(this.actions)) {
        this.__registerActions(this.actions);
      }

      if (typeof this.initialize === "function") {
        this.initialize(options);
      }

      if (typeof this.getInitialStoreState === "function") {
        this._storeState = this.getInitialStoreState();
      } else {
        this._storeState = {};
      }
    };
    BaseStore.prototype = _.extend({}, // destination object
                                   Backbone.Events,
                                   baseStorePrototype,
                                   storeProto);
    return BaseStore;
  }

  return createStore;
})();

/**
 * Store mixin generator. Usage:
 *
 *     StoreMixin.register({roomStore: new RoomStore(…)});
 *     var Comp = React.createClass({
 *       mixins: [StoreMixin("roomStore")]
 *     });
 */
loop.store.StoreMixin = (function() {
  var _stores = {};
  function StoreMixin(id) {
    function _getStore() {
      if (!_stores[id]) {
        throw new Error("Unavailable store " + id);
      }
      return _stores[id];
    }
    return {
      getStore: function() {
        return _getStore();
      },
      getStoreState: function() {
        return this.getStore().getStoreState();
      },
      componentWillMount: function() {
        this.getStore().on("change", function() {
          this.setState(this.getStoreState());
        }, this);
      },
      componentWillUnmount: function() {
        this.getStore().off("change");
      }
    };
  }
  StoreMixin.register = function(stores) {
    _.extend(_stores, stores);
  };
  return StoreMixin;
})();
