local Prop = require("gfx.prop")
local Element = require("gfx.element")
local Event = require("gfx.event")
local Children = Prop:new{ defaultState = {}, uid = "CHILDREN" }

function Children:with(elementType)
  local propSelf = self
  function elementType:_iterateChildren(opts)
    return ipairs(self:_children())
  end

  function elementType:_children()
    return propSelf:getState(self)
  end

  function elementType:childCount()
    return #self:_children()
  end

  function elementType:_childAt(index)
    return self:_children()[elementType:_validChildIndex(index)]
  end

  function elementType:_validChildIndex(from)
    return math.max(1, math.min((from or self:childCount()) + 1, self:childCount() + 1))
  end

  function elementType:_triggerChildChanged(index)
    if type(self.onChildrenChanged) == "function" then
        self:onChildrenChanged(index)
    end
    self:_reload()
  end

  function elementType:_addChild(child, index)
    index = self:_validChildIndex(index)
    table.insert(propSelf:getState(self), index, child)
    self:_triggerChildChanged(index)
  end

  function elementType:_removeChild(locator)
    local index = nil
    local locatorType = type(locator)
    if locatorType == "table" then
      for i,v in self:_iterateChildren() do
        if v == locator then
          index = i
          break
        end
      end
    elseif locatorType == "string" then
      for i,v in self:_iterateChildren() do
        if v:getId() == locator then
          index = i
          break
        end
      end
    else--if locatorType == "number" then
      index = self:_validChildIndex(locator)
    end

    -- Child not found
    if index == nil then
        return false, nil
    end

    local result = table.remove(propSelf:getState(self), index)
    self:_triggerChildChanged(index)
    return true, result
  end

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

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

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

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

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

    return self:findChildById(id)
  end

  function elementType:findChildById(id)
    for _,child in self:_iterateChildren() do
      local result = child:findById(id)
      if result then
          return result
      end
    end
    return nil
  end

  function elementType: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 self:_iterateChildren() do
        if child:handleEvent(evtLocalCoords) then
          return true
        end
      end
      return false
    else
      for _,child in self:_iterateChildren() do
        if child:handleEvent(evtLocalCoords) then
          return true
        end
      end
      return false
    end
  end

  return elementType
end

return Children