/*!
  backbone.fetch-cache v1.4.0
  by Andy Appleton - https://github.com/mrappleton/backbone-fetch-cache.git
 */

// AMD wrapper from https://github.com/umdjs/umd/blob/master/amdWebGlobal.js

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module and set browser global
    define(['underscore', 'backbone', 'jquery'], function (_, Backbone, $) {
      return (root.Backbone = factory(_, Backbone, $));
    });
  } else {
    // Browser globals
    root.Backbone = factory(root._, root.Backbone, root.jQuery);
  }
}(this, function (_, Backbone, $) {

  // Setup
  var superMethods = {
    modelFetch: Backbone.Model.prototype.fetch,
    modelSync: Backbone.Model.prototype.sync,
    collectionFetch: Backbone.Collection.prototype.fetch
  },
  supportLocalStorage = (function() {
    var supported = typeof window.localStorage !== 'undefined';
    if (supported) {
      try {
        // impossible to write on some platforms when private browsing is on and
        // throws an exception = local storage not supported.
        localStorage.setItem("test_support", "test_support");
        localStorage.removeItem("test_support");
      } catch (e) {
        supported = false;
      }
    }
    return supported;
  })();

  Backbone.fetchCache = (Backbone.fetchCache || {});
  Backbone.fetchCache._cache = (Backbone.fetchCache._cache || {});

  Backbone.fetchCache.priorityFn = function(a, b) {
    if (!a || !a.expires || !b || !b.expires) {
      return a;
    }

    return a.expires - b.expires;
  };

  Backbone.fetchCache._prioritize = function() {
    var sorted = _.values(this._cache).sort(this.priorityFn);
    var index = _.indexOf(_.values(this._cache), sorted[0]);
    return _.keys(this._cache)[index];
  };

  Backbone.fetchCache._deleteCacheWithPriority = function() {
    Backbone.fetchCache._cache[this._prioritize()] = null;
    delete Backbone.fetchCache._cache[this._prioritize()];
    Backbone.fetchCache.setLocalStorage();
  };

  Backbone.fetchCache.getLocalStorageKey = function() {
    return 'backboneCache';
  };

  if (typeof Backbone.fetchCache.localStorage === 'undefined') {
    Backbone.fetchCache.localStorage = true;
  }

  // Shared methods
  function getCacheKey(instance, opts) {
    var url;

    if(opts && opts.url) {
      url = opts.url;
    } else {
      url = _.isFunction(instance.url) ? instance.url() : instance.url;
    }

    // Need url to use as cache key so return if we can't get it
    if(!url) { return; }

    if(opts && opts.data) {
      return url + "?" + $.param(opts.data);
    }
    return url;
  }

  function setCache(instance, opts, attrs) {
    opts = (opts || {});
    var key = Backbone.fetchCache.getCacheKey(instance, opts),
        expires = false;

    // Need url to use as cache key so return if we can't get it
    if (!key) { return; }

    // Never set the cache if user has explicitly said not to
    if (opts.cache === false) { return; }

    // Don't set the cache unless cache: true or prefill: true option is passed
    if (!(opts.cache || opts.prefill)) { return; }

    if (opts.expires !== false) {
      expires = (new Date()).getTime() + ((opts.expires || 5 * 60) * 1000);
    }

    Backbone.fetchCache._cache[key] = {
      expires: expires,
      value: attrs
    };

    Backbone.fetchCache.setLocalStorage();
  }

  function clearItem(key) {
    if (_.isFunction(key)) { key = key(); }
    delete Backbone.fetchCache._cache[key];
    Backbone.fetchCache.setLocalStorage();
  }

  function setLocalStorage() {
    if (!supportLocalStorage || !Backbone.fetchCache.localStorage) { return; }
    try {
      localStorage.setItem(Backbone.fetchCache.getLocalStorageKey(), JSON.stringify(Backbone.fetchCache._cache));
    } catch (err) {
      var code = err.code || err.number || err.message;
      if (code === 22) {
        this._deleteCacheWithPriority();
      } else {
        throw(err);
      }
    }
  }

  function getLocalStorage() {
    if (!supportLocalStorage || !Backbone.fetchCache.localStorage) { return; }
    var json = localStorage.getItem(Backbone.fetchCache.getLocalStorageKey()) || '{}';
    Backbone.fetchCache._cache = JSON.parse(json);
  }

  function nextTick(fn) {
    return window.setTimeout(fn, 0);
  }

  // Instance methods
  Backbone.Model.prototype.fetch = function(opts) {
    opts = _.defaults(opts || {}, { parse: true });
    var key = Backbone.fetchCache.getCacheKey(this, opts),
        data = Backbone.fetchCache._cache[key],
        expired = false,
        attributes = false,
        deferred = new $.Deferred(),
        self = this;

    function setData() {
      if (opts.parse) {
        attributes = self.parse(attributes, opts);
      }

      self.set(attributes, opts);
      if (_.isFunction(opts.prefillSuccess)) { opts.prefillSuccess(self, attributes, opts); }

      // Trigger sync events
      self.trigger('cachesync', self, attributes, opts);
      self.trigger('sync', self, attributes, opts);

      // Notify progress if we're still waiting for an AJAX call to happen...
      if (opts.prefill) { deferred.notify(self); }
      // ...finish and return if we're not
      else {
        if (_.isFunction(opts.success)) { opts.success(self, attributes, opts); }
        deferred.resolve(self);
      }
    }

    if (data) {
      expired = data.expires;
      expired = expired && data.expires < (new Date()).getTime();
      attributes = data.value;
    }

    if (!expired && (opts.cache || opts.prefill) && attributes) {
      // Ensure that cache resolution adhers to async option, defaults to true.
      if (opts.async == null) { opts.async = true; }

      if (opts.async) {
        nextTick(setData);
      } else {
        setData();
      }

      if (!opts.prefill) {
        return deferred;
      }
    }

    // Delegate to the actual fetch method and store the attributes in the cache
    // to set cache : false whenever it calls ajax (addded due to Test server caching issue)
    var arg = $.extend(true,[],arguments);
    if((arg.length > 0) && !_.isUndefined(arg[0].cache))
    	arg[0].cache = false ;
    superMethods.modelFetch.apply(this, arg)
      // resolve the returned promise when the AJAX call completes
      .done( _.bind(deferred.resolve, this, this) )
      // Set the new data in the cache
      .done( _.bind(Backbone.fetchCache.setCache, null, this, opts) )
      // Reject the promise on fail
      .fail( _.bind(deferred.reject, this, this) );

    // return a promise which provides the same methods as a jqXHR object
    return deferred;
  };

  // Override Model.prototype.sync and try to clear cache items if it looks
  // like they are being updated.
  Backbone.Model.prototype.sync = function(method, model, options) {
    // Only empty the cache if we're doing a create, update, patch or delete.
    if (method === 'read') {
      return superMethods.modelSync.apply(this, arguments);
    }

    var collection = model.collection,
        keys = [],
        i, len;

    // Build up a list of keys to delete from the cache, starting with this
    keys.push(Backbone.fetchCache.getCacheKey(model, options));

    // If this model has a collection, also try to delete the cache for that
    if (!!collection) {
      keys.push(Backbone.fetchCache.getCacheKey(collection));
      
      // it will delete full collection cache (pagination)
      _.each(Backbone.fetchCache._cache, function(url, val){
			var urlStr= keys[1]+"?";
			if(val.indexOf(urlStr) != -1) 
				keys.push(val);
		});
    }

    // Empty cache for all found keys
    for (i = 0, len = keys.length; i < len; i++) { clearItem(keys[i]); }

    return superMethods.modelSync.apply(this, arguments);
  };

  Backbone.Collection.prototype.fetch = function(opts) {
    opts = _.defaults(opts || {}, { parse: true });
    var key = Backbone.fetchCache.getCacheKey(this, opts),
        data = Backbone.fetchCache._cache[key],
        expired = false,
        attributes = false,
        deferred = new $.Deferred(),
        self = this;

    function setData() {
      self[opts.reset ? 'reset' : 'set'](attributes, opts);
      if (_.isFunction(opts.prefillSuccess)) { opts.prefillSuccess(self); }

      // Trigger sync events
      self.trigger('cachesync', self, attributes, opts);
      self.trigger('sync', self, attributes, opts);

      // Notify progress if we're still waiting for an AJAX call to happen...
      if (opts.prefill) { deferred.notify(self); }
      // ...finish and return if we're not
      else {
        if (_.isFunction(opts.success)) { opts.success(self, attributes, opts); }
        deferred.resolve(self);
      }
    }

    if (data) {
      expired = data.expires;
      expired = expired && data.expires < (new Date()).getTime();
      attributes = data.value;
    }

    if (!expired && (opts.cache || opts.prefill) && attributes) {
      // Ensure that cache resolution adhers to async option, defaults to true.
      if (opts.async == null) { opts.async = true; }

      if (opts.async) {
        nextTick(setData);
      } else {
        setData();
      }

      if (!opts.prefill) {
        return deferred;
      }
    }

    // Delegate to the actual fetch method and store the attributes in the cache
    // to set cache : false whenever it calls ajax (addded due to Test server caching issue)
    var arg = $.extend(true,[],arguments);
    if((arg.length > 0) && !_.isUndefined(arg[0].cache))
    	arg[0].cache = false ;
    superMethods.collectionFetch.apply(this, arg)
      // resolve the returned promise when the AJAX call completes
      .done( _.bind(deferred.resolve, this, this) )
      // Set the new data in the cache
      .done( _.bind(Backbone.fetchCache.setCache, null, this, opts) )
      // Reject the promise on fail
      .fail( _.bind(deferred.reject, this, this) );

    // return a promise which provides the same methods as a jqXHR object
    return deferred;
  };

  // Prime the cache from localStorage on initialization
  getLocalStorage();

  // Exports

  Backbone.fetchCache._superMethods = superMethods;
  Backbone.fetchCache.setCache = setCache;
  Backbone.fetchCache.getCacheKey = getCacheKey;
  Backbone.fetchCache.clearItem = clearItem;
  Backbone.fetchCache.setLocalStorage = setLocalStorage;
  Backbone.fetchCache.getLocalStorage = getLocalStorage;

  return Backbone;
}));