/*
 * typeahead.js
 * https://github.com/twitter/typeahead.js
 * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
 */

var PersistentStorage = (function() {
  'use strict';

  var ls, methods;

  try {
    ls = window.localStorage;

    // while in private browsing mode, some browsers make
    // localStorage available, but throw an error when used
    ls.setItem('~~~', '!');
    ls.removeItem('~~~');
  } catch (err) {
    ls = null;
  }

  // constructor
  // -----------

  function PersistentStorage(namespace) {
    this.prefix = ['__', namespace, '__'].join('');
    this.ttlKey = '__ttl__';
    this.keyMatcher = new RegExp('^' + _.escapeRegExChars(this.prefix));
  }

  // instance methods
  // ----------------

  if (ls && window.JSON) {
    methods = {

      // ### private

      _prefix: function(key) {
        return this.prefix + key;
      },

      _ttlKey: function(key) {
        return this._prefix(key) + this.ttlKey;
      },

      // ### public

      get: function(key) {
        if (this.isExpired(key)) {
          this.remove(key);
        }

        return decode(ls.getItem(this._prefix(key)));
      },

      set: function(key, val, ttl) {
        if (_.isNumber(ttl)) {
          ls.setItem(this._ttlKey(key), encode(now() + ttl));
        }

        else {
          ls.removeItem(this._ttlKey(key));
        }

        return ls.setItem(this._prefix(key), encode(val));
      },

      remove: function(key) {
        ls.removeItem(this._ttlKey(key));
        ls.removeItem(this._prefix(key));

        return this;
      },

      clear: function() {
        var i, key, keys = [], len = ls.length;

        for (i = 0; i < len; i++) {
          if ((key = ls.key(i)).match(this.keyMatcher)) {
            // gather keys to remove after loop exits
            keys.push(key.replace(this.keyMatcher, ''));
          }
        }

        for (i = keys.length; i--;) {
          this.remove(keys[i]);
        }

        return this;
      },

      isExpired: function(key) {
        var ttl = decode(ls.getItem(this._ttlKey(key)));

        return _.isNumber(ttl) && now() > ttl ? true : false;
      }
    };
  }

  else {
    methods = {
      get: _.noop,
      set: _.noop,
      remove: _.noop,
      clear: _.noop,
      isExpired: _.noop
    };
  }

  _.mixin(PersistentStorage.prototype, methods);

  return PersistentStorage;

  // helper functions
  // ----------------

  function now() {
    return new Date().getTime();
  }

  function encode(val) {
    // convert undefined to null to avoid issues with JSON.parse
    return JSON.stringify(_.isUndefined(val) ? null : val);
  }

  function decode(val) {
    return JSON.parse(val);
  }
})();
