local capi = {screen=screen,client=client,mouse=mouse, keygrabber = keygrabber}
local math = math
local wibox        = require( "wibox"           )
local awful        = require( "awful"           )
local beautiful    = require( "beautiful"       )
local surface      = require( "gears.surface"   )
local pango        = require( "lgi"             ).Pango
local pangocairo   = require( "lgi"             ).PangoCairo
local mouse        = require( "collision.mouse" )
local util         = require( "collision.util"  )
local shape        = require( "gears.shape"     )
local scale        = pango.SCALE

local module = {}

local wiboxes = {}
local wiboxes_s = setmetatable({},{__mode="k"})
local bgs = {}
local size  = 75
local pss   = capi.mouse.screen

-- Keep an index of the last selection client for each screen
local last_clients = setmetatable({},{__mode="kv"})
local last_clients_coords = {}

local screens,screens_inv = util.get_ordered_screens()

local function current_screen(focus)
  return (not focus) and capi.mouse.screen or (capi.client.focus and capi.client.focus.screen or capi.mouse.screen)
end

local function fit(self,_,width,height)
    -- Compute the optimal font size
    local padding   = self._private.padding or 10
    local tm        = self._private.top_margin or 0
    height          = math.max(0, height - tm)
    local min       = math.min(width, height)
    local font_size = math.max(0, min - 2*padding)

    self._private.desc:set_size( font_size * scale )
    self._private.layout:set_font_description(self._private.desc)

    local _, geo = self._private.layout:get_pixel_extents()

    -- The extra "min" is in case a font "lie" and is too big.
    return math.min(geo.x+geo.width, min), math.min(geo.y+geo.height, min) + tm
end

local function draw(self, _, cr, width, height)
    -- Some layouts never call fit()...
    fit(self, context, width, height)

    local _, geo = self._private.layout:get_pixel_extents()
    local tm     = self._private.top_margin or 0
    local sz     = math.min(width, height - tm)

    -- Translate the canvas to center the text
    local dy = ( sz - geo.height  )/2 - geo.y + tm
    local dx = ( sz - geo.width   )/2 - geo.x
    cr:move_to(dx, dy)

    -- Show the text
    cr:show_layout(self._private.layout)
end

local function set_screen(self, s)
    self._private.layout.text = tostring(s.index)
    self:emit_signal("widget::layout_changed")
end

local function set_padding(self, padding)
    self._private.padding = padding
    self:emit_signal("widget::layout_changed")
end

local function set_top_margin(margin)
    self._private.top_margin = margin or 0
    self:emit_signal("widget::layout_changed")
end

local function new_constrained_text(s)
    local wdg = wibox.widget.base.empty_widget()

    -- Add the basic functions
    rawset(wdg, "draw"           , draw           )
    rawset(wdg, "fit"            , fit            )
    rawset(wdg, "set_screen"     , set_screen     )
    rawset(wdg, "set_padding"    , set_padding    )
    rawset(wdg, "set_top_margin" , set_top_margin )

    -- Create the pango objects
    local pango_crx = pangocairo.font_map_get_default():create_context()
    local pango_l   = pango.Layout.new(pango_crx)
    local desc      = pango.FontDescription()
    desc:set_family( "Verdana"         )
    desc:set_weight( pango.Weight.BOLD )

    wdg._private.layout = pango_l
    wdg._private.desc   = desc
    wdg:emit_signal("widget::layout_changed")

    if s then set_screen(wdg, s) end

    return wdg
end

local function create_wibox(s)
    s = capi.screen[s]

    if wiboxes_s[s] then return wiboxes_s[s] end

    local wa = s.workarea

    -- Create a round wibox
    local w = wibox {
        width  = size,
        height = size,
        x      = math.floor(wa.x+wa.width /2-size/2),
        y      = math.floor(wa.y+wa.height/2-size/2),
        ontop  = true
    }

    -- Theme options
    local sh       = beautiful.collision_screen_shape or shape.circle
    local bw       = beautiful.collision_screen_border_width
    local bc       = beautiful.collision_screen_border_color
    local padding  = beautiful.collision_screen_padding or 10
    local bg       = beautiful.collision_screen_bg or beautiful.bg_alternate or "#ff0000"
    local fg       = beautiful.collision_screen_fg or beautiful.fg_normal    or "#0000ff"
    local bg_focus = beautiful.collision_screen_bg_focus or beautiful.bg_urgent or "#ff0000"
    local fg_focus = beautiful.collision_screen_fg_focus or beautiful.fg_urgent or "#ff0000"

    -- Setup the widgets
    w:setup {
        {
            nil,
            {
                nil,
                {
                    screen  = s,
                    padding = padding,
                    widget  = new_constrained_text
                },
                nil,
                layout = wibox.layout.align.vertical
            },
            nil,
            layout = wibox.layout.align.horizontal
        },
        bg                 = bg,
        shape              = sh or shape.circle,
        shape_border_width = bw,
        shape_border_color = bc,
        id                 = "main_background",
        widget             = wibox.container.background
    }

    -- Set the wibox shape
    surface.apply_shape_bounding(w, sh)

    wiboxes_s[s] = w
    wiboxes[s.index] = w --DEPRECATED
    bgs[s] = w:get_children_by_id("main_background")[1]

    return w
end

-- Hopefully, the wiboxes will be gargabe collected
local init = false
local function init_wiboxes()
    if init then return end

    awful.screen.connect_for_each_screen(function(s)
        create_wibox(s)
    end)

    init = true

    return true
end

local function select_screen(scr_index,move,old_screen)
  if capi.screen[scr_index] ~= capi.screen[old_screen or 1] then
    local c = last_clients[capi.screen[scr_index]]

    -- If the client is leaked elsewhere, prevent an error message
    if c and not pcall(function() return c.valid end) and not c.valid then
      last_clients[capi.screen[scr_index]] = nil
      c = nil
    end

    if c and c.valid and c:isvisible() then
      local geom = c:geometry()
      if last_clients_coords[scr_index] and last_clients_coords[scr_index].client == c then
        capi.mouse.coords(last_clients_coords[scr_index])
      else
        capi.mouse.coords({x=geom.x+geom.width/2,y=geom.y+geom.height/2+55})
      end
    else
      local geom = capi.screen[scr_index].geometry
      capi.mouse.coords({x=geom.x+geom.width/2,y=geom.y+geom.height/2+55})
    end
    mouse.highlight()
  end

  if move then
    local t = capi.screen[scr_index].selected_tag
    if t then
      t.screen = old_screen
      awful.tag.viewonly(t)
    end
  else
    local c = capi.mouse.current_client
    if c then
      capi.client.focus = c
    end
  end

  return capi.screen[scr_index]
end

local function in_rect(c,point)
  if not c then return true end
  local geo = c:geometry()
  return (
    geo.x < point.x             and geo.y < point.y              and
    geo.x + geo.width > point.x and geo.y + geo.height > point.y
  )
end

local function save_cursor_position()
  local coords = capi.mouse.coords()
  local c = capi.client.focus
  -- Be sure that that mouse in inside of the selected client before doing that
  if c and in_rect(c,coords) then
    last_clients_coords[c.screen] = {
      client = c,
      x      = coords.x,
      y      = coords.y,
    }
  else
    last_clients_coords[capi.mouse.screen] = nil
  end
end

local function next_screen(ss,dir,move)
  if capi.screen.count() == 1 then return 1 end

  local scr_index = capi.screen[screens_inv[ss]].index

  if type(scr_index) == "screen" then
    scr_index = scr_index.index
  end
  if dir == "left" then
    scr_index = scr_index == 1 and #screens or scr_index - 1
  elseif dir == "right" then
    scr_index = scr_index == #screens and 1 or scr_index+1
  end

  return select_screen(screens_inv[capi.screen[scr_index]],move,ss)
end

function module.display(_,dir,move)
  if #wiboxes == 0 then
    init_wiboxes()
  end
  save_cursor_position()
  module.reload(nil,dir)
  local ss = current_screen(move)
  next_screen(ss,dir,move)
  module.reload(nil,dir)
end

local function highlight_screen(ss)
  ss = capi.screen[ss]
  if pss ~= ss then

    local bg       = beautiful.collision_screen_bg or beautiful.bg_alternate or "#ff0000"
    local fg       = beautiful.collision_screen_fg or beautiful.fg_normal    or "#0000ff"
    local bg_focus = beautiful.collision_screen_bg_focus or beautiful.bg_urgent or "#ff0000"
    local fg_focus = beautiful.collision_screen_fg_focus or beautiful.fg_urgent or "#ff0000"

    bgs[pss].bg = bg
    bgs[pss].fg = fg

    pss = ss

    bgs[ss].bg = bg_focus
    bgs[ss].fg = fg_focus
  end
end

function module.hide()
  if #wiboxes == 0 then return end

  for s=1, capi.screen.count() do
    wiboxes[s].visible = false
  end
  mouse.hide()
end

local function show()
  for s=1, capi.screen.count() do
    wiboxes[s].visible = true
  end
end

function module.reload(mod,dir,_,_,move)
  local ss = current_screen(move)
  if dir then
    ss = next_screen(ss,dir:lower(),move or (mod and #mod == 4))
  end

  highlight_screen(ss)

  show()

  return true
end

function module.select_screen(idx)
    save_cursor_position()
    select_screen(screens_inv[idx],false)
    if #wiboxes == 0 then
        init_wiboxes()
    end

    highlight_screen(screens_inv[idx])

    show()

    capi.keygrabber.run(function(_, _, event)
        if event == "release" then
            module.hide()
            mouse.hide()
            capi.keygrabber.stop()
            return false
        end
        return true
    end)
end

-- Make sure this keeps working when a new screen is added
awful.screen.connect_for_each_screen(function(s)
    if next(wiboxes) then
        create_wibox(s)
    end
end)

capi.client.connect_signal("focus",function(c)
    last_clients[c.screen] = c
end)

return module
-- kate: space-indent on; indent-width 4; replace-tabs on;
