/**
 * openbox-xdgmenu
 * 
 * This command line application generates an OpenBox menu XML segment based on
 * an Xdg menu structure.
 * 
 * Usage: openbox-xdgmenu <Xdg menu file>
 * 
 * Copyright (C) Nathan Fisher <nfisher@grafpup.com>
 * Copyright (C) 2008 Siegfried-A. Gevatter <rainct@ubuntu.com>
 * Copyright (C) 2011 Kévin Joly <joly.kevin25@gmail.com>
 * Copyright (C) 2016 James Budiono <jamesbond3142@gmail.com>
 * Originally based upon code by Raul Suarez <rarsa@yahoo.com>
 *
 * 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/>.
 * 
 * Dependencies:
 *   gnome-menus
 *   glib
 *   gtk
 */

#include <string.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include <gnome-menus/gmenu-tree.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

/*=============================================================================
 * Declarations
 */
static void show_help();
static void process_directory(GMenuTreeDirectory *directory, gboolean isRoot);
static void process_entry(GMenuTreeEntry *entry);
static void process_separator(GMenuTreeSeparator *entry);
static char *find_icon(char *icon);

gboolean option_show_amount_of_entries = 0;
gboolean option_do_not_use_categories = 0;
gboolean option_do_not_use_icon = 0;

/*=============================================================================
 * Main Function
 */
int main (int argc, char **argv)
{
    int i;
    char *xdgfile = NULL;
    
    gtk_init_check(&argc, &argv);
    
    for (i = 1; i < argc; i++)
    {
        if ((strcmp (argv[i], "-h") == 0) || (strcmp (argv[i], "--help") == 0))
        {
          show_help ();
          g_free (xdgfile);
          return 0;
        }
        else if ((strcmp (argv[i], "--show-amount") == 0) ||
        (strcmp (argv[i], "-a") == 0))
        {
            option_show_amount_of_entries = 1;
        }
        else if ((strcmp (argv[i], "--plain") == 0) ||
        (strcmp (argv[i], "-p") == 0))
        {
            option_do_not_use_categories = 1;
        }
        else if ((strcmp (argv[i], "--no-icon") == 0) ||
        (strcmp (argv[i], "-n") == 0))
        {
            option_do_not_use_icon = 1;
        }        
        else if (argv[i][0] != '-')
        {
            if (xdgfile != NULL) {
                g_printf ("Unexpected amount of arguments.\n");
                g_free (xdgfile);
                return 1;
            }
            xdgfile = g_strdup (argv[i]);
        }
        else
        {
            g_printf ("Unknown argument: %s\n", argv[i]);
            return 1;
        }
    }
    
    if (xdgfile == NULL) {
        show_help ();
        g_free (xdgfile);
        return 1;
    }
    
    FILE * file;
    if (!(file = fopen(xdgfile, "r")))
    {
        g_printf ("Could not read file \"%s\".\n", xdgfile);
        g_free (xdgfile);
        return 2;
    }
    fclose(file);
    
    GMenuTree *menuTree = gmenu_tree_lookup (xdgfile,  GMENU_TREE_FLAGS_NONE );
    
    GMenuTreeDirectory *rootDirectory = gmenu_tree_get_root_directory(menuTree);
    
    g_printf ("<openbox_pipe_menu>\n");
    
    process_directory(rootDirectory, 1);
    
    gmenu_tree_item_unref (rootDirectory);
    
    g_printf ("</openbox_pipe_menu>\n");
    
    g_free (xdgfile);
    return 0;
}

/*=============================================================================
 * Shows usage parameters
 */
void show_help()
{
    g_printf ("Creates an OpenBox menu from an Xdg menu structure.\n");
    g_printf ("\n");
    g_printf ("Usage:\n");
    g_printf ("  openbox-xdgmenu [options] <Xdg menu file>\n");
    g_printf ("\n");
    g_printf ("Options:\n");
    g_printf ("  -a: Show the amount of items in each category next to its name.\n");
    g_printf ("  -p: Do not use categories.\n");
    g_printf ("\n");
    g_printf ("For example:\n");
    g_printf ("  openbox-xdgmenu \"/etc/xdg/menus/applications.menu\"\n\n");
}

/*=============================================================================
 * This function processes a directory entry and all it's child nodes
 */
void process_directory(GMenuTreeDirectory *directory, gboolean isRoot)
{
    int hasSeparator = 0;
    int hasMenu = 0;
    GMenuTreeItemType entryType;
    GSList *entryList = gmenu_tree_directory_get_contents (directory);
    GSList *l;
    
    if (option_do_not_use_categories == 00 && isRoot == 0 &&
    g_slist_length(entryList) > 0)
    {
        hasMenu = 1;
        char *icon = find_icon((char*)gmenu_tree_directory_get_icon(directory));

        if (option_show_amount_of_entries == 1)
        {
            g_printf(
              "<menu id=\"xdg-menu-%s\" label=\"%s (%d)\" icon=\"%s\">\n",
              g_strjoinv("&amp;", g_strsplit(gmenu_tree_directory_get_name(directory),"&",0)),
              g_strjoinv("&amp;", g_strsplit(gmenu_tree_directory_get_name(directory),"&",0)),
              g_slist_length(entryList),
              icon);
        }
        else
        {
            g_printf(
              "<menu id=\"xdg-menu-%s\" label=\"%s\" icon=\"%s\">\n",
              g_strjoinv("&amp;", g_strsplit(gmenu_tree_directory_get_name(directory),"&",0)),
              g_strjoinv("&amp;", g_strsplit(gmenu_tree_directory_get_name(directory),"&",0)),
              icon);
        }
        if (icon) g_free(icon);
    }

    for (l = entryList; l; l = l->next)
    {
        GMenuTreeItem *item = l->data;
        
        entryType = gmenu_tree_item_get_type (GMENU_TREE_ITEM(item));
        
        switch (entryType)
        {
            case GMENU_TREE_ITEM_DIRECTORY:
                if (hasSeparator)
                {
                    process_separator(GMENU_TREE_SEPARATOR(item));
                    hasSeparator = 0;
                }
                process_directory(GMENU_TREE_DIRECTORY(item), 0);
                break;
            case GMENU_TREE_ITEM_ENTRY:
                if (hasSeparator)
                {
                    process_separator(GMENU_TREE_SEPARATOR(item));
                    hasSeparator = 0;
                }
                process_entry(GMENU_TREE_ENTRY(item));
                break;
            case GMENU_TREE_ITEM_SEPARATOR:
                hasSeparator = 1;
                break;
        }
        
        gmenu_tree_item_unref (item);
    }
    
    if (hasMenu == 1)
    {
        g_printf("</menu>\n");
    }
    
    g_slist_free (entryList);
}

/*=============================================================================
 * This function adds an application entry
 */
void process_entry(GMenuTreeEntry *entry)
{
    char *name = g_strdup (gmenu_tree_entry_get_name(entry));
    char *exec = g_strdup (gmenu_tree_entry_get_exec(entry));
    char *icon = find_icon((char*)gmenu_tree_entry_get_icon(entry));
    int i;
    
    for (i = 0; i < strlen(exec) - 1; i++) {
        if (exec[i] == '%')
        {
            switch (exec[i+1]) {
                case 'f': case 'F':
                case 'u': case 'U':
                case 'd': case 'D':
                case 'n': case 'N':
                case 'i': case 'c': case 'k': case 'v': case 'm':
                    exec[i] = ' ';
                    exec[i+1] = ' ';
                    i++;
                    break;
            }
        }
    }
    
    g_printf("<item label=\"%s\" icon=\"%s\">\n", g_strjoinv("&amp;", g_strsplit(name,"&",0)), icon),
    g_printf("<action name=\"Execute\"><execute>%s</execute></action>\n", exec),
    g_printf("</item>\n");
    
    g_free(icon);
    g_free(name);
    g_free(exec);
}

/*=============================================================================
 * This function adds a separator
 */
void process_separator(GMenuTreeSeparator *entry)
{
    g_printf("<separator /> \n");
}

/*=============================================================================
 * Given an icon name (or path), this function returns the full path
 * to the icon image file, if it can be found. If not, "" is returned.
 * Must free the string with with g_free.
 */
char *find_icon(char *icon)
{	
	char *tmp;
	struct stat sb;
	char *icon_extensions[] = { ".png", ".xpm", ".svg", "", 0 }; // supported icon types
	char *icon_dirs[] = {
		"/usr/share/pixmaps",
		"/usr/share/pixmaps/midi-icons",
		"/usr/share/pixmaps/mini-icons",
		0
	};
	char **dir, **ext;	
	int icon_sizes[] = { 48, 32, 24, 16, 0 }; // GTK icon sizes
	int *size;

	// no icon
	if (option_do_not_use_icon || !icon) return g_strdup("");
	
	// if full path already given and it exists, return it
	if (stat(icon,&sb)==0 && !S_ISDIR(sb.st_mode)) return g_strdup(icon);
	
	// drop all before search		
	if ((tmp = strrchr(icon, '/'))) icon = tmp++;
	
	// find in pixmap dirs
	for (dir = icon_dirs; *dir; dir++) 
	{
		for (ext = icon_extensions; *ext; ext++) 
		{
			tmp = g_strdup_printf("%s/%s%s",*dir, icon, *ext);
			//g_print("%s\n",tmp);
			if (stat(tmp,&sb)==0 && !S_ISDIR(sb.st_mode)) return tmp;
			g_free(tmp);
		}
	}
		
	// still nothing, now find using gtk
	GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
	for (size = icon_sizes; *size; size++) {
		GtkIconInfo *icon_info = gtk_icon_theme_lookup_icon (icon_theme, icon, *size, 0);
		if (icon_info) {
			tmp = g_strdup(gtk_icon_info_get_filename(icon_info));
			g_object_unref(icon_info);
			return tmp;
		}
	}
	return g_strdup("");
} 

/*=============================================================================
 */
