define(function(require) {
  "use strict";

  var _ = require('underscore'),
      Backbone = require('backbone');

  var CHANGE_EVENT = 'change';

  var Store = function(attributes, options) {
    //var attrs = attributes || {};
    options || (options = {});
    this.attributes = {};
    if (options.collection) this.collection = options.collection;

    this.models = null;
    this.isFetching = false;

    this.pollingEnabled = false;
    this.modelsBuilding = [];
    this.pollingFrequency = 5*1000;

    this.initialize.apply(this, arguments);
  };

  _.extend(Store.prototype, Backbone.Events, {

    // ---------------
    // Event listeners
    // ---------------

    addChangeListener: function(callback) {
      this.on(CHANGE_EVENT, callback);
    },

    removeChangeListener: function(callback) {
      this.off(CHANGE_EVENT, callback);
    },

    emitChange: function() {
      this.trigger(CHANGE_EVENT);
    },

    // --------------
    // CRUD functions
    // --------------

    add: function(model){
      this.models.add(model);
    },

    update: function(model){
      var existingModel = this.models.get(model);
      if(existingModel) {
        this.models.add(model, {merge: true});
      }else{
        console.error("Model doesn't exist: " + model.id || model.cid);
      }
    },

    remove: function(model){
      this.models.remove(model);
    },

    // --------------
    // Core functions
    // --------------

    initialize: function(){},

    fetchModels: function () {
      if (!this.models && !this.isFetching) {
        this.isFetching = true;
        var models = new this.collection();
        var queryString = "";

        // Build the query string if queryParameters have been provided
        if(this.queryParams){
          queryString = Object.keys(this.queryParams).map(function(key, index){
            return key + "=" + this.queryParams[key];
          }.bind(this)).join("&");
          queryString = queryString ? "?" + queryString : queryString;
        }

        models.fetch({
          url: _.result(models, 'url') + queryString
        }).done(function(){
          this.isFetching = false;
          this.models = models;
          if(this.pollingEnabled) {
            this.models.each(this.pollNowUntilBuildIsFinished.bind(this));
          }
          this.emitChange();
        }.bind(this));
      }
    },

    getAll: function () {
      if(!this.models) {
        this.fetchModels()
      }else{
        return this.models;
      }
    },

    get: function (modelId) {
      if(!this.models) {
        this.fetchModels();
      } else {
        return this.models.get(modelId);
      }
    },

    findWhere: function(params){
      if(!this.models) return this.fetchModels();

      var keys = Object.keys(params);

      var models = this.models.filter(function(model){
        var matchesCriteria = true;

        keys.forEach(function(key){
          if(!matchesCriteria) return;

          var tokens = key.split('.');
          if(tokens.length === 1){
            if(model.get(key) !== params[key]) matchesCriteria = false;
          }else{
            if(model.get(tokens[0])[tokens[1]] !== params[key]) matchesCriteria = false;
          }
        });

        return matchesCriteria;
      });

      return new this.collection(models);
    },

    // -----------------
    // Polling functions
    // -----------------

    pollNowUntilBuildIsFinished: function(model) {
      if (model.id && this.modelsBuilding.indexOf(model) < 0) {
        this.modelsBuilding.push(model);
        this.fetchNowAndRemoveIfFinished(model);
      }
    },

    pollUntilBuildIsFinished: function(model) {
      if (model.id && this.modelsBuilding.indexOf(model) < 0) {
        this.modelsBuilding.push(model);
        this.fetchAndRemoveIfFinished(model);
      }
    },

    fetchAndRemoveIfFinished: function(model) {
      if(!model.fetchFromCloud) throw new Error("model missing required method for polling: fetchFromCloud");
      if(!this.isInFinalState) throw new Error("store missing required method for polling: isInFinalState");

      setTimeout(function () {
        model.fetchFromCloud(function() {
          this.update(model);
          var index = this.modelsBuilding.indexOf(model);
          if(this.isInFinalState(model)) {
            this.modelsBuilding.splice(index, 1);
          } else {
            this.fetchAndRemoveIfFinished(model);
          }
          this.emitChange();
        }.bind(this));
      }.bind(this), this.pollingFrequency);
    },

    fetchNowAndRemoveIfFinished: function(model) {
      if(!model.fetchFromCloud) throw new Error("model missing required method for polling: fetchFromCloud");
      if(!this.isInFinalState) throw new Error("store missing required method for polling: isInFinalState");

      model.fetchFromCloud(function () {
        this.update(model);
        var index = this.modelsBuilding.indexOf(model);
        if (this.isInFinalState(model)) {
          this.modelsBuilding.splice(index, 1);
        } else {
          this.fetchAndRemoveIfFinished(model);
        }
        this.emitChange();
      }.bind(this));
    }

  });

  Store.extend = Backbone.Model.extend;

  return Store;
});

@@ -3320,32 +3320,666 @@
 odels);%0A    %7D,%0A%0A
+    findOne: function(params)%7B%0A      if(!this.models) return this.fetchModels();%0A%0A      var keys = Object.keys(params);%0A%0A      var model = this.models.find(function(model)%7B%0A        var matchesCriteria = true;%0A%0A        keys.forEach(function(key)%7B%0A          if(!matchesCriteria) return;%0A%0A          var tokens = key.split('.');%0A          if(tokens.length === 1)%7B%0A            if(model.get(key) !== params%5Bkey%5D) matchesCriteria = false;%0A          %7Delse%7B%0A            if(model.get(tokens%5B0%5D)%5Btokens%5B1%5D%5D !== params%5Bkey%5D) matchesCriteria = false;%0A          %7D%0A        %7D);%0A%0A        return matchesCriteria;%0A      %7D);%0A%0A      return model;%0A    %7D,%0A%0A
     // ---------

add findOne method to base store