---------------------------------------------------------------------------
-- Wicked widgets for the awesome window manager
---------------------------------------------------------------------------
-- Lucas de Vries <lucas@glacicle.com>
-- Licensed under the WTFPL
-- Version: v1.0pre-awe3.0rc4
---------------------------------------------------------------------------

-- Require libs
require("awful")

---- {{{ Grab environment
local ipairs = ipairs
local pairs = pairs
local print = print
local type = type
local tonumber = tonumber
local tostring = tostring
local math = math
local table = table
local awful = awful
local os = os
local io = io
local string = string

-- Grab C API
local capi =
{
    awesome = awesome,
    screen = screen,
    client = client,
    mouse = mouse,
    button = button,
    titlebar = titlebar,
    widget = widget,
    hooks = hooks,
    keygrabber = keygrabber
}

-- }}}

-- Wicked: Widgets for the awesome window manager
module("wicked")

---- {{{ Initialise variables
local registered = {}
local widget_cache = {}

-- Initialise function tables
widgets = {}
helper = {}

local nets = {}
local cpu_total = {}
local cpu_active = {}
local cpu_usage = {}

-- }}}

---- {{{ Helper functions

----{{{ Max width
function helper.max_width(str, width)
    l = str:len()
    
    if l > width then
        r = math.floor(width/2)
        a = str:sub(1,r)
        b = str:sub(l-r, l)
        str = a .. "..." .. b
    end

    return str
end
----}}}

----{{{ Force a fixed width on a string with spaces
function helper.fixed_width(str, width)
    l = str:len()
    n = width-l
    if n >= 0 then
        for i = 1, n do
            str = str.." "
        end
    else
        str = str:sub(0, l+n)
    end
    return str
end
----}}}

---- {{{ Format a string with args
function helper.format(format, args)
    -- TODO: Find a more efficient way to do this

    -- Format a string
    for var,val in pairs(args) do
        format = string.gsub(format, '$'..var, val)
    end

    -- Return formatted string
    return format
end
-- }}}

---- {{{ Padd a number to a minimum amount of digits
function helper.padd(number, padding)
    s = tostring(number)

    if padding == nil then
        return s
    end

    for i=1,padding do
        if math.floor(number/math.pow(10,(i-1))) == 0 then
            s = "0"..s
        end
    end

    if number == 0 then
        s = s:sub(2)
    end

    return s
end
-- }}}

---- {{{ Convert amount of bytes to string
function helper.bytes_to_string(bytes, sec, padding)
    if bytes == nil or tonumber(bytes) == nil then
        return ''
    end

    bytes = tonumber(bytes)

    signs = {}
    signs[1] = '  b'
    signs[2] = 'KiB'
    signs[3] = 'MiB'
    signs[4] = 'GiB'
    signs[5] = 'TiB'

    sign = 1

    while bytes/1024 > 1 and signs[sign+1] ~= nil do
        bytes = bytes/1024
        sign = sign+1
    end

    bytes = bytes*10
    bytes = math.floor(bytes)/10

    if padding then
        bytes = helper.padd(bytes*10, padding+1)
        bytes = bytes:sub(1, bytes:len()-1).."."..bytes:sub(bytes:len())
    end

    if sec then
        return tostring(bytes)..signs[sign]..'ps'
    else
        return tostring(bytes)..signs[sign]
    end
end
-- }}}

---- {{{ Split by whitespace
function helper.splitbywhitespace(str)
    values = {}
    start = 1
    splitstart, splitend = string.find(str, ' ', start)
    
    while splitstart do
        m = string.sub(str, start, splitstart-1)
        if m:gsub(' ','') ~= '' then
            table.insert(values, m)
        end

        start = splitend+1
        splitstart, splitend = string.find(str, ' ', start)
    end

    m = string.sub(str, start)
    if m:gsub(' ','') ~= '' then
        table.insert(values, m)
    end

    return values
end
-- }}}

--{{{ Escape a string
function helper.escape(text)
    if text then
        text = text:gsub("&", "&amp;")
        text = text:gsub("<", "&lt;")
        text = text:gsub(">", "&gt;")
        text = text:gsub("'", "&apos;")
        text = text:gsub("\"", "&quot;")
    end
    return text
end

-- }}}

-- }}}

---- {{{ Widget types

---- {{{ MPD widget type
function widgets.mpd()
    ---- Get data from mpc
    local nowplaying_file = io.popen('mpc')
    local nowplaying = nowplaying_file:read()

    -- Close the command
    nowplaying_file:close()

    -- Check that it's not nil
    if nowplaying == nil then
        return {''}
    end
    
    -- Escape
    nowplaying = helper.escape(nowplaying)

    -- Return it
    return {nowplaying}
end

widget_cache[widgets.mpd] = {}
-- }}}

---- {{{ MOCP widget type
function widgets.mocp(format, max_width)
	local playing = ''

    ---- Get data from mocp
    local info = io.popen('mocp -i')
    local state = info:read()
	state = state.gsub(state, 'State: ', '')

	if (state == "PLAY") then
		local file = info:read()
		file = file.gsub(file, 'File: ', '')
		local title = info:read()
		title = title.gsub(title, 'Title: ', '')
		local artist = info:read()
		artist = artist.gsub(artist, 'Artist: ', '')
		local songtitle = info:read()
		songtitle = songtitle.gsub(songtitle, 'SongTitle: ', '')
		local album = info:read()
		album = album.gsub(album, 'Album: ', '')
		
		-- Try artist - (song)title
		if (artist:len() > 0) then
			playing = artist .. ' - ' .. (songtitle ~= '' and songtitle or title)
			
		-- Else try title or songtitle
		elseif (artist:len() == 0 and (title:len() > 0 or songtitle:len() > 0)) then
			playing = (title ~= '' and title or songtitle)

		-- Else use the filename
		else
			file = string.reverse(file)
			i = string.find(file, '/')
			if (i ~= nil) then
				file = string.sub(file, 0, i-1)
			end
			playing = string.reverse(file)
		end
	else
		playing = state
	end

	-- Close file
	info:close()

	-- Apply maximum width
	if (max_width ~= nil) then
		playing = helper.max_width(playing, max_width)
	end

	playing = helper.escape(playing)

    -- Return it
    return {playing}
end

widget_cache[widgets.mocp] = {}
-- }}}

---- {{{ CPU widget type
function widgets.cpu(format, padding)
    -- Calculate CPU usage for all available CPUs / cores and return the
    -- usage

    -- Perform a new measurement
    ---- Get /proc/stat
    local cpu_lines = {}
    local cpu_usage_file = io.open('/proc/stat')
    for line in cpu_usage_file:lines() do
        if string.sub(line, 1, 3) == 'cpu' then
            table.insert(cpu_lines, helper.splitbywhitespace(line))
        end
    end
    cpu_usage_file:close()

    ---- Ensure tables are initialized correctly
    while #cpu_total < #cpu_lines do
        table.insert(cpu_total, 0)
    end
    while #cpu_active < #cpu_lines do
        table.insert(cpu_active, 0)
    end
    while #cpu_usage < #cpu_lines do
        table.insert(cpu_usage, 0)
    end

    ---- Setup tables
    total_new     = {}
    active_new    = {}
    diff_total    = {}
    diff_active   = {}

    for i,v in ipairs(cpu_lines) do
        ---- Calculate totals
        total_new[i]    = 0
        for j = 2, #v do
            total_new[i] = total_new[i] + v[j]
        end
        active_new[i]   = v[2] + v[3] + v[4]
    
        ---- Calculate percentage
        diff_total[i]   = total_new[i]  - cpu_total[i]
        diff_active[i]  = active_new[i] - cpu_active[i]
        cpu_usage[i]    = math.floor(diff_active[i] / diff_total[i] * 100)

        ---- Store totals
        cpu_total[i]    = total_new[i]
        cpu_active[i]   = active_new[i]
    end

    if padding ~= nil then
        for k,v in pairs(cpu_usage) do
            if type(padding) == "table" then
                p = padding[k]
            else
                p = padding
            end

            cpu_usage[k] = helper.padd(cpu_usage[k], p)
        end
    end

    return cpu_usage
end

widget_cache[widgets.cpu] = {}
-- }}}

---- {{{ Memory widget type
function widgets.mem(format, padding)
    -- Return MEM usage values
    local f = io.open('/proc/meminfo')

    ---- Get data
    for line in f:lines() do
        line = helper.splitbywhitespace(line)

        if line[1] == 'MemTotal:' then
            mem_total = math.floor(line[2]/1024)
        elseif line[1] == 'MemFree:' then
            free = math.floor(line[2]/1024)
        elseif line[1] == 'Buffers:' then
            buffers = math.floor(line[2]/1024)
        elseif line[1] == 'Cached:' then
            cached = math.floor(line[2]/1024)
        end
    end
    f:close()

    ---- Calculate percentage
    mem_free=free+buffers+cached
    mem_inuse=mem_total-mem_free
    mem_usepercent = math.floor(mem_inuse/mem_total*100)

    if padding then
        if type(padding) == "table" then
            mem_usepercent = helper.padd(mem_usepercent, padding[1])
            mem_inuse = helper.padd(mem_inuse, padding[2])
            mem_total = helper.padd(mem_total, padding[3])
            mem_free = helper.padd(mem_free, padding[4])
        else
            mem_usepercent = helper.padd(mem_usepercent, padding)
            mem_inuse = helper.padd(mem_inuse, padding)
            mem_total = helper.padd(mem_total, padding)
            mem_free = helper.padd(mem_free, padding)
        end
    end

    return {mem_usepercent, mem_inuse, mem_total, mem_free}
end

widget_cache[widgets.mem] = {}
-- }}}

---- {{{ Swap widget type
function widgets.swap(format, padding)
    -- Return SWAP usage values
    local f = io.open('/proc/meminfo')

    ---- Get data
    for line in f:lines() do
        line = helper.splitbywhitespace(line)

        if line[1] == 'SwapTotal:' then
            swap_total = math.floor(line[2]/1024)
        elseif line[1] == 'SwapFree:' then
            free = math.floor(line[2]/1024)
        elseif line[1] == 'SwapCached:' then
            cached = math.floor(line[2]/1024)
        end
    end
    f:close()

    ---- Calculate percentage
    swap_free=free+cached
    swap_inuse=swap_total-swap_free
    swap_usepercent = math.floor(swap_inuse/swap_total*100)

    if padding then
        if type(padding) == "table" then
            swap_usepercent = helper.padd(swap_usepercent, padding[1])
            swap_inuse = helper.padd(swap_inuse, padding[2])
            swap_total = helper.padd(swap_total, padding[3])
            swap_free = helper.padd(swap_free, padding[4])
        else
            swap_usepercent = helper.padd(swap_usepercent, padding)
            swap_inuse = helper.padd(swap_inuse, padding)
            swap_total = helper.padd(swap_total, padding)
            swap_free = helper.padd(swap_free, padding)
        end
    end

    return {swap_usepercent, swap_inuse, swap_total, swap_free}
end

widget_cache[widgets.swap] = {}
-- }}}

---- {{{ Date widget type
function widgets.date(format)
    -- Get format
    if format == nil then
        return os.date()
    else
        return os.date(format)
    end
end
-- }}}

---- {{{ Filesystem widget type
function widgets.fs(format, padding)
    local f = io.popen('df -hP')
    local args = {}

    for line in f:lines() do
        vars = helper.splitbywhitespace(line)
        
        if vars[1] ~= 'Filesystem' and #vars == 6 then
            vars[5] = vars[5]:gsub('%%','')

            if padding then
                if type(padding) == "table" then
                    vars[2] = helper.padd(vars[2], padding[1])
                    vars[3] = helper.padd(vars[3], padding[2])
                    vars[4] = helper.padd(vars[4], padding[3])
                    vars[5] = helper.padd(vars[5], padding[4])
                else
                    vars[2] = helper.padd(vars[2], padding)
                    vars[3] = helper.padd(vars[3], padding)
                    vars[4] = helper.padd(vars[4], padding)
                    vars[5] = helper.padd(vars[5], padding)
                end
            end

            args['{'..vars[6]..' size}'] = vars[2]
            args['{'..vars[6]..' used}'] = vars[3]
            args['{'..vars[6]..' avail}'] = vars[4]
            args['{'..vars[6]..' usep}'] = vars[5]
        end
    end

    f:close()
    return args
end
-- }}}

---- {{{ Net widget type
function widgets.net(format, padding)
    local f = io.open('/proc/net/dev')
    args = {}

    for line in f:lines() do
        line = helper.splitbywhitespace(line)

        local p = line[1]:find(':')
        if p ~= nil then
            name = line[1]:sub(0,p-1)
            line[1] = line[1]:sub(p+1)

            if tonumber(line[1]) == nil then
                line[1] = line[2]
                line[9] = line[10]
            end

            if padding then
                args['{'..name..' rx}'] = helper.bytes_to_string(line[1], nil, padding)
                args['{'..name..' tx}'] = helper.bytes_to_string(line[9], nil, padding)
            else
                args['{'..name..' rx}'] = helper.bytes_to_string(line[1])
                args['{'..name..' tx}'] = helper.bytes_to_string(line[9])
            end

            args['{'..name..' rx_b}'] = math.floor(line[1]*10)/10
            args['{'..name..' tx_b}'] = math.floor(line[9]*10)/10
            
            args['{'..name..' rx_kb}'] = math.floor(line[1]/1024*10)/10
            args['{'..name..' tx_kb}'] = math.floor(line[9]/1024*10)/10

            args['{'..name..' rx_mb}'] = math.floor(line[1]/1024/1024*10)/10
            args['{'..name..' tx_mb}'] = math.floor(line[9]/1024/1024*10)/10

            args['{'..name..' rx_gb}'] = math.floor(line[1]/1024/1024/1024*10)/10
            args['{'..name..' tx_gb}'] = math.floor(line[9]/1024/1024/1024*10)/10

            if nets[name] == nil then 
                nets[name] = {}
                args['{'..name..' down}'] = 'n/a'
                args['{'..name..' up}'] = 'n/a'
                
                args['{'..name..' down_b}'] = 0
                args['{'..name..' up_b}'] = 0

                args['{'..name..' down_kb}'] = 0
                args['{'..name..' up_kb}'] = 0

                args['{'..name..' down_mb}'] = 0
                args['{'..name..' up_mb}'] = 0

                args['{'..name..' down_gb}'] = 0
                args['{'..name..' up_gb}'] = 0

                nets[name].time = os.time()
            else
                interval = os.time()-nets[name].time
                nets[name].time = os.time()

                down = (line[1]-nets[name][1])/interval
                up = (line[9]-nets[name][2])/interval

                if padding then
                    args['{'..name..' down}'] = helper.bytes_to_string(down, true, padding)
                    args['{'..name..' up}'] = helper.bytes_to_string(up, true, padding)
                else
                    args['{'..name..' down}'] = helper.bytes_to_string(down, true)
                    args['{'..name..' up}'] = helper.bytes_to_string(up, true)
                end

                args['{'..name..' down_b}'] = math.floor(down*10)/10
                args['{'..name..' up_b}'] = math.floor(up*10)/10

                args['{'..name..' down_kb}'] = math.floor(down/1024*10)/10
                args['{'..name..' up_kb}'] = math.floor(up/1024*10)/10

                args['{'..name..' down_mb}'] = math.floor(down/1024/1024*10)/10
                args['{'..name..' up_mb}'] = math.floor(up/1024/1024*10)/10

                args['{'..name..' down_gb}'] = math.floor(down/1024/1024/1024*10)/10
                args['{'..name..' up_gb}'] = math.floor(up/1024/1024/1024*10)/10
            end

            nets[name][1] = line[1]
            nets[name][2] = line[9]
        end
    end

    f:close()
    return args
end
widget_cache[widgets.net] = {}
-- }}}

---- {{{ Uptime widget type
function widgets.uptime(format, padding)
    --Get uptime from /proc/uptime
	local f = io.open("/proc/uptime")
	uptime_line = f:read()
	
	f:close()

	args = {}
    --/proc/uptime has the format "<up time> <idle time>"
  	if uptime_line:find(" ") ~= nil then

		pend = uptime_line:find(" ",0,true)
                
        	uptime_line_part = uptime_line:sub(0,pend-1)		

		total_uptime  = math.floor( tonumber(uptime_line_part) )

		uptime_days    =  math.floor( total_uptime / (3600 * 24) )
		uptime_hours   =  math.floor( ( total_uptime % (3600 * 24) ) / 3600 )
       	 	uptime_minutes =  math.floor( ( ( total_uptime % (3600 * 24) ) % 3600 ) / 60 )
        	uptime_seconds =  math.floor( ( ( total_uptime % (3600  * 24) ) % 3600) % 60 )

		if padding then
			
			if type(padding) == "table" then
				total_uptime   = helper.padd(total_uptime   , padding[1])
				uptime_days    = helper.padd(uptime_days    , padding[2])
				uptime_hours   = helper.padd(uptime_hours   , padding[3])
 				uptime_minutes = helper.padd(uptime_minutes , padding[4])
				uptime_seconds = helper.padd(uptime_seconds , padding[5])
			else
				total_uptime   = helper.padd(total_uptime   , padding)
				uptime_days    = helper.padd(uptime_days    , padding)
				uptime_hours   = helper.padd(uptime_hours   , padding)
				uptime_minutes = helper.padd(uptime_minutes , padding)
  				uptime_seconds = helper.padd(uptime_seconds , padding)
			end	
		
		end
	end
	
	return {total_uptime, uptime_days, uptime_hours, uptime_minutes, uptime_seconds}

end
widget_cache[widgets.uptime] = {}
-- }}}

-- For backwards compatibility: custom function
widgets["function"] = function ()
    return {}
end

-- }}}

---- {{{ Main functions
---- {{{ Register widget
function register(widget, wtype, format, timer, field, padd)
    local reg = {}
    local widget = widget

    -- Set properties
    reg.type = wtype
    reg.format = format
    reg.timer = timer
    reg.field = field
    reg.padd = padd
    reg.widget = widget

    -- Update function
    reg.update = function ()
        update(widget, reg)
    end

    -- Default to timer=1
    if reg.timer == nil then
        reg.timer = 1
    end

    -- Allow using a string widget type
    if type(reg.type) == "string" then
        reg.type = widgets[reg.type]
    end

    -- Register reg object
    regregister(reg)

    -- Return reg object for reuse
    return reg
end
-- }}}

-- {{{ Register from reg object
function regregister(reg)
    if not reg.running then
        -- Put widget in table
        if registered[reg.widget] == nil then
            registered[reg.widget] = {}
            table.insert(registered[reg.widget], reg)
        else
            already = false

            for w, i in pairs(registered) do
                if w == reg.widget then
                    for k,v in pairs(i) do
                        if v == reg then
                            already = true
                            break
                        end
                    end

                    if already then
                        break
                    end
                end
            end

            if not already then
                table.insert(registered[reg.widget], reg)
            end
        end

        -- Start timer
        if reg.timer > 0 then
            awful.hooks.timer.register(reg.timer, reg.update)
        end

        -- Initial update
        reg.update()

        -- Set running
        reg.running = true
    end
end
-- }}}

-- {{{ Unregister widget
function unregister(widget, keep, reg)
    if reg == nil then
        for w, i in pairs(registered) do
            if w == widget then
                for k,v in pairs(i) do
                    reg = unregister(w, keep, v)
                end
            end
        end

        return reg
    end

    if not keep then
        for w, i in pairs(registered) do
            if w == widget then
                for k,v in pairs(i) do
                    if v == reg then
                        table.remove(registered[w], k)
                    end
                end
            end
        end
    end

    awful.hooks.timer.unregister(reg.update)

    reg.running = false
    return reg
end
-- }}}

-- {{{ Suspend wicked, halt all widget updates
function suspend()
    for w, i in pairs(registered) do
        for k,v in pairs(i) do
            unregister(w, true, v)
        end
    end
end
-- }}}

-- {{{ Activate wicked, restart all widget updates
function activate(widget)
    for w, i in pairs(registered) do
        if widget == nil or w == widget then
            for k,v in pairs(i) do
                regregister(v)
            end
        end
    end
end
-- }}}

-- {{{ Enable caching for a widget type
function enable_caching(widget)
    if widget_cache[widget] == nil then
        widget_cache[widget] = {}
    end
end
-- }}}

---- {{{ Update widget
function update(widget, reg, disablecache)
    -- Check if there are any equal widgets
    if reg == nil then
        for w, i in pairs(registered) do
            if w == widget then
                for k,v in pairs(i) do
                    update(w, v, disablecache)
                end
            end
        end

        return
    end

    local t = os.time()
    local data = {}

    -- Check if we have output chached for this widget,
    -- newer than last widget update.
    if widget_cache[reg.type] ~= nil then
        local c = widget_cache[reg.type]

        if c.time == nil or c.time <= t-reg.timer or disablecache then
            c.time = t
            c.data = reg.type(reg.format, reg.padd)
        end
        
        data = c.data
    else
        data = reg.type(reg.format, reg.padd)
    end

    if type(data) == "table" then
        if type(reg.format) == "string" then
            data = helper.format(reg.format, data)
        elseif type(reg.format) == "function" then
            data = reg.format(widget, data)
        end
    end
    
    if reg.field == nil then
        widget.text = data
    elseif widget.plot_data_add ~= nil then
        widget:plot_data_add(reg.field, tonumber(data))
    elseif widget.bar_data_add ~= nil then
        widget:bar_data_add(reg.field, tonumber(data))
    end
    return data
end

-- }}}

-- }}}

-- vim: set filetype=lua fdm=marker tabstop=4 shiftwidth=4 nu:
