/*
 * MIT License
 * 
 * Copyright (c) 2021 DnD5e Helpers Team
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
import {MODULE} from './module.js'

let updateQueues = new Map();

/** 
 * Safely manages concurrent updates to the provided entity type
 * @function warpgate.plugin.queueUpdate
 * @param {Function} updateFn   the function that handles the actual update (can be async)
 */
export function queueUpdate(updateFn) {

  /** queue the update for this entity */
  getQueue().queueUpdate(updateFn);
}

export function flush() {
  return getQueue().flush();
}

function getQueue(entity = "default"){
  /** if this is a new entity type, create the queue object to manage it */
  if(!updateQueues.has(entity)) {
    updateQueues.set(entity, new UpdateQueue(entity));
  }

  /** queue the update for this entity */
  return updateQueues.get(entity);
}

/** 
 * Helper class to manage database updates that occur from
 * hooks that may fire back to back.
 * @ignore
 */
class UpdateQueue {
  constructor(entityType) {

    /** self identification */
    this.entityType = entityType;

    /** buffer of update functions to run */
    this.queue = [];

    /** Semaphore for 'batch update in progress' */
    this.inFlight = false;
  }

  queueUpdate(fn) {
    this.queue.push(fn);

    /** only kick off a batch of updates if none are in flight */
    if (!this.inFlight) {
      this.runUpdate();
    }
  }

  flush() {
    return MODULE.waitFor( () => !this.inFlight )
  }

  async runUpdate(){

    this.inFlight = true;

    while(this.queue.length > 0) {


      /** grab the last update in the list and hold onto its index
       *  in case another update pushes onto this array before we
       *  are finished.
       */
      const updateIndex = this.queue.length-1;
      const updateFn = this.queue[updateIndex];

      /** wait for the update to complete */
      await updateFn();

      /** remove this entry from the queue */
      this.queue.splice(updateIndex,1);
    }

    this.inFlight = false;

  }
}
