-- Where a shiny SHOWS on the flat art: the battle pics and the status page.
--
-- The Stadium models carry genuinely recoloured texels (see ShinyPalette and
-- the extraction). Everything drawn as a Game Boy pic cannot, because the
-- engine bakes a species palette into an image cache keyed by path and
-- palette name -- a cache with no notion of WHICH Rattata is being drawn. So
-- the flat side is answered two ways:
--
--   the battle pic     tinted at draw time, per side, with the multiply
--                      ShinyPalette.tintFor derives from that species' own
--                      shiny slide. Under ADVANCED (`redpp`, the pokered-gbc
--                      colour pack) the pic is at its most colourful and the
--                      tint reads clearly; under the DMG modes there is
--                      barely any colour to shift, which is why the status
--                      page also carries a plain, mode-proof MARK.
--   the status page    a star beside the level, drawn in the engine's own
--                      GB pixel grid so it is palette-processed like every
--                      other pixel on the screen rather than floating over
--                      the finished frame.
--
-- Both are monkeypatches, idempotent by sentinel, the pattern the rest of
-- this mod uses (OverworldBattle.install, Stadium.install). The summary
-- screen has no hook at all -- there is no `ui.summary.*` anywhere in the
-- engine -- so a wrap is the only route to it, and it is deliberately a thin
-- one: draw the engine's screen, then add one glyph.

-- the mod namespace (see main.lua): V.require loads a sibling module
local V = ...

local Shiny = V.require("Shiny")
local ShinyPalette = V.require("ShinyPalette")

local ShinyUI = {}

-- ------- the tint, applied to one draw
--
-- Lifted in shape from OverworldBattle.withTint, and for the same reason it
-- exists there: the pics layer sets its own colour many times over as it
-- draws (the faint slide's fade, the damage blink), so the way to tint the
-- result without clobbering any of that is to multiply every colour it sets
-- on its way past. Restored unconditionally, including on error, because a
-- leaked setColor would tint the entire rest of the frame.
function ShinyUI.withTint(tint, fn, ...)
  if not tint then return fn(...) end
  local r, g, b = tint[1] or 1, tint[2] or 1, tint[3] or 1
  if r > 0.999 and g > 0.999 and b > 0.999 then return fn(...) end
  local gfx = love.graphics
  local setColor = gfx.setColor
  gfx.setColor = function(cr, cg, cb, ca, ...)
    if type(cr) == "table" then
      return setColor({ (cr[1] or 1) * r, (cr[2] or 1) * g, (cr[3] or 1) * b,
                        cr[4] }, cg, ...)
    end
    if cr == nil then return setColor(cr, cg, cb, ca, ...) end
    return setColor(cr * r, (cg or 1) * g, (cb or 1) * b, ca, ...)
  end
  local ok, err = pcall(fn, ...)
  gfx.setColor = setColor
  setColor(1, 1, 1, 1)
  if not ok then error(err, 0) end
end

-- The tint for a mon, or nil when it is not shiny or we have no colours.
function ShinyUI.tintFor(mon, data)
  if not (mon and Shiny.isShiny(mon)) then return nil end
  local def = data and data.pokemon and data.pokemon[mon.species]
  local dex = def and def.dex
  if not dex then return nil end
  return ShinyPalette.tintFor(dex)
end

-- ------- the star
--
-- Drawn as rectangles rather than as a font character because the Game Boy
-- charmap has no star, and a letter would read as a typo. Four spokes and a
-- centre, in the same near-black the screen's text uses, so the palette pass
-- treats it exactly like a glyph -- under ADVANCED and under every DMG mode
-- alike.
--
-- Seven pixels square, which is the largest that fits the gap beside the
-- level without touching the DrawLineBox bracket at column 19.
function ShinyUI.drawStar(px, py)
  local g = love.graphics
  local r, gg, b, a = g.getColor()
  g.setColor(0, 0, 0, 1)
  -- vertical, horizontal, then the four diagonal nubs
  g.rectangle("fill", px + 3, py, 1, 7)
  g.rectangle("fill", px, py + 3, 7, 1)
  g.rectangle("fill", px + 1, py + 1, 1, 1)
  g.rectangle("fill", px + 5, py + 1, 1, 1)
  g.rectangle("fill", px + 1, py + 5, 1, 1)
  g.rectangle("fill", px + 5, py + 5, 1, 1)
  g.setColor(r, gg, b, a)
end

-- ------- install

function ShinyUI.install()
  ShinyUI.installSummary()
  ShinyUI.installBattlePics()
end

-- The status page. Wraps the draw and adds the star afterwards, so the
-- engine's own layout is untouched and a layout change upstream costs us
-- the glyph's position and nothing else.
function ShinyUI.installSummary()
  local ok, SummaryMenu = pcall(require, "src.ui.SummaryMenu")
  if not ok or type(SummaryMenu) ~= "table" then return end
  if SummaryMenu.dramaticShapeShiny then return end
  local inner = SummaryMenu.draw
  if type(inner) ~= "function" then return end

  function SummaryMenu:draw(...)
    local out = { inner(self, ...) }
    -- page 1 only: page 2 wipes the block the level sits in
    -- (status_screen.asm ClearScreenArea over (9,2)), so a mark left there
    -- would be half-erased by the engine's own clear.
    if self.page == 1 and Shiny.isShiny(self.mon) then
      -- beside PrintLevel at (14,2): column 13, row 2, in the gap the
      -- level's own leading space leaves
      pcall(ShinyUI.drawStar, 104, 17)
    end
    return unpack(out)
  end

  -- The summary PIC, through its PALETTE.
  --
  -- Recolouring the sprite's pixels here does NOT work, and it is worth
  -- writing down why rather than leaving it to be re-attempted: the summary
  -- art is four-shade DMG grey, and the screen's colour is applied
  -- afterwards by the palette pass over the finished frame. Whatever RGB is
  -- put in the ImageData is remapped away by it. The colour of that pic
  -- lives in the palette and nowhere else, so the palette is what has to
  -- move. (Tried it, shot it, reverted it.)
  --
  -- This is the ADVANCED-palette answer, and it is better than a multiply:
  -- SetPal_StatusScreen hands the pic zone the species palette
  -- (PaletteFX.monPal), and sgbPalettes is a method on the MENU, so unlike
  -- the battle pic's image cache it knows which individual is on screen.
  -- Running those four colours through the species' own shiny transform
  -- gives the summary a genuinely recoloured Pokemon -- brightening
  -- included, which a draw-colour multiply cannot do.
  --
  -- Only ZONE entries are touched. The first palette the engine returns is
  -- the whole-screen HP-bar one, and rotating that would recolour the text.
  local innerPal = SummaryMenu.sgbPalettes
  if type(innerPal) == "function" then
    function SummaryMenu:sgbPalettes(game, ...)
      local out = innerPal(self, game, ...)
      if type(out) ~= "table" or not Shiny.isShiny(self.mon) then return out end
      local def = game and game.data and game.data.pokemon
                  and game.data.pokemon[self.mon.species]
      local fn = def and def.dex
                 and ShinyPalette.transform(ShinyPalette.forDex(def.dex))
      if not fn then return out end
      for _, z in ipairs(out) do
        if type(z) == "table" and z.w and z.h and type(z.colors) == "table" then
          -- copied, never mutated in place: monPal hands back the dataset's
          -- own palette table, and writing through it would recolour every
          -- Pokemon of the species everywhere for the rest of the process
          local cols = {}
          for i, c in ipairs(z.colors) do
            if type(c) == "table" and c[1] then
              local r, g, b = fn(c[1], c[2], c[3])
              cols[i] = { r, g, b }
            else
              cols[i] = c
            end
          end
          z.colors = cols
        end
      end
      return out
    end
  end

  SummaryMenu.dramaticShapeShiny = true
end

-- The battle pics. The engine's pic layer is reached through
-- BattleState:drawPicsLayer, which draws BOTH sides in one call -- so a
-- per-side tint has to bracket each side separately, which is exactly what
-- OverworldBattle.sideTexture already does when it renders one side at a
-- time into its own canvas. That is where the tint belongs on the 3D path;
-- this wrap covers the FLAT path, where the engine draws the battle itself.
function ShinyUI.installBattlePics()
  local ok, BattleState = pcall(require, "src.battle.BattleState")
  if not ok or type(BattleState) ~= "table" then return end
  if BattleState.dramaticShapeShinyPics then return end
  local inner = BattleState.drawPicsLayer
  if type(inner) ~= "function" then return end

  function BattleState:drawPicsLayer(...)
    -- THE 3D PATH HAS ALREADY DONE THIS, per side and better: when the mod
    -- is rendering one side into its own canvas it brackets that draw with
    -- that side's own tint (OverworldBattle.sideTexture). Tinting again here
    -- would square it. Asked as a question rather than left to install
    -- order, because both wraps are installed from main.lua and whichever
    -- ran first would otherwise silently decide the outcome.
    local okOw, Ow = pcall(V.require, "OverworldBattle")
    if okOw and Ow and Ow.texturingSide and Ow.texturingSide() then
      return inner(self, ...)
    end

    -- Both sides at once, so when they disagree the tint cannot be applied
    -- per-side here without splitting the engine's own draw. When only ONE
    -- side is shiny we tint the whole layer by it: the other side's pic is
    -- dimmed slightly, which is far less wrong than a shiny drawn in its
    -- ordinary colours -- and when both are shiny each gets the mean.
    local data = self.game and self.game.data
    local a = self.player and ShinyUI.tintFor(self.player.mon, data)
    local b = self.enemy and ShinyUI.tintFor(self.enemy.mon, data)
    local tint = a or b
    if a and b then
      tint = { (a[1] + b[1]) / 2, (a[2] + b[2]) / 2, (a[3] + b[3]) / 2 }
    end
    if not tint then return inner(self, ...) end
    local args = { ... }
    local out
    ShinyUI.withTint(tint, function() out = { inner(self, unpack(args)) } end)
    return unpack(out or {})
  end

  BattleState.dramaticShapeShinyPics = true
end

return ShinyUI
