local Event = require("gfx.event")
local Logger = require("logging").getGlobalLogger()

local Element = {
  x = 1,
  y = 1,
  width = 1,
  height = 1,
  bgColor = colors.black,
  fgColor = colors.white,
  window = nil,
  parent = nil,
  visible = true,
  dirty = true,
  id = "",
  onClick = nil,
  onKey = nil,
  onChar = nil,
  onEvent = nil
}
Element.__index = Element


function Element:new(o)
  local obj = o or {}

  setmetatable(obj, self)
  obj.__index = obj

  if obj.parent then
    obj:_reload()
  end

  return obj
end

function Element:draw()
  Logger:trace("draw")
  local dirty = self:_isDirty()
  if dirty then
    local win = self:_getWindow()
  -- Calling draw() without a valid window is a logical error
  ---@diagnostic disable-next-line: need-check-nil
    win.setCursorPos(1, 1)
  end
  self.dirty = false
  return dirty
end

function Element:getX()
  return self.x
end

function Element:getY()
  return self.y
end

function Element:setPos(x, y)
  Logger:trace("setPos", x, y)
  if (x ~= nil and self.x ~= x) or (y ~= nil and self.y ~= y) then
    self.x = x or self.x
    self.y = y or self.y

    self:setDirty()
    self:_reload()
  end
end

function Element:setX(x)
  if self.x ~= x then
    self.x = x
    self:setDirty()
    self:_reload()
  end
end

function Element:setY(y)
  if self.y ~= y then
    self.y = y
    self:setDirty()
    self:_reload()
  end
end

function Element:setParent(parent)
  Logger:trace("setParent", self:getId(), parent, self.parent ~= parent)
  if self.parent ~= parent then
    self.parent = parent
    self:setDirty()
    self:_reload()
  end
end

function Element:setFgColor(color)
  if color ~= self.fgColor then
    self.fgColor = color

    self:setDirty()
    local win = self:_getWindow()
    if win ~= nil then
      win.setTextColor(color)
    end
  end
end

function Element:setBgColor(color)
  if color ~= self.bgColor then
    self.bgColor = color

    self:setDirty()
    local win = self:_getWindow()
    if win ~= nil then
      win.setBackgroundColor(color)
    end
  end
end

function Element:getFgColor()
  return self.fgColor
end

function Element:getBgColor()
  return self.bgColor
end

function Element:_getWindow()
  return self.window
end

function Element:_setWindow(window)
  self.window = window
end

function Element:setVisible(visible)
  self.visible = visible
  self:setDirty()

  local win = self:_getWindow()
  if win ~= nil then
    win.setVisible(visible)
  end
end

function Element:_isDirty()
  return self.dirty
end

function Element:resize(opts)
  if (opts.width ~= nil and self.x ~= opts.width) or (opts.height ~= nil and self.y ~= opts.height) then
    self.width = opts.width or self.width
    self.height = opts.height or self.height
    self:setDirty()
    self:_reload()
  end
end

function Element:setWidth(width)
  if width ~= self.width then
    self.width = width
    self:setDirty()
    self:_reload()
  end
end

function Element:setHeight(height)
  if height ~= self.height then
    self.height = height
    self:setDirty()
    self:_reload()
  end
end

function Element:getWidth()
  return self.width
end

function Element:getHeight()
  return self.height
end

function Element:getPos()
  return self.x, self.y
end

function Element:isVisible()
  return self.visible
end

function Element:setDirty(fullInvalidate)
  self.dirty = true
end

function Element:findById(id)
  if self:getId() == id then
    return self
  else
    return nil
  end
end

function Element:getId()
  return self.id
end

function Element:handleEvent(evt)
  if Event.isClickEvent(evt) and Event.containsClick(self, evt) and self.onClick ~= nil then
    local x, y, source = Event.getClickParams(evt)
    return not not self.onClick(self, x, y, source)
  elseif Event.isKeyEvent(evt) and self.onKey ~= nil then
    local key, held = Event.getKeyParams(evt)
    return not not self.onKey(self, key, held)
  elseif Event.isCharEvent(evt) and self.onChar ~= nil then
    local keyChar = Event.getCharValue(evt)
    return not not self.onChar(self, keyChar)
  end
  return self.onEvent ~= nil and self.onEvent(self, evt)
end

function Element:setOnClick(onClick)
  self.onClick = onClick
end

function Element:_reload()
  Logger:trace("_reload", self:getId(), self:getX(), self:getY(), self:getWidth(), self:getHeight(), self:isVisible(), Logger.plain({bg=self:getBgColor(), fg=self:getFgColor()}))
  if self.parent ~= nil then
    local win = window.create(self.parent, self:getX(), self:getY(), self:getWidth(), self:getHeight(), self:isVisible())
    win.setBackgroundColor(self:getBgColor())
    win.setTextColor(self:getFgColor())
    win.setVisible(self.visible)
    self:_setWindow(win)
    self:setDirty(true)
  end
end

return Element