local ItemStack = require("storage.itemstack")
local Sentinel = require("storage.sentinel")

local Chest = Sentinel:tag({}, Sentinel.CHEST)
Chest.__index = Chest

-- Homogeneity allows chest scan to clone empty itemDetail slot to all empty slots in chest
function Chest:fromPeripheral(chest, homogeneous)
    if type(chest) == "string" then
        local chestResult = peripheral.wrap(chest)

        if chestResult == nil then
            chestResult = peripheral.find(chest)
        end

        chest = chestResult
    end
    
    if chest == nil then
        return nil
    end

    local obj = {
        name = peripheral.getName(chest),
        chest = chest,
        size = chest.size(),
        homogeneous = homogeneous
    }
    setmetatable(obj, self)
    obj.__index = obj

    obj:rescan()

    return obj
end

function Chest:rescan()
  -- Clear stack data
  for i=1,self:getSize() do
    self[i] = nil
  end

  self.size = self.chest.size()
  local emptyStack = nil
  local listData = self.chest.list()
  for i=1,self:getSize() do
    local data = listData[i]
    local stack
    if data == nil and self.homogeneous then
      -- Empty stack
      if emptyStack == nil then
        emptyStack = ItemStack:fromDetail(self.chest, self.chest.getItemDetail(i), i)
        if not emptyStack:isEmpty() then
          error("Expected empty stack detail, but got populated stack!")
        end
      end
      stack = emptyStack:clone(i)
    else
      stack = ItemStack:fromDetail(self.chest, self.chest.getItemDetail(i), i)
    end

    self[i] = stack
  end
end

function Chest:toSerializable()
  local ser = {
    name = self:getName(),
    size = self:getSize(),
    homogeneous = self.homogeneous
  }

  local skipEmpty = false
  for i,v in ipairs(self) do
    local stackSer = v:toSerializable()

    -- Skip all but the first homogeneous slot
    if v:isEmpty() then
        if skipEmpty then
            goto continue
        end

        skipEmpty = self.homogeneous
    end
    ser[i] = stackSer
      ::continue::
  end

  return ser
end

function Chest:fromSerializable(ser, inv)
  -- Auto-wrap if inventory is not supplied
  if inv == nil then
    inv = peripheral.wrap(ser.name)
    if inv == nil then
        return nil
    end
  end

  local obj = {
    name = ser.name,
    size = ser.size,
    homogeneous = ser.homogeneous,
    chest = inv
  }

  local emptyStack = nil
  for i=1,ser.size do
    local stackSer = ser[i]
    local stack
    if stackSer == nil then
      if emptyStack == nil then
        -- Missing inventory slot data! Must get item detail
        stack = ItemStack:fromDetail(inv, inv.getItemDetail(i), i)
      else
        stack = emptyStack:clone(i)
      end
    else
        stack = ItemStack:fromSerializable(stackSer, inv)
    end

    if ser.homogeneous and emptyStack == nil and stack:isEmpty() then
        emptyStack = stackSer
    end

    obj[i] = stack
  end

  setmetatable(obj, self)
  obj.__index = obj

  return obj
end

function Chest:hasChanged(thorough)
  local mySize = self:getSize()
  if mySize ~= self.chest.size() then
    return true
  end

  local listData = self.chest.list()
  for i=1,mySize do
    if (listData[i] == nil) ~= (self[i] == nil) then
        return true
    end

    if listData[i] ~= nil and self[i]:hasChanged(listData[i], thorough) then
        return true
    end
  end

  return false
end

function Chest:getSize()
  return self.size
end

function Chest:getName()
    return self.name
end

function Chest:getInventory()
  return self.chest
end

function Chest:find(query)
  local result = {}

  for i=1,self:getSize() do
    if self[i]:matches(query) then
        table.insert(result, self[i])
    end
  end

  return result
end

return Chest