import { Deferrable } from "@ethersproject/properties"
import { BaseProvider, BlockTag, TransactionRequest } from "@ethersproject/providers"
import { once, EventEmitter } from 'events'

const DEBUG_MODE_ENABLED = !!process.env.LLAMA_DEBUG_MODE
const maxParallelCalls = !!process.env.LLAMA_SDK_MAX_PARALLEL ? +process.env.LLAMA_SDK_MAX_PARALLEL : 100

const COUNTERS: Record<string, Counter> = {}
const emitter = new EventEmitter()
emitter.setMaxListeners(500000)

export async function call(provider: BaseProvider, data: Deferrable<TransactionRequest>, block: BlockTag, chain?: string) {
  if (!chain) chain = 'noChain'
  const counter: Counter = getChainCounter(chain)
  const currentId = counter.requestCount++
  const eventId = `${chain}-${currentId}`

  if (counter.activeWorkers > maxParallelCalls) {
    counter.queue.push(eventId)
    await once(emitter, eventId)
  }

  counter.activeWorkers++

  if (DEBUG_MODE_ENABLED) {
    const showEveryX = counter.queue.length > 100 ? 50 : 10 // show log fewer times if lot more are queued up
    if (currentId % showEveryX === 0) console.log(`chain: ${chain} request #: ${currentId} queue: ${counter.queue.length} active requests: ${counter.activeWorkers}`)
  }

  let response
  try {
    response = await provider.call(data, block)
    onComplete()
  } catch (e) {
    onComplete()
    throw e
  }

  return response

  function onComplete() {
    counter.activeWorkers--
    if (counter.queue.length) {
      const nextRequestId = counter.pickFromTop ? counter.queue.shift() : counter.queue.pop()
      counter.pickFromTop = !counter.pickFromTop
      emitter.emit(<string> nextRequestId)
    }
  }
}

function getChainCounter(chain: string) {
  if (!COUNTERS[chain])
    COUNTERS[chain] = {
      activeWorkers: 0,
      queue: [],
      requestCount: 0,
      pickFromTop: true,
    }
  return COUNTERS[chain]
}

interface Counter {
  activeWorkers: number;
  requestCount: number;
  queue: string[];
  pickFromTop: boolean;
}