// Copyright (c) 2017 PSForever
package net.psforever.objects.guid.source

import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey, SecureKey}

/**
  * A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
  * Produce a series of numbers from 0 to a maximum number (inclusive) to be used as globally unique identifiers (GUID's).
  * @param max the highest number to be generated by this source;
  *            must be a positive integer or zero
  * @throws IllegalArgumentException if `max` is less than zero (therefore the count of generated numbers is at most zero)
  */
class MaxNumberSource(val max: Int) extends NumberSource {
  if (max < 0) {
    throw new IllegalArgumentException(s"non-negative integers only, not $max")
  }
  private val ary: Array[Key] = Array.ofDim[Key](max + 1)
  (0 to max).foreach(x => { ary(x) = new Key })

  def size: Int = ary.length

  def countAvailable: Int = ary.count { _.policy == AvailabilityPolicy.Available }

  def countUsed: Int = ary.count { _.policy == AvailabilityPolicy.Leased }

  def countDangling: Int = ary.count { key => key.policy == AvailabilityPolicy.Leased && key.obj.isEmpty }

  def test(number: Int): Boolean = -1 < number && number < size

  def get(number: Int): Option[SecureKey] = {
    if (test(number)) {
      Some(new SecureKey(number, ary(number)))
    } else {
      None
    }
  }

  def get(obj: IdentifiableEntity) : Option[SecureKey] = {
    ary.zipWithIndex.find { case (key, _) =>
      key.obj match {
        case Some(o) => o eq obj
        case _ => false
      }
    } match {
      case Some((key, number)) => Some(new SecureKey(number, key))
      case _=> None
    }
  }

  def getAvailable(number: Int): Option[LoanedKey] = {
    var out: Option[LoanedKey] = None
    if (test(number)) {
      val key: Key = ary(number)
      if (key.policy == AvailabilityPolicy.Available) {
        key.policy = AvailabilityPolicy.Leased
        out = Some(new LoanedKey(number, key))
      }
    }
    out
  }

  /**
    * Consume the number of a `Monitor` and release that number from its previous assignment/use.
    * @param number the number
    * @return any object previously using this number
    */
  def returnNumber(number: Int): Option[IdentifiableEntity] = {
    var out: Option[IdentifiableEntity] = None
    if (test(number)) {
      val existing: Key = ary(number)
      if (existing.policy == AvailabilityPolicy.Leased) {
        out = existing.obj
        existing.policy = AvailabilityPolicy.Available
        existing.obj = None
      }
    }
    out
  }

  def clear(): List[IdentifiableEntity] = {
    ary.foreach { _.policy = AvailabilityPolicy.Available }
    ary.collect {
      case key if key.obj.nonEmpty =>
        val obj = key.obj.get
        key.obj = None
        obj
    }.toList
  }
}

object MaxNumberSource {
  def apply(max: Int): MaxNumberSource = {
    new MaxNumberSource(max)
  }
}
