--[[
  Copyright 2017-2019 Stefano Mazzucco <stefano AT curso DOT re>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

  This program was inspired by the
  [Awesome Pulseaudio Widget (APW)](https://github.com/mokasin/apw)
]]
local string = string

local awful = require("awful")
local gears = require("gears")

local wibox = require("wibox")
local naughty = require("naughty")

local pulse = require("pulseaudio_dbus")

local lgi = require('lgi')
local icon_theme = lgi.Gtk.IconTheme.get_default()
local IconLookupFlags = lgi.Gtk.IconLookupFlags

local icon_size = 64
local icon_flags = {IconLookupFlags.GENERIC_FALLBACK}

local function lookup_icon(name)
  return icon_theme:lookup_icon(name, icon_size, icon_flags)
end

local function load_icon(icon)
  if icon ~= nil then
    return icon:load_surface()
  end
end

local icon = {
  high = lookup_icon("audio-volume-high-symbolic"),
  med = lookup_icon("audio-volume-medium-symbolic"),
  low = lookup_icon("audio-volume-low-symbolic"),
  muted = lookup_icon("audio-volume-muted-symbolic"),
}

local widget = wibox.widget {
  resize = true,
  widget = wibox.widget.imagebox
}

widget.tooltip = awful.tooltip({ objects = { widget },})

function widget:update_appearance(v)
  local i, msg

  if v == "Muted" then
    msg = v
    i = load_icon(icon.muted)
  else
    v = v == "Unmuted" and self.sink:get_volume_percent()[1] or tonumber(v)
    msg = string.format("%d%%", v)
    if v <= 33 then
      i = load_icon(icon.low)
    elseif v <= 66 then
      i = load_icon(icon.med)
    else
      i = load_icon(icon.high)
    end
  end

  self.image = i
  self.tooltip:set_text(msg)

end

function widget:notify(v)
  local msg = tonumber(v) and string.format("%d%%", v) or v

  if self.notification then
    naughty.destroy(self.notification, naughty.notificationClosedReason.dismissedByCommand)
  end

  self.notification = naughty.notify(
    {
      text=msg,
      timeout=self.notification_timeout_seconds
    }
  )

end

function widget:update_sink(object_path)
  self.sink = pulse.get_device(self.connection, object_path)
end

function widget:update_sources(sources)
  for _, source_path in ipairs(sources) do
    local s = pulse.get_device(self.connection, source_path)
    if s.Name and not s.Name:match("%.monitor$") then
      self.source = s
      break
    else
      self.source = nil
    end
  end
end

function widget.volume_up()
  if not widget.sink:is_muted() then
    widget.sink:volume_up()
  end
end

function widget.volume_down()
  if not widget.sink:is_muted() then
    widget.sink:volume_down()
  end
end

function widget.toggle_muted()
  widget.sink:toggle_muted()
end

function widget.volume_up_mic()
  if widget.source and not widget.source:is_muted() then
    widget.source:volume_up()
  end
end

function widget.volume_down_mic()
  if widget.source and not widget.source:is_muted() then
    widget.source:volume_down()
  end
end

function widget.toggle_muted_mic()
  if widget.source then
    widget.source:toggle_muted()
  end
end

widget:buttons(gears.table.join(
                 awful.button({ }, 1, widget.toggle_muted),
                 awful.button({ }, 3, function () awful.spawn(widget.mixer) end),
                 awful.button({ }, 4, widget.volume_up),
                 awful.button({ }, 5, widget.volume_down)))

function widget:connect_device(device)
  if not device then
    return
  end

  if device.signals.VolumeUpdated then
    device:connect_signal(
      function (this, volume)
        -- FIXME: BaseVolume for sources (i.e. microphones) won't give the correct percentage
        local v = math.ceil(tonumber(volume[1]) / this.BaseVolume * 100)
        if this.object_path == self.sink.object_path then
          self:update_appearance(v)
          self:notify(v)
        end
      end,
      "VolumeUpdated"
    )
  end

  if device.signals.MuteUpdated then
    device:connect_signal(
      function (this, is_mute)
        local m = is_mute and "Muted" or "Unmuted"
        if this.object_path == self.sink.object_path then
          self:update_appearance(m)
          self:notify(m)
        end
      end,
      "MuteUpdated"
    )
  end
end

function widget:init()
  local status, address = pcall(pulse.get_address)
  if not status then
    naughty.notify({title="Error while loading the PulseAudio widget",
                    text=address,
                    preset=naughty.config.presets.critical})
    return self
  end

  self.mixer = "pavucontrol"
  self.notification_timeout_seconds = 1

  self.connection = pulse.get_connection(address)
  self.core = pulse.get_core(self.connection)

  -- listen on ALL objects as sinks and sources may change
  self.core:ListenForSignal("org.PulseAudio.Core1.Device.VolumeUpdated", {})
  self.core:ListenForSignal("org.PulseAudio.Core1.Device.MuteUpdated", {})

  self.core:ListenForSignal("org.PulseAudio.Core1.NewSink", {self.core.object_path})
  self.core:connect_signal(
    function (_, newsink)
      self:update_sink(newsink)
      self:connect_device(self.sink)
      local volume = self.sink:is_muted() and "Muted" or self.sink:get_volume_percent()[1]
      self:update_appearance(volume)
    end,
    "NewSink"
  )

  self.core:ListenForSignal("org.PulseAudio.Core1.NewSource", {self.core.object_path})
  self.core:connect_signal(
    function (_, newsource)
      self:update_sources({newsource})
      self:connect_device(self.source)
    end,
    "NewSource"
  )

  self:update_sources(self.core:get_sources())
  self:connect_device(self.source)

  local sink_path = assert(self.core:get_sinks()[1], "No sinks found")
  self:update_sink(sink_path)
  self:connect_device(self.sink)

  local volume = self.sink:is_muted() and "Muted" or self.sink:get_volume_percent()[1]
  self:update_appearance(volume)

  self.__index = self

  return self
end

return widget:init()
