local Chest = require("storage.chest")
local Sentinel = require("storage.sentinel")

local Storage = Sentinel:tag({}, Sentinel.STORAGE)
Storage.__index = Storage

function Storage.assumeHomogeneous(names)
  local mappings = {}
  for _,name in ipairs(names) do
    mappings[name] = true
  end
  return mappings
end

function Storage:fromPeripherals(names)
  local obj = {
    count = #names
  }
  for name,homogeneous in pairs(names) do
    table.insert(obj, Chest:fromPeripheral(name, homogeneous))
  end

  setmetatable(obj, self)
  obj.__index = obj

  return obj
end

function Storage:toSerializable()
  local ser = {
    count = self.count
  }
  for _,chest in ipairs(self) do
    table.insert(ser, chest:toSerializable())
  end

  return ser
end

function Storage:fromSerializable(ser)
  local obj = { count = ser.count }

  for _,chestSer in ipairs(ser) do
    local chest = Chest:fromSerializable(chestSer)
    if chest ~= nil then
        table.insert(obj, chest)
    end
  end
  
  setmetatable(obj, self)
  obj.__index = self

  return obj
end

function Storage:isAttached(name)
  for index,chest in ipairs(self) do
    if chest:getName() == name then
        return true, chest, index
    end
  end
  return false, nil, nil
end

function Storage:attach(name, homogeneous)
  if self:isAttached(name) then
    return
  end

  table.insert(self, Chest:fromPeripheral(name, homogeneous))
end

function Storage:detach(name)
  local attached, _, index = self:isAttached(name)
  if attached then
    return table.remove(self, index)
  end
  return nil
end

function Storage:find(query)
  local result = {}
  for _,chest in ipairs(self) do
    for _,stack in ipairs(chest:find(query)) do
        table.insert(result, stack)
    end
  end

  return result
end

-- Find all stacks eligible to accept the given query
function Storage:findInsertTargets(sourceStack)
  local result = self:find(function(stack) return sourceStack:canTransfer(stack) end)

  -- Insertion should prioritize filling populated stacks
  table.sort(result, function(a, b) return a:getCount() > b:getCount() end)

  return result
end

function Storage:findExtractTargets(targetStack)
  -- Only transfer non-empty stacks
  local result = self:find(function(stack) return (not stack:isEmpty()) and stack:canTransfer(targetStack) end)

  -- Extraction should prioritize emptying populated stacks
  table.sort(result, function(a, b) return a:getCount() < b:getCount() end)

  return result
end

function Storage:insertStack(stack)
  local targets = self:findInsertTargets(stack)

  for _,target in ipairs(targets) do
    if stack:isEmpty() then
      return true
    end
    stack:transferTo(target)
  end

  return false
end

return Storage