ratings = require('./ratings')
alts = require('./alts')

INITIAL_RANGE = 100
RANGE_INCREMENT = 100

class QueuedPlayer
  constructor: (player) ->
    @player = player
    @range = INITIAL_RANGE
    @rating = null  # needs to be updated by getRatings

  intersectsWith: (other) ->
    leftMin = @rating - (@range / 2)
    leftMax = @rating + (@range / 2)
    rightMin = other.rating - (other.range / 2)
    rightMax = other.rating + (other.range / 2)

    return false  if leftMin > rightMax
    return false  if leftMax < rightMin
    true

# A queue of users waiting for a battle
class @BattleQueue
  constructor: ->
    @queue = {}
    @newPlayers = []
    @recentlyMatched = {}
    @length = 0
    @ranked = true

  # Adds a player to the queue.
  # "name" can either be the real name, or an alt
  add: (playerId, name, team, ratingKey=playerId) ->
    return false  if !playerId
    return false  if playerId of @queue

    playerObject = {id: playerId, name, team, ratingKey}
    player = new QueuedPlayer(playerObject)
    @queue[playerId] = player
    @newPlayers.push(player)
    @length += 1
    return true
  
  setUnranked: ->
    @ranked = false

  remove: (playerIds) ->
    playerIds = Array(playerIds)  if playerIds not instanceof Array
    for playerId in playerIds
      if playerId of @queue
        delete @queue[playerId]
        @length -= 1

  queuedPlayers: ->
    Object.keys(@queue)

  hasUserId: (playerId) ->
    @queue[playerId]?

  hasRecentlyMatched: (player1Id, player2Id) ->
    players = [player1Id, player2Id].sort()
    key = "#{players[0]}:#{players[1]}"
    @recentlyMatched[key]?

  addRecentMatch: (player1Id, player2Id) ->
    players = [player1Id, player2Id].sort()
    key = "#{players[0]}:#{players[1]}"
    @recentlyMatched[key] = true
    setTimeout((=> delete @recentlyMatched[key]), 30 * 60 * 1000) # expire in 30 minutes

  size: ->
    @length

  # An internal function which loads ratings for newly queued players
  # and removes them from the newly queued list
  updateNewPlayers: (next) ->
    if @ranked == true
      ratingKeys = (queued.player.ratingKey for queued in @newPlayers)
      return next(null)  if ratingKeys.length == 0

      ratings.getRatings ratingKeys, (err, returnedRatings) =>
        if err then return next(err)

        ratings.setActive ratingKeys, (err) =>
          if err then return next(err)

          # Update the ratings in the player objects
          for rating, i in returnedRatings
            continue  unless @hasUserId(@newPlayers[i].player.id)
            @newPlayers[i].rating = rating

          # reset the new players list, we're done
          @newPlayers.splice(0, @newPlayers.length)
          next(null)
    else
      next(null)

  # Returns an array of pairs. Each pair is a queue object that contains
  # a player and team key, corresponding to the player socket and player's team.
  pairPlayers: (next) ->
    return next(null, [])  if @size() == 0

    @updateNewPlayers (err) =>
      if err then return next(err, null)

      sortedPlayers = (queued for id, queued of @queue)
      if @ranked
        sortedPlayers.sort((a, b) -> a.rating - b.rating)

      alreadyMatched = (false for [0...sortedPlayers.length])

      pairs = []
      for leftIdx in [0...sortedPlayers.length]
        continue  if alreadyMatched[leftIdx]

        for rightIdx in [(leftIdx + 1)...sortedPlayers.length]
          continue  if alreadyMatched[rightIdx]

          left = sortedPlayers[leftIdx]
          right = sortedPlayers[rightIdx]
          leftPlayer = left.player
          rightPlayer = right.player

          # Continue if these two players already played
          continue  if @hasRecentlyMatched(leftPlayer.id, rightPlayer.id) and @ranked
          # If the rating difference is too large break out, we have no possible match for left
          break  unless left.intersectsWith(right)

          # Everything checks out, so make the pair and break out
          pairs.push([leftPlayer, rightPlayer])
          @remove([leftPlayer.id, rightPlayer.id])
          @addRecentMatch(leftPlayer.id, rightPlayer.id) if @ranked
          alreadyMatched[leftIdx] = alreadyMatched[rightIdx] = true
          break

      # Expand the range of all unmatched players
      queued.range += RANGE_INCREMENT  for id, queued of @queue
      # Return the list of paired players

      next(null, pairs)
