/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 * @format
 */
"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    return new Promise(function(resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

const _require = require("metro-core"),
  Logger = _require.Logger;

/**
 * Main cache class. Receives an array of cache instances, and sequentially
 * traverses them to return a previously stored value. It also ensures setting
 * the value in all instances.
 *
 * All get/set operations are logged via Metro's logger.
 */
class Cache {
  constructor(stores) {
    this._hits = new WeakMap();
    this._stores = stores;
  }

  get(key) {
    var _this = this;

    return _asyncToGenerator(function*() {
      const stores = _this._stores;
      const length = stores.length;

      for (let i = 0; i < length; i++) {
        const store = stores[i];
        const name = store.constructor.name + "::" + key.toString("hex");
        let value = null;
        const logStart = Logger.log(
          Logger.createActionStartEntry({
            action_name: "Cache get",
            log_entry_label: name
          })
        );

        try {
          const valueOrPromise = store.get(key);

          if (valueOrPromise && typeof valueOrPromise.then === "function") {
            value = yield valueOrPromise;
          } else {
            value = valueOrPromise;
          }
        } finally {
          Logger.log(Logger.createActionEndEntry(logStart));
          Logger.log(
            Logger.createEntry({
              action_name: "Cache " + (value == null ? "miss" : "hit"),
              log_entry_label: name
            })
          );

          if (value != null) {
            _this._hits.set(key, store);

            return value;
          }
        }
      }

      return null;
    })();
  }

  set(key, value) {
    const stores = this._stores;

    const stop = this._hits.get(key);

    const length = stores.length;
    const promises = [];

    for (let i = 0; i < length && stores[i] !== stop; i++) {
      const store = stores[i];
      const name = store.constructor.name + "::" + key.toString("hex");
      Logger.log(
        Logger.createEntry({
          action_name: "Cache set",
          log_entry_label: name
        })
      );
      promises.push(stores[i].set(key, value));
    }

    Promise.all(promises).catch(err => {
      process.nextTick(() => {
        throw err;
      });
    });
  }
}

module.exports = Cache;