require './helpers'

{BattleServer} = require('../server/server')
{Conditions, DEFAULT_FORMAT} = require '../shared/conditions'
{Protocol} = require '../shared/protocol'
{Factory} = require './factory'
alts = require('../server/alts')
should = require('should')

generateTeam = ->
  [ Factory("Magikarp")
    Factory("Gyarados")
    Factory('Hitmonchan')
    Factory("Celebi")
    Factory("Blissey")
    Factory("Alakazam") ]

describe 'BattleServer', ->
  it 'can create a new battle', ->
    server = new BattleServer()
    battleId = server.createBattle()
    server.battles.should.have.ownProperty battleId

  it "sends the 'spectateBattle' event for each matched player", (done) ->
    server = new BattleServer()
    players = []
    players.push server.findOrCreateUser(id: 1, name: 'abc', @stubSpark())
    players.push server.findOrCreateUser(id: 2, name: 'def', @stubSpark())

    spies = []
    for player in players
      spy = @sandbox.spy(player.sparks[0], 'send')
      spies.push(spy)

    for player in players
      server.queuePlayer(player.name, generateTeam()).should.be.empty
    server.beginBattles (err, ids) ->
      throw new Error(err.message)  if err
      return  if ids.length == 0
      for spy in spies
        spy.calledWith('spectateBattle').should.be.true
      done()

  describe "#queuePlayer", ->
    it "queues players", ->
      server = new BattleServer()
      derp = server.findOrCreateUser(id: 1, name: 'derp', @stubSpark())
      server.queuePlayer(derp.name, generateTeam()).should.be.empty
      server.queues[DEFAULT_FORMAT].size().should.equal(1)

    it "does not queue players already queued", ->
      server = new BattleServer()
      derp = server.findOrCreateUser(id: 1, name: 'derp', @stubSpark())
      server.queuePlayer(derp.name, generateTeam()).should.be.empty
      server.queuePlayer(derp.name, generateTeam()).should.be.empty
      server.queues[DEFAULT_FORMAT].size().should.equal(1)

  describe "#getOngoingBattles", ->
    it "returns one object for each queued battle", (done) ->
      server = new BattleServer()
      nBattles = 3
      for i in [1..nBattles]
        first = 2 * i
        second = (2 * i) + 1
        server.findOrCreateUser(id: first, name: String(first), @stubSpark())
        server.findOrCreateUser(id: second, name: String(second), @stubSpark())
        server.queuePlayer(String(first), generateTeam()).should.be.empty
        server.queuePlayer(String(second), generateTeam()).should.be.empty

      server.beginBattles ->
        server.getOngoingBattles().should.have.length(nBattles)
        done()

  describe "#registerChallenge", ->
    it "registers a challenge to a player", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(user, other.name, format, team, conditions)
      server.challenges.should.have.property(user.name)
      server.challenges[user.name].should.have.property(other.name)

      challenge = server.challenges[user.name][other.name]
      challenge.should.have.property("team")
      challenge.should.have.property("format")
      challenge.should.have.property("conditions")
      challenge.team.should.equal(team)
      challenge.format.should.equal(format)
      challenge.conditions.should.equal(conditions)

    it "does not override old challenges", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      diffFormat = 'xy500'
      diffTeam = generateTeam()
      diffTeam[0] = Factory("Celebi")

      server.registerChallenge(user, other.name, format, team)
      server.registerChallenge(user, other.name, diffFormat, diffTeam)

      challenge = server.challenges[user.name][other.name]
      challenge.format.should.equal(format)
      challenge.team.should.equal(team)

    it "returns an error if the team is invalid", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      format = 'xy1000'

      mock = @sandbox.mock(user).expects('error').once()
      team = []
      server.registerChallenge(user, other.name, format, team)
      mock.verify()

    it "returns an error if the team is over 1000 PBV with 1000 PBV clause", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      format = 'xy1000'
      team = generateTeam()
      team[0] = Factory("Arceus", moves: [ "Recover" ])
      conditions = [ Conditions.PBV_1000 ]

      mock = @sandbox.mock(user).expects('error').once()
      server.registerChallenge(user, other.name, format, team, conditions)
      mock.verify()

    it "returns an error on a rated challenge", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      format = 'xy1000'
      team = generateTeam()
      conditions = [ Conditions.RATED_BATTLE ]

      mock = @sandbox.mock(user).expects('error').once()
      server.registerChallenge(user, other.name, format, team, conditions)
      mock.verify()

    it "returns an error if the format is invalid", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = "UNRELEASED INFERNO RED AND WEIRD YELLOWISH GREEN"
      conditions = []

      mock = @sandbox.mock(user).expects('error').once()
      server.registerChallenge(user, other.name, format, team, conditions)
      mock.verify()

    it "returns an error if the challengee is offline", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      mock = @sandbox.mock(user).expects('error').once()
      server.registerChallenge(user, "husk", format, team, conditions)
      mock.verify()

    it "returns an error if you challenge yourself", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      mock = @sandbox.mock(user).expects('error').once()
      server.registerChallenge(user, user.name, format, team, conditions)
      mock.verify()

    it "sends an error if a challenge already exists for that pair", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(user, other.name, format, team, conditions)

      mock = @sandbox.mock(other).expects('error').once()
      server.registerChallenge(other, user.name, format, team, conditions)
      mock.verify()

    it "sends a 'challenge' event to the challengee", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      otherSpy = @sandbox.spy(other, 'send')
      otherSpy.withArgs('challenge', user.name, format, conditions)

      server.registerChallenge(user, other.name, format, team, conditions)

      otherSpy.withArgs('challenge', user.name, format, conditions)
        .calledOnce.should.be.true

    it "returns an error if the server is locked down", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      format = 'xy1000'
      team = generateTeam()
      conditions = [ Conditions.PBV_1000 ]

      mock = @sandbox.mock(user).expects('error').once()
      server.lockdown()
      server.registerChallenge(user, other.name, format, team, conditions)
      mock.verify()

  describe "#cancelChallenge", ->
    it "sends a 'cancelChallenge' to both the challengee and challenger", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      userSpy = @sandbox.spy(user, 'send')
      userSpy.withArgs('cancelChallenge', other.name)
      otherSpy = @sandbox.spy(other, 'send')
      otherSpy.withArgs('cancelChallenge', user.name)

      server.registerChallenge(user, other.name, format, team, conditions)
      server.cancelChallenge(user, other.name)

      userSpy.withArgs('cancelChallenge', other.name).calledOnce.should.be.true
      otherSpy.withArgs('cancelChallenge', user.name).calledOnce.should.be.true

    it "removes the challenge from the internal hash", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(user, other.name, format, team, conditions)
      should.exist server.challenges[user.name][other.name]
      server.cancelChallenge(user, other.name)
      should.not.exist server.challenges[user.name][other.name]

  describe "#rejectChallenge", ->
    it "sends a 'rejectChallenge' to the challengee and challenger", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(user, other.name, format, team, conditions)

      userSpy = @sandbox.spy(user, 'send')
      userSpy.withArgs('rejectChallenge', other.name)
      otherSpy = @sandbox.spy(other, 'send')
      otherSpy.withArgs('rejectChallenge', user.name)

      server.rejectChallenge(other, user.name)

      userSpy.withArgs('rejectChallenge', other.name).calledOnce.should.be.true
      otherSpy.withArgs('rejectChallenge', user.name).calledOnce.should.be.true

    it "removes the challenge from the internal hash", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(user, other.name, format, team, conditions)
      should.exist server.challenges[user.name][other.name]
      server.rejectChallenge(other, user.name)
      should.not.exist server.challenges[user.name][other.name]

    it "returns an error if no such challenge exists", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(user, other.name, format, team, conditions)
      mock = @sandbox.mock(other).expects('error').once()
      server.rejectChallenge(other, "bogus dude")
      mock.verify()

  describe "#acceptChallenge", ->
    initServer = ->
      @server = new BattleServer()
      @user = @server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      @other = @server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())

    it "creates a battle with the teams given by both players", ->
      initServer.call(this)
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      @server.registerChallenge(@user, @other.name, format, team, conditions)
      mock = @sandbox.mock(@server).expects('createBattle').once()
      @server.acceptChallenge(@other, @user.name, team)
      mock.verify()

    it "returns an error to a player if their team is invalid", ->
      initServer.call(this)
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      @server.registerChallenge(@user, @other.name, format, team, conditions)
      mock = @sandbox.mock(@other).expects('error').once()
      @server.acceptChallenge(@other, @user.name, [])
      mock.verify()

    it "returns an error to a player if their team violates clauses", ->
      initServer.call(this)
      team = generateTeam()
      acceptTeam = generateTeam()
      acceptTeam[0] = Factory("Mewtwo", moves: [ "Psychic" ])
      format = 'xy1000'
      conditions = [ Conditions.PBV_1000 ]

      @server.registerChallenge(@user, @other.name, format, team, conditions)
      mock = @sandbox.mock(@other).expects('error').once()
      @server.acceptChallenge(@other, @user.name, acceptTeam)
      mock.verify()

    it "removes the challenge from the internal hash", ->
      initServer.call(this)
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      @server.registerChallenge(@user, @other.name, format, team, conditions)
      should.exist @server.challenges[@user.name][@other.name]
      @server.acceptChallenge(@other, @user.name, team)
      should.not.exist @server.challenges[@user.name][@other.name]

    it "sends a 'challengeSuccess' event to both players", ->
      initServer.call(this)
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      userSpy = @sandbox.spy(@user, 'send')
      userSpy.withArgs('challengeSuccess', @other.name)
      otherSpy = @sandbox.spy(@other, 'send')
      otherSpy.withArgs('challengeSuccess', @user.name)

      @server.registerChallenge(@user, @other.name, format, team, conditions)
      @server.acceptChallenge(@other, @user.name, team)

      userSpy.withArgs('challengeSuccess', @other.name).calledOnce.should.be.true
      otherSpy.withArgs('challengeSuccess', @user.name).calledOnce.should.be.true

    it "returns an error if no such challenge exists", ->
      initServer.call(this)
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      @server.registerChallenge(@user, @other.name, format, team, conditions)
      mock = @sandbox.mock(@other).expects('error').once()
      @server.acceptChallenge(@other, "bogus dude", team)
      mock.verify()

    it "overrides the user's name with the alt name in battle", ->
      initServer.call(this)
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      @server.registerChallenge(@user, @other.name, format, team, conditions, "Bruce Wayne")
      battleId = @server.acceptChallenge(@other, @user.name, team, "Jason Todd")
      battle = @server.findBattle(battleId)
      battle.battle.playerNames.should.eql ["Bruce Wayne", "Jason Todd"]

    it "sets the rating key to be the unique alt id if there is an alt", ->
      initServer.call(this)
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      @server.registerChallenge(@user, @other.name, format, team, conditions, "Bruce Wayne")
      battleId = @server.acceptChallenge(@other, @user.name, team, "Jason Todd")
      battle = @server.findBattle(battleId)

      battle.battle.getPlayer("Batman").ratingKey.should.equal alts.uniqueId(@user.name, "Bruce Wayne")
      battle.battle.getPlayer("Robin").ratingKey.should.equal alts.uniqueId(@other.name, "Jason Todd")

  describe "#leave", ->
    it "removes challenges by that player", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", spark = @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(user, other.name, format, team, conditions)
      should.exist server.challenges[user.name]
      should.exist server.challenges[user.name][other.name]
      server.leave(spark)
      should.not.exist server.challenges[user.name]

    it "removes challenges to that player", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", spark = @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(other, user.name, format, team, conditions)
      should.exist server.challenges[other.name]
      should.exist server.challenges[other.name][user.name]
      server.leave(spark)
      should.exist server.challenges[other.name]
      should.not.exist server.challenges[other.name][user.name]

  describe "#lockdown", ->
    it "cancels all challenges", ->
      server = new BattleServer()
      user = server.findOrCreateUser(id: 1, name: "Batman", @stubSpark())
      other = server.findOrCreateUser(id: 2, name: "Robin", @stubSpark())
      team = generateTeam()
      format = 'xy1000'
      conditions = []

      server.registerChallenge(other, user.name, format, team, conditions)
      should.exist server.challenges[other.name]
      should.exist server.challenges[other.name][user.name]
      server.lockdown()
      should.not.exist server.challenges[other.name]

  describe "#validateTeam", ->
    it "returns non-empty if given anything that's not an array", ->
      server = new BattleServer()
      server.validateTeam().should.not.be.empty

    it "returns empty if given a non-empty array containing Pokemon", ->
      server = new BattleServer()
      server.validateTeam(generateTeam()).should.be.empty

    it "returns non-empty if given an empty array", ->
      server = new BattleServer()
      server.validateTeam([]).should.not.be.empty

    it "returns non-empty if a team member is not a valid Pokemon", ->
      server = new BattleServer()
      invalidPokemon = {}
      server.validateTeam([ invalidPokemon ]).should.not.be.empty

    it "returns non-empty if a team member has a fake species name", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = {species: "NOTREALMON"}
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a team member has no moveset", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Hitmonchan", moves: null)
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a team member has an empty moveset", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Hitmonchan", moves: [])
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a team member has a bogus moveset", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Hitmonchan", moves: true)
      server.validateTeam(team).should.not.be.empty
      team[0] = Factory("Hitmonchan", moves: ["Super Powerful Punch"])
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a team member has an illegal moveset", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Raichu", moves: [ "Volt Tackle", "Encore" ])
      server.validateTeam(team, 'bw').should.not.be.empty

    # TODO: 4 is a magic constant
    it "returns non-empty if a pokemon has more than 4 moves", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory "Hitmonchan",
        moves:  [
                  "Ice Punch"
                  "Fire Punch"
                  "Close Combat"
                  "Mach Punch"
                  "Rapid Spin"
                ]
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has a move it can't learn", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", moves: [ "Fissure" ])
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has a fake move", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", moves: [ "Splash", "Armageddon" ])
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has an ability it can't have", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", ability: "Wonder Guard")
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has a bogus ability", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", ability: "Being Batman")
      server.validateTeam(team).should.not.be.empty

    it "returns empty if a pokemon has a hidden ability", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Cloyster", ability: "Overcoat")
      server.validateTeam(team).should.be.empty

    it "returns non-empty if a pokemon has a level below 1", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", level: 0)
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has a bogus level", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", level: "hi")
      server.validateTeam(team).should.not.be.empty

    # TODO: 100 is a magic constant
    it "returns non-empty if a pokemon has a level over 100", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", level: 101)
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has an iv below 0", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", ivs: { hp: -1 })
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has an iv above 31", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", ivs: { hp: 32 })
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has bogus ivs", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", ivs: true)
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has an ev below 0", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", evs: { hp: -1 })
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has an ev above 255", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", evs: { hp: 256 })
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has an ev total above 510", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", evs: { hp: 255, defense: 255, speed: 255 })
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has bogus evs", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Magikarp", evs: true)
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has an invalid gender", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Metagross", gender: "Alien")
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has a gender it can't have", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Metagross", gender: "F")
      server.validateTeam(team).should.not.be.empty

      team = generateTeam()
      team[0] = Factory("Blissey", gender: "M")
      server.validateTeam(team).should.not.be.empty

      team = generateTeam()
      team[0] = Factory("Gallade", gender: "F")
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has a bogus forme", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Blissey", forme: "Super Ultra Mega Blissey")
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon has a battle-only forme", ->
      server = new BattleServer()
      team = generateTeam()
      team[0] = Factory("Meloetta", forme: "pirouette", moves: ["Relic Song"])
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon cannot have its forme"

    it "returns non-empty if the format is fake", ->
      server = new BattleServer()
      server.validateTeam(generateTeam(), 'bogusformat').should.not.be.empty

    it "returns non-empty if a pokemon's nickname matches another species", ->
      server = new BattleServer()
      team = generateTeam()
      team[0].name = "Latios"
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon's nickname is blank", ->
      server = new BattleServer()
      team = generateTeam()
      team[0].name = ""
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon's nickname contains illegal chars", ->
      server = new BattleServer()
      team = generateTeam()
      team[0].name = "some chars \uFE20 are illegal"
      server.validateTeam(team).should.not.be.empty

    it "returns non-empty if a pokemon's nickname is past a certain length", ->
      server = new BattleServer()
      team = generateTeam()
      team[0].name = ("A"  for x in [0...50]).join('')
      server.validateTeam(team).should.not.be.empty

  describe "#beginBattles", ->
    it "creates a battle per pair", (done) ->
      server = new BattleServer()
      for i in [1..4]
        server.findOrCreateUser(id: i, name: "user#{i}", @stubSpark())
        server.queuePlayer("user#{i}", generateTeam())
      server.beginBattles (err, battleIds) ->
        battleIds.length.should.equal(2)
        done()

  describe "users", ->
    it "are recorded to be playing in which battles", (done) ->
      server = new BattleServer()
      [ user1, user2, user3 ] = [ "a", "b", "c" ]
      for name, i in [ user1, user2, user3 ]
        server.findOrCreateUser(id: i, name: name, @stubSpark())
      server.queuePlayer(user1, generateTeam()).should.be.empty
      server.queuePlayer(user2, generateTeam()).should.be.empty
      server.queuePlayer(user3, generateTeam()).should.be.empty
      server.beginBattles (err, battleIds) ->
        server.getUserBattles(user1).should.eql(battleIds)
        server.getUserBattles(user2).should.eql(battleIds)
        server.getUserBattles(user3).should.be.empty
        done()

    it "no longer records battles once they end", (done) ->
      server = new BattleServer()
      [ user1, user2, user3 ] = [ "a", "b", "c" ]
      for name, i in [ user1, user2, user3 ]
        server.findOrCreateUser(id: i, name: name, @stubSpark())
      server.queuePlayer(user1, generateTeam()).should.be.empty
      server.queuePlayer(user2, generateTeam()).should.be.empty
      server.queuePlayer(user3, generateTeam()).should.be.empty
      server.beginBattles (err, battleIds) ->
        for battleId in battleIds
          battle = server.findBattle(battleId)
          battle.endBattle()
        server.getUserBattles(user1).should.be.empty
        server.getUserBattles(user2).should.be.empty
        server.getUserBattles(user3).should.be.empty
        done()

    it "can join multiple times", ->
      server = new BattleServer()
      server.findOrCreateUser(id: 1, name: "hey", spark1 = @stubSpark())
      server.join(spark1)
      (=>
        server.findOrCreateUser(id: 1, name: "hey", spark2 = @stubSpark())
        server.join(spark2)
      ).should.not.throw()

    it "records battles they're under an alt in", (done) ->
      server = new BattleServer()
      [ user1, user2 ] = [ "a", "b" ]
      for name, i in [ user1, user2 ]
        server.findOrCreateUser(id: i, name: name, @stubSpark())
      server.queuePlayer(user1, generateTeam(), null, 'alt1').should.be.empty
      server.queuePlayer(user2, generateTeam(), null, 'alt2').should.be.empty
      server.beginBattles (err, battleIds) ->
        server.getUserBattles(user1).should.not.be.empty
        server.getUserBattles(user2).should.not.be.empty
        done()

    it "auto-rejoin battles they're under an alt in", (done) ->
      server = new BattleServer()
      [ user1, user2 ] = [ "a", "b" ]
      server.findOrCreateUser(id: 1, name: user1, spark1 = @stubSpark())
      server.findOrCreateUser(id: 2, name: user2, spark2 = @stubSpark())
      server.queuePlayer(user1, generateTeam(), null, 'alt1').should.be.empty
      server.queuePlayer(user2, generateTeam(), null, 'alt2').should.be.empty
      server.beginBattles (err, battleIds) =>
        [battleId] = battleIds
        battle = server.findBattle(battleId).battle

        # test spark1
        spy = @sandbox.spy(battle, 'tellPlayer').withArgs(user1, Protocol.RECEIVE_TEAM)
        server.join(spark1)
        spy.calledOnce.should.be.true
        battle.tellPlayer.restore()

        # test spark2
        spy = @sandbox.spy(battle, 'tellPlayer').withArgs(user2, Protocol.RECEIVE_TEAM)
        server.join(spark2)
        spy.calledOnce.should.be.true
        battle.tellPlayer.restore()
        done()

    it "automatically leaves a battle when leaving the server", (done) ->
      server = new BattleServer()
      [ user1, user2 ] = [ "a", "b" ]
      server.findOrCreateUser(id: 1, name: user1, spark1 = @stubSpark())
      server.findOrCreateUser(id: 2, name: user2, spark2 = @stubSpark())
      server.join(spark1)
      server.join(spark2)
      server.queuePlayer(user1, generateTeam(), null, 'alt1').should.be.empty
      server.queuePlayer(user2, generateTeam(), null, 'alt2').should.be.empty
      server.beginBattles (err, battleIds) =>
        [battleId] = battleIds
        battle = server.findBattle(battleId).battle

        # test spark1
        spy = @sandbox.spy(battle, 'remove').withArgs(spark1)
        broadcastSpy = @sandbox.spy(battle, 'send')
        broadcastSpy = broadcastSpy.withArgs('leaveChatroom', battle.id, 'alt1')
        server.leave(spark1)
        spark1.end()
        spy.calledOnce.should.be.true
        broadcastSpy.calledOnce.should.be.true
        battle.remove.restore()
        battle.send.restore()

        # test spark2
        spy = @sandbox.spy(battle, 'remove').withArgs(spark2)
        broadcastSpy = @sandbox.spy(battle, 'send')
        broadcastSpy = broadcastSpy.withArgs('leaveChatroom', battle.id, 'alt2')
        server.leave(spark2)
        spark2.end()
        spy.calledOnce.should.be.true
        broadcastSpy.calledOnce.should.be.true
        battle.remove.restore()
        battle.send.restore()
        done()

  describe "a battle", ->
    beforeEach (done) ->
      @server = new BattleServer()
      [ @user1, @user2, @user3 ] = [ "a", "b", "c" ]
      for name, i in [ @user1, @user2, @user3 ]
        @server.findOrCreateUser(id: i, name: name, @stubSpark())
      @server.queuePlayer(@user1, generateTeam()).should.be.empty
      @server.queuePlayer(@user2, generateTeam()).should.be.empty
      @server.queuePlayer(@user3, generateTeam()).should.be.empty
      @server.queuedPlayers().should.have.length(3)
      @server.beginBattles (err, battleIds) =>
        @battleIds = battleIds
        @relevantUser = @server.findBattle(@battleIds[0]).battle.playerIds[0]
        done()

    it "removes from user battles if ended", ->
      @server.getUserBattles(@relevantUser).should.not.be.empty
      battle = @server.findBattle(@battleIds[0])
      battle.endBattle()
      @server.getUserBattles(@user1).should.be.empty
      @server.getUserBattles(@user2).should.be.empty
      @server.getUserBattles(@user3).should.be.empty

    it "removes from user battles if forfeited", ->
      @server.getUserBattles(@relevantUser).should.not.be.empty
      battle = @server.findBattle(@battleIds[0])
      battle.forfeit(@relevantUser)
      @server.getUserBattles(@user1).should.be.empty
      @server.getUserBattles(@user2).should.be.empty
      @server.getUserBattles(@user3).should.be.empty
