{Attachment, Status} = require('../../server/xy/attachment')
{Battle} = require('../../server/xy/battle')
{Pokemon} = require('../../server/xy/pokemon')
{Weather} = require('../../shared/weather')
{Ability} = require '../../server/xy/data/abilities'
{Item} = require '../../server/xy/data/items'
util = require '../../server/xy/util'
{Factory} = require '../factory'
should = require 'should'
shared = require '../shared'

require '../helpers'

describe "XY Abilities:", ->
  testWeatherAbility = (name, weather) ->
    describe name, ->
      it "causes #{weather} that ends after 5 turns", ->
        shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
        @battle.weatherDuration.should.equal(5)

      it "does not activate if the weather is already #{weather}", ->
        shared.build(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
        @battle.setWeather(weather, 2)
        @controller.beginBattle()
        @battle.weatherDuration.should.equal(2)

      it "is lengthened by rocks", ->
        for itemName, item of Item
          break  if item.lengthensWeather == weather
        shared.create.call this,
          gen: 'xy'
          team1: [Factory("Magikarp", ability: name, item: itemName)]
        @battle.weatherDuration.should.equal(8)

  testWeatherAbility("Drizzle", Weather.RAIN)
  testWeatherAbility("Drought", Weather.SUN)
  testWeatherAbility("Sand Stream", Weather.SAND)
  testWeatherAbility("Snow Warning", Weather.HAIL)

  testNormalTypeChangeAbility = (name, type) ->
    describe name, ->
      it "changes Normal-type moves used by attacker to #{type}-type", ->
        shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
        spy = @sandbox.spy(@p1, 'editMoveType')
        tackle = @battle.getMove('Tackle')
        @battle.performMove(@p1, tackle)
        spy.returned(type).should.be.true

      it "does not change non-Normal-type moves used by attacker", ->
        shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
        spy = @sandbox.spy(@p1, 'editMoveType')
        ember = @battle.getMove('Ember')
        @battle.performMove(@p1, ember)
        spy.returned(type).should.be.false
        spy.returned(ember.type).should.be.true

      it "boosts Normal-type moves by 1.3x", ->
        shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
        tackle = @battle.getMove('Tackle')
        tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)

      it "does not boost regular #{type}-type moves", ->
        shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
        for move in @battle.MoveList
          if move.type == type && !move.isNonDamaging() then break
        move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)

      it "does not boost non-#{type}-type moves", ->
        shared.create.call(this, gen: 'xy', team1: [Factory("Magikarp", ability: name)])
        ember = @battle.getMove('Ember')
        ember.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)

      it "is unaffected by the original immunities", ->
        shared.create.call this,
          gen: 'xy'
          team1: [Factory("Magikarp", ability: name)]
          team2: [Factory("Gengar")]
        tackle = @battle.getMove('Tackle')
        mock = @sandbox.mock(tackle).expects('hit').once()
        @battle.performMove(@p1, tackle)
        mock.verify()

  testNormalTypeChangeAbility("Aerilate", "Flying")
  testNormalTypeChangeAbility("Pixilate", "Fairy")
  testNormalTypeChangeAbility("Refrigerate", "Ice")

  describe "Shadow Tag", ->
    it "does not affect ghosts", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Gengar")]
        team2: [Factory("Magikarp", ability: "Shadow Tag")]
      @p1.isSwitchBlocked().should.be.false
      @battle.beginTurn()
      @p1.isSwitchBlocked().should.be.false

  testAuraAbility = (name, type) ->
    describe name, ->
      it "raises base power of #{type} attacks by 1.33x", ->
        shared.create.call this,
          gen: 'xy'
          team1: [Factory("Magikarp", ability: name)]
        move = @battle.findMove (m) ->
          m.type == type && !m.isNonDamaging()
        move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1547)

      it "decreases #{type} attacks by 3/4x if Aura Break is on the field", ->
        shared.create.call this,
          gen: 'xy'
          team1: [Factory("Magikarp", ability: name)]
          team2: [Factory("Magikarp", ability: "Aura Break")]
        move = @battle.findMove (m) ->
          m.type == type && !m.isNonDamaging()
        move.modifyBasePower(@battle, @p1, @p2).should.equal(0xC00)

      it "does nothing to moves not of #{type} type", ->
        shared.create.call this,
          gen: 'xy'
          team1: [Factory("Magikarp", ability: name)]
        move = @battle.findMove (m) ->
          m.type != type && !m.isNonDamaging()
        move.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)

  testAuraAbility("Dark Aura", "Dark")
  testAuraAbility("Fairy Aura", "Fairy")

  describe "Gale Wings", ->
    it "adds 1 to the priority of the user's Flying moves", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Gale Wings")]
      gust = @battle.getMove("Gust")
      @p1.editPriority(0, gust).should.equal(1)

    it "does not change priority otherwise", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Gale Wings")]
      tackle = @battle.getMove("Tackle")
      @p1.editPriority(0, tackle).should.equal(0)

  describe "Bulletproof", ->
    it "makes user immune to bullet moves", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Bulletproof")]
      shadowBall = @battle.getMove('Shadow Ball')
      @p1.isImmune(shadowBall.type, move: shadowBall).should.be.true

  describe "Competitive", ->
    it "boosts special attack by 2 every time a stat is lowered", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Competitive")]
      @p1.boost(defense: -1, @p2)
      @p1.stages.should.containEql(specialAttack: 2)
      @p1.boost(specialAttack: -1, defense: -2, evasion: 1, @p2)
      @p1.stages.should.containEql(specialAttack: 5)

    it "does not boost special attack if the stat was self-lowered", ->
      shared.create.call this,
        team1: [Factory("Magikarp", ability: "Competitive")]
      @battle.performMove(@p1, @battle.getMove("Close Combat"))
      boosts = {specialAttack: 0, defense: -1, specialDefense: -1}
      @p1.stages.should.containEql(boosts)

  describe "Fur Coat", ->
    it "modifies physical attacks by 0x800", ->
      shared.create.call this,
        gen: 'xy'
        team2: [Factory("Magikarp", ability: "Fur Coat")]
      tackle = @battle.getMove('Tackle')
      tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x800)

    it "doesn't modify other attacks", ->
      shared.create.call this,
        gen: 'xy'
        team2: [Factory("Magikarp", ability: "Fur Coat")]
      thunderbolt = @battle.getMove('Thunderbolt')
      thunderbolt.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)

  describe "Gooey", ->
    it "lowers the attacker's speed by 1 on contact", ->
      shared.create.call this,
        gen: 'xy'
        team2: [Factory("Magikarp", ability: "Gooey")]
      tackle = @battle.getMove('Tackle')
      @p1.stages.should.containEql(speed: 0)
      @battle.performMove(@p1, tackle)
      @p1.stages.should.containEql(speed: -1)

    it "does not lower the attacker's speed by 1 on non-contact", ->
      shared.create.call this,
        gen: 'xy'
        team2: [Factory("Magikarp", ability: "Gooey")]
      thunderbolt = @battle.getMove('Thunderbolt')
      @p1.stages.should.containEql(speed: 0)
      @battle.performMove(@p1, thunderbolt)
      @p1.stages.should.containEql(speed: 0)

    it "works even if the defender faints", ->
      shared.create.call this,
        gen: 'xy'
        team2: [Factory("Magikarp", ability: "Gooey"), Factory("Magikarp")]
      @p2.currentHP = 1
      tackle = @battle.getMove('Tackle')
      @p1.stages.should.containEql(speed: 0)
      @battle.performMove(@p1, tackle)
      @p1.stages.should.containEql(speed: -1)

  describe "Mega Launcher", ->
    it "boosts pulse moves by x1.5", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Mega Launcher")]
      waterPulse = @battle.getMove('Water Pulse')
      waterPulse.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)

    it "does not boost non-pulse moves", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Mega Launcher")]
      tackle = @battle.getMove('Tackle')
      tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)

  describe "Overcoat", ->
    it "makes the user immune to weather", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Overcoat")]
      @p1.isWeatherDamageImmune(Weather.SAND).should.be.true
      @p1.isWeatherDamageImmune(Weather.HAIL).should.be.true

    it "makes the user immune to powder moves", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Overcoat")]
      spore = @battle.getMove("Spore")
      mock = @sandbox.mock(spore).expects('hit').never()
      @battle.performMove(@p2, spore)
      mock.verify()

  describe "Parental Bond", ->
    it "hits twice if the move has only one target", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Parental Bond")]
      tackle = @battle.getMove('Tackle')
      targets = @battle.getTargets(tackle, @p1)
      tackle.calculateNumberOfHits(@battle, @p1, targets).should.equal(2)

    it "hits once if the move has multiple targets", ->
      shared.create.call this,
        gen: 'xy'
        numActive: 2
        team1: [Factory("Magikarp", ability: "Parental Bond"), Factory("Magikarp")]
        team2: (Factory("Magikarp")  for x in [0..1])
      earthquake = @battle.getMove('Earthquake')
      targets = @battle.getTargets(earthquake, @p1)
      earthquake.calculateNumberOfHits(@battle, @p1, targets).should.equal(1)

    it "hits once if the move is non-damaging", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Parental Bond")]
      willOWisp = @battle.getMove('Will-O-Wisp')
      targets = @battle.getTargets(willOWisp, @p1)
      willOWisp.calculateNumberOfHits(@battle, @p1, targets).should.equal(1)

    it "hits for half damage the second hit", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Parental Bond")]
      tackle = @battle.getMove('Tackle')
      spy = @sandbox.spy(tackle, 'modifyDamage')
      @battle.performMove(@p1, tackle)
      spy.calledTwice.should.be.true
      spy.returnValues[0].should.equal(0x1000)
      spy.returnValues[1].should.equal(0x800)

    it "hits the same number otherwise if the move is multi-hit", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Parental Bond")]
      shared.biasRNG.call(this, "choice", 'num hits', 4)
      shared.biasRNG.call(this, "randInt", 'num hits', 4)
      pinMissile = @battle.getMove('Pin Missile')
      targets = @battle.getTargets(pinMissile, @p1)
      pinMissile.calculateNumberOfHits(@battle, @p1, targets).should.equal(4)

    it "doesn't hit for half damage on the second hit using multi-hit moves", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Parental Bond")]
      shared.biasRNG.call(this, "choice", 'num hits', 4)
      shared.biasRNG.call(this, "randInt", 'num hits', 4)
      pinMissile = @battle.getMove('Pin Missile')
      spy = @sandbox.spy(pinMissile, 'modifyDamage')
      @battle.performMove(@p1, pinMissile)
      spy.callCount.should.equal(4)
      spy.returnValues[0].should.equal(0x1000)
      spy.returnValues[1].should.equal(0x1000)

    it "recoils only once, but with both attack damages combined", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Parental Bond")]
      takeDown = @battle.getMove("Take Down")
      spy = @sandbox.spy(takeDown, 'hit')
      @battle.performMove(@p1, takeDown)
      totalDamage = spy.returnValues.reduce((a, b) -> a + b)
      recoilDamage = Math.round(totalDamage * -takeDown.recoil / 100)
      (@p1.stat('hp') - @p1.currentHP).should.equal(recoilDamage)

  describe "Protean", ->
    it "changes the Pokemon's type when using a move", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Protean")]
      @p1.types.should.eql([ "Water" ])
      @controller.makeMove(@id1, "Tackle")
      @controller.makeMove(@id2, "Splash")
      @p1.types.should.eql([ "Normal" ])

  describe "Stance Change", ->
    it "changes to blade forme when using an attacking move", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Aegislash", ability: "Stance Change")]
      @p1.forme.should.equal("default")
      @battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
      @p1.forme.should.equal("blade")

    it "changes to shield forme when using King's Shield", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Aegislash", ability: "Stance Change")]
      @battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
      @p1.forme.should.equal("blade")
      @battle.performMove(@p1, @battle.getMove("King's Shield"))
      @p1.forme.should.equal("default")

    it "changes to shield forme after switching out and back in", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Aegislash", ability: "Stance Change"), Factory("Magikarp")]
      @battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
      @p1.forme.should.equal("blade")
      @battle.performSwitch(@p1, 1)
      @battle.performSwitch(@team1.first(), 1)
      @p1.forme.should.equal("default")

    it "does not change formes when using any other move", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Aegislash", ability: "Stance Change")]
      @battle.performMove(@p1, @battle.getMove("Swords Dance"))
      @p1.forme.should.equal("default")
      @battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
      @p1.forme.should.equal("blade")
      @battle.performMove(@p1, @battle.getMove("Swords Dance"))
      @p1.forme.should.equal("blade")

    it "doesn't attempt to change forme for Pokemon who aren't Aegislash", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Ditto", ability: "Stance Change")]
      @battle.performMove(@p1, @battle.getMove("Shadow Sneak"))
      @p1.forme.should.not.equal("blade")

    it "doesn't attempt to change forme to default for non-Aegislashes", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Rotom", forme: "wash", ability: "Stance Change")]
      @battle.performMove(@p1, @battle.getMove("King's Shield"))
      @p1.forme.should.not.equal("default")

    it "cannot be skill-swapped"
    it "cannot be replaced with another ability"

  describe "Strong Jaw", ->
    it "boosts bite moves by x1.5", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Strong Jaw")]
      bite = @battle.getMove('Bite')
      bite.modifyBasePower(@battle, @p1, @p2).should.equal(0x1800)

    it "does not boost non-bite moves", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Strong Jaw")]
      thunderbolt = @battle.getMove('Thunderbolt')
      thunderbolt.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)

  describe "Tough Claws", ->
    it "boosts contact moves by x1.3", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Tough Claws")]
      tackle = @battle.getMove('Tackle')
      tackle.modifyBasePower(@battle, @p1, @p2).should.equal(0x14CD)

    it "does not boost non-contact moves", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Tough Claws")]
      thunderbolt = @battle.getMove('Thunderbolt')
      thunderbolt.modifyBasePower(@battle, @p1, @p2).should.equal(0x1000)

  describe "Mummy", ->
    it "doesn't change ability if attacker has Stance Change", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Mummy")]
        team2: [Factory("Magikarp", ability: "Stance Change")]
      tackle = @battle.getMove("Tackle")
      @p2.hasAbility("Mummy").should.be.false
      @battle.performMove(@p2, tackle)
      @p2.hasAbility("Mummy").should.be.false

  testAttachmentImmuneAbility = (name, attachments, options = {}) ->
    describe name, ->
      it "prevents the pokemon from receiving a specific attachment", ->
        shared.create.call this,
          gen: 'xy'
          team1: [Factory("Magikarp", gender: "F", ability: name)]
          team2: [Factory("Magikarp", gender: "M")]
        for attachment in attachments
          should.not.exist @p1.attach(attachment, source: @p2)
          @p1.has(attachment).should.be.false

      shouldCure = options.cure ? true
      if shouldCure
        it "removes the attachment if the pokemon already has it", ->
          shared.create.call this,
            gen: 'xy'
            team1: [Factory("Magikarp", gender: "F")]
            team2: [Factory("Magikarp", gender: "M")]
          for attachment in attachments
            @p1.attach(attachment, source: @p2)
            @p1.has(attachment).should.be.true
            @p1.copyAbility(Ability[name.replace(/\s+/g, '')])
            @p1.update()
            @p1.has(attachment).should.be.false
            @p1.copyAbility(null)

  testAttachmentImmuneAbility("Aroma Veil", [Attachment.Attract, Attachment.Disable,
    Attachment.Encore, Attachment.Taunt, Attachment.Torment], cure: false)
  testAttachmentImmuneAbility("Oblivious", [Attachment.Attract, Attachment.Taunt])
  testAttachmentImmuneAbility("Sweet Veil", [Status.Sleep], cure: false)

  describe "Effect Spore", ->
    it "doesn't affect Grass types", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Effect Spore")]
        team2: [Factory("Bulbasaur")]

      # Sleep
      shared.biasRNG.call(this, "randInt", 'effect spore', 1)
      @battle.performMove(@p2, @battle.getMove('Tackle'))
      @p2.has(Status.Sleep).should.be.false

    it "doesn't affect Overcoat Pokemon", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Effect Spore")]
        team2: [Factory("Magikarp", ability: "Overcoat")]

      # Paralysis
      shared.biasRNG.call(this, "randInt", 'effect spore', 2)
      @battle.performMove(@p2, @battle.getMove('Tackle'))
      @p2.has(Status.Paralyze).should.be.false

    it "doesn't affect Safety Goggles holders", ->
      shared.create.call this,
        gen: 'xy'
        team1: [Factory("Magikarp", ability: "Effect Spore")]
        team2: [Factory("Magikarp", item: "Safety Goggles")]

      # Poison
      shared.biasRNG.call(this, "randInt", 'effect spore', 3)
      @battle.performMove(@p2, @battle.getMove('Tackle'))
      @p2.has(Status.Poison).should.be.false
