-- grid :: a menu with arbitrary widget placement and size on a grid.

--[[
  Copyright © 2019 Kazhnuz

  Permission is hereby granted, free of charge, to any person obtaining a copy of
  this software and associated documentation files (the "Software"), to deal in
  the Software without restriction, including without limitation the rights to
  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  the Software, and to permit persons to whom the Software is furnished to do so,
  subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]

local cwd  = (...):gsub('%.grid$', '') .. "."

local Menu = require(cwd .. "parent")
local GridBox = Menu:extend()

local menuutils = require(cwd .. "widgets.utils")

-- INIT FUNCTIONS
-- Initialize and configure the menu

function GridBox:new(menusystem, name, x, y, w, h, colNumber, lineNumber)
  self.view = {}
  self.view.slotNumber  = colNumber * lineNumber
  self.view.colNumber   = colNumber
  self.view.lineNumber  = lineNumber
  self.view.firstSlot = 1
  GridBox.super.new(self, menusystem, name, x, y, w, h)
  self.h = lineNumber * self.widget.h -- On fait en sorte que la hauteur
  self.w = colNumber  * self.widget.w -- et la largeur
  -- soit un multiple du nombre de slot et de leur dimensions
  self.cursor = {}
  self.cursor.x = 0
  self.cursor.y = 0

  -- La gridbox possède la particularité de pouvoir fusioner des slots, on fait
  -- donc une liste de slots disponibles, qui serviront par la suite.
  self.slots = {}
end

function GridBox:addSlot(widgetID, x, y, w, h)
  local slot = {}
  slot.x = x
  slot.y = y
  slot.w = w
  slot.h = h
  slot.widgetID = widgetID

  table.insert(self.slots, slot)
end

function GridBox:updateWidgetSize()
  self.widget.h = math.floor( self.h / self.view.lineNumber )
  self.widget.w = math.floor( self.w / self.view.colNumber )
end

-- INFO FUNCTIONS
-- Get the info of the widgets

function GridBox:getWidgetSize(id)
  local slot = self:getWidgetSlot(id)
  if slot == 0 then
    return 1, 1
  else
    return self.widget.w * self.slots[slot].w, self.widget.h * self.slots[slot].h
  end
end

function GridBox:getSlotHitbox(slot)
  local x, y, w, h
  x = self.slots[slot].x * self.widget.w
  y = self.slots[slot].y * self.widget.h
  w = self.slots[slot].w * self.widget.w
  h = self.slots[slot].h * self.widget.h

  return x, y, w, h
end

function GridBox:getSlotCenter(slot)
  local x, y, w, h = self:getSlotHitbox(slot)

  return x + (w/2), y + (h/2)
end

function GridBox:getWidgetID(slot)
  local widgetID
  if self.slots[slot] ~= nil then
    widgetID = self.slots[slot].widgetID
  else
    widgetID = 0
  end
  return widgetID
end

function GridBox:haveWidget(slot)
  local id = self:getWidgetID(slot)
  return self.widget.list[id] ~= nil
end

function GridBox:getWidgetSlot(widgetID)
  local slot = 0
  for i,v in ipairs(self.slots) do
    if (self.slots[i].widgetID == widgetID) then
      slot = i
    end
  end

  return slot
end

function GridBox:getWidgetAtPoint(x, y)
  local x = x or 0
  local y = y or 0
  local widgetID = nil

  for i,v in ipairs(self.slots) do
    local xx, yy, ww, hh = self:getSlotHitbox(i)
    if (x >= xx) and (y >= yy) and (x < xx + ww) and (y < yy + hh) then
      widgetID = v.widgetID
    end
  end

  return widgetID
end

-- UPDATE FUNCTIONS
-- Update the Grid and its view

function GridBox:update(dt)
  self.view.firstSlot = 1
end

-- KEYS FUNCTIONS
-- Handle the keyboard/manette functions

function GridBox:keyreleased(key, code)
  slotID = self:getWidgetSlot(self.widget.selected)
  local col, line = self.cursor.x, self.cursor.y
  if key == 'left' then
    self:moveCol(-1)
  end

  if key == 'right' then
    self:moveCol(1)
  end

  if key == 'up' then
    self:moveLine(-1)
  end

  if key == 'down' then
    self:moveLine(1)
  end

  if key == "A" and self.widget.selected <= #self.widget.list then
    self.widget.list[self.widget.selected]:action("key")
  end
end

function GridBox:moveCol(direction)
  local orig_x, orig_y = self:getSlotCenter(self.widget.selected)
  local distance = self.w -- on met directement à la distance max possible le système
  local nearastWidget = 0
  for i,v in ipairs(self.slots) do
    local xx, yy = self:getSlotCenter(i)
    -- On commence par vérifier si le slot est bien positionné par rapport au
    -- widget de base
    if utils.math.sign(xx - orig_x) == direction then
      if utils.math.pointDistance(orig_x, orig_y, xx, yy) < distance then
        distance = utils.math.pointDistance(orig_x, orig_y, xx, yy)
        nearestWidget = v.widgetID
      end
    end
  end

  if nearestWidget ~= 0 then
    self.widget.selected = nearestWidget
  end
end

function GridBox:moveLine(direction)
  local orig_x, orig_y = self:getSlotCenter(self.widget.selected)
  local distance = self.h -- on met directement à la distance max possible le système
  local nearastWidget = 0
  for i,v in ipairs(self.slots) do
    local xx, yy = self:getSlotCenter(i)
    -- On commence par vérifier si le slot est bien positionné par rapport au
    -- widget de base
    if utils.math.sign(yy - orig_y) == direction then
      if utils.math.pointDistance(orig_x, orig_y, xx, yy) < distance then
        distance = utils.math.pointDistance(orig_x, orig_y, xx, yy)
        nearestWidget = v.widgetID
      end
    end
  end

  if nearestWidget ~= 0 then
    self.widget.selected = nearestWidget
  end
end

-- MOUSE FUNCTIONS
-- Handle the mouse and activate the widgets with it

function GridBox:mousemoved(x, y)
  local widgetID = self:getWidgetAtPoint(x, y)

  if widgetID ~= nil then
    self.widget.selected = widgetID
    self:getFocus()
  end

  if self.widget.selected < 1 then
    self.widget.selected = 1
  end
  if self.widget.selected > #self.widget.list then
    self.widget.selected = #self.widget.list
  end

end

function GridBox:mousepressed(x, y, button, isTouch)
  local widgetID = self:getWidgetAtPoint(x, y)

  if widgetID ~= nil then
    self.widget.selected = widgetID
    self:getFocus()

    if #self.widget.list > 0 and self.widget.selected > 1 and self.widget.selected <= #self.widget.list then
      self.widget.list[self.widget.selected]:action("pointer")
    end
  end
end

-- DRAW FUNCTIONS
-- Draw the menu and its content

function GridBox:draw()

  for i,v in ipairs(self.slots) do
    if self:haveWidget(i) then
      local widgetx = self.x + (v.x * self.widget.w)
      local widgety = self.y + (v.y * self.widget.h)
      if self.widget.selected == v.widgetID and self:haveFocus() == true then
        self.widget.list[v.widgetID]:drawSelected(widgetx, widgety)
      else
        self.widget.list[v.widgetID]:draw(widgetx, widgety)
      end
    end
  end
end

function GridBox:drawCursor()
  self:updateView()
  if (self.widget.selected >= 1 and self.widget.selected <= #self.widget.list) then
    local slot  = self:getWidgetSlot(self.widget.selected)
    local w, h  = self:getWidgetSize(slot)
    local x     = self.slots[slot].x * self.widget.w
    local y     = self.slots[slot].y * self.widget.h
    menuutils.drawCursor(self.x + x, self.y + y, w, h)
  end
end

return GridBox
