-- BATTLE LAYOUT = WIDE, screen shakes (#562).
--
-- A battle sets rWY to 0 (engine/battle/core.asm), so the window
-- PredefShakeScreenVertically / PredefShakeScreenHorizontally /
-- AnimationShakeScreenHorizontallySlow displace IS the whole screen: pics,
-- both HUDs and the message window move together, which is what
-- BattleState:drawClassic does by offsetting its whole BG canvas.  The wide
-- composition used to apply fx.shakeX/shakeY to the two pic regions only, so
-- an applying-attack shake slid the monsters across a nailed-down HUD and
-- text box -- TAIL WHIP's slow 3px creep (wAnimationType 6) being the case
-- the report named.
--
-- Headless: WideBattle.draw is played into a recording love.graphics and the
-- Font / HudTiles entry points, so what is asserted is where each piece of
-- the composition landed, not pixels.
package.path = "./?.lua;./?/init.lua;" .. package.path

local T = require("tests.modkit")
local WideBattle = require("src.battle.WideBattle")
local Font = require("src.render.Font")
local HudTiles = require("src.render.HudTiles")

-- ---------------------------------------------------------------- recorder
-- The stub's push/pop keep no transform, so the translate stack is tracked
-- here: `tx, ty` is the offset in force when a draw call is issued.  Font and
-- HudTiles are patched in place (WideBattle captured these very tables at
-- require time, so swapping package.loaded would not reach it).
local g = love.graphics
local realPush, realPop = g.push, g.pop
local tx, ty, stack = 0, 0, {}
local scissors, marks

g.push = function(...) stack[#stack + 1] = { tx, ty }; realPush(...) end
g.pop = function()
  local s = table.remove(stack)
  if s then tx, ty = s[1], s[2] end
  realPop()
end
g.translate = function(dx, dy) tx, ty = tx + (dx or 0), ty + (dy or 0) end
-- restoreScissor calls setScissor() with no arguments to clear; only a real
-- rect is a region being opened
g.setScissor = function(x, y, w, h)
  if x then scissors[#scissors + 1] = { x = x, y = y, w = w, h = h } end
end

local function mark(name) marks[#marks + 1] = { name = name, tx = tx, ty = ty } end
Font.drawBox = function() mark("box") end
Font.draw = function() mark("text") end
Font.drawCode = function() mark("code") end
Font.split = function() return {} end
Font.spansFitting = function() return math.huge end
HudTiles.tile = function() mark("tile") end
HudTiles.drawHPBar = function() mark("hpbar") end

-- A battle stripped to what the composition reads.  drawPicsLayer and
-- drawAnimLayer only record the offset they were handed, which is the whole
-- question: are the two pic regions and everything else moving as one?
local function battleWith(fx, sprites)
  return {
    data = {},
    frame = 0,
    phase = "messages",
    current = {},
    shown = {},
    fx = fx,
    enemy = { name = "GENGAR", shownHP = 60,
              mon = { level = 25, stats = { hp = 60 } } },
    player = { name = "NIDORINO", shownHP = 55,
               mon = { level = 24, stats = { hp = 55 } } },
    animPlaying = sprites ~= nil,
    animPlayer = sprites and { stepIndex = 1, steps = { { sprites = sprites } } },
    drawPicsLayer = function() mark("pics") end,
    drawAnimLayer = function() mark("anim") end,
    growInScale = function() return nil end,
    drawBallRow = function() end,
    statusLabel = function() return "" end,
    statusHUDVisible = function() return true end,
    bottomUIVisible = function() return true end,
    caughtMarkerVisible = function() return false end,
    extendedHUD = function() return false end,
  }
end

local function draw(fx, sprites)
  tx, ty, stack = 0, 0, {}
  scissors, marks = {}, {}
  WideBattle.draw(battleWith(fx, sprites))
  return scissors, marks
end

local function marksNamed(list, name)
  local out = {}
  for _, m in ipairs(list) do if m.name == name then out[#out + 1] = m end end
  return out
end

-- Every piece of the composition drawn in wide-surface coordinates sits at
-- (dx, dy).  The two pic passes and the anim layer carry their own anchor on
-- top of the shake and are checked against it by hand.
local SELF_ANCHORED = { pics = true, anim = true }
local function everythingAt(list, dx, dy, label)
  local seen = 0
  for _, m in ipairs(list) do
    if not SELF_ANCHORED[m.name] then
      seen = seen + 1
      if m.tx ~= dx or m.ty ~= dy then
        T.check(false, ("%s: %s drew at (%d, %d), wanted (%d, %d)")
                         :format(label, m.name, m.tx, m.ty, dx, dy))
        return
      end
    end
  end
  T.check(seen >= 6,
    label .. ": the HUDs, the status bars and the message box all drew")
  T.check(true, ("%s: all %d pieces moved to (%d, %d) together")
                  :format(label, seen, dx, dy))
end

-- ------------------------------------------------------------ still screen
-- The baseline the shaken frames are measured against: the player's 160x144
-- region is inset 32px from the top of the wider field, the enemy's starts at
-- x=160, and both draw their classic-space pics at the wide anchors.
local s, m = draw(nil)
T.eq(s[1].x, 0, "unshaken, the player region clips from the left edge")
T.eq(s[1].y, 32, "unshaken, the player region starts below the foe's HUD")
T.eq(s[2].x, 160, "unshaken, the enemy region clips from the halfway mark")
T.eq(s[2].y, 0, "unshaken, the enemy region starts at the top")
local pics = marksNamed(m, "pics")
T.eq(#pics, 2, "one pics pass per side")
T.eq(pics[1].tx, 20, "unshaken, the player pic sits on the player anchor")
T.eq(pics[1].ty, 8, "unshaken, the player pic sits on the player baseline")
T.eq(pics[2].tx, 136, "unshaken, the enemy pic sits on the enemy anchor")
everythingAt(m, 0, 0, "no shake")

-- -------------------------------------------------- horizontal shake (#562)
-- TAIL WHIP is wAnimationType 6, AnimationShakeScreenHorizontallySlow b=3:
-- the screen creeps out to 3px and back, twice, in silence.  Before the fix
-- the two pic regions took the 3px and drawHUDs / drawTextArea did not, so
-- the foe glided sideways over a stationary HUD and message box.
s, m = draw({ shakeX = 3, shakeY = 0 })
T.eq(s[1].x, 3, "a horizontal shake carries the player region's clip with it")
T.eq(s[2].x, 163, "...and the enemy region's")
pics = marksNamed(m, "pics")
T.eq(pics[1].tx, 23, "the player pic rides the shake")
T.eq(pics[2].tx, 139, "the enemy pic rides the shake")
everythingAt(m, 3, 0, "3px horizontal shake")

-- ---------------------------------------------------- vertical shake (#562)
-- wAnimationType 1 (PredefShakeScreenVertically b=8) drops the whole window.
-- The region rects move with their contents, so a pic pushed toward
-- FIELD_BOTTOM is not sheared off its own window edge on the way down.
s, m = draw({ shakeX = 0, shakeY = 8 })
T.eq(s[1].y, 40, "a vertical shake carries the player region's clip down")
T.eq(s[1].h, WideBattle.FIELD_BOTTOM - 32,
  "the player region keeps its height, so the pic is not cropped short")
T.eq(s[2].y, 8, "the enemy region's clip drops the same 8px")
pics = marksNamed(m, "pics")
T.eq(pics[1].ty, 16, "the player pic drops with it")
everythingAt(m, 0, 8, "8px vertical shake")

-- --------------------------------------------------- the animations-off path
-- With ANIMATION = OFF an extracted anim that carries a shake flag falls back
-- to fx.shake, a +-2 alternation read off battle.frame; it moves the screen
-- through the same path.
s, m = draw({ shake = 24 })
T.eq(s[1].x, 2, "the animations-off fallback shakes the regions too")
everythingAt(m, 2, 0, "animations-off fallback")

-- ------------------------------------------------------- the OAM anim layer
-- Battle animation sprites are OAM, not BG, so the window shake never moves
-- them -- drawClassic draws its anim layer outside the offset for the same
-- reason.  Their own offset is WideBattle.animationOffset, and that stays put
-- while the screen under them shakes.
local sprites = { { x = 104 }, { x = 144 } }
local ax, ay = WideBattle.animationOffset(sprites)
s, m = draw({ shakeX = 3, shakeY = 0 }, sprites)
local anim = marksNamed(m, "anim")
T.eq(#anim, 1, "the anim layer drew")
T.eq(anim[1].tx, ax, "the anim layer keeps its own anchor, not the shake")
T.eq(anim[1].ty, ay, "...on both axes")
T.eq(s[#s].x, 0, "the anim region spans the field from the left edge")
T.eq(s[#s].w, WideBattle.WIDTH, "...the full wide surface")

T.finish("wide battle shake")
