---------------------------------------------------------------------------
-- @author Alexander Yakushev <yakushev.alex@gmail.com>
-- @copyright 2010-2013 Alexander Yakushev
-- @release v1.2.4
---------------------------------------------------------------------------

local wibox = require("wibox")
local awful = require('awful')
local beautiful = require('beautiful')
local naughty = require('naughty')
local format = string.format

local module_path = (...):match ("(.+/)[^/]+$") or ""

local awesompd = {}

-- Function for checking icons and modules. Checks if a file exists,
-- and if it does, returns the path to file, nil otherwise.
function awesompd.try_load(file)
   if awful.util.file_readable(file) then
      return file
   end
end

-- Function for loading modules.
function awesompd.try_require(module)
   if awesompd.try_load(awful.util.getdir("config") .. '/'..
                        module_path .. module .. ".lua") then
      return require(module_path .. module)
   else
      return require(module)
   end
end

local utf8 = awesompd.try_require("utf8")
asyncshell = awesompd.try_require("asyncshell")
local jamendo = awesompd.try_require("jamendo")

-- Constants
awesompd.PLAYING = "Playing"
awesompd.PAUSED = "Paused"
awesompd.STOPPED = "MPD stopped"
awesompd.DISCONNECTED = "Disconnected"

awesompd.MOUSE_LEFT = 1
awesompd.MOUSE_MIDDLE = 2
awesompd.MOUSE_RIGHT = 3
awesompd.MOUSE_SCROLL_UP = 4
awesompd.MOUSE_SCROLL_DOWN = 5

awesompd.NOTIFY_VOLUME = 1
awesompd.NOTIFY_REPEAT = 2
awesompd.NOTIFY_RANDOM = 3
awesompd.NOTIFY_SINGLE = 4
awesompd.NOTIFY_CONSUME = 5
awesompd.FORMAT_MP3 = jamendo.FORMAT_MP3
awesompd.FORMAT_OGG = jamendo.FORMAT_OGG
awesompd.ESCAPE_SYMBOL_MAPPING = {}
awesompd.ESCAPE_SYMBOL_MAPPING["&"] = "&amp;"
-- Menus do not handle symbol escaping correctly, so they need their
-- own mapping.
awesompd.ESCAPE_MENU_SYMBOL_MAPPING = {}
awesompd.ESCAPE_MENU_SYMBOL_MAPPING["&"] = "'n'"

-- /// Current track variables and functions /// 

-- Returns a string for the given track to be displayed in the widget
-- and notification.
function awesompd.get_display_name(track)
   if track.display_name then
      return track.display_name
   elseif track.artist_name and track.track_name then
      return track.artist_name .. " - " .. track.name
   end
end

-- Returns a track display name, album name (if exists) and album
-- release year (if exists).
function awesompd.get_extended_info(track)
   local result = awesompd.get_display_name(track)
   if track.album_name then
      result = result .. "\n" .. track.album_name
   end
   if track.year then
      result = result .. "\n" .. track.year
   end
   return result
end

-- Returns true if the current status is either PLAYING or PAUSED
function awesompd:playing_or_paused()
   return self.status == awesompd.PLAYING 
      or self.status == awesompd.PAUSED
end

-- /// Helper functions ///

-- Just like awful.util.pread, but takes an argument how to read like
-- "*line" or "*all".
function awesompd.pread(com, mode)
   local f = io.popen(com, 'r')
   local result = nil
   if f then
      result = f:read(mode)
      f:close()
   end
   return result
end

-- Slightly modified function awful.util.table.join.
function awesompd.ajoin(buttons)
    local result = {}
    for i = 1, #buttons do
        if buttons[i] then
            for k, v in pairs(buttons[i]) do
                if type(k) == "number" then
                    table.insert(result, v)
                else
                    result[k] = v
                end
            end
        end
    end
    return result
 end

-- Splits a given string with linebreaks into an array.
function awesompd.split(s)
   local l = { n = 0 }
   if s == "" then
      return l
   end
   s = s .. "\n"
   local f = function (s) 
                l.n = l.n + 1
		l[l.n] = s
	     end
   local p = "%s*(.-)%s*\n%s*"
   s = string.gsub(s,p,f)
   return l
end

-- Returns the given string if it is not nil or non-empty, otherwise
-- returns nil.
local function non_empty(s)
   if s and s ~= "" then
      return s
   end
end

-- Icons

function awesompd.load_icons(path)
   awesompd.ICONS = {}
   awesompd.ICONS.PLAY = awesompd.try_load(path .. "/play_icon.png")
   awesompd.ICONS.PAUSE = awesompd.try_load(path .. "/pause_icon.png")
   awesompd.ICONS.PLAY_PAUSE = awesompd.try_load(path .. "/play_pause_icon.png")
   awesompd.ICONS.STOP = awesompd.try_load(path .. "/stop_icon.png")
   awesompd.ICONS.NEXT = awesompd.try_load(path .. "/next_icon.png")
   awesompd.ICONS.PREV = awesompd.try_load(path .. "/prev_icon.png")
   awesompd.ICONS.CHECK = awesompd.try_load(path .. "/check_icon.png")
   awesompd.ICONS.RADIO = awesompd.try_load(path .. "/radio_icon.png")
   awesompd.ICONS.DEFAULT_ALBUM_COVER = 
      awesompd.try_load(path .. "/default_album_cover.png")
end

-- Function that returns a new awesompd object.
function awesompd:create()
-- Initialization
   local instance = {}
   setmetatable(instance,self)
   self.__index = self
   instance.current_server = 1
   instance.widget = wibox.layout.fixed.horizontal()
   instance.notification = nil
   instance.scroll_pos = 1
   instance.text = ""
   instance.to_notify = false
   instance.album_cover = nil
   instance.current_track = { }
   instance.recreate_menu = true
   instance.recreate_playback = true
   instance.recreate_list = true
   instance.recreate_servers = true
   instance.recreate_options = true
   instance.recreate_jamendo_formats = true
   instance.recreate_jamendo_order = true
   instance.recreate_jamendo_browse = true
   instance.current_number = 0
   instance.menu_shown = false
   instance.state_volume = "NaN"
   instance.state_repeat = "NaN"
   instance.state_random = "NaN"
   instance.state_single = "NaN"
   instance.state_consume = "NaN"

-- Default user options
   instance.servers = { { server = "localhost", port = 6600 } }
   instance.font = "Monospace"
   instance.font_color = beautiful.fg_normal
   instance.background = beautiful.bg_normal
   instance.scrolling = true
   instance.output_size = 30
   instance.update_interval = 10
   instance.path_to_icons = ""
   instance.ldecorator = " "
   instance.rdecorator = " "
   instance.jamendo_format = awesompd.FORMAT_MP3
   instance.show_album_cover = true
   instance.album_cover_size = 50
   instance.browser = "firefox"
-- Smart Update (sets timer to check/update widget near when the current track should end)
   instance.track_position = "00:00"
   instance.track_duration = "00:00"
   instance.smart_update_timer = timer({ timeout = instance.update_interval })
   -- Widget configuration
   instance.widget:connect_signal("mouse::enter", function(c)
	    instance:update_track()
	    instance:notify_track()
	 end)
   instance.widget:connect_signal("mouse::leave", function(c)
	    instance:hide_notification()
	 end)
   return instance
end

-- Registers timers for the widget
function awesompd:run()
   self.load_icons(self.path_to_icons)
   jamendo.set_current_format(self.jamendo_format)
   if self.album_cover_size > 100 then
      self.album_cover_size = 100
   end

   self.text_widget = wibox.widget.textbox()
   if self.widget_icon then
      self.icon_widget = wibox.widget.imagebox()
      self.icon_widget:set_image(self.widget_icon)
      self.widget:add(self.icon_widget)
   end
   self.widget:add(self.text_widget)

   self:update_track()
   self:check_playlists()

   if scheduler then
      scheduler.register_recurring("awesompd_scroll", 1,
                                   function() self:update_widget() end)
      scheduler.register_recurring("awesompd_update", self.update_interval,
                                   function() self:update_track() end)
   else
      self.update_widget_timer = timer({ timeout = 1 })
      self.update_widget_timer:connect_signal("timeout", function()
                                                 self:update_widget()
                                                         end)
      self.update_widget_timer:start()
      self.update_track_timer = timer({ timeout = self.update_interval })
      self.update_track_timer:connect_signal("timeout", function()
                                                self:update_track()
                                                        end)
      self.update_track_timer:start()
   end
end

-- Function that registers buttons on the widget.
function awesompd:register_buttons(buttons)
   widget_buttons = {}
   self.global_bindings = {}
   for b=1, #buttons do
      if type(buttons[b][1]) == "string" then
         mods = { buttons[b][1] }
      else
         mods = buttons[b][1]
      end
      if type(buttons[b][2]) == "number" then 
         -- This is a mousebinding, bind it to the widget
         table.insert(widget_buttons, 
                      awful.button(mods, buttons[b][2], buttons[b][3]))
      else 
         -- This is a global keybinding, remember it for later usage in append_global_keys
         table.insert(self.global_bindings, awful.key(mods, buttons[b][2], buttons[b][3]))
      end
   end
   self.widget:buttons(self.ajoin(widget_buttons))
end

-- Takes the current table with keybindings and adds widget's own
-- global keybindings that were specified in register_buttons.
-- If keytable is not specified, then adds bindings to default
-- globalkeys table. If specified, then adds bindings to keytable and
-- returns it.
function awesompd:append_global_keys(keytable)
   if keytable then
      for i = 1, #self.global_bindings do
         keytable = awful.util.table.join(keytable, self.global_bindings[i])
      end
      return keytable
   else
      for i = 1, #self.global_bindings do
         globalkeys = awful.util.table.join(globalkeys, self.global_bindings[i])
      end
   end
end

-- /// Group of mpc command functions ///

-- Returns a mpc command with all necessary parameters. Boolean
-- human_readable argument configures if the command special
-- formatting of the output (to be later used in parsing) should not
-- be used.
function awesompd:mpcquery(human_readable)
   local result =
      "mpc -h " .. self.servers[self.current_server].server ..
      " -p " .. self.servers[self.current_server].port .. " "
   if human_readable then
      return result
   else
      return result ..' -f "%file%-<>-%name%-<>-%title%-<>-%artist%-<>-%album%" '
   end
end

-- Takes a command to mpc and a hook that is provided with awesompd
-- instance and the result of command execution.
function awesompd:command(com,hook)
   local file = io.popen(self:mpcquery() .. com)
   if hook then
      hook(self,file)
   end
   file:close()
end

-- Takes a command to mpc and read mode and returns the result.
function awesompd:command_read(com, mode)
   mode = mode or "*line"
   self:command(com, function(_, f)
                        result = f:read(mode)
                     end)
   return result
end

function awesompd:command_playpause()
   return function()
             self:command("toggle",self.update_track)
          end
end

function awesompd:command_next_track()
   return function()
             self:command("next",self.update_track)
          end
end

function awesompd:command_prev_track()
   return function()
             self:command("seek 0")
             self:command("prev",self.update_track)
          end
end

function awesompd:command_stop()
   return function()
             self:command("stop",self.update_track)
          end
end

function awesompd:command_play_specific(n)
   return function()
             self:command("play " .. n,self.update_track)
          end
end

function awesompd:command_volume_up()
   return function()
             self:command("volume +5")
             self:update_track() -- Nasty! I should replace it with proper callback later.
             self:notify_state(self.NOTIFY_VOLUME)
          end
end

function awesompd:command_volume_down()
   return function()
             self:command("volume -5")
             self:update_track()
             self:notify_state(self.NOTIFY_VOLUME)
          end
end

function awesompd:command_load_playlist(name)
   return function()
             self:command("load \"" .. name .. "\"", function() 
                                              self.recreate_menu = true 
                                           end)
          end
end

function awesompd:command_replace_playlist(name)
   return function()
             self:command("clear")
             self:command("load \"" .. name .. "\"")
             self:command("play 1", self.update_track)
          end
end

function awesompd:command_clear_playlist()
   return function()
             self:command("clear", self.update_track)
             self.recreate_list = true
             self.recreate_menu = true
          end
end

function awesompd:command_open_in_browser(link)
   return function()
             if self.browser then
                awful.util.spawn(self.browser .. " '" .. link .. "'")
             end
          end
end

--- Change to the previous server.
function awesompd:command_previous_server()
   return function()
             servers = table.getn(self.servers)
             if servers == 1 or servers == nil then
                return
             else
                if self.current_server > 1 then
                   self:change_server(self.current_server - 1)
                else
                   self:change_server(servers)
                end
             end
          end
end

--- Change to the previous server.
function awesompd:command_next_server()
   return function()
             servers = table.getn(self.servers)
             if servers == 1 or servers == nil then
                return
             else
                if self.current_server < servers then
                   self:change_server(self.current_server + 1)
                else
                   self:change_server(1)
                end
             end
          end
end

-- /// End of mpc command functions ///

-- /// Menu generation functions ///

function awesompd:command_show_menu()
   return 
   function()
      self:hide_notification()
      if self.recreate_menu then 
         local new_menu = {}
         if self.main_menu ~= nil then 
            self.main_menu:hide() 
         end 
         if self.status ~= awesompd.DISCONNECTED
         then 
            self:check_list() 
            self:check_playlists()
            local jamendo_menu = { { "Search by", 
                                     { { "Nothing (Top 100)", self:menu_jamendo_top() },
                                       { "Artist", self:menu_jamendo_search_by(jamendo.SEARCH_ARTIST) },
                                       { "Album", self:menu_jamendo_search_by(jamendo.SEARCH_ALBUM) },
                                       { "Tag", self:menu_jamendo_search_by(jamendo.SEARCH_TAG) }}} }
            local browse_menu = self:menu_jamendo_browse()
            if browse_menu then 
               table.insert(jamendo_menu, browse_menu)
            end
            table.insert(jamendo_menu, self:menu_jamendo_format())
            table.insert(jamendo_menu, self:menu_jamendo_order())

            new_menu = { { "Playback", self:menu_playback() },
                         { "Options", self:menu_options() },
                         { "List", self:menu_list() },
                         { "Playlists", self:menu_playlists() },
                         { "Jamendo", jamendo_menu } }
         end 
         table.insert(new_menu, { "Servers", self:menu_servers() }) 
         self.main_menu = awful.menu({ items = new_menu, theme = { width = 300 } }) 
         self.recreate_menu = false 
      end 
      self.main_menu:toggle() 
   end 
end

-- Returns an icon for a checkbox menu item if it is checked, nil
-- otherwise.
function awesompd:menu_item_toggle(checked)
   return checked and self.ICONS.CHECK or nil
end

-- Returns an icon for a radiobox menu item if it is selected, nil
-- otherwise.
function awesompd:menu_item_radio(selected)
   return selected and self.ICONS.RADIO or nil
end

-- Returns the playback menu. Menu contains of:
-- Play\Pause - always
-- Previous - if the current track is not the first 
-- in the list and playback is not stopped
-- Next - if the current track is not the last 
-- in the list and playback is not stopped
-- Stop - if the playback is not stopped
-- Clear playlist - always
function awesompd:menu_playback()
   if self.recreate_playback then
      local new_menu = {}
      table.insert(new_menu, { "Play\\Pause", 
                               self:command_toggle(), 
                               self.ICONS.PLAY_PAUSE })
      if self:playing_or_paused() then
         if self.list_array and self.list_array[self.current_number-1] then
            table.insert(new_menu, 
                         { "Prev: " .. 
                           awesompd.protect_string(jamendo.replace_link(
                                                      self.list_array[self.current_number - 1]),
                                                   true),
                        self:command_prev_track(), self.ICONS.PREV })
         end
         if self.list_array and self.current_number ~= #self.list_array then
            table.insert(new_menu, 
                         { "Next: " .. 
                           awesompd.protect_string(jamendo.replace_link(
                                                      self.list_array[self.current_number + 1]), 
                                                   true), 
                        self:command_next_track(), self.ICONS.NEXT })
         end
         table.insert(new_menu, { "Stop", self:command_stop(), self.ICONS.STOP })
         table.insert(new_menu, { "", nil })
      end
      table.insert(new_menu, { "Clear playlist", self:command_clear_playlist() })
      self.recreate_playback = false
      playback_menu = new_menu
   end
   return playback_menu
end

-- Returns the current playlist menu. Menu consists of all elements in the playlist.
function awesompd:menu_list()
   if self.recreate_list then
      local new_menu = {}
      if self.list_array then
         local total_count = #self.list_array 
         local start_num = (self.current_number - 15 > 0) and self.current_number - 15 or 1
         local end_num = (self.current_number + 15 < total_count ) and self.current_number + 15 or total_count
         for i = start_num, end_num do
            table.insert(new_menu, { jamendo.replace_link(self.list_array[i]),
                                     self:command_play_specific(i),
                                     self.current_number == i and 
                                        (self.status == self.PLAYING and self.ICONS.PLAY or self.ICONS.PAUSE)
                                     or nil} )
         end
      end
      self.recreate_list = false
      self.list_menu = new_menu
   end
   return self.list_menu
end
	     
-- Returns the playlists menu. Menu consists of all files in the playlist folder.
function awesompd:menu_playlists()
   if self.recreate_playlists then
      local new_menu = {}
      if #self.playlists_array > 0 then
	 for i = 1, #self.playlists_array do
	    local submenu = {}
	    submenu[1] = { "Add to current", self:command_load_playlist(self.playlists_array[i]) }
	    submenu[2] = { "Replace current", self:command_replace_playlist(self.playlists_array[i]) }
	    new_menu[i] = { self.playlists_array[i], submenu }
	 end
	 table.insert(new_menu, {"", ""}) -- This is a separator
      end
      table.insert(new_menu, { "Refresh", function() self:check_playlists() end })
      self.recreate_playlists = false
      self.playlists_menu = new_menu
   end
   return self.playlists_menu
end

-- Returns the server menu. Menu consists of all servers specified by user during initialization.
function awesompd:menu_servers()
   if self.recreate_servers then
      local new_menu = {}
      for i = 1, #self.servers do
	 table.insert(new_menu, {"Server: " .. self.servers[i].server .. 
				 ", port: " .. self.servers[i].port,
			      function() self:change_server(i) end,
                              self:menu_item_radio(i == self.current_server)})
      end
      self.servers_menu = new_menu
   end
   return self.servers_menu
end

-- Returns the options menu. Menu works like checkboxes for it's elements.
function awesompd:menu_options()
   if self.recreate_options then 
      local new_menu = { { "Repeat", self:menu_toggle_repeat(), 
                           self:menu_item_toggle(self.state_repeat == "on")},
                         { "Random", self:menu_toggle_random(), 
                           self:menu_item_toggle(self.state_random == "on")},
                         { "Single", self:menu_toggle_single(), 
                           self:menu_item_toggle(self.state_single == "on")},
                         { "Consume", self:menu_toggle_consume(), 
                           self:menu_item_toggle(self.state_consume == "on")} }
      self.options_menu = new_menu
      self.recreate_options = false      
   end
   return self.options_menu
end

function awesompd:menu_toggle_random()
   return function()
             self:command("random",self.update_track)
             self:notify_state(self.NOTIFY_RANDOM)
          end
end

function awesompd:menu_toggle_repeat()
   return function()
             self:command("repeat",self.update_track)
             self:notify_state(self.NOTIFY_REPEAT)
          end
end

function awesompd:menu_toggle_single()
   return function()
             self:command("single",self.update_track)
             self:notify_state(self.NOTIFY_SINGLE)
          end
end

function awesompd:menu_toggle_consume()
   return function()
             self:command("consume",self.update_track)
             self:notify_state(self.NOTIFY_CONSUME)
          end
end

function awesompd:menu_jamendo_top()
   return 
   function ()
      local track_table = jamendo.return_track_table()
      if not track_table then
         self:show_notification("Can't connect to Jamendo server", "Please check your network connection")
      else
         self:add_jamendo_tracks(track_table)
         self:show_notification("Jamendo Top 100 by " ..
                                jamendo.current_request_table.params.order.short_display,
                                format("Added %s tracks to the playlist",
                                       #track_table))
      end
   end
end

function awesompd:menu_jamendo_format()
   if self.recreate_jamendo_formats then
      local setformat =
         function(format)
            return function()
                      jamendo.set_current_format(format)
                      self.recreate_menu = true
                      self.recreate_jamendo_formats = true
                   end
         end

      local iscurr = 
         function(f)
            return jamendo.current_request_table.params.streamencoding.value
               == f.value
         end

      local new_menu = {}
      for _, format in pairs(jamendo.ALL_FORMATS) do
         table.insert(new_menu, { format.display, setformat(format),
                                  self:menu_item_radio(iscurr(format))})
      end
      self.recreate_jamendo_formats = false
      self.jamendo_formats_menu = { 
         "Format: " ..
            jamendo.current_request_table.params.streamencoding.short_display,
         new_menu }
   end
   return self.jamendo_formats_menu
end

function awesompd:menu_jamendo_browse()
   if self.recreate_jamendo_browse and self.browser 
      and self.current_track.unique_name then
      local track = jamendo.get_track_by_link(self.current_track.unique_name)
      local new_menu
      if track then
         local artist_link = 
            "http://www.jamendo.com/artist/" .. track.artist_link_name
         local album_link =
            "http://www.jamendo.com/album/" .. track.album_id
         new_menu = { { "Artist's page" , 
                        self:command_open_in_browser(artist_link) },
                      { "Album's page" ,
                        self:command_open_in_browser(album_link) } }
         self.jamendo_browse_menu = { "Browse on Jamendo", new_menu }
      else
         self.jamendo_browse_menu = nil
      end
   end
   return self.jamendo_browse_menu
end

function awesompd:menu_jamendo_order()
   if self.recreate_jamendo_order then
      local setorder =
         function(order)
            return function()
                      jamendo.set_current_order(order)
                      self.recreate_menu = true
                      self.recreate_jamendo_order = true
                   end
         end

      local iscurr = 
         function(o)
            return jamendo.current_request_table.params.order.value
               == o.value
         end

      local new_menu = {}
      for _, order in pairs(jamendo.ALL_ORDERS) do
         table.insert(new_menu, { order.display, setorder(order),
                                  self:menu_item_radio(iscurr(order))})
      end
      self.recreate_jamendo_order = false
      self.jamendo_order_menu = { 
         "Order: " ..
            jamendo.current_request_table.params.order.short_display,
         new_menu }
   end
   return self.jamendo_order_menu
end

function awesompd:menu_jamendo_search_by(what)
   return function()
             local callback = 
                function(s)
                   local result = jamendo.search_by(what, s)
                   if result then
                      local track_count = #result.tracks
                      self:add_jamendo_tracks(result.tracks)
                      self:show_notification(format("%s \"%s\" was found",
                                                    what.display,
                                                    result.search_res.name),
                                             format("Added %s tracks to the playlist",
                                                    track_count))
                   else
                      self:show_notification("Search failed",
                                             format("%s \"%s\" was not found",
                                                    what.display, s))
                   end
                end
             self:display_inputbox("Search music on Jamendo",
                                   what.display, callback)
          end
end

-- Checks if the current playlist has changed after the last check.
function awesompd:check_list()
   local bus = io.popen(self:mpcquery(true) .. "playlist")
   local info = bus:read("*all")
   bus:close()
   if info ~= self.list_line then
      self.list_line = info
      if string.len(info) > 0 then
	 self.list_array = self.split(string.sub(info,1,string.len(info)))
      else
	 self.list_array = {}
      end
      self.recreate_menu = true
      self.recreate_list = true
   end
end

-- Checks if the collection of playlists changed after the last check.
function awesompd:check_playlists()
   local bus = io.popen(self:mpcquery(true) .. "lsplaylists")
   local info = bus:read("*all")
   bus:close()
   if info ~= self.playlists_line then
      self.playlists_line = info
      if string.len(info) > 0 then
	 self.playlists_array = self.split(info)
      else
	 self.playlists_array = {}
      end
      self.recreate_menu = true
      self.recreate_playlists = true
   end
end

-- Changes the current server to the specified one.
function awesompd:change_server(server_number)
   self.current_server = server_number
   self:hide_notification()
   self.recreate_menu = true
   self.recreate_playback = true
   self.recreate_list = true
   self.recreate_playlists = true
   self.recreate_servers = true
   self:update_track()
end

function awesompd:add_jamendo_tracks(track_table)
   for i = 1, #track_table do
      self:command("add '" .. string.gsub(track_table[i].stream, '\\/', '/') .. "'")
   end
   self.recreate_menu = true
   self.recreate_list = true
end

-- /// End of menu generation functions ///

function awesompd:show_notification(hint_title, hint_text, hint_image)
   self:hide_notification()
   self.notification = naughty.notify({ title      =  hint_title
					, text       = awesompd.protect_string(hint_text)
					, timeout    = 5
					, position   = "top_right"
                                        , icon       = hint_image
                                        , icon_size  = self.album_cover_size
                                     })
end

function awesompd:hide_notification()
   if self.notification ~= nil then
      naughty.destroy(self.notification)
      self.notification = nil
   end
end

function awesompd:notify_track()
   if self:playing_or_paused() then
      local caption = self.status_text
      local nf_text = self.get_display_name(self.current_track)
      local al_cover = nil
      if self.show_album_cover then
         nf_text = self.get_extended_info(self.current_track)
         al_cover = self.current_track.album_cover
      end
      self:show_notification(caption, nf_text, al_cover)
   end
end

function awesompd:notify_state(state_changed)
   state_array = { "Volume: " .. self.state_volume ,
		   "Repeat: " .. self.state_repeat ,
		   "Random: " .. self.state_random ,
		   "Single: " .. self.state_single ,
		   "Consume: " .. self.state_consume }
   state_header = state_array[state_changed]
   table.remove(state_array,state_changed)
   full_state = state_array[1]
   for i = 2, #state_array do
      full_state = full_state .. "\n" .. state_array[i]
   end
   self:show_notification(state_header, full_state)
end

function awesompd:wrap_output(text)
   return format('<span font="%s" color="%s" background="%s">%s%s%s</span>',
                 self.font, self.font_color, self.background,
                 (text == "" and "" or self.ldecorator), awesompd.protect_string(text),
                 (text == "" and "" or self.rdecorator))
end

-- This function actually sets the text on the widget.
function awesompd:set_text(text)
   self.text_widget:set_markup(self:wrap_output(text))
end

function awesompd.find_pattern(text, pattern, start)
   return utf8.sub(text, string.find(text, pattern, start))
end

-- Scroll the given text by the current number of symbols.
function awesompd:scroll_text(text)
   local result = text
   if self.scrolling then
      if self.output_size < utf8.len(text) then
         text = text .. " - "
         if self.scroll_pos + self.output_size - 1 > utf8.len(text) then
            result = utf8.sub(text, self.scroll_pos)
            result = result .. utf8.sub(text, 1, self.scroll_pos + self.output_size - 1 - utf8.len(text))
            self.scroll_pos = self.scroll_pos + 1
            if self.scroll_pos > utf8.len(text) then
               self.scroll_pos = 1
            end
         else
            result = utf8.sub(text, self.scroll_pos, self.scroll_pos + self.output_size - 1)
            self.scroll_pos = self.scroll_pos + 1
         end
      end
   end
   return result
end

-- This function is called every second.
function awesompd:update_widget()
   self:set_text(self:scroll_text(self.text))
   self:check_notify()
end

-- This function is called by update_track each time content of
-- the widget must be changed.
function awesompd:update_widget_text()
   if self:playing_or_paused() then
      self.text = self.get_display_name(self.current_track)
   else
      self.text = self.status
   end
end

-- Checks if notification should be shown and shows if positive.
function awesompd:check_notify()
   if self.to_notify then
      self:notify_track()
      self.to_notify = false
   end
end

function awesompd:notify_connect()
   self:show_notification("Connected", "Connection established to " .. self.servers[self.current_server].server ..
		 " on port " .. self.servers[self.current_server].port)
end

function awesompd:notify_disconnect()
   self:show_notification("Disconnected", "Cannot connect to " .. self.servers[self.current_server].server ..
		 " on port " .. self.servers[self.current_server].port)
end

local function to_seconds(minsec)
   local min, sec = minsec:match("(%d+):(%d+)")
   return tonumber(min) * 60 + tonumber(sec)
end

function awesompd:update_track(file)
   local file_exists = (file ~= nil)
   if not file_exists then
      file = io.popen(self:mpcquery())
   end
   local track_line = file:read("*line")
   local status_line = file:read("*line")
   local options_line = file:read("*line")
   if not file_exists then
      file:close()
   end

   if not track_line or string.len(track_line) == 0 then
      if self.status ~= awesompd.DISCONNECTED then
	 self:notify_disconnect()
	 self.recreate_menu = true
         self.status = awesompd.DISCONNECTED
         self.current_track = { }
         self:update_widget_text()
      end
   else
      if self.status == awesompd.DISCONNECTED then
	 self:notify_connect()
	 self.recreate_menu = true
         self:update_widget_text()
      end
      if string.find(track_line,"volume:") or string.find(track_line,"Updating DB") then
	 if self.status ~= awesompd.STOPPED then
            self.status = awesompd.STOPPED
	    self.current_number = 0
	    self.recreate_menu = true
	    self.recreate_playback = true
	    self.recreate_list = true
            self.album_cover = nil
            self.current_track = { }
            self:update_widget_text()
	 end
         self:update_state(track_line)
      else
         self:update_state(options_line)
         local _, _, new_file, station, title, artist, album =
            string.find(track_line, "(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)")
         local display_name, force_update = artist .. " - " .. title, false
         -- The following code checks if the current track is an
         -- Internet link. Internet radios change tracks, but the
         -- current file stays the same, so we should manually compare
         -- its title.
         if string.match(new_file, "http://") and
            -- The following line is awful. This needs to be replaced ASAP.
            not string.match(new_file, "http://storage%-new%.newjamendo%.com") then
            album = non_empty(station) or ""
            display_name = non_empty(title) or new_file
            if display_name ~= self.current_track.display_name then
               force_update = true
            end
         end
	 if new_file ~= self.current_track.unique_name or force_update then
            self.current_track = jamendo.get_track_by_link(new_file)
            if not self.current_track then
               self.current_track = { display_name = display_name,
                                      album_name = album }
            end
            self.current_track.unique_name = new_file
            if self.show_album_cover then
               self.current_track.album_cover = self:get_cover(new_file)
            end
	    self.to_notify = true
	    self.recreate_menu = true
	    self.recreate_playback = true
	    self.recreate_list = true
	    self.current_number = tonumber(self.find_pattern(status_line,"%d+"))
            self:update_widget_text()

            -- If the track is not the last, asynchronously download
            -- the cover for the next track.
            if self.list_array and self.current_number ~= #self.list_array then
               -- Get the link (in case it is Jamendo stream) to the next track
               local next_track = 
                  self:command_read('playlist -f "%file%" | head -' .. 
                                    self.current_number + 1 .. ' | tail -1', "*line")
               jamendo.try_get_cover_async(next_track)
            end
	 end
	 local tmp_pst = string.find(status_line,"%d+%:%d+%/")
	 self.track_position, self.track_duration = status_line:match("(%d+:%d+)/(%d+:%d+)")
	 local progress = self.find_pattern(status_line,"%#%d+/%d+") .. " " .. string.sub(status_line,tmp_pst)
         local new_status = awesompd.PLAYING
	 if string.find(status_line,"paused") then
            new_status = awesompd.PAUSED
	 end
	 if new_status ~= self.status then
	    self.to_notify = true
	    self.recreate_list = true
            self.status = new_status
            self:update_widget_text()
	 end
	 self.status_text = self.status .. " " .. progress
      end
   end
   self:smart_update()
end

function awesompd:smart_update()
   -- Kill any set timers
   if self.smart_update_timer.started then
      self.smart_update_timer:stop()
   end
   if (self.status == awesompd.PLAYING) then
      local pos = to_seconds(self.track_position)
      local dur = to_seconds(self.track_duration)
      local rem = (dur - pos) + 1
      if (rem < self.update_interval) then
	 -- Little time remaining, lets update when it runs out 
	 local smart_timer = timer({ timeout = rem })
	 smart_timer:connect_signal("timeout", function()
	    smart_timer:stop()
	    self:update_track()
	 end)
	 self.smart_update_timer = smart_timer
	 smart_timer:start()
      end
   end
end

function awesompd:update_state(state_string)
   self.state_volume = self.find_pattern(state_string,"%d+%% ")
   if string.find(state_string,"repeat: on") then
      self.state_repeat = self:check_set_state(self.state_repeat, "on")
   else
      self.state_repeat = self:check_set_state(self.state_repeat, "off")
   end
   if string.find(state_string,"random: on") then
      self.state_random = self:check_set_state(self.state_random, "on")
   else
      self.state_random = self:check_set_state(self.state_random, "off")
   end
   if string.find(state_string,"single: on") then
      self.state_single = self:check_set_state(self.state_single, "on")
   else
      self.state_single = self:check_set_state(self.state_single, "off")
   end
   if string.find(state_string,"consume: on") then
      self.state_consume = self:check_set_state(self.state_consume, "on")
   else
      self.state_consume = self:check_set_state(self.state_consume, "off")
   end
end

function awesompd:check_set_state(statevar, val)
   if statevar ~= val then
      self.recreate_menu = true
      self.recreate_options = true
   end
   return val
end

function awesompd:run_prompt(welcome,hook)
   awful.prompt.run({ prompt = welcome },
		    self.promptbox[mouse.screen].widget,
		    hook)
end

-- Replaces control characters with escaped ones.
-- for_menu - defines if the special escable table for menus should be
-- used.
function awesompd.protect_string(str, for_menu)
	  return awful.util.escape(str)
end

-- Initialize the inputbox.
function awesompd:init_inputbox()
   local width = 200
   local height = 30
   local border_color = beautiful.bg_focus or '#535d6c'
   local margin = 4
   local wbox = wibox({ name = "awmpd_ibox", height = height , width = width, 
                        border_color = border_color, border_width = 1 })
   local ws = screen[mouse.screen].workarea

   wbox.screen = mouse.screen
   wbox.ontop = true

   local wprompt = awful.widget.prompt()
   local wtbox = wibox.widget.textbox()
   local wtmarginbox = wibox.layout.margin(wtbox, margin)
   local tw, th = wtbox:fit(-1, -1)
   wbox:geometry({ x = ws.width - width - 5, y = 25,
                   width = 200, height = th * 2 + margin})
   local layout = wibox.layout.flex.vertical()
   layout:add(wtmarginbox)
   layout:add(wprompt)
   wbox:set_widget(layout)
   self.inputbox = { wibox = wbox,
                     title = wtbox,
                     prompt = wprompt }
end

-- Displays an inputbox on the screen (looks like naughty with prompt).
-- title_text - bold text on the first line
-- prompt_text - preceding text on the second line
-- hook - function that will be called with input data
-- Use it like this:
-- self:display_inputbox("Search music on Jamendo", "Artist", print)
function awesompd:display_inputbox(title_text, prompt_text, hook)
   if not self.inputbox then
      self:init_inputbox()
   end
   if self.inputbox.wibox.visible then -- Inputbox already exists, replace it
      keygrabber.stop()
   end

   local exe_callback = function(s)
                           hook(s)
                           self.inputbox.wibox.visible = false
                        end
   local done_callback = function()
                            self.inputbox.wibox.visible = false
                         end
   self.inputbox.title:set_markup("<b>" .. title_text .. "</b>")
   awful.prompt.run( { prompt = " " .. prompt_text .. ": ", bg_cursor = "#222222" }, 
                     self.inputbox.prompt.widget,
                     exe_callback, nil, nil, nil, done_callback, nil, nil)
   self.inputbox.wibox.visible = true
end

-- Gets the cover for the given track. First looks in the Jamendo
-- cache. If the track is not a Jamendo stream, looks in local
-- folders. If there is no cover art either returns the default album
-- cover.
function awesompd:get_cover(track)
   local radio_cover = nil
   if self.radio_covers then
      for station, cover in pairs(self.radio_covers) do
         if track:match(station) then
            radio_cover = cover
            break
         end
      end
   end
   return radio_cover or jamendo.try_get_cover(track) or
      self:try_get_local_cover(track) or self.ICONS.DEFAULT_ALBUM_COVER
end

-- Tries to find an album cover for the track that is currently
-- playing.
function awesompd:try_get_local_cover(current_file)
   if self.mpd_config then
      local result
      -- First find the music directory in MPD configuration file
      local _, _, music_folder = string.find(
         self.pread('cat ' .. self.mpd_config .. ' | grep -v ^"#" | grep music_directory', "*line"),
         'music_directory%s+"(.+)"')
      music_folder = music_folder .. "/"
      
      -- If the music_folder is specified with ~ at the beginning,
      -- replace it with user home directory
      if string.sub(music_folder, 1, 1) == "~" then
         local user_folder = self.pread("echo ~", "*line")
         music_folder = user_folder .. string.sub(music_folder, 2)
      end

      -- Get the path to the file currently playing.
      local _, _, current_file_folder = string.find(current_file, '(.+%/).*')

      -- Check if the current file is not some kind of http stream or
      -- Spotify track (like spotify:track:5r65GeuIoebfJB5sLcuPoC)
      if not current_file_folder or string.match(current_file, "%w+://") then
          return -- Let the default image to be the cover
      end

      local folder = music_folder .. current_file_folder
      
      -- Get all images in the folder. Also escape occasional single
      -- quotes in folder name.
      local request = format("ls '%s' | grep -P '\\.jpg|\\.png|\\.gif|\\.jpeg'",
                             string.gsub(folder, "'", "'\\''"))

      local covers = self.pread(request, "*all")
      local covers_table = self.split(covers)
      
      if covers_table.n > 0 then
         result = folder .. covers_table[1]
         if covers_table.n > 1 then
            -- Searching for front cover with grep because Lua regular
            -- expressions suck:[
            local front_cover = 
               self.pread('echo "' .. covers .. 
                          '" | grep -P -i "cover|front|folder|albumart" | head -n 1', "*line")
            if front_cover then
               result = folder .. front_cover
            end
         end
      end
      return result
   end   
end

-- /// Deprecated, left for some backward compatibility in
-- configuration ///

function awesompd:command_toggle()
   return self:command_playpause()
end

return awesompd
