--- === MenubarFlag ===
---
--- Color the menubar according to the current keyboard layout
---
--- Download: [https://github.com/Hammerspoon/Spoons/raw/master/Spoons/MenubarFlag.spoon.zip](https://github.com/Hammerspoon/Spoons/raw/master/Spoons/MenubarFlag.spoon.zip)
---
--- Functionality inspired by [ShowyEdge](https://pqrs.org/osx/ShowyEdge/index.html.en)

local obj={}
obj.__index = obj

local draw = require "hs.drawing"
local col = draw.color.x11

-- Metadata
obj.name = "MenubarFlag"
obj.version = "0.1"
obj.author = "Diego Zamboni <diego@zzamboni.org>"
obj.homepage = "https://github.com/Hammerspoon/Spoons"
obj.license = "MIT - https://opensource.org/licenses/MIT"

--- MenubarFlag.allScreens
--- Variable
--- Boolean to specify whether the indicators should be shown on all monitors or just the current one. Defaults to `true`
obj.allScreens = true

--- MenubarFlag.indicatorHeight
--- Variable
--- Number to specify the height of the indicator. Specify 0.0-1.0 to specify a percentage of the height of the menu bar, larger values indicate a fixed height in pixels. Defaults to 1.0
obj.indicatorHeight = 1.0

--- MenubarFlag.indicatorAlpha
--- Variable
--- Number to specify the indicator transparency (0.0 - invisible; 1.0 - fully opaque). Defaults to 0.3
obj.indicatorAlpha = 0.3

--- MenubarFlag.indicatorInAllSpaces
--- Variable
--- Boolean to specify whether the indicator should be shown in all spaces (this includes full-screen mode). Defaults to `true`
obj.indicatorInAllSpaces = true

--- MenubarFlag.colors
--- Variable
--- Table that contains the configuration of indicator colors
---
--- Notes:
---  * The table below indicates the colors to use for a given keyboard layout.
---  * The index is the name of the layout as it appears in the input source menu.
---  * The value of each indicator is a table made of an arbitrary number of segments, which will be distributed evenly across the width of the screen.
---  * Each segment must be a valid `hs.drawing.color` specification (most commonly, you should just use the named colors from within the tables). If a layout is not found, then the indicators are removed when that layout is active.
---  * Indicator specs can be static flag-like:
--- ```
---   Spanish = {col.green, col.white, col.red},
---   German = {col.black, col.red, col.yellow},
--- ```
--- or complex, programmatically-generated:
--- ```
--- ["U.S."] = (
---    function() res={}
---       for i = 0,10,1 do
---          table.insert(res, col.blue)
---          table.insert(res, col.white)
---          table.insert(res, col.red)
---       end
---       return res
---    end)()
--- ```
--- or solid colors:
--- ```
---   Spanish = {col.red},
---   German = {col.yellow},
--- ```
---  * Contributions of indicator specs are welcome!
obj.colors = {
   ["U.S."] = { }, -- empty list or no table entry means "no indicator"
   Spanish = {col.red, col.yellow, col.red},
   German = {col.black, col.red, col.yellow},
}

--- MenubarFlag.timerFreq
--- Variable
--- Number to indicate how frequently (in seconds) should the menubar indicator be updated. Defaults to 1.0.
---
--- Notes:
---  * Sometimes Hammerspoon misses the callback when the keyboard layout changes. As a workaround, MenuBarFlag can automatically update the indicator at a fixed frequency.
---  * The timer can be disabled by setting this parameter to 0.
obj.timerFreq = 1.0

obj.logger = hs.logger.new('MenubarFlag')
obj.timer = nil
----------------------------------------------------------------------

-- Internal variables
local prevlayout = nil
local ind = nil

-- Initialize the empty indicator table
function initIndicators()
   prevlayout = nil
   if ind ~= nil then
      delIndicators()
   end
   ind = {}
end

-- Delete existing indicator objects
function delIndicators()
   if ind ~= nil then
      for i,v in ipairs(ind) do
         if v ~= nil then
            v:delete()
         end
      end
      ind = nil
   end
end

--- MenubarFlag:drawIndicators(src)
--- Method
--- Draw the indicators corresponding to the given layout name
---
--- Parameters:
---  * src - name of the layout to draw. If the given element exists in `MenubarFlag.colors`, it will be drawn. If it does not exist, then the indicators will be removed from the screen.
---
--- Returns:
---  * The MenubarFlag object
function obj:drawIndicators(src)
   --   hs.alert.show("in drawindicators src=" .. src .. "  prevlayout=" .. (prevlayout or "nil"))

   if src ~= prevlayout then
      initIndicators()

      def = self.colors[src]
      self.logger.df("Indicator definition for %s: %s", src, hs.inspect(def))
      if def ~= nil then
         if self.allScreens then
            screens = hs.screen.allScreens()
         else
            screens = { hs.screen.mainScreen() }
         end
         for i,screen in ipairs(screens) do
            local screeng = screen:fullFrame()
            local width = screeng.w / #def
            for i,v in ipairs(def) do
               if self.indicatorHeight >= 0.0 and self.indicatorHeight <= 1.0 then
                  height = self.indicatorHeight*(screen:frame().y - screeng.y)
               else
                  height = self.indicatorHeight
               end
               c = draw.rectangle(hs.geometry.rect(screeng.x+(width*(i-1)), screeng.y,
                                                   width, height))
               c:setFillColor(v)
               c:setFill(true)
               c:setAlpha(self.indicatorAlpha)
               c:setLevel(draw.windowLevels.overlay)
               c:setStroke(false)
               if self.indicatorInAllSpaces then
                  c:setBehavior(draw.windowBehaviors.canJoinAllSpaces)
               end
               c:show()
               table.insert(ind, c)
            end
         end
      else
         self.logger.df("Removing indicators for %s because there is no color definitions for it.", src)
         delIndicators()
      end
   end

   prevlayout = src

   return self
end

--- MenubarFlag:getLayoutAndDrawindicators()
--- Method
--- Draw indicators for the current keyboard method or layout
---
--- Parameters:
---  * None
---
--- Returns:
---  * The MenubarFlag object
function obj:getLayoutAndDrawIndicators()
   return self:drawIndicators(hs.keycodes.currentMethod() or hs.keycodes.currentLayout())
end

--- MenubarFlag:start()
--- Method
--- Start the keyboard layout watcher to draw the menubar indicators.
---
--- Parameters:
---  * None
function obj:start()
   initIndicators()
   self:getLayoutAndDrawIndicators()
   hs.keycodes.inputSourceChanged(function()
         self:getLayoutAndDrawIndicators()
   end)
   -- This solves the problem that the callback would not be called until the second layout change after a restart
   hs.focus()
   if (self.timerFreq > 0.0) then
     self.timer = hs.timer.new(self.timerFreq, function() self:getLayoutAndDrawIndicators() end):start()
   end
   return self
end

--- MenubarFlag:stop()
--- Method
--- Remove indicators and stop the keyboard layout watcher
---
--- Parameters:
---  * None
function obj:stop()
  delIndicators()
  if self.timer ~= nil then
    self.timer:stop()
    self.timer = nil
  end
  hs.keycodes.inputSourceChanged(nil)
  return self
end

return obj
