--------------------------------------------------------------------------------
-- Unsupported Spaces extension. Uses private APIs but works okay.
-- (http://github.com/asmagill/hammerspoon_asm.undocumented)
spaces = require("hs._asm.undocumented.spaces")

-- Get output of a bash command
function os.capture(cmd)
  local f = assert(io.popen(cmd, 'r'))
  local s = assert(f:read('*a'))
  f:close()
  s = string.gsub(s, '^%s+', '')
  s = string.gsub(s, '%s+$', '')
  s = string.gsub(s, '[\n\r]+', ' ')
  return s
end

-- Update the fan and temp. Needs iStats CLI tool from homebrew.
local function updateStats()
  fanSpeed = os.capture("iStats fan speed | cut -c14- | sed 's/\\..*//'")
  temp = os.capture("iStats cpu temp | cut -c11- | sed 's/\\..*//'")
end

-- Makes (and updates) the topbar menu filled with the current Space, the
-- temperature and the fan speed. The Space only updates if the space is changed
-- with the Hammerspoon shortcut (option + arrows does not work). 
local function makeStatsMenu(calledFromWhere)
  if statsMenu == nil then
    statsMenu = hs.menubar.new()
  end
  if calledFromWhere == "spaceChange" then
    currentSpace = tostring(spaces.currentSpace()) 
  else
    updateStats()
  end
  statsMenu:setTitle("Space " .. currentSpace .. " | Fan: " .. fanSpeed .. " | Temp: " .. temp)
end

-- Gets a list of windows and iterates until the window title is non-empty.
-- This avoids focusing the hidden windows apparently placed on top of all
-- Google Chrome windows. It also checks if the empty title belongs to Chrome,
-- because some apps don't give any of their windows a title, and should still
-- be focused.
local function spaceChange()
  makeStatsMenu("spaceChange")
  visibleWindows = hs.window.orderedWindows()
  for i, window in ipairs(visibleWindows) do
    if window:application():title() == "Google Chrome" then
      if window:title() ~= "" then
        window:focus()
        break
      end
    else
      window:focus()
      break
    end
  end
end

--------------------------------------------------------------------------------
-- Options

-- How often to update Fan and Temp
updateStatsInterval = 20
statsMenuTimer = hs.timer.new(updateStatsInterval, makeStatsMenu)
statsMenuTimer:start()

-- instant window resizing
hs.window.animationDuration = 0

local mash = {"cmd", "alt", "ctrl"}
local mashshift = {"cmd", "alt", "ctrl", "shift"}


--------------------------------------------------------------------------------
-- Applications launch

local function newChromeWindow()
  os.execute("/Applications/'Google Chrome.app'/Contents/MacOS/'Google Chrome' --new-window")
  visibleWindows = hs.window.orderedWindows()
  visibleWindows[2]:focus()
end

hs.hotkey.bind(mash, "c", function()
  newChromeWindow()
end)


--------------------------------------------------------------------------------
-- snap windows to edges
-- (and make them half-width or half-height)

hs.hotkey.bind(mash, "y", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  f.x = max.x
  f.y = max.y
  f.w = max.w / 2
  f.h = max.h
  win:setFrame(f)
end)

hs.hotkey.bind(mash, "u", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  f.x = max.x
  f.y = max.y + (max.h / 2)
  f.w = max.w
  f.h = max.h / 2
  win:setFrame(f)
end)

hs.hotkey.bind(mash, "i", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  f.x = max.x
  f.y = max.y
  f.w = max.w
  f.h = max.h / 2
  win:setFrame(f)
end)

hs.hotkey.bind(mash, "o", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  f.x = max.x + (max.w / 2)
  f.y = max.y
  f.w = max.w / 2
  f.h = max.h
  win:setFrame(f)
end)

--------------------------------------------------------------------------------
-- Manual window moving and resizing
-- Credit to GitHub user: ztomer

hs.grid.MARGINX = 0
hs.grid.MARGINY = 0
hs.grid.GRIDHEIGHT = 18
hs.grid.GRIDWIDTH = 18

--Alter gridsize
hs.hotkey.bind(mashshift, '=', function() hs.grid.adjustHeight( 1) end)
hs.hotkey.bind(mashshift, '-', function() hs.grid.adjustHeight(-1) end)
hs.hotkey.bind(mash, '=', function() hs.grid.adjustWidth( 1) end)
hs.hotkey.bind(mash, '-', function() hs.grid.adjustWidth(-1) end)

--Snap windows
hs.hotkey.bind(mash, ';', function() hs.grid.snap(hs.window.focusedWindow()) end)
hs.hotkey.bind(mash, "'", function() hs.fnutils.map(hs.window.visibleWindows(), hs.grid.snap) end)

--Move windows
hs.hotkey.bind(mash, 'j', hs.grid.pushWindowDown)
hs.hotkey.bind(mash, 'k', hs.grid.pushWindowUp)
hs.hotkey.bind(mash, 'h', hs.grid.pushWindowLeft)
hs.hotkey.bind(mash, 'l', hs.grid.pushWindowRight)

--resize windows
hs.hotkey.bind(mashshift, 'k', hs.grid.resizeWindowShorter)
hs.hotkey.bind(mashshift, 'j', hs.grid.resizeWindowTaller)
hs.hotkey.bind(mashshift, 'l', hs.grid.resizeWindowWider)
hs.hotkey.bind(mashshift, 'h', hs.grid.resizeWindowThinner)

-- toggle window zoom (acts like Alt+Shift+GreenPlusButton)
hs.hotkey.bind(mash, "m", function()
  local win = hs.window.focusedWindow()
  local frame = win:frame()
  local id = win:id()

  -- init table to save window state
  savedwin = savedwin or {}
  savedwin[id] = savedwin[id] or {}

  if (savedwin[id].maximized == nil or savedwin[id].maximized == false) then
    savedwin[id].frame = frame
    savedwin[id].maximized = true
    win:maximize()
  else
    savedwin[id].maximized = false
    win:setFrame(savedwin[id].frame)
    savedwin[id] = nil
  end
end)

--------------------------------------------------------------------------------
-- switch Spaces
hs.hotkey.bind(mash, '1', function()
  spaces.moveToSpace("1")
  spaceChange()
end)
hs.hotkey.bind(mash, '2', function()
  spaces.moveToSpace("2")
  spaceChange()
end)
hs.hotkey.bind(mash, '3', function()
  spaces.moveToSpace("3")
  spaceChange()
end)
hs.hotkey.bind(mash, '4', function()
  spaces.moveToSpace("4")
  spaceChange()
end)
hs.hotkey.bind(mash, '5', function()
  spaces.moveToSpace("5")
  spaceChange()
end)
hs.hotkey.bind(mash, '6', function()
  spaces.moveToSpace("6")
  spaceChange()
end)


--------------------------------------------------------------------------------
-- Initialize
currentSpace = tostring(spaces.currentSpace())
updateStats()
makeStatsMenu()