{_} = require 'underscore'
{Attachment, Status, VolatileAttachment} = require '../attachment'
{Weather} = require '../../../shared/weather'
util = require '../util'

@Ability = Ability = {}

makeAbility = (name, func) ->
  condensed = name.replace(/\s+/g, '')
  class Ability[condensed] extends VolatileAttachment
    @displayName: name
    displayName: name
    ability: true
    func?.call(this)

# TODO: Implement.
makeAbility 'Pickup'

# Ability templates

makeWeatherPreventionAbility = (name) ->
  makeAbility name, ->
    @preventsWeather = true

    this::switchIn = ->
      @pokemon.activateAbility()
      @battle.cannedText('WEATHER_DISABLED')

makeWeatherPreventionAbility("Air Lock")
makeWeatherPreventionAbility("Cloud Nine")

makeCriticalHitPreventionAbility = (name) ->
  makeAbility name, ->
    @preventsCriticalHits = true

makeCriticalHitPreventionAbility("Battle Armor")
makeCriticalHitPreventionAbility("Shell Armor")

makeBoostProtectionAbility = (name, protection) ->
  makeAbility name, ->
    this::transformBoosts = (boosts, source) ->
      return boosts  if source == @pokemon
      didProtect = false
      for stat of boosts
        if (!protection || stat in protection) && boosts[stat] < 0
          didProtect = true
          boosts[stat] = 0
      @pokemon.activateAbility()  if didProtect
      boosts

makeBoostProtectionAbility("Big Pecks", [ "defense" ])
makeBoostProtectionAbility("Clear Body")
makeBoostProtectionAbility("Hyper Cutter", [ "attack" ])
makeBoostProtectionAbility("Keen Eye", [ "accuracy" ])
makeBoostProtectionAbility("White Smoke")

makeWeatherSpeedAbility = (name, weather) ->
  makeAbility name, ->
    this::switchIn = ->
      @doubleSpeed = @battle.hasWeather(weather)

    this::informWeather = (newWeather) ->
      @doubleSpeed = (weather == newWeather)

    this::editSpeed = (speed) ->
      if @doubleSpeed then 2 * speed else speed

    this::isWeatherDamageImmune = (currentWeather) ->
      return true  if weather == currentWeather

makeWeatherSpeedAbility("Chlorophyll", Weather.SUN)
makeWeatherSpeedAbility("Swift Swim", Weather.RAIN)
makeWeatherSpeedAbility("Sand Rush", Weather.SAND)

makeLowHealthAbility = (name, type) ->
  makeAbility name, ->
    this::modifyBasePower = (move, target) ->
      return 0x1000  if move.getType(@battle, @pokemon, target) != type
      return 0x1000  if @pokemon.currentHP > Math.floor(@pokemon.stat('hp') / 3)
      return 0x1800

makeLowHealthAbility("Blaze", "Fire")
makeLowHealthAbility("Torrent", "Water")
makeLowHealthAbility("Overgrow", "Grass")
makeLowHealthAbility("Swarm", "Bug")

makeWeatherAbility = makeWeatherAbility ? (name, weather) ->
  makeAbility name, ->
    this::switchIn = ->
      @pokemon.activateAbility()
      @battle.setWeather(weather)

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

makeFilterAbility = (name) ->
  makeAbility name, ->
    this::modifyDamageTarget = (move, user) ->
      if util.typeEffectiveness(move.type, user.types) > 1
        0xC00
      else
        0x1000

makeFilterAbility("Filter")
makeFilterAbility("Solid Rock")

makeContactStatusAbility = (name, attachment) ->
  makeAbility name, ->
    this::isAliveCheck = -> true

    this::afterBeingHit = (move, user, target, damage, isDirect) ->
      return  if !move.hasFlag("contact")
      return  if @battle.rng.next("contact status") >= .3
      return  if !isDirect
      @pokemon.activateAbility()
      user.attach(attachment, source: @pokemon)

makeContactStatusAbility("Cute Charm", Attachment.Attract)
makeContactStatusAbility("Flame Body", Status.Burn)
makeContactStatusAbility("Poison Point", Status.Poison)
makeContactStatusAbility("Static", Status.Paralyze)

makeStatusBoostAbility = (name, statuses, spectra) ->
  makeAbility name, ->
    this::modifyBasePower = (move, target) ->
      if move.spectra == spectra && statuses.some((s) => @pokemon.has(s))
        0x1800
      else
        0x1000

makeStatusBoostAbility("Flare Boost", [Status.Burn], 'special')
makeStatusBoostAbility("Toxic Boost", [Status.Poison, Status.Toxic], 'physical')

makeHugePowerAbility = (name) ->
  makeAbility name, ->
    this::modifyAttack = (move) ->
      if move.isPhysical() then 0x2000 else 0x1000

makeHugePowerAbility("Huge Power")
makeHugePowerAbility("Pure Power")

makeAttachmentImmuneAbility = (name, immuneAttachments, options = {}) ->
  makeAbility name, ->
    this::shouldAttach = (attachment) ->
      if attachment in immuneAttachments
        @pokemon.activateAbility()
        return false
      return true

    shouldCure = options.cure ? true
    if shouldCure
      this::update = ->
        for attachment in immuneAttachments
          if @pokemon.has(attachment)
            @pokemon.activateAbility()
            @pokemon.unattach(attachment)

makeAttachmentImmuneAbility("Immunity", [Status.Poison, Status.Toxic])
makeAttachmentImmuneAbility("Inner Focus", [Attachment.Flinch], cure: false)
makeAttachmentImmuneAbility("Insomnia", [Status.Sleep])
makeAttachmentImmuneAbility("Limber", [Status.Paralyze])
makeAttachmentImmuneAbility("Magma Armor", [Status.Freeze])
makeAttachmentImmuneAbility("Oblivious", [Attachment.Attract])
makeAttachmentImmuneAbility("Own Tempo", [Attachment.Confusion])
makeAttachmentImmuneAbility("Vital Spirit", [Status.Sleep])
makeAttachmentImmuneAbility("Water Veil", [Status.Burn])

makeContactHurtAbility = (name) ->
  makeAbility name, ->
    this::isAliveCheck = -> true

    this::afterBeingHit = (move, user, target, damage, isDirect) ->
      return  unless move.hasFlag('contact')
      return  unless isDirect
      amount = user.stat('hp') >> 3
      @pokemon.activateAbility()
      if user.damage(amount)
        @battle.cannedText('POKEMON_HURT', user)

makeContactHurtAbility("Iron Barbs")
makeContactHurtAbility("Rough Skin")

makeRedirectAndBoostAbility = (name, type) ->
  makeAbility name, ->
    # TODO: This should be implemented as isImmune instead.
    # TODO: Type-immunities should come before ability immunities.
    this::shouldBlockExecution = (move, user) ->
      return  if move.getType(@battle, user, @pokemon) != type || user == @pokemon
      @pokemon.activateAbility()
      @pokemon.boost(specialAttack: 1)  unless @pokemon.isImmune(type)
      return true

makeRedirectAndBoostAbility("Lightningrod", "Electric")
makeRedirectAndBoostAbility("Storm Drain", "Water")

makeTypeImmuneAbility = (name, type, stat) ->
  makeAbility name, ->
    this::shouldBlockExecution = (move, user) ->
      return  if move.getType(@battle, user, @pokemon) != type || user == @pokemon
      @pokemon.activateAbility()
      @battle.message "#{@pokemon.name}'s #{name} increased its #{stat}!"
      hash = {}
      hash[stat] = 1
      @pokemon.boost(hash)
      return true

makeTypeImmuneAbility("Motor Drive", "Electric", "speed")
makeTypeImmuneAbility("Sap Sipper", "Grass", "attack")

makeTypeAbsorbMove = (name, type) ->
  makeAbility name, ->
    this::shouldBlockExecution = (move, user) ->
      return  if move.getType(@battle, user, @pokemon) != type || user == @pokemon
      @pokemon.activateAbility()
      amount = @pokemon.stat('hp') >> 2
      if @pokemon.heal(amount)
        @battle.cannedText('RECOVER_HP', @pokemon)
      return true

makeTypeAbsorbMove("Water Absorb", "Water")
makeTypeAbsorbMove("Volt Absorb", "Electric")

makeAbilityCancelAbility = (name, cannedText) ->
  makeAbility name, ->
    this::switchIn = ->
      @pokemon.activateAbility()
      @battle.cannedText(cannedText, @pokemon)

    this::beforeMove = (move, pokemon, targets) ->
      for target in targets
        continue  if !@battle.isPokemon(target)
        target.attach(Attachment.AbilityCancel)

    this::afterMove = (move, pokemon, targets) ->
      for target in targets
        continue  if !@battle.isPokemon(target)
        target.unattach(Attachment.AbilityCancel)

makeAbilityCancelAbility('Mold Breaker', 'MOLD_BREAKER')
makeAbilityCancelAbility('Teravolt', 'TERAVOLT')
makeAbilityCancelAbility('Turboblaze', 'TURBOBLAZE')

# Unique Abilities

makeAbility "Adaptability"

makeAbility "Aftermath", ->
  this::isAliveCheck = -> true

  this::afterFaint = ->
    hit = @pokemon.lastHitBy
    return  if !hit
    {team, slot, damage, move, turn} = hit
    pokemon = team.at(slot)
    if move.hasFlag('contact')
      amount = (pokemon.stat('hp') >> 2)
      @pokemon.activateAbility()
      pokemon.damage(amount)
      @battle.cannedText('POKEMON_HURT', pokemon)

makeAbility 'Analytic', ->
  this::modifyBasePower = ->
    if !@battle.hasActionsLeft() then 0x14CD else 0x1000

makeAbility "Anger Point", ->
  this::informCriticalHit = ->
    @pokemon.activateAbility()
    @battle.message "#{@pokemon.name} maxed its Attack!"
    @pokemon.boost(attack: 12)

makeAbility "Anticipation", ->
  this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    moves = _(opponent.moves  for opponent in opponents).flatten()
    for move in moves
      effectiveness = util.typeEffectiveness(move.type, @pokemon.types) > 1
      if effectiveness || move.hasFlag("ohko")
        @pokemon.activateAbility()
        @battle.cannedText('ANTICIPATION', @pokemon)
        break

makeAbility "Arena Trap", ->
  this::beginTurn = this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    for opponent in opponents
      opponent.blockSwitch()  unless opponent.isImmune("Ground")

makeAbility "Bad Dreams", ->
  this::endTurn = ->
    opponents = @battle.getOpponents(@pokemon)
    for opponent in opponents
      continue  unless opponent.has(Status.Sleep)
      amount = opponent.stat('hp') >> 3
      @pokemon.activateAbility()
      if opponent.damage(amount)
        @battle.cannedText('BAD_DREAMS', opponent)

makeAbility "Color Change", ->
  this::afterBeingHit = (move, user, target, damage) ->
    {type} = move
    if !move.isNonDamaging() && !target.hasType(type)
      @pokemon.activateAbility()
      @battle.cannedText('COLOR_CHANGE', target, type)
      target.types = [ type ]

makeAbility "Compoundeyes", ->
  this::editAccuracy = (accuracy) ->
    Math.floor(1.3 * accuracy)

# Hardcoded in Pokemon#boost
makeAbility "Contrary"

makeAbility "Cursed Body", ->
  this::isAliveCheck = -> true

  this::afterBeingHit = (move, user, target, damage, isDirect) ->
    return  if !isDirect
    return  if user == target
    return  if user.has(Attachment.Substitute)
    return  if @battle.rng.next("cursed body") >= .3
    return  if user.has(Attachment.Disable)
    @pokemon.activateAbility()
    user.attach(Attachment.Disable, {move})

# Implementation is done in moves.coffee, specifically makeExplosionMove.
makeAbility 'Damp'

makeAbility 'Defeatist', ->
  this::modifyAttack = ->
    halfHP = (@pokemon.stat('hp') >> 1)
    if @pokemon.currentHP <= halfHP then 0x800 else 0x1000

makeAbility 'Defiant', ->
  this::afterEachBoost = (boostAmount, source) ->
    return  if source.team == @pokemon.team
    @pokemon.activateAbility()
    @pokemon.boost(attack: 2)  if boostAmount < 0

makeAbility 'Download', ->
  this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    return  if opponents.length == 0
    totalDef = opponents.reduce(((s, p) -> s + p.stat('defense')), 0)
    totalSpDef = opponents.reduce(((s, p) -> s + p.stat('specialDefense')), 0)
    @pokemon.activateAbility()
    if totalSpDef <= totalDef
      @pokemon.boost(specialAttack: 1)
    else
      @pokemon.boost(attack: 1)

makeAbility 'Dry Skin', ->
  this::modifyBasePowerTarget = (move, user) ->
    if move.getType(@battle, user, @pokemon) == 'Fire' then 0x1400 else 0x1000

  this::endTurn = ->
    amount = (@pokemon.stat('hp') >> 3)
    if @battle.hasWeather(Weather.SUN)
      @pokemon.activateAbility()
      @pokemon.damage(amount)
    else if @battle.hasWeather(Weather.RAIN)
      @pokemon.activateAbility()
      @pokemon.heal(amount)

  this::shouldBlockExecution = (move, user) ->
    return  if move.getType(@battle, user, @pokemon) != 'Water' || user == @pokemon
    @pokemon.activateAbility()
    @pokemon.heal((@pokemon.stat('hp') >> 2))
    return true

# Implementation is in Attachment.Sleep
makeAbility 'Early Bird'

makeAbility 'Effect Spore', ->
  this::isAliveCheck = -> true

  this::afterBeingHit = (move, user, target, damage) ->
    return  unless move.hasFlag("contact")
    switch @battle.rng.randInt(1, 10, "effect spore")
      when 1
        if user.attach(Status.Sleep)
          @pokemon.activateAbility()
      when 2
        if user.attach(Status.Paralyze)
          @pokemon.activateAbility()
      when 3
        if user.attach(Status.Poison)
          @pokemon.activateAbility()

makeAbility 'Flash Fire', ->
  this::shouldBlockExecution = (move, user) ->
    return  if move.getType(@battle, user, @pokemon) != 'Fire' || user == @pokemon
    if @pokemon.attach(Attachment.FlashFire)
      @pokemon.activateAbility()
      @battle.cannedText('FLASH_FIRE', @pokemon)
    else
      @battle.cannedText('IMMUNITY', @pokemon)
    return true

makeAbility 'Forecast'

makeAbility 'Forewarn', ->
  VariablePowerMoves =
    'Crush Grip'   : true
    'Dragon Rage'  : true
    'Endeavor'     : true
    'Flail'        : true
    'Frustration'  : true
    'Grass Knot'   : true
    'Gyro Ball'    : true
    'SonicBoom'    : true
    'Hidden Power' : true
    'Low Kick'     : true
    'Natural Gift' : true
    'Night Shade'  : true
    'Psywave'      : true
    'Return'       : true
    'Reversal'     : true
    'Seismic Toss' : true
    'Trump Card'   : true
    'Wring Out'    : true

  CounterMoves =
    "Counter"     : true
    "Mirror Coat" : true
    "Metal Burst" : true

  @consider = consider = (move) ->
    if move.hasFlag('ohko')
      160
    else if CounterMoves[move.name]
      120
    else if VariablePowerMoves[move.name]
      80
    else
      move.power

  this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    return  if opponents.length == 0
    moves = _(opponent.moves  for opponent in opponents).flatten()
    maxPower = Math.max(moves.map((m) -> consider(m))...)
    possibles = moves.filter((m) -> consider(m) == maxPower)
    finalMove = @battle.rng.choice(possibles, "forewarn")
    pokemon = _(opponents).find((p) -> finalMove in p.moves)
    @pokemon.activateAbility()
    @battle.cannedText('FOREWARN', pokemon, finalMove)

makeAbility 'Friend Guard', ->
  this::modifyDamageTarget = (move, user) ->
    return 0xC00  if user.team == @pokemon.team
    return 0x1000

makeAbility "Frisk", ->
  this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    return  if opponents.length == 0
    # TODO: Do you select from opponents with items, or all alive opponents?
    opponent  = @battle.rng.choice(opponents, "frisk")
    if opponent.hasItem()
      @pokemon.activateAbility()
      item = opponent.getItem()
      @battle.cannedText('FRISK', @pokemon, item)

# Implemented in items.coffee; makePinchBerry
makeAbility "Gluttony"

makeAbility "Guts", ->
  this::modifyAttack = (move, target) ->
    return 0x1800  if @pokemon.hasStatus() && move.isPhysical()
    return 0x1000

makeAbility 'Harvest', ->
  this::endTurn = ->
    return  unless @pokemon.lastItem?.type == 'berries'
    shouldHarvest = @battle.hasWeather(Weather.SUN)
    shouldHarvest ||= @battle.rng.randInt(0, 1, "harvest") == 1
    if shouldHarvest
      @pokemon.activateAbility()
      @battle.cannedText('HARVEST', @pokemon, @pokemon.lastItem)
      @pokemon.setItem(@pokemon.lastItem, clearLastItem: true)

makeAbility 'Healer', ->
  this::endTurn = ->
    for adjacent in @pokemon.team.getAdjacent(@pokemon)
      if @battle.rng.randInt(1, 10, "healer") <= 3
        @pokemon.activateAbility()
        adjacent.cureStatus()

makeAbility 'Heatproof', ->
  this::modifyBasePowerTarget = (move, user) ->
    return 0x800  if move.getType(@battle, user, @pokemon) == 'Fire'
    return 0x1000

makeAbility 'Heavy Metal', ->
  this::calculateWeight = (weight) ->
    2 * weight

makeAbility 'Honey Gather'

makeAbility 'Hustle', ->
  this::modifyAttack = (move, target) ->
    return 0x1800  if move.isPhysical()
    return 0x1000

  this::editAccuracy = (accuracy, move) ->
    return Math.floor(0.8 * accuracy)  if move.isPhysical()
    return accuracy

makeAbility "Hydration", ->
  this::endTurn = ->
    if @battle.hasWeather(Weather.RAIN) && @pokemon.hasStatus()
      @pokemon.activateAbility()
      @pokemon.cureStatus()

makeAbility 'Ice Body', ->
  this::endTurn = ->
    if @battle.hasWeather(Weather.HAIL)
      @pokemon.activateAbility()
      amount = @pokemon.stat('hp') >> 4
      @pokemon.heal(amount)

  this::isWeatherDamageImmune = (weather) ->
    return true  if weather == Weather.HAIL

makeAbility 'Illuminate'

makeAbility 'Imposter', ->
  this::switchIn = ->
    opponents = @battle.getAllOpponents(@pokemon)
    index = @team.indexOf(@pokemon)
    opponent = opponents[index]
    return  if !opponent
    return  if opponent.isFainted() || opponent.has(Attachment.Substitute)
    @pokemon.attach(Attachment.Transform, target: opponent)

# Hardcoded in Move#isDirectHit
# Hardcoded in Attachment.Reflect and Attachment.LightScreen
makeAbility 'Infiltrator'

makeAbility 'Intimidate', ->
  this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    for opponent in opponents
      unless opponent.has(Attachment.Substitute)
        @pokemon.activateAbility()
        opponent.boost(attack: -1, @pokemon)

makeAbility 'Iron Fist', ->
  this::modifyBasePower = (move) ->
    if move.hasFlag('punch') then 0x1333 else 0x1000

makeAbility 'Justified', ->
  this::afterBeingHit = (move, user, target, damage, isDirect) ->
    if !move.isNonDamaging() && move.getType(@battle, user, @pokemon) == 'Dark' && isDirect
      @pokemon.activateAbility()
      @pokemon.boost(attack: 1)

makeAbility 'Klutz', ->
  this::beginTurn = this::switchIn = ->
    @pokemon.blockItem()

makeAbility 'Leaf Guard', ->
  this::shouldAttach = (attachment) ->
    if attachment.status && @battle.hasWeather(Weather.SUN)
      @pokemon.activateAbility()
      return false
    return true

makeAbility 'Levitate', ->
  this::isImmune = (type) ->
    return true  if type == 'Ground'

makeAbility 'Light Metal', ->
  this::calculateWeight = (weight) ->
    weight >> 1

# Implemented in Pokemon#drain
makeAbility 'Liquid Ooze'

makeAbility 'Magic Bounce', ->
  this::beginTurn = this::switchIn = ->
    @pokemon.attach(Attachment.MagicCoat)
    @team.attach(Attachment.MagicCoat)

makeAbility 'Magic Guard', ->
  this::transformHealthChange = (damage, options) ->
    switch options.source
      when 'move' then return damage
      else return 0

makeAbility 'Magnet Pull', ->
  this::beginTurn = this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    opponents = opponents.filter((p) -> p.hasType("Steel"))
    opponent.blockSwitch()  for opponent in opponents

makeAbility 'Marvel Scale', ->
  this::editDefense = (defense) ->
    if @pokemon.hasStatus() then Math.floor(1.5 * defense) else defense

makeAbility 'Minus', ->
  this::modifyAttack = (move, target) ->
    allies = @team.getActiveAlivePokemon()
    if move.isSpecial() && allies.some((p) -> p.has(Ability.Plus))
      0x1800
    else
      0x1000

makeAbility 'Moody', ->
  allBoosts = [ "attack", "defense", "speed", "specialAttack",
                "specialDefense", "accuracy", "evasion" ]
  this::endTurn = ->
    possibleRaises = allBoosts.filter (stat) =>
      @pokemon.stages[stat] < 6
    raiseStat = @battle.rng.choice(possibleRaises, "moody raise")

    possibleLowers = allBoosts.filter (stat) =>
      @pokemon.stages[stat] > -6 && stat != raiseStat
    lowerStat = @battle.rng.choice(possibleLowers, "moody lower")

    boosts = {}
    boosts[raiseStat] = 2   if raiseStat
    boosts[lowerStat] = -1  if lowerStat
    @pokemon.activateAbility()
    @pokemon.boost(boosts)

makeAbility 'Moxie', ->
  this::afterSuccessfulHit = (move, user, target) ->
    if target.isFainted()
      @pokemon.activateAbility()
      @pokemon.boost(attack: 1)

makeAbility 'Multiscale', ->
  this::modifyDamageTarget = ->
    return 0x800  if @pokemon.currentHP == @pokemon.stat('hp')
    return 0x1000

makeAbility 'Multitype'

makeAbility 'Mummy', ->
  this::isAliveCheck = -> true

  this::afterBeingHit = (move, user) ->
    if move.hasFlag("contact") && user.hasChangeableAbility() && !user.hasAbility("Mummy")
      @pokemon.activateAbility()
      user.copyAbility(@constructor)
      @battle.cannedText('MUMMY', user)

makeAbility 'Natural Cure', ->
  this::switchOut = ->
    @pokemon.cureStatus(message: false)

# Hardcoded in Move#willMiss
makeAbility 'No Guard'

makeAbility 'Normalize', ->
  this::editMoveType = (type, target) ->
    return "Normal"  if @pokemon != target
    return type

makeAbility 'Overcoat', ->
  this::isWeatherDamageImmune = -> true

makeAbility 'Pickpocket', ->
  this::afterBeingHit = (move, user, target, damage) ->
    return  if !move.hasFlag("contact") || target.hasItem() || !user.canLoseItem()
    @pokemon.activateAbility()
    @battle.cannedText('PICKPOCKET', target, user, user.item)
    target.setItem(user.item)
    user.removeItem()

makeAbility 'Plus', ->
  this::modifyAttack = (move, target) ->
    allies = @team.getActiveAlivePokemon()
    if move.isSpecial() && allies.some((p) -> p.has(Ability.Minus))
      0x1800
    else
      0x1000

makeAbility 'Poison Heal', ->
  # Poison damage neutralization is hardcoded in Attachment.Poison and Toxic.
  this::endTurn = ->
    # Return early so that:
    # 1. We don't trigger ability activation if the pokemon won't be healed.
    # 2. Ability activation must happen before HP animation.
    return  if @pokemon.currentHP == @pokemon.stat('hp')
    if @pokemon.has(Status.Poison) || @pokemon.has(Status.Toxic)
      @pokemon.activateAbility()
      amount = @pokemon.stat('hp') >> 3
      @pokemon.heal(amount)

makeAbility 'Prankster', ->
  this::editPriority = (priority, move) ->
    return priority + 1  if move.isNonDamaging()
    return priority

# PP deduction hardcoded in Battle
makeAbility 'Pressure', ->
  this::switchIn = ->
    @pokemon.activateAbility()
    @battle.cannedText('PRESSURE', @pokemon)

# Speed drop negation hardcoded into Attachment.Paralyze
makeAbility 'Quick Feet', ->
  this::editSpeed = (speed) ->
    if @pokemon.hasStatus() then Math.floor(1.5 * speed) else speed

makeAbility 'Rain Dish', ->
  this::endTurn = ->
    return  unless @battle.hasWeather(Weather.RAIN)
    @pokemon.activateAbility()
    amount = @pokemon.stat('hp') >> 4
    @pokemon.heal(amount)

makeAbility 'Rattled', ->
  this::afterBeingHit = (move, user, target, damage, isDirect) ->
    type = move.getType(@battle, user, @pokemon)
    if type in [ "Bug", "Ghost", "Dark" ] && !move.isNonDamaging() && isDirect
      @pokemon.activateAbility()
      @pokemon.boost(speed: 1)

makeAbility 'Reckless', ->
  this::modifyBasePower = (move, target) ->
    kickMoves = [ @battle.getMove("Jump Kick"), @battle.getMove("Hi Jump Kick")]
    if move.recoil < 0 || move in kickMoves
      0x1333
    else
      0x1000

makeAbility 'Rivalry', ->
  this::modifyBasePower = (move, target) ->
    return 0x1400  if @pokemon.gender == target.gender
    return 0xC00   if (@pokemon.gender == 'F' && target.gender == 'M') ||
                      (@pokemon.gender == 'M' && target.gender == 'F')
    return 0x1000

makeAbility 'Regenerator', ->
  this::switchOut = ->
    amount = Math.floor(@pokemon.stat('hp') / 3)
    # Uses setHP directly to bypass Heal Block's effect
    @pokemon.setHP(@pokemon.currentHP + amount)

# Hardcoded in move.coffee
makeAbility 'Rock Head'

makeAbility 'Run Away'

makeAbility 'Sand Force', ->
  this::modifyBasePower = (move, target) ->
    return 0x1000  unless @battle.hasWeather(Weather.SAND)
    type = move.getType(@battle, @pokemon, target)
    return 0x14CD  if type in ['Rock', 'Ground', 'Steel']
    return 0x1000

  this::isWeatherDamageImmune = (weather) ->
    return true  if weather == Weather.SAND

makeAbility 'Sand Veil', ->
  this::editEvasion = (accuracy) ->
    if @battle.hasWeather(Weather.SAND)
      Math.floor(.8 * accuracy)
    else
      accuracy

  this::isWeatherDamageImmune = (weather) ->
    return true  if weather == Weather.SAND

makeAbility 'Scrappy', ->
  this::shouldIgnoreImmunity = (moveType, target) ->
    return target.hasType('Ghost') && moveType in [ 'Normal', 'Fighting' ]

# Hardcoded in server/bw/data/moves
makeAbility 'Serene Grace'

makeAbility 'Shadow Tag', ->
  this::beginTurn = this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    for opponent in opponents
      opponent.blockSwitch()  unless opponent.hasAbility('Shadow Tag')

makeAbility 'Shed Skin', ->
  this::endTurn = ->
    return  unless @pokemon.hasStatus()
    if @battle.rng.randInt(1, 10, "shed skin") <= 3
      @pokemon.cureStatus()

makeAbility 'Sheer Force', ->
  this::modifyBasePower = (move, target) ->
    return 0x14CD  if move.hasSecondaryEffect()
    return 0x1000

# Hardcoded in Move#shouldTriggerSecondary
makeAbility 'Shield Dust'

makeAbility 'Simple', ->
  this::transformBoosts = (boosts) ->
    newBoosts = {}
    for stat, boost of boosts
      newBoosts[stat] = 2 * boost
    newBoosts

makeAbility "Skill Link", ->
  this::calculateNumberOfHits = (move, targets) ->
    move.maxHits

makeAbility 'Slow Start', ->
  this::initialize = ->
    @turns = 5

  this::switchIn = ->
    @pokemon.activateAbility()
    @battle.cannedText('SLOW_START_START', @pokemon)

  this::endTurn = ->
    @turns -= 1
    if @turns == 0
      @battle.cannedText('SLOW_START_END', @pokemon)

  this::modifyAttack = (move, target) ->
    return 0x800  if move.isPhysical() && @turns > 0
    return 0x1000

  this::editSpeed = (speed) ->
    return speed >> 1  if @turns > 0
    return speed

makeAbility 'Sniper', ->
  this::modifyDamage = (move, target) ->
    return 0x1800  if @pokemon.crit
    return 0x1000

makeAbility 'Snow Cloak', ->
  this::editEvasion = (accuracy) ->
    if @battle.hasWeather(Weather.HAIL)
      Math.floor(.8 * accuracy)
    else
      accuracy

  this::isWeatherDamageImmune = (weather) ->
    return true  if weather == Weather.HAIL

makeAbility 'Solar Power', ->
  this::modifyAttack = (move, target) ->
    return 0x1800  if move.isSpecial() && @battle.hasWeather(Weather.SUN)
    return 0x1000

  this::endTurn = ->
    if @battle.hasWeather(Weather.SUN)
      amount = (@pokemon.stat('hp') >> 3)
      @pokemon.activateAbility()
      @pokemon.damage(amount)
      @battle.cannedText('POKEMON_HURT', @pokemon)

makeAbility 'Soundproof', ->
  this::isImmune = (type, move) ->
    return true  if move?.hasFlag('sound')

makeAbility 'Speed Boost', ->
  this::endTurn = ->
    return  if @pokemon.turnsActive <= 0
    @pokemon.boost(speed: 1)

makeAbility 'Stall', ->
  this::afterTurnOrder = ->
    @battle.delay(@pokemon)

# Hardcoded in Attachment.Flinch
makeAbility 'Steadfast'

# Hardcoded in Pokemon#canLoseItem
makeAbility 'Sticky Hold'

makeAbility 'Sturdy', ->
  this::transformHealthChange = (amount, options) ->
    if @pokemon.currentHP == @pokemon.stat('hp')
      if amount >= @pokemon.currentHP && options.source == 'move'
        @pokemon.activateAbility()
        @battle.cannedText('ENDURE', @pokemon)
        return @pokemon.currentHP - 1
    return amount

makeAbility 'Suction Cups', ->
  this::shouldPhase = (phaser) ->
    @pokemon.activateAbility()
    @battle.cannedText('ANCHOR', @pokemon)
    return false

# Hardcoded in Move#criticalHitLevel
makeAbility 'Super Luck'

# Hardcoded in status.coffee
makeAbility 'Synchronize'

makeAbility 'Tangled Feet', ->
  this::editEvasion = (evasion) ->
    if @pokemon.has(Attachment.Confusion) then evasion >> 1 else evasion

makeAbility 'Technician', ->
  this::modifyBasePower = (move, target) ->
    return 0x1800  if move.basePower(@battle, @pokemon, target) <= 60
    return 0x1000

makeAbility 'Telepathy', ->
  this::shouldBlockExecution = (move, user) ->
    return  if move.isNonDamaging() || user == @pokemon
    return  if user not in @team.pokemon
    @pokemon.activateAbility()
    @battle.cannedText('AVOID_ALLIES', @pokemon)
    return true

makeAbility 'Thick Fat', ->
  this::modifyAttackTarget = (move, user) ->
    return 0x800  if move.getType(@battle, user, @pokemon) in [ 'Fire', 'Ice' ]
    return 0x1000

makeAbility 'Tinted Lens', ->
  this::modifyDamage = (move, target) ->
    return 0x2000  if move.typeEffectiveness(@battle, @pokemon, target) < 1
    return 0x1000

makeAbility 'Trace', ->
  bannedAbilities =
    "Flower Gift" : true
    "Forecast"    : true
    "Illusion"    : true
    "Imposter"    : true
    "Multitype"   : true
    "Trace"       : true
    "Zen Mode"    : true

  this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    abilities = _(opponent.ability  for opponent in opponents).compact()
    abilities = abilities.filter((a) -> a.displayName not of bannedAbilities)
    return  if abilities.length == 0
    ability = @battle.rng.choice(abilities, "trace")
    # TODO: Display whose ability it traced.
    shouldshow = {reveal: true}
    shouldshow.reveal = false if @pokemon.has(Attachment.Illusion)
    @pokemon.copyAbility(ability, shouldshow)
    @battle.cannedText('TRACE', ability) if !@pokemon.has(Attachment.Illusion)

makeAbility 'Truant', ->
  this::initialize = ->
    @truanted = true

  this::beforeMove = ->
    @truanted = !@truanted
    if @truanted
      @pokemon.activateAbility()
      @battle.cannedText('TRUANT', @pokemon)
      return false

# Hardcoded in Move
makeAbility "Unaware"

# Hardcoded in Pokemon#removeItem
makeAbility 'Unburden'

makeAbility 'Unnerve', ->
  this::beginTurn = this::switchIn = ->
    opponents = @battle.getOpponents(@pokemon)
    # TODO: Unnerve likely doesn't last until the end of the turn.
    # More research is needed here.
    for opponent in opponents
      opponent.blockItem()  if opponent.item?.type == 'berries'

makeAbility 'Victory Star', ->
  this::editAccuracy = (accuracy) ->
    Math.floor(accuracy * 1.1)

makeAbility 'Weak Armor', ->
  this::afterBeingHit = (move, user) ->
    if move.isPhysical() then @pokemon.boost(defense: -1, speed: 1)

makeAbility 'Wonder Guard', ->
  this::shouldBlockExecution = (move, user) ->
    return  if move == @battle.getMove("Struggle")
    return  if move.isNonDamaging() || user == @pokemon
    return  if move.typeEffectiveness(@battle, user, @pokemon) > 1
    @pokemon.activateAbility()
    return true

# Hardcoded in Move#chanceToHit
makeAbility 'Wonder Skin'
