local Event = require("gfx.event")
local Element = require("gfx.element")
local List = Element:new{
  children = {},
  vertical = false
}

local function adjustPositions(elements, vertical, from)
    local newDims = 1
    local getDim = vertical and function(e) return e:getHeight() end or function(e) return e:getWidth() end
    for i=1,from-1 do
      newDims = newDims + getDim(elements[i])
    end
    
    local setDim = vertical and function(e, dim) e:setY(dim) end or function(e, dim) e:setX(dim) end
    for i=from,#elements do
      setDim(elements[i], newDims)
      newDims = newDims + getDim(elements[i])
    end
end

function List:insertChild(child, atIndex)
  local index = math.min(math.max(1, atIndex or #self.children), #self.children)
  table.insert(self.children, index, child)

  -- Update window references
  self:_reload()
end

function List:removeChild(child)
  local index
  local searchType = type(child)
  if searchType == "string" then
    for i,v in ipairs(self.children) do
        if v:getId() == child then
            index = i
            break
        end
    end
    return false, nil
  elseif searchType == "table" then
    for i,v in ipairs(self.children) do
        if v == child then
            index = i
        end
    end
    return false, nil
  else
    index = child
  end

  if index <= 0 or index > #self.children then
    return false, nil
  end

  local removed = table.remove(self.children, index)
  self:_reload()

  return true, removed
end

function List:isVertical()
  return self.vertical
end

function List:isHorizontal()
  return not self:isVertical()
end

function List:setDirty(fullInvalidate)
  Element.setDirty(self, fullInvalidate)
  if fullInvalidate then
    for _,child in self.children do
      child:setDirty(fullInvalidate)
    end
  end
end

function List:draw()
  local dirty = Element.draw(self)
  if dirty then
    self:_getWindow().clear()
    for _,child in ipairs(self.children) do
      child:draw()
    end
  end
  return dirty
end

local function maxOrSum(shouldSum, values, getValue)
  if shouldSum then
    local sum = 0
    for _,v in ipairs(values) do
      sum = sum + getValue(v)
    end
    return sum
  else
    local max = 0
    for _,v in ipairs(values) do
      max = math.max(max, getValue(v))
    end
    return max
  end
end

function List:getHeight()
  return maxOrSum(self:isVertical(), self.children, function(e) return e:getHeight() end)
end

function List:getWidth()
  return maxOrSum(self:isHorizontal(), self.children, function(e) return e:getWidth() end)
end

function List:findById(id)
  local find = Element.findById(self, id)
  if find then
    return find
  end

  for _,v in ipairs(self.children) do
    local result = v:findById(id)
    if result then
        return result
    end
  end
  return nil
end

function List:handleEvent(evt)
  if Element.handleEvent(self, evt) then
    return true
  end

  local evtLocalCoords = Event.toElementLocalPosition(evt, self)

  if Event.isClickEvent(evt) then
    -- If click is not inside list bounds, we can safely ignore it
    if not Event.containsClick(self, evt) then
      return false
    end

    for _,child in ipairs(self.children) do
      if child:handleEvent(evtLocalCoords) then
        return true
      end
    end
    return false
  else
    for _,child in ipairs(self.children) do
      if child:handleEvent(evtLocalCoords) then
        return true
      end
    end
    return false
  end
end

function List:_isDirty()
  if Element._isDirty(self) then
    return true
  end

  for _,child in ipairs(self.children) do
    if child:_isDirty() then
      return true
    end
  end
  return false
end

function List:_reload()
  Element._reload(self)

  -- Reload child windows
  local win = self:_getWindow()
  for _,child in ipairs(self.children) do
    if child:_getWindow() ~= win then
      child:setParent(win)
    end
  end

  adjustPositions(self.children, self:isVertical(), 1)
end

return List