export default class Cache {
  constructor({ data, meta, limit }) {
    this.meta = meta;
    this.limit = limit || 0;
    this.stable = 0;
    this.queue = [];
    if (data) this.queue.push(data);
    Object.defineProperty(this, "length", {
      get() {
        return this.queue.length - this.stable;
      }
    });
  }
  find(id, sort = false) {
    const isArray = id instanceof Array;
    let result = isArray ? findAll(this, id, sort) : findOne(this, id, sort);
    return pack(this, id, isArray, result);
  }
  get(id, cb) {
    const o = this.find(id, true);
    if (o.complete || !cb) {
      return o;
    } else {
      const result = getResult(cb.handle || cb);
      this.push(result, cb.stable, cb.type);
      const o2 = this.get(id);
      if (o.complete) o.fresh = true;
      return;
    }
  }
  async getAsync(id, cb) {
    const o = this.find(id, true);
    if (o.complete || !cb) {
      return Promise.resolve(o);
    } else {
      const { resultPromise, state } = getResult(cb.handle || cb);
      const result = await resultPromise;
      if (!state) return Promise.reject(result);
      this.push(result, cb.stable, cb.type);
      return this.getAsync(id).then(r => {
        r.fresh = true;
        return r;
      });
    }
  }
  push(data, stable = false, type = "update") {
    if (data instanceof Array) {
      pushAll(this, data, stable, type);
    } else if (data instanceof Object) {
      pushOne(this, data, stable, type);
    } else {
      throw new TypeError("data type must be Object or Array");
    }
    return this;
  }
  delete(data = {}) {
    if (this.limit) return this;
    if (data.index || data.index === 0) {
      this.queue.splice(data.index, 1);
      if (data.stable) this.stable--;
    } else {
      for (
        let i = 0;
        i < this.length + this.stable && this.length >= this.limit;
        i++
      ) {
        if (!this.queue[i].stable) {
          this.queue.splice(i--, 1);
        }
      }
    }
    return this;
  }
  static merger = merger;
}

function merger(o1, o2) {
  for (let i in o2) {
    if (
      o2[i] instanceof Object &&
      !(o2[i] instanceof Array) &&
      o1[i] instanceof Object &&
      !(o1[i] instanceof Array)
    ) {
      merger(o1[i], o2[i]);
    } else {
      o1[i] = o2[i];
    }
  }
  return o1;
}
function getResult(cb) {
  try {
    return {
      resultPromise: cb(),
      state: true
    };
  } catch (e) {
    console.error("cb of Queue getAsync is unvalid", e);
    return { state: false, resultPromise: e };
  }
}
function pushOne(q, data, stable, type) {
  if (data.id == null || data.data == null)
    throw new TypeError("must have id and data");
  if (stable) data.stable = true;
  const old = findOne(q, data.id, false);
  if (!old.data || type === "update") {
    if (data.stable) q.stable++;
    q.delete();
    q.queue.push(data);
    return q;
  }
  switch (type) {
    case "merger":
      merger(old, data);
      if (old.stable && data.stable === false) q.delete();
    case "save":
      sortQueue(q.queue, old.index);
      break;
  }
  return q;
}
function pushAll(q, arr, stable, type) {
  arr.forEach(val => {
    pushOne(q, val, stable, type);
  });
  return q;
}
function sortQueue(queue, i) {
  queue.push(queue.splice(i, 1)[0]);
}
function findOne(q, id, sort) {
  let treasure = { data: null, id };
  q.queue.forEach((val, i) => {
    if (val.id === id) {
      if (sort) {
        sortQueue(q.queue, i);
      } else {
        treasure.index = i;
      }
      if (val.meta) treasure.meta = val.meta;
      treasure.data = val.data;
    }
  });
  return treasure;
}
function findAll(q, arr, sort) {
  const result = [];
  arr.forEach(id => {
    result.push(findOne(q, id, sort));
  });
  return result;
}
function pack(q, id, isArray, result) {
  let complete = true,
    lost = [];
  if (isArray) {
    result.forEach(val => {
      if (!val.data) {
        complete = false;
        lost.push(val.id);
      }
    });
  } else if (!result.data) {
    complete = false;
    lost.push(id);
  }
  return {
    result,
    complete,
    lost,
    meta: q.meta
  };
}

//stable 带有stable的data不会被删除,不会占用limit的限制
/*
  push 2种模式
  merger  合并
  updata  替换  默认
  save    保留
*/

//getAsync cb 回调函数
/*
  可以直接是函数
  也可以是对象{
    handle,
    stable,
    type,
  }
 */
