-- Named save slots on top of WorldState.
--
-- The index is a json list of {id, name, saved}. Slot data
-- lives under integer ids rather than sanitised names, so a
-- display name can be anything at all without ever touching
-- a storage key. Ids from json come back as floats, so every
-- one goes through WorldState.num before it is concatenated.

WorldSlots = {}

WorldSlots.INDEX  = "WorldIndex"
WorldSlots.PREFIX = "WSlot"
WorldSlots.THUMB  = "VPthumb_"
WorldSlots.MAX    = 60

function WorldSlots.key(id)
  return WorldSlots.PREFIX.."."..WorldState.num(id)
end

function WorldSlots.thumbKey(id)
  return asset..(WorldSlots.THUMB..WorldState.num(id))
end

----------------------------------------------------------------
-- index
----------------------------------------------------------------

function WorldSlots.readIndex()
  local txt = readLocalData(WorldSlots.INDEX, nil)
  if type(txt) ~= "string" or #txt == 0 then return {} end
  
  local ok, list = pcall(json.decode, txt)
  if not ok or type(list) ~= "table" then return {} end
  
  local out = {}
  for _, e in ipairs(list) do
    if e and e.id then
      out[#out + 1] = {
        id = WorldState.num(e.id),
        name = tostring(e.name or "Unnamed"),
        saved = WorldState.num(e.saved)
      }
    end
  end
  
  -- newest first, matching the VEP browser's ordering
  table.sort(out, function(a, b)
    if a.saved ~= b.saved then return a.saved > b.saved end
    return a.name < b.name
  end)
  
  return out
end

function WorldSlots.writeIndex(list)
  local ok, txt = pcall(json.encode, list)
  if ok and txt then
    saveLocalData(WorldSlots.INDEX, txt)
  end
end

function WorldSlots.nextId(list)
  local top = 0
  for _, e in ipairs(list) do
    if e.id > top then top = e.id end
  end
  return top + 1
end

function WorldSlots.find(list, id)
  for i, e in ipairs(list) do
    if e.id == id then return i, e end
  end
  return nil
end

----------------------------------------------------------------
-- save
----------------------------------------------------------------

-- Serializes the live volume into a new slot, or overwrites
-- an existing one if the name matches exactly. Returns the
-- slot id.
function WorldSlots.save(name, thumb)
  local list = WorldSlots.readIndex()
  name = tostring(name or "")
  if name == "" then name = os.date("%Y-%m-%d %H:%M") end
  
  local id = nil
  for _, e in ipairs(list) do
    if e.name == name then id = e.id break end
  end
  
  if not id then
    if #list >= WorldSlots.MAX then return nil, "slot limit reached" end
    id = WorldSlots.nextId(list)
    list[#list + 1] = {id = id, name = name, saved = 0}
  end
  
  local blob, pal, states, carts = WorldState.serialize()
  WorldState.writeTo(WorldSlots.key(id), blob, pal, states, carts)
  
  local _, entry = WorldSlots.find(list, id)
  if entry then entry.saved = math.floor(os.time()) end
  WorldSlots.writeIndex(list)
  
  if thumb then
    pcall(function() saveImage(WorldSlots.thumbKey(id), thumb) end)
  end
  
  return id
end

----------------------------------------------------------------
-- load and delete
----------------------------------------------------------------

function WorldSlots.load(id)
  return WorldState.readFrom(WorldSlots.key(id))
end

function WorldSlots.delete(id)
  WorldState.clearKey(WorldSlots.key(id))
  pcall(function() saveImage(WorldSlots.thumbKey(id), nil) end)
  
  local list = WorldSlots.readIndex()
  local i = WorldSlots.find(list, id)
  if i then table.remove(list, i) end
  WorldSlots.writeIndex(list)
end

function WorldSlots.thumb(id)
  local img = nil
  pcall(function() img = readImage(WorldSlots.thumbKey(id)) end)
  return img
end

----------------------------------------------------------------
-- thumbnail capture
----------------------------------------------------------------

-- Renders the live scene to an image. Must be called from
-- draw(), never from touched() -- setContext inside a touch
-- handler draws outside Codea's normal frame. This is the
-- same deferral the VEP browser uses for pending deletes.
function WorldSlots.capture(size)
  size = size or 256
  local cam = scene.camera:get(craft.camera)
  if not cam then return nil end
  
  local img = image(size, size)
  local ok = pcall(function()
    setContext(img, true)
    cam:draw()
    setContext()
  end)
  
  if not ok then
    pcall(setContext)
    return nil
  end
  return img
end
