--- === Seal.plugins.apps ===
---
--- A plugin to add launchable apps/scripts, making Seal act as a launch bar
local obj = {}
obj.__index = obj
obj.__name = "seal_apps"
obj.appCache = {}

--- Seal.plugins.apps.appSearchPaths
--- Variable
--- Table containing the paths to search for launchable items
---
--- Notes:
---  * If you change this, you will need to call `spoon.Seal.plugins.apps:restart()` to force Spotlight to search for new items.
obj.appSearchPaths = {
   "/Applications",
   "/System/Applications",
   "~/Applications",
   "/Developer/Applications",
   "/Applications/Xcode.app/Contents/Applications",
   "/System/Library/PreferencePanes",
   "/Library/PreferencePanes",
   "~/Library/PreferencePanes",
   "/System/Library/CoreServices/Applications",
   "/System/Library/CoreServices/",
   "/usr/local/Cellar",
   "/Library/Scripts",
   "~/Library/Scripts"
}

local modifyNameMap = function(info, add)
   for _, item in ipairs(info) do
      icon = nil
      local displayname = item.kMDItemDisplayName or hs.fs.displayName(item.kMDItemPath)
      displayname = displayname:gsub("%.app$", "", 1)
      if string.find(item.kMDItemPath, "%.prefPane$") then
         displayname = displayname .. " preferences"
         if add then
            icon = hs.image.iconForFile(item.kMDItemPath)
         end
      end
      if add then
         bundleID = item.kMDItemCFBundleIdentifier
         if (not icon) and (bundleID) then
           icon = hs.image.imageFromAppBundle(bundleID)
         end
         obj.appCache[displayname] = {
            path = item.kMDItemPath,
            bundleID = bundleID,
            icon = icon
         }
      else
         obj.appCache[displayname] = nil
      end
   end
end

local updateNameMap = function(obj, msg, info)
   if info then
      -- all three can occur in either message, so check them all!
      if info.kMDQueryUpdateAddedItems   then modifyNameMap(info.kMDQueryUpdateAddedItems,   true)  end
      if info.kMDQueryUpdateChangedItems then modifyNameMap(info.kMDQueryUpdateChangedItems, true)  end
      if info.kMDQueryUpdateRemovedItems then modifyNameMap(info.kMDQueryUpdateRemovedItems, false) end
   else
      -- shouldn't happen for didUpdate or inProgress
      print("~~~ userInfo from SpotLight was empty for " .. msg)
   end
end

--- Seal.plugins.apps:start()
--- Method
--- Starts the Spotlight app searcher
---
--- Parameters:
---  * None
---
--- Returns:
---  * None
---
--- Notes:
---  * This is called automatically when the plugin is loaded
function obj:start()
    obj.spotlight = hs.spotlight.new():queryString([[ (kMDItemContentType = "com.apple.application-bundle") || (kMDItemContentType = "com.apple.systempreference.prefpane")  || (kMDItemContentType = "com.apple.applescript.text")  || (kMDItemContentType = "com.apple.applescript.script") ]])
       :callbackMessages("didUpdate", "inProgress")
       :setCallback(updateNameMap)
       :searchScopes(obj.appSearchPaths)
       :start()
end

--- Seal.plugins.apps:stop()
--- Method
--- Stops the Spotlight app searcher
---
--- Parameters:
---  * None
---
--- Returns:
---  * None
function obj:stop()
    obj.spotlight:stop()
    obj.spotlight = nil
    obj.appCache = {}
end

--- Seal.plugins.apps:restart()
--- Method
--- Restarts the Spotlight app searcher
---
--- Parameters:
---  * None
---
--- Returns:
---  * None
function obj:restart()
    self:stop()
    self:start()
end

hs.application.enableSpotlightForNameSearches(true)
obj:start()

function obj:commands()
   return {kill = {
              cmd = "kill",
              fn = obj.choicesKillCommand,
              plugin = obj.__name,
              name = "Kill",
              description = "Kill an application"
                  },
           reveal = {
              cmd = "reveal",
              fn = obj.choicesRevealCommand,
              plugin = obj.__name,
              name = "Reveal",
              description = "Reveal an application in the Finder"
           }
   }
end

function obj:bare()
   return self.choicesApps
end

function obj.choicesApps(query)
   local choices = {}
   if query == nil or query == "" then
      return choices
   end
   for name,app in pairs(obj.appCache) do
      if string.match(name:lower(), query:lower()) then
         local choice = {}
         local instances = {}
         if app["bundleID"] then
            instances = hs.application.applicationsForBundleID(app["bundleID"])
         end
         if #instances > 0 then
            choice["text"] = name .. " (Running)"
         else
            choice["text"] = name
         end
         choice["subText"] = app["path"]
         if app["icon"] then
            choice["image"] = app["icon"]
         end
         choice["path"] = app["path"]
         choice["uuid"] = obj.__name.."__"..(app["bundleID"] or name)
         choice["plugin"] = obj.__name
         choice["type"] = "launchOrFocus"
         table.insert(choices, choice)
      end
   end
   return choices
end

function obj.choicesKillCommand(query)
   local choices = {}
   if query == nil then
      return choices
   end
   local apps = hs.application.runningApplications()
   for k, app in pairs(apps) do
      local name = app:name()
      if string.match(name:lower(), query:lower()) and app:mainWindow() then
         local choice = {}
         choice["text"] = "Kill "..name
         choice["subText"] = app:path().." PID: "..app:pid()
         choice["pid"] = app:pid()
         choice["plugin"] = obj.__name
         choice["type"] = "kill"
         choice["image"] = hs.image.imageFromAppBundle(app:bundleID())
         table.insert(choices, choice)
      end
   end
   return choices
end

function obj.choicesRevealCommand(query)
   local choices = {}
   if query == nil then
      return choices
   end
   local apps = obj.choicesApps(query)
   for k, app in pairs(apps) do
      local name = app.text
      if string.match(name:lower(), query:lower()) then
         local choice = {}
         choice["text"] = "Reveal "..name
         choice["path"] = app.path
         choice["subText"] = app.path
         choice["plugin"] = obj.__name
         choice["type"] = "reveal"
         if app.image then
            choice["image"] = app.image
         end
         table.insert(choices, choice)
      end
   end
   return choices
end

function obj.completionCallback(rowInfo)
   if rowInfo["type"] == "launchOrFocus" then
      if string.find(rowInfo["path"], "%.applescript$") or string.find(rowInfo["path"], "%.scpt$") then
         hs.task.new("/usr/bin/osascript", nil, { rowInfo["path"] }):start()
      else
         hs.task.new("/usr/bin/open", nil, { rowInfo["path"] }):start()
      end
   elseif rowInfo["type"] == "kill" then
      hs.application.get(rowInfo["pid"]):kill()
   elseif rowInfo["type"] == "reveal" then
      hs.osascript.applescript(string.format([[tell application "Finder" to reveal (POSIX file "%s")]], rowInfo["path"]))
      hs.application.launchOrFocus("Finder")
   end
end

return obj
