-- awaken.lua
-- Mystical Agriculture Awakening Altar automation for CC:Tweaked.
--
-- chest_6 is fed by a pattern provider and holds exactly one craft's ingredients.
-- Elemental essences go to the vessels, the remaining items fill the pedestals,
-- and the first slot's item goes in the altar. Output is handled externally.

local CONFIG = {
  inputChest    = "sophisticatedstorage:chest_6",
  altarMatch    = "awakening_altar",
  pedestalMatch = "awakening_pedestal",
  vesselMatch   = "essence_vessel",
  craftTimeout  = 300,
  pollDelay     = 0.5,
  restDelay     = 2,
}

local ESSENCES = {
  ["mysticalagriculture:air_essence"]   = true,
  ["mysticalagriculture:earth_essence"] = true,
  ["mysticalagriculture:water_essence"] = true,
  ["mysticalagriculture:fire_essence"]  = true,
}

local function findPeripherals(match)
  local found = {}
  for _, name in ipairs(peripheral.getNames()) do
    if name:find(match, 1, true) then found[#found + 1] = name end
  end
  table.sort(found)
  return found
end

local altarNames    = findPeripherals(CONFIG.altarMatch)
local pedestalNames = findPeripherals(CONFIG.pedestalMatch)
local vesselNames   = findPeripherals(CONFIG.vesselMatch)

if #altarNames ~= 1 then
  error("Expected exactly 1 awakening altar, found " .. #altarNames, 0)
end
if #pedestalNames == 0 then error("No pedestals on the network", 0) end
if #vesselNames == 0 then error("No essence vessels on the network", 0) end

local altarName = altarNames[1]
local input     = peripheral.wrap(CONFIG.inputChest)
if not input then error("Input chest not found: " .. CONFIG.inputChest, 0) end

local function isEmpty(name)
  return next(peripheral.call(name, "list")) == nil
end

local function pedestalsEmpty()
  for _, ped in ipairs(pedestalNames) do
    if not isEmpty(ped) then return false end
  end
  return true
end

local function altarIsClear()
  return isEmpty(altarName) and pedestalsEmpty()
end

-- Splits the input chest into essence stacks and solid-item stacks.
local function readInput()
  local list = input.list()
  local slots = {}
  for slot in pairs(list) do slots[#slots + 1] = slot end
  table.sort(slots)

  local essence, solids, solidCount = {}, {}, 0
  for _, slot in ipairs(slots) do
    local item = list[slot]
    if ESSENCES[item.name] then
      essence[#essence + 1] = { slot = slot, name = item.name, count = item.count }
    else
      solids[#solids + 1] = { slot = slot, name = item.name, count = item.count }
      solidCount = solidCount + item.count
    end
  end
  return essence, solids, solidCount
end

-- Sends each essence stack to a vessel that is empty or already holds that type.
local function chargeVessels(essence)
  for _, e in ipairs(essence) do
    local target
    for _, ves in ipairs(vesselNames) do
      local held
      for _, item in pairs(peripheral.call(ves, "list")) do held = item.name break end
      if held == e.name or held == nil then target = ves break end
    end
    if not target then
      return false, "no vessel free for " .. e.name
    end
    local moved = input.pushItems(target, e.slot, e.count)
    if moved < e.count then
      return false, ("only moved %d/%d %s into a vessel")
        :format(moved, e.count, e.name)
    end
  end
  return true
end

-- Fills the pedestals one item at a time, then puts the center item in the altar.
local function loadAltar(solids, solidCount)
  local needed = #pedestalNames + 1
  if solidCount ~= needed then
    return false, ("input has %d solid items, expected %d")
      :format(solidCount, needed)
  end

  local queue = {}
  for _, s in ipairs(solids) do
    for _ = 1, s.count do queue[#queue + 1] = s end
  end

  local center = table.remove(queue, 1)

  for i, ped in ipairs(pedestalNames) do
    local src = queue[i]
    if input.pushItems(ped, src.slot, 1) < 1 then
      return false, "could not place " .. src.name .. " on " .. ped
    end
  end

  if input.pushItems(altarName, center.slot, 1) < 1 then
    return false, "could not place " .. center.name .. " in the altar"
  end
  return true
end

-- Blocks until the altar and every pedestal are empty again.
local function waitForClear()
  local deadline = os.epoch("utc") + CONFIG.craftTimeout * 1000
  while os.epoch("utc") < deadline do
    if altarIsClear() then return true end
    sleep(CONFIG.pollDelay)
  end
  return false
end

local function runOnce()
  local essence, solids, solidCount = readInput()

  if #essence == 0 and solidCount == 0 then return end

  if not altarIsClear() then
    print("Altar still loaded, waiting.")
    return
  end

  if solidCount < #pedestalNames + 1 then
    print(("Waiting for the provider: %d/%d solid items so far")
      :format(solidCount, #pedestalNames + 1))
    return
  end

  local charged, cerr = chargeVessels(essence)
  if not charged then
    print("Vessel charge failed: " .. cerr)
    return
  end

  local placed, perr = loadAltar(solids, solidCount)
  if not placed then
    print("Load failed: " .. perr)
    return
  end

  print("Loaded, crafting.")
  if waitForClear() then
    print("Altar clear, ready for the next batch.")
  else
    print("Timed out waiting for the altar to clear.")
  end
end

print(("Altar: %s | Pedestals: %d | Vessels: %d")
  :format(altarName, #pedestalNames, #vesselNames))

while true do
  local ok, err = pcall(runOnce)
  if not ok then print("Error: " .. tostring(err)) end
  sleep(CONFIG.restDelay)
end