--------------------------------------
--                                  --
-- The Geyser Layout Manager by guy --
--                                  --
--------------------------------------

--- Represents a generic container with positional information.
-- Has no notion of contents and is used to contain other windows
-- and impose some sense of order.
-- @class table
-- @name Geyser.Container
-- @field parent The parent class of this window
-- @field type The type of this window, usually lowercase of the classname and
--             can be used in checks for certain types. For a Container
--             instance, type is “container.”
-- @field name The name of this window. This is the same name that Mudlet will
--             use internally for primitive types like labels and
--             miniconsoles.  If not specified in the constraints table during
--             creation, an anonymous name unique to this session will be
--             made up.
-- @field x The x-coordinate relative to this window's container, not absolute
--          from the Mudlet main window. This is not a number, but a
--          constraint specification. To find out the numeric x-coordinate in
--          terms of pixels from the Mudlet main window's origin, use the
--          get_x() method. Default is "10px".
-- @field y The y-coordinate relative to this window's container, not absolute
--          from the Mudlet main window. This is not a number, but a
--          constraint specification. To find out the numeric y-coordinate in
--          terms of pixels from the Mudlet main window's origin, use the
--          get_y() method. Default is "10px".
-- @field width The width of this window, possibly relative to the window's
--              container. This is not a number, but a constraint
--              specification. To find out the numeric width in terms of
--              pixels, use the get_width() method. Default is "300px".
-- @field height The height of this window, possibly relative to the window's
--               container. This is not a number, but a constraint
--               specification. To find out the numeric width in terms of
--               pixels, use the get_width() method. Default is "200px".
-- @field windowList This is a list of all windows held by this container. It
--                   used to handle proper resizing of held windows as well as
--                   during show()s and hide()s to recursive show and hide all
--                   children windows.
-- @field windows This is an ordered list of the names of the window held by this container.
-- @field fontSize The default size font used when calculating dimensions
--                 based on the character constraint. Default is 8.
-- @field h_policy This sets if this widget should be stretched/shrinked horizontally or not
-- @field v_policy This sets if this widget should be stretched/shrinked vertically or not
-- @field v_stretch_factor This sets by how much more then window will be stretched horizontally in comparison to
--                 other windows
-- @field v_stretch_factor This sets by how much more then window will be stretched vertically in comparison to
--                 other windows

Geyser.Container = {
  name = "ContainerClass",
  x = "10px",
  y = "10px",
  height = "200px",
  width = "300px",
  windowList = {},
  windows = {},
  fontSize = 8,
  h_policy = Geyser.Dynamic,
  v_policy = Geyser.Dynamic,
  h_stretch_factor = 1.0,
  v_stretch_factor = 1.0,
}

function Geyser.Container:calculate_dynamic_window_size()
  local total_count = #self.windows
  local fixed_horizontal_count = 0
  local fixed_vertical_count = 0
  local fixed_width_sum = 0
  local fixed_height_sum = 0
  local ret_size = {}
  ret_size.width = self.get_width()
  ret_size.height = self.get_height()
  local dynamic_horizontal_count = 0.0
  local dynamic_vertical_count = 0.0

  if total_count <= 1 then
    --If there is only one window it can have all the width/height, if there are none then it doesn't matter
    return ret_size
  end
  for _, window in pairs(self.windowList) do
    if window.h_policy == Geyser.Fixed then
      fixed_horizontal_count = fixed_horizontal_count + 1
      fixed_width_sum = fixed_width_sum + window.get_width()
    else
      dynamic_horizontal_count = dynamic_horizontal_count + window.h_stretch_factor - 1.0
    end
    if window.v_policy == Geyser.Fixed then
      fixed_vertical_count = fixed_vertical_count + 1
      fixed_height_sum = fixed_height_sum + window.get_height()
    else
      dynamic_vertical_count = dynamic_vertical_count + window.v_stretch_factor - 1.0
    end
  end
  dynamic_horizontal_count = dynamic_horizontal_count + total_count - fixed_horizontal_count
  dynamic_vertical_count = dynamic_vertical_count + total_count - fixed_vertical_count
  if dynamic_horizontal_count == 0 then
    ret_size.width = 0
  else
    ret_size.width = (self.get_width() - fixed_width_sum) / dynamic_horizontal_count
  end
  if dynamic_vertical_count == 0 then
    ret_size.height = 0
  else
    ret_size.height = (self.get_height() - fixed_height_sum) / dynamic_vertical_count
  end
  return ret_size
end

--- Responsible for placing/moving/resizing this window to the correct place/size.
-- Called on window resize events.
function Geyser.Container:reposition ()
  local x, y, w, h = self:get_x(), self:get_y(), self:get_width(), self:get_height()
  moveWindow(self.name, self:get_x(), self:get_y())
  resizeWindow(self.name, self:get_width(), self:get_height())

  -- deal with all children of this container
  for k, v in pairs(self.windowList) do
    if k ~= self and not v.nestLabels then
      v:reposition()
    end
  end

  -- Calls optional redraw method if it is available to cause a gui element to
  -- redraw itself after moving.
  if self.redraw then
    self:redraw()
  end
end

--- Hides this window and all its contained windows.
function Geyser.Container:hide (auto)
  if not (self.hidden or self.auto_hidden) then
    self:hide_impl()
  end
  auto = auto or false
  if auto then
    self.auto_hidden = true
  else
    self.hidden = true
  end
  for _, v in pairs(self.windowList) do
    v:hide(true)
  end
end

function Geyser.Container:hide_impl()
  hideWindow(self.name)
end

--- Shows this window and all windows it contains.
function Geyser.Container:show (auto)
  auto = auto or false
  if auto then
    self.auto_hidden = false
  else
    self.hidden = false
  end
  if not self.hidden and not self.auto_hidden then
    self:show_impl()
  end
  for _, v in pairs(self.windowList) do
    v:show(true)
  end
end

function Geyser.Container:show_impl()
  showWindow(self.name)
end

--- Moves this window according to the new x and y contraints set.
-- @param x New x constraint to use. If nil, uses current value.
-- @param y New y constraint to use. If nil, uses current value.
function Geyser.Container:move (x, y)
  self.x = x or self.x
  self.y = y or self.y
  self:set_constraints(self)
end

--- Resizes this window according to the new width and height constraints set.
-- @param width New width constraint to use.  If nil, uses current value.
-- @param height New height constraint to use.  If nil, uses current value.
function Geyser.Container:resize (width, height)
  self.width = width or self.width
  self.height = height or self.height
  self:set_constraints(self)
end

--- Sets the default font size for this window.
-- Will resizes this window if necessary to meet constraints.
-- @param fontSize The new font size to use.
function Geyser.Container:setFontSize (fontSize)
  if type(fontSize) ~= "number" then
    error("fontSize must be a number")
    return
  end
  self.fontSize = fontSize or self.fontSize
  self:set_constraints()
end

--- Sets all contraints (x, y, width, height) for this window.
-- @param cons Any Lua table that contains appropriate constraint entries.
function Geyser.Container:set_constraints (cons)
  cons = cons or self
  Geyser.set_constraints(self, cons, self.container)
  for k, v in pairs(self.windowList) do
    v:set_constraints(v)
  end
end

--- Flashes a white box over the dimensions of this container.
-- This is very useful to see where a container actually is if you've
-- forgotten its details.
-- @param time Time in seconds to flash for, default is 1.0s.
function Geyser.Container:flash (time)
  local time = time or 1.0
  local x, y, width, height = self.get_x(), self.get_y(), self.get_width(), self.get_height()
  local name = self.name .. "_dimensions_flash"
  createLabel(name, x, y, width, height, 1)
  resizeWindow(name, width, height)
  moveWindow(name, x, y)
  setBackgroundColor(name, 190, 190, 190, 128)
  if self.callback then
    setLabelClickCallback(name, self.callback, unpack(self.args))
  end
  showWindow(name)
  tempTimer(time, "hideWindow(\"" .. name .. "\")")
end

Geyser.Container.parent = Geyser.Container -- I'm my own grandpa too!

-- Someone has to be the root!
setmetatable(Geyser.Container, Geyser)

--- Constructor for containers.
-- This function creates a new container/window
-- @param cons Any Lua table that contains appropriate constraint entries.
--             Include any parameter such as name or fontSize in cons
--             that are to be used for the new window.
function Geyser.Container:new(cons, container)
  -- create new table for the container and copy over constraints
  local me = Geyser.copyTable(cons)

  -- enforce a default type, name and parent
  me.type = me.type or "container"
  me.name = me.name or Geyser.nameGen()
  me.windowList = {}
  me.windows = {}
  me.hidden = false
  me.auto_hidden = false
  -- Set the metatable.
  setmetatable(me, self)
  self.__index = self

  -- If we're not not a class definition then add to a controlling
  -- container.
  if not string.find(me.name, ".*Class") then
    -- If passed in a container, add me to that container
    if container then
      container:add(me)
    else
      -- Else assume the root window is my container
      Geyser:add(me)
    end
  end

  --print("New in " .. self.name .. " : " .. me.name)
  return me
end
