/*
 * https://github.com/morethanwords/tweb
 * Copyright (C) 2019-2021 Eduard Kuzmenko
 * https://github.com/morethanwords/tweb/blob/master/LICENSE
 */

import indexOfAndSplice from "../helpers/array/indexOfAndSplice";
import throttle from "../helpers/schedulers/throttle";
import { logger, LogTypes } from "../lib/logger";

const PARALLEL_LIMIT = 8;

export type LazyLoadElementBase = {
  load: () => Promise<any>
};

export default class LazyLoadQueueBase {
  public queueId = 0;
  protected queue: Array<LazyLoadElementBase> = [];
  protected inProcess: Set<LazyLoadElementBase> = new Set();

  protected lockPromise: Promise<void> = null;
  protected unlockResolve: () => void = null;

  protected log = logger('LL', LogTypes.Error);
  protected processQueue: () => void;

  constructor(protected parallelLimit = PARALLEL_LIMIT) {
    this.processQueue = throttle(() => this._processQueue(), 20, false);
  }

  public clear() {
    this.inProcess.clear(); // ацтеки забьются, будет плохо

    this.queue.length = 0;
    // unreachable code
    /* for(let item of this.inProcess) { 
      this.lazyLoadMedia.push(item);
    } */
  }

  public lock() {
    if(this.lockPromise) return;

    //const perf = performance.now();
    this.lockPromise = new Promise((resolve, reject) => {
      this.unlockResolve = resolve;
    });

    /* if(DEBUG) {
      this.lockPromise.then(() => {
        this.log('was locked for:', performance.now() - perf);
      });
    } */
  }

  public unlock() {
    if(!this.unlockResolve) return;

    this.unlockResolve();
    this.unlockResolve = this.lockPromise = null;

    this.processQueue();
  }

  protected async processItem(item: LazyLoadElementBase) {
    if(this.lockPromise) {
      return;
    }

    this.inProcess.add(item);

    /* if(DEBUG) {
      this.log('will load media', this.lockPromise, item);
    } */

    try {
      //await new Promise((resolve) => setTimeout(resolve, 2e3));
      //await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
      //await item.load(item.div);
      await this.loadItem(item);
    } catch(err) {
      if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(err as string)) {
        this.log.error('loadMediaQueue error:', err/* , item */);
      }
    }

    this.inProcess.delete(item);

    /* if(DEBUG) {
      this.log('loaded media', item);
    } */

    this.processQueue();
  }

  protected loadItem(item: LazyLoadElementBase) {
    return item.load();
  }

  protected getItem() {
    return this.queue.shift();
  }

  protected addElement(method: 'push' | 'unshift', el: LazyLoadElementBase) {
    this.queue[method](el);
    this.processQueue();
  }

  protected _processQueue(item?: LazyLoadElementBase) {
    if(!this.queue.length || this.lockPromise || (this.parallelLimit > 0 && this.inProcess.size >= this.parallelLimit)) return;

    //console.log('_processQueue start');
    let added = 0;
    do {
      if(item) {
        indexOfAndSplice(this.queue, item);
      } else {
        item = this.getItem();
      }
  
      if(item) {
        this.processItem(item);
      } else {
        break;
      }

      item = null;
      ++added;
    } while(this.inProcess.size < this.parallelLimit && this.queue.length);
    //console.log('_processQueue end, added', added, this.queue.length);
  }

  public push(el: LazyLoadElementBase) {
    this.addElement('push', el);
  }

  public unshift(el: LazyLoadElementBase) {
    this.addElement('unshift', el);
  }
}
