--!name Dice
--!icon dice
--!category Games
--
-- Dice roller for Snail OS. Pick a die, set how many, OK to roll.
-- Faces live in dice.art (d4–d20 silhouettes + d6 pip faces).

local DICE = {
  {id = "d4",  sides = 4,  label = "D4"},
  {id = "d6",  sides = 6,  label = "D6"},
  {id = "d8",  sides = 8,  label = "D8"},
  {id = "d10", sides = 10, label = "D10"},
  {id = "d12", sides = 12, label = "D12"},
  {id = "d20", sides = 20, label = "D20"},
}

local N = #DICE
local COUNT_MIN, COUNT_MAX = 1, 8
local HIST_MAX = 6

local die_i = 6 -- default d20
local count = 1
local results = {} -- last roll values
local total = 0
local history = {} -- strings
local seeded = false
local entropy = 1

local function clamp(v, lo, hi)
  if v < lo then return lo end
  if v > hi then return hi end
  return v
end

local function ensure_seed()
  if seeded then return end
  seeded = true
  -- Mix key-press entropy; math.randomseed is available in Snail Lua.
  math.randomseed((entropy * 1103515245 + 12345) % 2147483647)
  -- Warm the generator a little.
  for _ = 1, 3 do math.random() end
end

local function bump_entropy(n)
  entropy = (entropy + n) % 1000003
  if entropy == 0 then entropy = 1 end
end

local function die()
  return DICE[die_i]
end

local function roll_one(sides)
  return math.random(1, sides)
end

local function do_roll()
  ensure_seed()
  local d = die()
  results = {}
  total = 0
  for i = 1, count do
    local v = roll_one(d.sides)
    results[i] = v
    total = total + v
  end
  local line
  if count == 1 then
    line = string.format("%s = %d", d.label, results[1])
  else
    local parts = {}
    for i = 1, count do
      parts[i] = tostring(results[i])
    end
    line = string.format("%dx%s %s = %d", count, d.label, table.concat(parts, "+"), total)
  end
  table.insert(history, 1, line)
  while #history > HIST_MAX do
    table.remove(history)
  end
end

local function save()
  snail.save(string.format("%d %d %d", die_i, count, entropy))
end

local function load()
  local s = snail.load()
  if not s then return end
  local a, b, c = s:match("(%d+) (%d+) (%d+)")
  if not a then
    a, b = s:match("(%d+) (%d+)")
    c = "1"
  end
  if not a then return end
  die_i = clamp(tonumber(a) or 6, 1, N)
  count = clamp(tonumber(b) or 1, COUNT_MIN, COUNT_MAX)
  entropy = tonumber(c) or 1
  if entropy < 1 then entropy = 1 end
end

local function result_sprite()
  local d = die()
  if d.sides == 6 and count == 1 and results[1] then
    return "p" .. tostring(results[1])
  end
  return d.id
end

local function result_title()
  if #results == 0 then
    return die().label
  end
  if count == 1 then
    return tostring(results[1])
  end
  return tostring(total)
end

function start()
  snail.status("")
  load()
end

function key(k)
  bump_entropy(#k + die_i * 7 + count)
  if k == "left" then
    die_i = die_i - 1
    if die_i < 1 then die_i = N end
    results = {}
    total = 0
  elseif k == "right" then
    die_i = die_i + 1
    if die_i > N then die_i = 1 end
    results = {}
    total = 0
  elseif k == "up" then
    count = clamp(count + 1, COUNT_MIN, COUNT_MAX)
    results = {}
    total = 0
  elseif k == "down" then
    count = clamp(count - 1, COUNT_MIN, COUNT_MAX)
    results = {}
    total = 0
  elseif k == "ok" then
    do_roll()
  elseif k == "top" then
    results = {}
    total = 0
    history = {}
  end
  save()
end

function draw()
  local d = die()
  snail.title(result_title())
  snail.center(true)
  snail.image(result_sprite())
  snail.center(false)
  snail.gap()
  if #results == 0 then
    snail.text(string.format("%dx%s  —  press OK", count, d.label))
  elseif count == 1 then
    snail.text(string.format("%s rolled", d.label))
  else
    local parts = {}
    for i = 1, count do
      parts[i] = tostring(results[i])
    end
    snail.text(table.concat(parts, " + "))
    snail.small(string.format("sum %d", total))
  end
  snail.rule()
  snail.small("L/R die · UP/DN count · OK roll · TOP clear")
  if #history > 0 then
    snail.gap()
    snail.small("Recent")
    for i = 1, math.min(4, #history) do
      snail.small(history[i])
    end
  end
  snail.hint("OK roll   L/R die   UP/DN qty")
end
