/* globals indexedDB */
/**
 * focalStorage
 *
 * A Promise-based, localStorage-like wrapper around IndexedDB.
 *
 * -
 *
 * Borrowed lots of code from
 *
 * https://github.com/mozilla/localForage/blob/080f665/src/drivers/indexeddb.js and
 * https://github.com/mozilla-b2g/gaia/blob/12d71f3/shared/js/async_storage.js
 *
 * -
 *
 * This file defines an asynchronous version of the localStorage API, backed by
 * an IndexedDB database. It creates a focalStorage object that has methods
 * like the localStorage object's.
 *
 * To store a value use setItem:
 *
 *   focalStorage.setItem('key', 'value');
 *
 * If you want confirmation that the value has been stored, pass a callback
 * function as the third argument:
 *
 *  focalStorage.setItem('key', 'newvalue').then(function () {
 *    console.log('new value stored');
 *  });
 *
 * To read a value, call getItem(), but note that you must supply a callback
 * function that the value will be passed to asynchronously:
 *
 *  focalStorage.getItem('key').then(function (value) {
 *    console.log('The value of key is:', value);
 *  });
 *
 * Note that unlike localStorage, focalStorage does not allow you to store and
 * retrieve values by setting and querying properties directly. You cannot just
 * write focalStorage.key; you have to explicitly call setItem() or getItem().
 *
 * removeItem(), clear(), length(), and key() are like the same-named methods of
 * localStorage, but, like getItem() and setItem() they take a callback
 * argument.
 *
 * The asynchronous nature of getItem() makes it tricky to retrieve multiple
 * values. But unlike localStorage, focalStorage does not require the values you
 * store to be strings. So if you need to save multiple values and want to
 * retrieve them together, in a single asynchronous operation, just group the
 * values into a single object. The properties of this object may not include
 * DOM elements, but they may include things like Blobs and typed arrays.
 */

'use strict';

System.register([], function (_export, _context) {
  "use strict";

  return {
    setters: [],
    execute: function () {
      const __SystemJSRewritingHack = {};
      _recorder_._src_external_focalStorage_js = _recorder_._src_external_focalStorage_js || {};
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'config', {
        get() {
          return config;
        },

        set(thisIsVererySecretVariableName) {
          config = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'keys', {
        get() {
          return keys;
        },

        set(thisIsVererySecretVariableName) {
          keys = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'key', {
        get() {
          return key;
        },

        set(thisIsVererySecretVariableName) {
          key = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'length', {
        get() {
          return length;
        },

        set(thisIsVererySecretVariableName) {
          length = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'clear', {
        get() {
          return clear;
        },

        set(thisIsVererySecretVariableName) {
          clear = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'removeItem', {
        get() {
          return removeItem;
        },

        set(thisIsVererySecretVariableName) {
          removeItem = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'setItem', {
        get() {
          return setItem;
        },

        set(thisIsVererySecretVariableName) {
          setItem = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'getItems', {
        get() {
          return getItems;
        },

        set(thisIsVererySecretVariableName) {
          getItems = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'getItem', {
        get() {
          return getItem;
        },

        set(thisIsVererySecretVariableName) {
          getItem = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'openTransaction', {
        get() {
          return openTransaction;
        },

        set(thisIsVererySecretVariableName) {
          openTransaction = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'openStore', {
        get() {
          return openStore;
        },

        set(thisIsVererySecretVariableName) {
          openStore = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'openDatabase', {
        get() {
          return openDatabase;
        },

        set(thisIsVererySecretVariableName) {
          openDatabase = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      var db = null;

      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'db', {
        get() {
          return db;
        },

        set(thisIsVererySecretVariableName) {
          db = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      function openDatabase() {
        return new Promise(function (resolve, reject) {
          if (db) {
            resolve(db);
            return;
          }

          var openreq = indexedDB.open(focalStorage.settings.name, focalStorage.settings.version);

          openreq.onsuccess = function () {
            _recorder_._src_external_focalStorage_js.db = openreq.result;
            resolve(db);
          };

          openreq.onupgradeneeded = function () {
            // First time setup: create an empty object store.
            openreq.result.createObjectStore(focalStorage.settings.storeName);
          };

          openreq.onerror = function () {
            reject(openreq.error);
          };
        });
      }

      function openStore(type) {
        return openDatabase().then(function () {
          var transaction = db.transaction(focalStorage.settings.storeName, type);

          return transaction.objectStore(focalStorage.settings.storeName);
        });
      }

      function openTransaction(type) {
        return openDatabase().then(function () {
          return db.transaction(focalStorage.settings.storeName, type);
        });
      }

      function getItem(key) {
        return new Promise(function (resolve, reject) {
          return openStore('readonly').then(function (store) {
            var req = store.get(key);

            req.onsuccess = function () {
              var value = req.result;
              if (value === undefined) {
                value = null;
              }

              resolve(value);
            };

            req.onerror = function () {
              reject(req.error);
            };
          }).catch(reject);
        });
      }

      function getItems() {
        return new Promise(function (resolve, reject) {
          return openStore('readonly').then(function (store) {
            var req = store.getAll();

            req.onsuccess = function () {
              var value = req.result;
              if (value === undefined) {
                value = null;
              }

              resolve(value);
            };

            req.onerror = function () {
              reject(req.error);
            };
          }).catch(reject);
        });
      }

      function setItem(key, value) {
        return new Promise(function (resolve, reject) {
          return openTransaction('readwrite').then(function (transaction) {

            var store = transaction.objectStore(focalStorage.settings.storeName);

            // Cast the key to a string, as that's all we can set as a key.
            if (typeof key !== 'string') {
              console.warn(key + ' used as a key, but it is not a string');
              key = String(key);
            }

            // The reason we don't _save_ null is because IE 10 does
            // not support saving the `null` type in IndexedDB.
            if (value === null) {
              value = undefined;
            }

            var req = store.put(value, key);

            transaction.oncomplete = function () {
              // Cast to undefined so the value passed to
              // callback/promise is the same as what one would get out
              // of `getItem()` later. This leads to some weirdness
              // (setItem('foo', undefined) will return `null`), but
              // it's not my fault localStorage is our baseline and that
              // it's weird.
              if (value === undefined) {
                value = null;
              }

              resolve(value);
            };

            transaction.onabort = transaction.onerror = function () {
              reject(req.error ? req.error : req.transaction.error);
            };
          }).catch(reject);
        });
      }

      function removeItem(key) {
        return new Promise(function (resolve, reject) {
          return openTransaction('readwrite').then(function (transaction) {
            var store = transaction.objectStore(focalStorage.settings.storeName);

            // Cast the key to a string, as that's all we can set as a key.
            if (typeof key !== 'string') {
              console.warn(key + ' used as a key, but it is not a string');
              key = String(key);
            }

            var req = store.delete(key);

            transaction.oncomplete = function () {
              resolve();
            };

            transaction.onerror = function () {
              reject(req.error);
            };

            // The request will also be aborted if storage space is exceeded.
            transaction.onabort = function () {
              reject(req.error ? req.error : req.transaction.error);
            };
          }).catch(reject);
        });
      }

      function clear() {
        return new Promise(function (resolve, reject) {
          return openTransaction('readwrite').then(function (transaction) {
            var store = transaction.objectStore(focalStorage.settings.storeName);

            var req = store.clear();

            transaction.oncomplete = function () {
              resolve();
            };

            transaction.onabort = transaction.onerror = function () {
              reject(req.error ? req.error : req.transaction.error);
            };
          }).catch(reject);
        });
      }

      function length(callback) {
        return new Promise(function (resolve, reject) {
          openStore('readonly').then(function (store) {
            var req = store.count();

            req.onsuccess = function () {
              resolve(req.result);
            };

            req.onerror = function () {
              reject(req.error);
            };
          }).catch(reject);
        });
      }

      function key(n) {
        return new Promise(function (resolve, reject) {
          if (n < 0) {
            resolve(null);
            return;
          }

          return openStore('readonly').then(function (store) {
            var advanced = false;
            var req = store.openCursor();

            req.onsuccess = function () {
              var cursor = req.result;

              if (!cursor) {
                // This means there weren't enough keys.
                resolve(null);
                return;
              }

              if (n === 0) {
                // We have the first key; return it if that's what they wanted.
                resolve(cursor.key);
              } else {
                if (!advanced) {
                  // Otherwise, ask the cursor to skip ahead `n` records.
                  advanced = true;
                  cursor.advance(n);
                } else {
                  // When we get here, we've got the nth key.
                  resolve(cursor.key);
                }
              }
            };

            req.onerror = function () {
              reject(req.error);
            };
          }).catch(reject);
        });
      }

      function keys() {
        return new Promise(function (resolve, reject) {
          return openStore('readonly').then(function (store) {
            var req = store.openCursor();
            var keys = [];

            req.onsuccess = function () {
              var cursor = req.result;

              if (!cursor) {
                resolve(keys);
                return;
              }

              keys.push(cursor.key);
              cursor.continue();
            };

            req.onerror = function () {
              reject(req.error);
            };
          }).catch(reject);
        });
      }

      /**
       * Assigns configurations.
       * @param {Object} settings Options object to use for `focalStorage` instance.
       */
      function config(settings) {
        focalStorage.settings = Object.assign({}, DEFAULT_SETTINGS, settings || {});
      }

      var focalStorage = {
        INDEXEDDB: 0,
        LOCALSTORAGE: 1,
        getItem: getItem,
        getItems: getItems,
        setItem: setItem,
        removeItem: removeItem,
        clear: clear,
        length: length,
        key: key,
        keys: keys
      };

      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'focalStorage', {
        get() {
          return focalStorage;
        },

        set(thisIsVererySecretVariableName) {
          focalStorage = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      var DEFAULT_SETTINGS = {
        driver: focalStorage.indexedDB,
        name: 'focalStorage',
        version: 1,
        storeName: 'keyvaluepairs'
      };

      Object.defineProperty(_recorder_._src_external_focalStorage_js, 'DEFAULT_SETTINGS', {
        get() {
          return DEFAULT_SETTINGS;
        },

        set(thisIsVererySecretVariableName) {
          DEFAULT_SETTINGS = thisIsVererySecretVariableName;
          return true;
        },

        enumerable: true,
        configurable: true
      });
      focalStorage.settings = DEFAULT_SETTINGS;

      _export('default', focalStorage);
    }
  };
});