-- Grab environment

local io = io
local os = os
local table = table
local type = type
local ipairs = ipairs
local pairs = pairs

local Gtk = require("lgi").Gtk

module("freedesktop.utils")

terminal = 'xterm'

icon_theme = nil
gtk_icon_theme = Gtk.IconTheme.get_default()

all_icon_sizes = {
    '128x128',
    '96x96',
    '72x72',
    '64x64',
    '48x48',
    '36x36',
    '32x32',
    '24x24',
    '22x22',
    '16x16'
}
all_icon_types = {
    'apps',
    'actions',
    'devices',
    'places',
    'categories',
    'status',
    'mimetypes'
}
all_icon_paths = { os.getenv("HOME") .. '/.icons/', '/usr/share/icons/' }

icon_sizes = {}

local mime_types = {}

function get_lines(...)
    local f = io.popen(...)
    return function () -- iterator
        local data = f:read()
        if data == nil then f:close() end
        return data
    end
end

function file_exists(filename)
    local file = io.open(filename, 'r')
    local result = (file ~= nil)
    if result then
        file:close()
    end
    return result
end

function lookup_icon(arg)
    if arg.icon:sub(1, 1) == '/' and (arg.icon:find('.+%.png') or arg.icon:find('.+%.xpm')) then
        -- icons with absolute path and supported (AFAICT) formats
        return arg.icon
    else
        local gtk_icon_info = Gtk.IconTheme.lookup_icon(gtk_icon_theme, arg.icon, 48, 0)
        if gtk_icon_info then
            filename = Gtk.IconInfo.get_filename(gtk_icon_info)
            if filename then
                return filename
            end
        end

        local icon_path = {}
        local icon_themes = {}
        local icon_theme_paths = {}
        if icon_theme and type(icon_theme) == 'table' then
            icon_themes = icon_theme
        elseif icon_theme then
            icon_themes = { icon_theme }
        end
        for i, theme in ipairs(icon_themes) do
            for j, path in ipairs(all_icon_paths) do
                table.insert(icon_theme_paths, path .. theme .. '/')
            end
            -- TODO also look in parent icon themes, as in freedesktop.org specification
        end
        table.insert(icon_theme_paths, '/usr/share/icons/hicolor/') -- fallback theme cf spec

        local isizes = icon_sizes
        for i, sz in ipairs(all_icon_sizes) do
            table.insert(isizes, sz)
        end

        for i, icon_theme_directory in ipairs(icon_theme_paths) do
            for j, size in ipairs(arg.icon_sizes or isizes) do
                for k, icon_type in ipairs(all_icon_types) do
                    table.insert(icon_path, icon_theme_directory .. size .. '/' .. icon_type .. '/')
                end
            end
        end
        -- lowest priority fallbacks
        table.insert(icon_path,  '/usr/share/pixmaps/')
        table.insert(icon_path,  '/usr/share/icons/')
        table.insert(icon_path,  '/usr/share/app-install/icons/')

        for i, directory in ipairs(icon_path) do
            if (arg.icon:find('.+%.png') or arg.icon:find('.+%.xpm')) and file_exists(directory .. arg.icon) then
                return directory .. arg.icon
            elseif file_exists(directory .. arg.icon .. '.png') then
                return directory .. arg.icon .. '.png'
            elseif file_exists(directory .. arg.icon .. '.xpm') then
                return directory .. arg.icon .. '.xpm'
            end
        end
    end
end

function lookup_file_icon(arg)
    load_mime_types()

    local extension = arg.filename:match('%a+$')
    local mime = mime_types[extension] or ''
    local mime_family = mime:match('^%a+') or ''

    -- possible icons in a typical gnome theme (i.e. Tango icons)
    local possible_filenames = {
        mime,
        'gnome-mime-' .. mime,
        mime_family,
        'gnome-mime-' .. mime_family,
        extension
    }

    for i, filename in ipairs(possible_filenames) do
        local icon = lookup_icon({icon = filename, icon_sizes = (arg.icon_sizes or all_icon_sizes)})
        if icon then
            return icon
        end
    end

    -- If we don't find ad icon, then pretend is a plain text file
    return lookup_icon({ icon = 'txt', icon_sizes = arg.icon_sizes or all_icon_sizes })
end

--- Load system MIME types
-- @return A table with file extension <--> MIME type mapping
function load_mime_types()
    if #mime_types == 0 then
        for line in io.lines('/etc/mime.types') do
            if not line:find('^#') then
                local parsed = {}
                for w in line:gmatch('[^%s]+') do
                    table.insert(parsed, w)
                end
                if #parsed > 1 then 
                    for i = 2, #parsed do
                        mime_types[parsed[i]] = parsed[1]:gsub('/', '-')
                    end
                end
            end
        end
    end
end

--- Parse a .desktop file
-- @param file The .desktop file
-- @param requested_icon_sizes A list of icon sizes (optional). If this list is given, it will be used as a priority list for icon sizes when looking up for icons. If you want large icons, for example, you can put '128x128' as the first item in the list.
-- @return A table with file entries.
function parse_desktop_file(arg)

    local function check_nil(f, v) 
        -- Almost the same as 
        -- return f and f or v
        -- but it will return false if f = false
        if f == nil then return v else return f end
    end

    --- Parses .desktop file considering groups.
    -- @param file Path to file
    -- @return A table with group enries. Each group entry is table with file entries.
    local function parse_file(file)
        local result = {}
        local group = nil

        for line in io.lines(file) do
            group = line:match("^%[([^%[%]%c]+)%]") or group
            if group then
                result[group] = check_nil(result[group], {})

                for key, value in line:gmatch("(%w+)=(.+)") do
                    result[group][key] = value
                end
            end
        end

        return result
    end

    -- Using only 'Desktop Entry' group.
    local program = parse_file(arg.file)['Desktop Entry']
    program.show = check_nil(program.show, true)
    program.file = arg.file

    -- Don't show the program if NoDisplay is true
    -- Only show the program if there is not OnlyShowIn attribute
    -- or if it's equal to 'awesome'
    if program.NoDisplay == "true" or program.OnlyShowIn ~= nil and program.OnlyShowIn ~= "awesome" then
        program.show = false
    end

    -- Look up for a icon.
    if program.Icon then
        program.icon_path = lookup_icon({ icon = program.Icon, icon_sizes = (arg.icon_sizes or all_icon_sizes) })
        if program.icon_path ~= nil and not file_exists(program.icon_path) then
           program.icon_path = nil
        end
    end

    -- Split categories into a table.
    if program.Categories then
        program.categories = {}
        for category in program.Categories:gmatch('[^;]+') do
            table.insert(program.categories, category)
        end
    end

    if program.Exec then
        local cmdline = program.Exec:gsub('%%c', program.Name)
        cmdline = cmdline:gsub('%%[fmuFMU]', '')
        cmdline = cmdline:gsub('%%k', program.file)
        if program.icon_path then
            cmdline = cmdline:gsub('%%i', '--icon ' .. program.icon_path)
        else
            cmdline = cmdline:gsub('%%i', '')
        end
        if program.Terminal == "true" then
            cmdline = terminal .. ' -e ' .. cmdline
        end
        program.cmdline = cmdline
    end

    return program
end

--- Parse a directory with .desktop files
-- @param dir The directory.
-- @param icons_size, The icons sizes, optional.
-- @return A table with all .desktop entries.
function parse_desktop_files(arg)
    local programs = {}
    local files = get_lines('find '.. arg.dir ..' -name "*.desktop" 2>/dev/null')
    for file in files do
        arg.file = file
        table.insert(programs, parse_desktop_file(arg))
    end
    return programs
end

--- Parse a directory files and subdirs
-- @param dir The directory.
-- @param icons_size, The icons sizes, optional.
-- @return A table with all .desktop entries.
function parse_dirs_and_files(arg)
    local files = {}
    local paths = get_lines('find '..arg.dir..' -maxdepth 1 -type d')
    for path in paths do
        if path:match("[^/]+$") then
            local file = {}
            file.filename = path:match("[^/]+$")
            file.path = path
            file.show = true
            file.icon = lookup_icon({ icon = "folder", icon_sizes = (arg.icon_sizes or all_icon_sizes) })
            table.insert(files, file)
        end
    end
    local paths = get_lines('find '..arg.dir..' -maxdepth 1 -type f')
    for path in paths do
        if not path:find("%.desktop$") then
            local file = {}
            file.filename = path:match("[^/]+$")
            file.path = path
            file.show = true
            file.icon = lookup_file_icon({ filename = file.filename, icon_sizes = (arg.icon_sizes or all_icon_sizes) })
            table.insert(files, file)
        end
    end
    return files
end