/*
 *	Lists the MIME Types.
 */
#include <time.h>
#include <fnmatch.h>
#include <glib.h>
#include <endeavour2.h>
#include "../config.h"


typedef enum {
	LSMIMETYPES_PRINT_ALL			= (1 << 0),
	LSMIMETYPES_PRINT_LONG			= (1 << 1),
	LSMIMETYPES_PRINT_DEREFERENCE_COMMANDS	= (1 << 2),
	LSMIMETYPES_PRINT_FULL_TIME		= (1 << 3)
} lsmimetypes_print_flags;

static void print_usage(const gchar *prog_name);

static gchar *get_time_stamp_str(
	const gulong t,
	const gulong ct,
	lsmimetypes_print_flags print_flags
);

static void print_time_stamp(
	const gulong t,
	const gulong ct,
	lsmimetypes_print_flags print_flags
);
static void print_mimetype(
	edv_context_struct *ctx,
	edv_mime_type_struct *m,
	const gulong ct,
	const lsmimetypes_print_flags print_flags
);
static void list_mimetypes(
	edv_context_struct *ctx,
	GList *types_list,
	const gulong ct,
	const lsmimetypes_print_flags print_flags
);


#define ATOI(s)		(((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)		(((s) != NULL) ? atol(s) : 0)
#define ATOF(s)		(((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)	(((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)	(((a) > (b)) ? (a) : (b))
#define MIN(a,b)	(((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)	(MIN(MAX((a),(l)),(h)))
#define STRLEN(s)	(((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)	(((s) != NULL) ? (*(s) == '\0') : TRUE)


static void print_usage(const gchar *prog_name)
{
	g_print(
"Usage: %s [type(s)...] [options]\n",
	    prog_name
	);

	g_print("\n\
    The [type(s)...] specifies the type names of the MIME Types to\n\
    list.\n\
\n\
    Note that if you specify wildcards (* or ?) then you need to put\n\
    them between double quotes (\") such as \"*\" or else the shell\n\
    will expand them as regular object names instead of recycled\n\
    object names.\n\
\n\
    The [options] can be any of the following:\n\
\n\
	-a                      Print all information.\n\
	-l                      Print class and value.\n\
	--dereference-commands  Print only the referenced command\n\
				(instead of both the referenced\n\
				command and the reference).\n\
	-L                      Same as --dereference-commands.\n\
	--full-time             Print full date and time.\n\
\n\
	--help                  Prints (this) help screen and exits.\n\
	--version               Prints version information and exits.\n\
\n\
    Return values:\n\
\n\
	0       Success.\n\
	1       General error.\n\
	2       Invalid value.\n\
	3       Systems error or memory allocation error.\n\
	4       User aborted.\n\
\n\
    To find a MIME Type assocated with an object, use:\n\
\n\
	open -l <object>\n\
\n"
	);
}


/*
 *	Returns a dynamically allocated string describing the timestamp.
 *
 *	The t specifies the time in seconds since EPOCH.
 *
 *	The ct specifies the current time in seconds since EPOCH.
 */
static gchar *get_time_stamp_str(
	const gulong t,
	const gulong ct,
	lsmimetypes_print_flags print_flags
)
{
	gchar *s;
	time_t _t = (time_t)t;
	struct tm *tm_ptr = localtime(&_t);

	if(t == 0l)
	    return(STRDUP(""));

	if(print_flags & LSMIMETYPES_PRINT_FULL_TIME)
	{
	    s = (gchar *)g_malloc(25 * sizeof(gchar));
	    strftime(
		(char *)s, 25,
		"%a %b %e %H:%M:%S %Y",
		tm_ptr
	    );
	    s[24] = '\0';
	}
	else
	{   
	    s = (gchar *)g_malloc(13 * sizeof(gchar));
	    /* Time is in the future? */
	    if(t > ct)
		strftime(
		    (char *)s, 13, "%b %e %H:%M",
		    tm_ptr
		);
	    /* Less than a year old? */
	    else if((gulong)(ct - t) < (365l * 24l * 60l * 60l))
		strftime(
		    (char *)s, 13, "%b %e %H:%M",
		    tm_ptr
		);
	    else
		strftime(
		    (char *)s, 13, "%b %e  %Y",
		    tm_ptr
		);
	    s[12] = '\0';
	}

	return(s);
}

/*
 *	Prints the timestamp.
 *
 *	The t specifies the time in seconds since EPOCH.
 *
 *	The ct specifies the current time in seconds since EPOCH.
 */
static void print_time_stamp(
	const gulong t,
	const gulong ct,
	lsmimetypes_print_flags print_flags
)
{
	gchar *s = get_time_stamp_str(t, ct, print_flags);
	if(s != NULL)
	{
	    g_print("%s", s);
	    g_free(s);
	}
}

/*
 *	Prints the specified MIME Type.
 *
 *	The ct specifies the current time in seconds since EPOCH.
 */
static void print_mimetype(
	edv_context_struct *ctx,
	edv_mime_type_struct *m,
	const gulong ct,
	const lsmimetypes_print_flags print_flags
)
{
	gint i;
	const gchar *s;

	if(m == NULL)
	    return;

	if((print_flags & LSMIMETYPES_PRINT_ALL) ||
	   (print_flags & LSMIMETYPES_PRINT_LONG)
	)
	{
	    switch(m->mt_class)
	    {
	      case EDV_MIME_TYPE_CLASS_SYSTEM:
		g_print("%s: System Object\n", m->type);
		break;
	      case EDV_MIME_TYPE_CLASS_FORMAT:
		g_print("%s: Format: \"%s\"\n", m->type, m->value);
		break;
	      case EDV_MIME_TYPE_CLASS_PROGRAM:
		g_print("%s: Program: \"%s\"\n", m->type, m->value);
		break;
	      case EDV_MIME_TYPE_CLASS_UNIQUE:
		g_print("%s: Unique: \"%s\"\n", m->type, m->value);
		break;
	    }
	}
	else
	{
	    g_print("%s\n", m->type);
	}


	/* Print the icon files */
	if(print_flags & LSMIMETYPES_PRINT_ALL)
	{
	    g_print("\tStandard Icons:\n");
	    i = EDV_MIME_TYPE_ICON_STATE_STANDARD;
	    s = m->small_icon_file[i];
	    g_print("\t\tSmall: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->medium_icon_file[i];
	    g_print("\t\tMedium: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->large_icon_file[i];
	    g_print("\t\tLarge: \"%s\"\n", (s != NULL) ? s : "");

	    g_print("\tSelected Icons:\n");
	    i = EDV_MIME_TYPE_ICON_STATE_SELECTED;
	    s = m->small_icon_file[i];
	    g_print("\t\tSmall: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->medium_icon_file[i];
	    g_print("\t\tMedium: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->large_icon_file[i];
	    g_print("\t\tLarge: \"%s\"\n", (s != NULL) ? s : "");

	    g_print("\tExtended Icons:\n");
	    i = EDV_MIME_TYPE_ICON_STATE_EXTENDED;
	    s = m->small_icon_file[i];
	    g_print("\t\tSmall: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->medium_icon_file[i];
	    g_print("\t\tMedium: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->large_icon_file[i];
	    g_print("\t\tLarge: \"%s\"\n", (s != NULL) ? s : "");

	    g_print("\tHidden Icons:\n");
	    i = EDV_MIME_TYPE_ICON_STATE_HIDDEN;
	    s = m->small_icon_file[i];
	    g_print("\t\tSmall: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->medium_icon_file[i];
	    g_print("\t\tMedium: \"%s\"\n", (s != NULL) ? s : "");
	    s = m->large_icon_file[i];
	    g_print("\t\tLarge: \"%s\"\n", (s != NULL) ? s : "");
	}

	/* Print the commands */
	if((print_flags & LSMIMETYPES_PRINT_ALL) &&
	   (m->commands_list != NULL)
	)
	{
	    const gint ncmds = g_list_length(m->commands_list);
	    const gchar *command;
	    GList *glist;
	    edv_mime_type_command_struct *cmd;

	    g_print("\tCommand%s:\n", (ncmds == 1) ? "" : "s");

	    for(glist = m->commands_list;
		glist != NULL;
		glist = g_list_next(glist)
	    )
	    {
		cmd = EDV_MIME_TYPE_COMMAND(glist->data);
		if(cmd == NULL)
		    continue;

		command = cmd->command;
		if(command == NULL)
		    continue;

		if(print_flags & LSMIMETYPES_PRINT_DEREFERENCE_COMMANDS)
		{
		    /* Does this command refer to another MIME Type? */
		    if(*command != '/')
		    {
			/* Print the referenced MIME Type */
			const edv_mime_type_struct *m2 = EDVMimeTypeMatchType(
			    ctx, command
			);
			if(m2 != NULL)
			{
			    if(m2->commands_list != NULL)
			    {
				GList *glist = m2->commands_list;
				edv_mime_type_command_struct *cmd2 = EDV_MIME_TYPE_COMMAND(glist->data);
				if(cmd2 != NULL)
				    g_print(
					"\t\t%s = \"%s\"\n",
					cmd->name, cmd2->command
				    );
				else
				    g_print(
					"\t\t%s = \"\"\n",
					cmd->name
				    );
			    }
			    else
			    {
				g_print(
				    "\t\t%s = \"\"\n",
				    cmd->name
				);
			    }
			}
			else
			{
			    g_print(
				"\t\t%s = \"%s\" (reference missing)\n",
				cmd->name, command
			    );
			}
		    }
		    else
		    {
			g_print(
			    "\t\t%s = \"%s\"\n",
			    cmd->name, command
			);
		    }
		}
		else
		{
		    g_print(
			"\t\t%s = \"%s\"",
			cmd->name, command
		    );

		    /* Does this command refer to another MIME Type? */
		    if(*command != '/')
		    {
			/* Print the referenced MIME Type */
			const edv_mime_type_struct *m2 = EDVMimeTypeMatchType(
			    ctx, command
			);
			if(m2 != NULL)
			{
			    if(m2->commands_list != NULL)
			    {
				GList *glist = m2->commands_list;
				edv_mime_type_command_struct *cmd2 = EDV_MIME_TYPE_COMMAND(glist->data);
				if(cmd2 != NULL)
				    g_print(" -> \"%s\"", cmd2->command);
			    }
			}
		    }
		    g_print("\n");
		}
	    }
	}

	/* Time stamps */
	if(print_flags & LSMIMETYPES_PRINT_ALL)
	{
	    g_print("\tLast Accessed: ");
	    print_time_stamp(m->access_time, ct, print_flags);
	    g_print("\n");
	    g_print("\tLast Modified: ");
	    print_time_stamp(m->modify_time, ct, print_flags);
	    g_print("\n");
	    g_print("\tLast Changed: ");
	    print_time_stamp(m->change_time, ct, print_flags);
	    g_print("\n");
	}

}


/*
 *	Prints the list of MIME Types.
 *
 *	The types_list specifies the MIME Type type name strings of
 *	the MIME Types to print (wildcards are allowed). If types_list
 *	is NULL then all the MIME Types will be printed.
 *
 *	The ct specifies the current time in seconds since EPOCH.
 */
static void list_mimetypes(
	edv_context_struct *ctx,
	GList *types_list,
	const gulong ct,
	const lsmimetypes_print_flags print_flags
)
{
	gint i, total;
	const gchar *type;
	GList *glist;
	edv_mime_type_struct *m, **list = EDVMimeTypeList(ctx, &total);

	if(types_list != NULL)
	{
	    /* Iterate through the types list */
	    for(glist = types_list; glist != NULL; glist = g_list_next(glist))
	    {
		type = (const gchar *)glist->data;
		if(type == NULL)
		    continue;

		/* Iterate through the MIME Types list and print the
		 * MIME types that match this type
		 */
		for(i = 0; i < total; i++)
		{
		    m = list[i];
		    if(m == NULL)
			continue;

		    if(m->type == NULL)
			continue;

		    if(fnmatch(
			type, m->type,
#ifdef FNM_CASEFOLD
			FNM_CASEFOLD |
#endif
			0
		    ) == 0)
			print_mimetype(ctx, m, ct, print_flags);
		}
	    }
	}
	else
	{
	    for(i = 0; i < total; i++)
		print_mimetype(ctx, list[i], ct, print_flags);
	}
}


int main(int argc, char *argv[])
{
	lsmimetypes_print_flags print_flags = 0;
	gint i;
	gulong ct;
	const gchar *arg;
	GList *types_list = NULL;
	edv_context_struct *ctx = EDVContextNew();

	EDVContextLoadConfigurationFile(ctx, NULL);

#define DO_FREE_LOCALS	{		\
 if(types_list != NULL) {		\
  g_list_foreach(			\
   types_list, (GFunc)g_free, NULL	\
  );					\
  g_list_free(types_list);		\
 }					\
					\
 EDVContextDelete(ctx);			\
}

	/* Parse arguments */
	for(i = 1; i < argc; i++)
	{
	    arg = argv[i];
	    if(arg == NULL)
		continue;

	    /* Help */
	    if(!g_strcasecmp(arg, "--help") ||
	       !g_strcasecmp(arg, "-help") ||
	       !g_strcasecmp(arg, "--h") ||
	       !g_strcasecmp(arg, "-h")
	    )
	    {
		print_usage(argv[0]);
		DO_FREE_LOCALS
		return(0);
	    }
	    /* Version */
	    else if(!g_strcasecmp(arg, "--version") ||
		    !g_strcasecmp(arg, "-version")
	    )
	    {
		g_print(
"Endeavour Mark II MIME Types List Version " PROG_VERSION "\n"
PROG_COPYRIGHT
		);
		DO_FREE_LOCALS
		return(0);
	    }
	    /* All */
	    else if(!strcmp(arg, "--a") ||
		    !strcmp(arg, "-a")
	    )
	    {
		print_flags |= LSMIMETYPES_PRINT_ALL;
	    }
	    /* Long */
	    else if(!strcmp(arg, "--l") ||
		    !strcmp(arg, "-l")
	    )
	    {
		print_flags |= LSMIMETYPES_PRINT_LONG;
	    }
	    /* Dereference Commands */
	    else if(!g_strcasecmp(arg, "--dereference-commands") ||
		    !g_strcasecmp(arg, "-dereference-commands") ||
		    !strcmp(arg, "--L") ||
		    !strcmp(arg, "-L")
	    )
	    {
		print_flags |= LSMIMETYPES_PRINT_DEREFERENCE_COMMANDS;
	    }
	    /* Full Time */
	    else if(!g_strcasecmp(arg, "--full-time") ||
		    !g_strcasecmp(arg, "-full-time")
	    )
	    {
		print_flags |= LSMIMETYPES_PRINT_FULL_TIME;
	    }
	    /* Single character option? */
	    else if((*arg == '-') ? (arg[1] != '-') : FALSE)
	    {
		const gchar *v = arg + 1;
		gchar c;
		while(*v != '\0')
		{
		    c = *v;
		    if(c == 'a')
		    {
		        print_flags |= LSMIMETYPES_PRINT_ALL;
		    }
		    else if(c == 'l')
		    {
		        print_flags |= LSMIMETYPES_PRINT_LONG;
		    }
		    else if(c == 'L')
		    {
		        print_flags |= LSMIMETYPES_PRINT_DEREFERENCE_COMMANDS;
		    }
		    else
		    {
		        g_printerr(
"-%c: Unsupported argument.\n",
			    c
			);
			DO_FREE_LOCALS
			return(2);
		    }
		    v++;
		}
	    }
	    /* All else assume MIME Type type */
	    else if((*arg != '-') && (*arg != '+'))
	    {
		types_list = g_list_append(types_list, STRDUP(arg));
	    }
	}

	/* Get the current time in seconds since EPOCH */
	ct = (gulong)time(NULL);

	/* Print the MIME Types */
	list_mimetypes(ctx, types_list, ct, print_flags);

	EDVContextSync(ctx);

	DO_FREE_LOCALS

	return(0);
#undef DO_FREE_LOCALS
}
