-- Kurt's balls and the specialty multipliers
-- (engine/items/item_effects.asm BallMultiplierFunctionTable).
--
--   luajit tests/gen2_specialty_balls_test.lua
--
-- ROM-free.  Heavy Ball's dex-weight brackets (Lugia's 4760 tenths-lb turns
-- catch rate 3 into 23, the walkthrough's whole reason to brew one), Level
-- Ball's three rungs, Lure Ball's fishing gate, and the three deliberate
-- cart bugs: Fast Ball knows exactly three species, Love Ball boosts
-- SAME-sex pairs, Moon Ball never boosts.  Friend Ball is happiness only,
-- proven through the real catch in BattleState.

package.path = "./?.lua;./?/init.lua;" .. package.path

local S = require("tests.harness").suite("gen2 specialty balls")
local check, eq = S.check, S.eq

love = require("tests.love_stub")

local Battle = require("src.battle.gen2.Battle")
local BattleState = require("src.ui.gen2.BattleState")
local Catching = require("src.battle.gen2.Catching")
local Input = require("src.core.Input")
local Mon = require("src.battle.gen2.Mon")

-- ---- Heavy Ball -----------------------------------------------------------
do
  -- Lugia: dex weight 4760 (tenths of a pound) converts to 2158 tenths-kg
  -- through the cart's w/2 - w/32 - w/64 walk; high byte 8 is the +20 rung.
  eq(Catching.heavyBallBoost(4760), 20, "Lugia sits in the +20 bracket")
  eq(Catching.specialtyRate(3, "HEAVY_BALL", { weight = 4760 }), 23,
    "catch rate 3 becomes 23 -- against the Ultra Ball's 6")
  -- A light mon LOSES 20, floored at 1 (`ld b, $1` on underflow).
  eq(Catching.heavyBallBoost(40), -20, "a 4 lb mon is in the -20 bracket")
  eq(Catching.specialtyRate(3, "HEAVY_BALL", { weight = 40 }), 1,
    "and the subtraction floors at 1")
  -- Snorlax: 10140 tenths-lb -> 4599 tenths-kg, high byte 17: +40.
  eq(Catching.heavyBallBoost(10140), 40, "Snorlax earns the +40 rung")
  eq(Catching.specialtyRate(25, "HEAVY_BALL", {}), 25,
    "no weight supplied, no change")
end

-- ---- Level Ball -----------------------------------------------------------
do
  local function level(rate, player, enemy)
    return Catching.specialtyRate(rate, "LEVEL_BALL",
      { playerLevel = player, level = enemy })
  end
  eq(level(30, 10, 10), 30, "an equal level is no boost")
  eq(level(30, 20, 15), 60, "below the player's level: x2")
  eq(level(30, 40, 15), 120, "below half: x4")
  eq(level(30, 44, 10), 240, "below a quarter: x8")
  eq(level(30, 40, 10), 120,
    "the rungs are strict: 10 is NOT below floor(40/4)")
  eq(level(200, 44, 10), 255, "capped at 255 like every sla")
end

-- ---- Lure Ball ------------------------------------------------------------
do
  eq(Catching.specialtyRate(45, "LURE_BALL", { fishing = true }), 135,
    "x3 on a BATTLETYPE_FISH encounter")
  eq(Catching.specialtyRate(45, "LURE_BALL", {}), 45,
    "and nothing anywhere else")
end

-- ---- Fast Ball's bug ------------------------------------------------------
do
  for species in pairs({ MAGNEMITE = 1, GRIMER = 1, TANGELA = 1 }) do
    eq(Catching.specialtyRate(45, "FAST_BALL", { species = species }), 180,
      species .. " is one of the three the bug leaves covered")
  end
  eq(Catching.specialtyRate(45, "FAST_BALL", { species = "DRATINI" }), 45,
    "DRATINI flees on the cart too, but the broken loop never sees it")
end

-- ---- Moon Ball's bug ------------------------------------------------------
do
  eq(Catching.specialtyRate(45, "MOON_BALL",
    { evolveItem = "MOON_STONE" }), 45,
    "a Moon Stone evolver gets nothing: the check reads Gen 1's constant")
  eq(Catching.specialtyRate(45, "MOON_BALL",
    { evolveItem = "MOON_STONE", fixBugs = true }), 180,
    "fixBugs restores the intended x4")
end

-- ---- Love Ball's bug ------------------------------------------------------
do
  local function love_(gender, playerGender, fix)
    return Catching.specialtyRate(30, "LOVE_BALL", {
      species = "NIDORAN_F", playerSpecies = "NIDORAN_F",
      gender = gender, playerGender = playerGender, fixBugs = fix,
    })
  end
  eq(love_("female", "female"), 240,
    "same species, SAME sex: the x8 the `ret nz` bug ships")
  eq(love_("female", "male"), 30, "opposite sexes get nothing")
  eq(love_("female", "male", true), 240, "fixBugs flips it back")
  eq(Catching.specialtyRate(30, "LOVE_BALL", {
    species = "NIDORAN_F", playerSpecies = "PIDGEY",
    gender = "female", playerGender = "female" }), 30,
    "different species never boost")
end

-- ---- through Catching.rate ------------------------------------------------
do
  -- Full HP Lugia at rate 3: (3*maxHp - 2*hp) with the >=256 shift path,
  -- both balls through the same formula; the Heavy Ball's 23 must beat the
  -- Ultra Ball's 6 on the final rate too.
  local heavy = Catching.rate({ maxHp = 200, hp = 200, catchRate = 3,
    ball = "HEAVY_BALL", weight = 4760 })
  local ultra = Catching.rate({ maxHp = 200, hp = 200, catchRate = 3,
    ball = "ULTRA_BALL" })
  check(heavy > ultra,
    ("the brewed ball out-catches the bought one (%d > %d)"):format(
      heavy, ultra))
end

-- ---- Friend Ball happiness through the real catch -------------------------
do
  local TYPES = { NORMAL = { id = "NORMAL", index = 0,
    category = "physical" } }
  local MOVES = { TACKLE = { id = "TACKLE", name = "TACKLE", power = 35,
    type = "NORMAL", accuracy = 95, pp = 35,
    effect = "EFFECT_NORMAL_HIT" } }
  local POKEMON = {
    growthRates = { GROWTH_MEDIUM_FAST = { numerator = 1, denominator = 1,
      squared = 0, linear = 0, constant = 0 } },
    CYNDAQUIL = {
      id = "CYNDAQUIL", index = 155, name = "CYNDAQUIL",
      baseStats = { hp = 39, attack = 52, defense = 43, speed = 65,
        specialAttack = 60, specialDefense = 50 },
      types = { "NORMAL", "NORMAL" }, catchRate = 45, baseExp = 65,
      growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 31,
      levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
    },
    PIDGEY = {
      id = "PIDGEY", index = 16, name = "PIDGEY",
      baseStats = { hp = 40, attack = 45, defense = 40, speed = 56,
        specialAttack = 35, specialDefense = 35 },
      types = { "NORMAL", "NORMAL" }, catchRate = 255, baseExp = 55,
      growthRate = "GROWTH_MEDIUM_FAST", genderRatio = 127,
      levelMoves = { { level = 1, move = "TACKLE" } }, evolutions = {},
    },
  }
  local DATA = {
    pokemon = POKEMON,
    moves = MOVES,
    type_chart = { types = TYPES, matchups = {} },
    items = {
      FRIEND_BALL = { id = "FRIEND_BALL", name = "FRIEND BALL",
        pocket = "BALL" },
      POKE_BALL = { id = "POKE_BALL", name = "POKe BALL", pocket = "BALL" },
    },
  }
  local perfect = { attack = 15, defense = 15, speed = 15, special = 15 }
  perfect.hp = Mon.hpDV(perfect)

  local function catchWith(ball)
    Input:init()
    local player = Mon.new(DATA, "CYNDAQUIL", 10, { dvs = perfect })
    player.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
    local wild = Mon.new(DATA, "PIDGEY", 5, { dvs = perfect })
    wild.moves = { { id = "TACKLE", pp = 35, maxPp = 35 } }
    local save = { party = { player }, inventory = { [ball] = 1 } }
    local game = {
      data = DATA, save = save, input = Input, options = {},
      stack = { push = function() end, pop = function() end,
        top = function() return nil end },
    }
    -- random 0: the catch roll always lands (rate 255 PIDGEY).
    local battle = Battle.new({ data = DATA, party = { player },
      wild = wild, save = save, random = function() return 0 end })
    local screen = BattleState.new(game, { battle = battle, save = save })
    screen:useItem(ball)
    return battle, save, wild
  end

  local battle, save, wild = catchWith("FRIEND_BALL")
  eq(battle.outcome, "caught", "the Friend Ball catch lands")
  eq(wild.happiness, Catching.FRIEND_BALL_HAPPINESS,
    "and the caught mon's happiness is set to 200")
  eq(save.party[2], wild, "in the party slot it landed in")

  battle, save, wild = catchWith("POKE_BALL")
  eq(battle.outcome, "caught", "the control catch lands too")
  eq(wild.happiness, 70, "an ordinary ball leaves the base 70")
end

S.finish()
