/*
 * GQmpeg
 * (C)1998, 1999 John Ellis
 *
 * Author: John Ellis
 *
 * This software is released under the GNU General Public License.
 * Please read the included file COPYING for more information.
 * This software comes with no warranty of any kind, use at you own risk!
 */

#include "gqmpeg.h"
#include "utildlg.h"

/*
 *-----------------------------------------------------------------------------
 * static vars
 *-----------------------------------------------------------------------------
 */

static GList *shuffle_list;
static GList *playlist;
static gint playlist_count = 0;
static gint playlist_length = 0;

/*
 *-----------------------------------------------------------------------------
 * static funcs
 *-----------------------------------------------------------------------------
 */

static GList *shuffle_list_find_node(gint p);
static void shuffle_list_add (gint p);
static void shuffle_list_remove (gpointer data);
static gint shuffle_list_get_first();
static gint shuffle_list_get_next(gint p);
static gint shuffle_list_get_prev(gint p);

static void playlist_append_dir_func(gchar *d, gint recursive);

static void real_playlist_init();
static void real_playlist_clear();
static void real_playlist_add_item(gchar *path);
static void real_playlist_insert_item(gchar *path, gint n);
static gint real_playlist_remove_item(gint n);
static gint real_playlist_move_item(gint s, gint t);

/*
 *-----------------------------------------------------------------------------
 * playlist shuffle functions (private)
 *-----------------------------------------------------------------------------
 */

static GList *shuffle_list_find_node(gint p)
{
	GList *work = g_list_nth(playlist, p);
	if (!work) return NULL;
	return g_list_find(shuffle_list, work->data);
}

static void shuffle_list_add (gint p)
{
	gint shuffle_p = (float)rand() / RAND_MAX * ( g_list_length(shuffle_list) + 1);
	GList *node = g_list_nth(playlist, p);
	if (shuffle_p == g_list_length(shuffle_list))
		shuffle_list = g_list_append(shuffle_list, node->data);
	else
		shuffle_list = g_list_insert(shuffle_list, node->data, shuffle_p);
}

static void shuffle_list_remove (gpointer data)
{
	shuffle_list = g_list_remove(shuffle_list, data);
}

static gint shuffle_list_get_next(gint p)
{
	GList *node;

	if (!shuffle_list) return -1;

	node = shuffle_list_find_node(p);

	if (node)
		{
		node = node->next;
		}
	else
		{
		node = shuffle_list;
		}

	if (!node) return -1;

	return g_list_index(playlist, node->data);
}

static gint shuffle_list_get_prev(gint p)
{
	GList *node;

	if (!shuffle_list) return -1;

	node = shuffle_list_find_node(p);

	if (node)
		{
		node = node->prev;
		}
	else
		{
		node = g_list_last(shuffle_list);
		}

	if (!node) return -1;

	return g_list_index(playlist, node->data);
}

static gint shuffle_list_get_first()
{
	if (!playlist) return -1;
	if (!shuffle_list) return 0;
	return g_list_index(playlist, shuffle_list->data);
}

/*
 *-----------------------------------------------------------------------------
 * playlist shuffle functions (public)
 *-----------------------------------------------------------------------------
 */

void shuffle_list_create(gint start_with_current)
{
	GList *work = NULL;
	GList *node = NULL;
	int i;

	if (shuffle_list)
		{
		shuffle_list_destroy();
		}

	if (debug_mode) printf("Shuffling:");

	if (playlist_count == 0) return;
		
	for (i=0; i < playlist_count; i++)
		{
		if (i != current_song_get_number() || !start_with_current)
			work = g_list_prepend(work, playlist_get_data(i));
		}
	work = g_list_reverse(work);

	if (start_with_current && current_song_get_number() >= 0 && current_song_get_number() < playlist_get_count())
		{
		node = g_list_nth(playlist, current_song_get_number());
		shuffle_list = g_list_prepend(shuffle_list, node->data);
		if (debug_mode) printf("%d ", current_song_get_number());
		}
	else if (playlist_get_count() > 1 && current_song_get_number() >= 0 && current_song_get_number() < playlist_get_count())
		{
		gint p;

		/* remove current song so we can not pick it */
		node = g_list_nth(work, current_song_get_number());
		work = g_list_remove(work, node->data);

		p = (float)rand() / RAND_MAX * g_list_length(work);
		node = g_list_nth(work, p);
		if (debug_mode) printf("%d ", g_list_index(playlist, node->data));
		shuffle_list = g_list_prepend(shuffle_list, node->data);

		/* and put current song back in*/
		work = g_list_insert(work, playlist_get_data(current_song_get_number()), current_song_get_number());

		work = g_list_remove(work, node->data);
		}

	while (work)
		{
		gint p =  (float)rand() / RAND_MAX * g_list_length(work);
		node = g_list_nth(work, p);
		if (debug_mode) printf("%d ",g_list_index(playlist, node->data));
		shuffle_list = g_list_prepend(shuffle_list, node->data);
		work = g_list_remove(work, node->data);
		}
	shuffle_list = g_list_reverse(shuffle_list);

	if (debug_mode) printf ("\n");
}

void shuffle_list_destroy()
{
	if (debug_mode) printf("Clearing shuffle list\n");
	if (!shuffle_list) return;
	g_list_free(shuffle_list);
	shuffle_list = NULL;
}

/*
 *-----------------------------------------------------------------------------
 * playlist loading functions (public)
 *-----------------------------------------------------------------------------
 */

/* returns FALSE if can't access file, TRUE if a playlist,
	(-1) otherwise (invalid playlist)*/
gint playlist_load(gchar *fn, gint append, gint strict)
{
	gchar s_buf[1025];
	gchar *s_buf_ptr;
	FILE *f;

	f = fopen (fn,"r");
	if (!f)
		{
		/* file open failed */
		printf("failed to open \"%s\"\n",fn);
		return FALSE;
		}
	else
		{
		if (strict)
			{
			/* check first line for valid playlist */
			fgets(s_buf,1024,f);
			if (strncmp(s_buf,"# GQmpeg",8) != 0)
				{
				/* we reach here, file is not a valid playlist */
				fclose (f);
				return -1;
				}
			}

		/* load playlist */
		if (!append) playlist_clear();
		display_freeze();
		while (fgets(s_buf,1024,f))
			{
			if (s_buf[0]=='#')
				{
				if (!append && obey_mode_in_playlist && strncmp(s_buf,"# Shuffl",8) == 0)
					{
					gint old_shuffle = shuffle_mode;
					gint old_repeat = repeat_mode;
					if (sscanf(s_buf, "# Shuffle: %d Repeat: %d",
							&shuffle_mode, &repeat_mode) > 0)
						{
						if (old_shuffle != shuffle_mode)
							{
							if (shuffle_mode)
								shuffle_list_create(FALSE);
							else
								shuffle_list_destroy();
							display_draw_shuffle(TRUE);
							}
						if (old_repeat != repeat_mode)
							display_draw_repeat(TRUE);
						}

					}
				continue;
				}
			if (s_buf[0]=='\n') continue;
			s_buf_ptr = s_buf;
			while (s_buf_ptr[0] != '\n' && s_buf_ptr[0] != '\0' && s_buf_ptr < s_buf + strlen(s_buf)) s_buf_ptr++;
			s_buf_ptr[0] = '\0';

			if (!strict)
				{
				/* if not strict gqmpeg playlist, we are probably importing an
				   unknown file, so only add lines that pass the filters */
				if (typelist_determine_type_id(s_buf) != -1 ||
				    file_is_in_filter(s_buf) )
					{
					playlist_add(s_buf);
					}
				else
					{
					printf("undetermined type:%s\n", s_buf);
					}
				}
			else
				{
				playlist_add(s_buf);
				}
			}
		display_thaw();
		fclose (f);
		}

	if (!append)
		{
		gint playlist_song;
		g_free(playlist_pathname);
		playlist_pathname = g_strdup(fn);
		g_free(playlist_filename);
		playlist_filename = g_strdup(filename_from_path(playlist_pathname));

		if (playlist_count > 0)
			{
			if (shuffle_mode)
				{
				playlist_song = shuffle_list_get_first();
				}
			else
				{
				playlist_song = 0;
				}
			}
		else
			{
			playlist_song = -1;
			}
		current_song_set(playlist_song, NULL);
		}

	display_draw_song_count(playlist_count, TRUE);

	return TRUE;
}

gint playlist_load_from_file(gchar *path, gint append, gint strict)
{
	gint load_val;
	load_val = playlist_load(path, append, strict);
	if (load_val == FALSE)
		{
		warning_dialog("Open failed","The specified file could\nnot be opened.");
		}
	if (load_val == -1)
		{
		warning_dialog("Invalid file","The specified file is\nnot a valid playlist.");
		}

	playlist_window_update_titles(playlist_filename);

	return load_val;
}

/*
 *-----------------------------------------------------------------------------
 * playlist saving functions (public)
 *-----------------------------------------------------------------------------
 */

gint playlist_save(gchar *fn)
{
	int i;
	FILE *f;

	f = fopen (fn,"w");
	if (!f)
		{
		/* file open failed */
		printf("failed to open %s\n",fn);
		return FALSE;
		}

	fprintf(f,"# GQmpeg song playlist\n");
	fprintf(f,"# created by version %d.%d.%d\n",GQMPEG_VERSION_MAJOR,GQMPEG_VERSION_MINOR,GQMPEG_VERSION_REV);

	if (save_mode_in_playlist)
		{
		fprintf(f,"# Shuffle:%d Repeat:%d (please only use 0 or 1)\n", shuffle_mode, repeat_mode);
		}

	for (i=0; i < playlist_count; i++)
		{
		fprintf(f,"%s\n",playlist_get_item(i));
		}

	fprintf(f,"# end of list #\n");
	fclose (f);

	if (strcmp(fn, playlist_pathname) != 0)
		{
		g_free(playlist_pathname);
		playlist_pathname = g_strdup(fn);
		g_free(playlist_filename);
		playlist_filename = g_strdup(filename_from_path(playlist_pathname));
		playlist_window_update_titles(playlist_filename);
		}

	return TRUE;
}

/*
 *-----------------------------------------------------------------------------
 * playlist load directory functions
 *-----------------------------------------------------------------------------
 */

static void playlist_append_dir_func(gchar *d, gint recursive)
{
	DIR *dp;
	struct dirent *dir;
	struct stat ent_sbuf;

	if((dp = opendir(d))==NULL)
		{
		/* dir not found */
		return;
		}

	while ((dir = readdir(dp)) != NULL)
		{
		/* skips removed files */
		if (dir->d_ino > 0)
			{
			gchar *name;
			gchar *path;

			name = dir->d_name; 
			path = g_strconcat(d, "/", name, NULL);

			if (stat(path,&ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
				{
				if (!file_is_hidden(name))
					{
					if (recursive && strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
						{
						playlist_append_dir_func(path, TRUE);
						}
					}
				}
			else
				{
				if (!file_is_hidden(name) && file_is_in_filter(name))
					{
					if (!is_playlist(path))
						{
						playlist_add(path);
						}

					}
				}
			g_free(path);
			}
		}

        closedir(dp);
}

void playlist_append_from_dir(gchar *path, gint recursive)
{
	playlist_append_dir_func(path, recursive);
	display_draw_song_count(playlist_count, TRUE);
}

/*
 *-----------------------------------------------------------------------------
 * playlist data functions (public)
 *-----------------------------------------------------------------------------
 */

SongData *playlist_new_data(gchar *path)
{
	SongData *sd;

	if (!path) return NULL;

	sd = player_module_songdata_init(path);
	player_module_songdata_update(sd, read_header_tags, playlist_accounting);

	return sd;
}

void playlist_free_data(SongData *sd)
{
	g_free(sd->path);
	g_free(sd->title);
	g_free(sd->artist);
	g_free(sd->album);
	/* g_free(sd->genre); not freed, points to static */
	g_free(sd->comment);

	if (sd->free_data_func) sd->free_data_func(sd->data);

	g_free(sd);
}

/*
 *-----------------------------------------------------------------------------
 * playlist basic functions (private)
 *-----------------------------------------------------------------------------
 */

static void real_playlist_init()
{
	playlist = NULL;
	playlist_count = 0;
	playlist_length = 0;
}

static void real_playlist_clear()
{
        if (playlist)
                {
                GList *list;
                list = playlist;
                while (list)
                        {
                        playlist_free_data(list->data);
                        list = list->next;
                        }
                g_list_free(playlist);
                playlist = NULL;
                }
	real_playlist_init();

	if (shuffle_mode) shuffle_list_destroy();
}

static void real_playlist_add_item(gchar *path)
{
	SongData *sd = playlist_new_data(path);
	playlist = g_list_append(playlist, sd);
	playlist_count++;
	if (sd->length) playlist_length += sd->length;

	if (shuffle_mode) shuffle_list_add (g_list_index(playlist, sd));
}

static void real_playlist_insert_item(gchar *path, gint n)
{
	SongData *sd = playlist_new_data(path);

	if (n >= playlist_count)
		{
		playlist = g_list_append(playlist, sd);
		}
	else
		{
		playlist = g_list_insert(playlist, sd, n);
		}

	playlist_count++;
	if (sd->length) playlist_length += sd->length;

	if (shuffle_mode) shuffle_list_add (g_list_index(playlist, sd));
}

static gint real_playlist_remove_item(gint n)
{
	SongData *sd;
	GList *list;
	if (n >= playlist_count) return FALSE;
	list = g_list_nth(playlist, n);

	if (!list) return FALSE;

	sd = list->data;

	if (shuffle_mode) shuffle_list_remove (sd);

	playlist = g_list_remove(playlist, sd);
	playlist_count--;
	if (sd->length) playlist_length -= sd->length;

	playlist_free_data(sd);

	return TRUE;
}

static gint real_playlist_move_item(gint s, gint t)
{
	SongData *sd;
	GList *list = NULL;

	if (s >= playlist_count || t >= playlist_count || s == t) return FALSE;
	list = g_list_nth(playlist, s);

	if (!list) return FALSE;

	sd = list->data;

	playlist = g_list_remove(playlist, sd);
	playlist = g_list_insert(playlist, sd, t);

	return TRUE;
}

/*
 *-----------------------------------------------------------------------------
 * playlist info retrieval functions (public)
 *-----------------------------------------------------------------------------
 */

SongData *playlist_get_data(gint n)
{
	GList *list;
	if (n >= playlist_count || n < 0) return NULL;
	list = g_list_nth(playlist, n);
	if (!list) return NULL;
	return list->data;
}

gchar *playlist_get_item(gint n)
{
	SongData *sd;
	sd = playlist_get_data(n);
	if (!sd) return NULL;
	return sd->path;
}

gchar *playlist_get_title(gint n)
{
	SongData *sd = playlist_get_data(n);
	if (!sd) return NULL;
	if (sd->title) return sd->title;

	if (!sd->path || sd->path[0] != '/')
		{
		return sd->path;
		}

	return filename_from_path(sd->path);
}

gchar *playlist_get_artist(gint n)
{
	SongData *sd = playlist_get_data(n);
	if (!sd) return NULL;
	return sd->artist;
}

gchar *playlist_get_album(gint n)
{
	SongData *sd = playlist_get_data(n);
	if (!sd) return NULL;
	return sd->album;
}

gchar *playlist_get_genre(gint n)
{
	SongData *sd = playlist_get_data(n);
	if (!sd) return NULL;
	return sd->genre;
}

gint playlist_item_is_live(gint n)
{
	SongData *sd = playlist_get_data(n);
	if (!sd) return FALSE;

	return sd->live;
}

gint playlist_item_is_custom(gint n)
{
	SongData *sd = playlist_get_data(n);
	if (!sd) return FALSE;

	return sd->custom;
}

gint playlist_get_index(gchar *path)
{
	SongData *sd;
	GList *list = playlist;
	gint c = 0;

	while (list)
		{
		sd = list->data;
		if (strcmp(sd->path, path) == 0) return c;
		c++;
		list = list->next;
		}

	return -1;
}

/* this is much faster than the above, but needs more specific data */
gint playlist_get_index_by_data(SongData *sd)
{
	return g_list_index(playlist, sd);
}

gint playlist_get_count()
{
	return playlist_count;
}

gint playlist_get_length()
{
	return playlist_length;
}

gint playlist_get_next(gint n)
{
	if (playlist_count == 0) return -1;

	if (shuffle_mode)
		{
		return shuffle_list_get_next(n);
		}
	else
		{
		if (n == -1)
			{
			return 0;
			}
		else if (n + 1 < playlist_count)
			{
			return n + 1;
			}
		}

	return -1;
}

gint playlist_get_prev(gint n)
{
	if (playlist_count == 0) return -1;

	if (shuffle_mode)
		{
		return shuffle_list_get_prev(n);
		}

	if (n == -1)
		{
		return playlist_count - 1;
		}
	else if (n - 1 >= 0)
		{
		return n - 1;
		}

	return -1;
}

gint playlist_get_first()
{
	if (playlist_count == 0) return -1;
	if (shuffle_mode)
		{
		return shuffle_list_get_first();
		}

	return 0;
}

/*
 *-----------------------------------------------------------------------------
 * playlist management functions (public)
 *-----------------------------------------------------------------------------
 */

static void playlist_recalc_length()
{
	GList *work;

	playlist_length = 0;

	work = playlist;
	while (work)
		{
		SongData *sd = work->data;
		if (sd->length) playlist_length += sd->length;
		work = work->next;
		}
}

void playlist_update_all_info(gint generic_info, gint format_info)
{
	GList *list;

	if (!playlist) return;

	list = playlist;
	while (list)
		{
		SongData *sd = list->data;
		player_module_songdata_update(sd, generic_info, format_info);
		list = list->next;
		}

	if (format_info)
		{
		playlist_recalc_length();
		}

	playlist_window_clist_populate();
}

void playlist_update_generic_info()
{
	playlist_update_all_info(read_header_tags, FALSE);
}

void playlist_update_format_info()
{
	playlist_update_all_info(FALSE, playlist_accounting);
}

void playlist_sort_by_func(GCompareFunc sort_func)
{
	if (!playlist) return;
	playlist = g_list_sort(playlist, sort_func);
}

/*
 *-----------------------------------------------------------------------------
 * playlist manipulation functions (public)
 *-----------------------------------------------------------------------------
 */

void playlist_add(gchar *path)
{
	real_playlist_add_item(path);

	playlist_window_clist_append(playlist_count - 1);

	if (playlist_count == 1 && current_song_get_path() == NULL)
		{
		current_song_set(0, NULL);
		}
	display_draw_song_count(playlist_count, TRUE);
}
 
void playlist_insert(gchar *path, gint n)
{
	real_playlist_insert_item(path, n);

	playlist_window_clist_insert(n);

	if (n <= current_song_get_number())
		{
		display_draw_song_number(current_song_get_number(), FALSE);
		}
	display_draw_song_count(playlist_count, TRUE);
}

void playlist_move(gint s, gint t)
{
	if (!real_playlist_move_item(s, t)) return;

	display_draw_song_number(current_song_get_number(), TRUE);

	playlist_window_clist_move(s, t);
}

gint playlist_remove(gchar *path, gint n, gint all)
{
	gint ret = FALSE;

	if (path)
		{
		n = playlist_get_index(path);
		if (n < 0) return FALSE;
		}

	while (n >= 0)
		{
		gint p = current_song_get_number();
		gint removed = real_playlist_remove_item(n);

		if (removed)
			{
			playlist_window_clist_remove(n);

			if (p == n)
				{
				if (p < playlist_count)
					{
					current_song_set(p, NULL);
					}
				else
					{
					current_song_set(playlist_count - 1, NULL);
					}
				}
			else if (n < p)
				{
				display_draw_song_number(current_song_get_number(), FALSE);
				}
			display_draw_song_count(playlist_count, TRUE);
			}

		if (path && all && removed)
			{
			n = playlist_get_index(path);
			}
		else
			{
			n = -1;
			}

		ret = ret | removed;
		}

	return ret;
}

void playlist_update(gint n, gchar *path)
{
	gint p = current_song_get_number();

	if (!real_playlist_remove_item(n)) return;
	

	if (path)
		{
		real_playlist_insert_item(path, n);
		playlist_window_clist_update_item(n);
		if (n == p)
			{
			current_song_set(n, NULL);
			}
		}
	else
		{
		playlist_window_clist_remove(n);
		if (n == p)
			{
			current_song_set_to_next();
			}
		}
}

void playlist_update_by_path(gchar *path)
{
	gint i;
	if (!path) return;

	for (i = 0; i < playlist_get_count(); i++)
		{
		if (strcmp(path, playlist_get_item(i)) == 0)
		playlist_update(i, path);
		}
}

void playlist_replace(gchar *old_path, gchar *new_path)
{
	gint n = playlist_get_index(old_path);
	while (n >= 0)
		{
		playlist_update(n, new_path);
		n = playlist_get_index(old_path);
		}
}

void playlist_clear()
{
	if (current_song_is_in_playlist()) current_song_set(-1, NULL);
	display_draw_song_count(0, TRUE);
	real_playlist_clear();
	playlist_window_clist_clear();
}

/*
 *-----------------------------------------------------------------------------
 * playlist utility functions (public)
 *-----------------------------------------------------------------------------
 */

gint is_playlist(gchar *path)
{
	if (strlen(path) > 8 && strncasecmp(path + (strlen(path) - 7), ".gqmpeg", 7) == 0) return TRUE;

	return FALSE;
}

/*
 *-----------------------------------------------------------------------------
 * playlist functions ()
 *-----------------------------------------------------------------------------
 */

