/*
 *
 *  Copyright (C) 2004 Mark Humphreys <marquee@users.sourceforge.net>
 *
 *  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 2 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <glib.h>
#include <libgnome/gnome-i18n.h>
#include <libgnomevfs/gnome-vfs.h>
#include <stdio.h>
#include <string.h>
#include "rb-debug.h"
#include "iriverdb.h"

#define IRIVERDB_FILE_NAME 		"iRivNavi.iDB"
#define IRIVERDB_PLAYLIST_NAME          "playlists"
#define IHP_FAT_START_OFFSET		0xA0C0
#define IHP_HEADER2_OFFSET		0x0020 
#define IHP_PLAYCOUNT_OFFSET		0x0040
#define IHP_FOOTER_OFFSET		0xA080
#define IHP_CLUSTER_SIZE		32
#define IHP_MAX_TRACK_SIZE		512
#define DEFAULT_MOUNT_PATH 		"/mnt/iriver"

#define iriverdb_warning(...) do { gchar *utf8=g_strdup_printf (__VA_ARGS__); gchar *loc=g_locale_from_utf8 (utf8, -1, NULL, NULL, NULL); fprintf (stderr, "%s", loc); g_free (loc); g_free (utf8);} while (FALSE)

enum _iRiverParserState {
	IRIVER_PARSER_INIT,
	IRIVER_PARSER_SONGS,
	IRIVER_PARSER_PLAYLISTS,
	IRIVER_PARSER_END,
	IRIVER_PARSER_ERROR
};
typedef enum _iRiverParserState iRiverParserState;

struct _iRiverParser {
	iRiverParserState state;
	gchar *mount_path;
	guint32 nr_tracks;
	guint32 read_count;

	GnomeVFSHandle *read_handle;

	gchar *header1;
	gchar *header2;
	gchar *footer;
};

static guint32
iriverDB_get_uint32 (iRiverParser *parser, GnomeVFSFileOffset offset)
{
	guint32 val = 0;
	GnomeVFSResult result;
	char buffer[4];
	GnomeVFSFileSize bytes_read;

	result = gnome_vfs_seek (parser->read_handle, GNOME_VFS_SEEK_START, offset);
	result = gnome_vfs_read (parser->read_handle, buffer, 4, &bytes_read);		

  	val =  ((guint32)buffer[3]) << 24;
  	val += ((guint32)buffer[2]) << 16;
  	val += ((guint32)buffer[1]) << 8;
  	val += ((guint32)buffer[0]);

	return val;
}

static guint
iriverDB_get_uint (iRiverParser *parser)
{
	guint val = 0;
	GnomeVFSResult result;
	char buffer[2];
	GnomeVFSFileSize bytes_read;

	result = gnome_vfs_read (parser->read_handle, buffer, 2, &bytes_read);		
  	val = ((guint32)buffer[1]) << 8;
  	val += ((guint32)buffer[0]);

	return val;
}

static char *
iriver_get_db_path (const char *mount_path)
{
	gchar *result;

	result = g_strconcat ("file://", 
			DEFAULT_MOUNT_PATH, 
			GNOME_VFS_URI_PATH_STR, 
			IRIVERDB_FILE_NAME, 
			NULL);

	return result;
}

iRiverParser *
iriver_parser_new (const gchar *mount_point)
{
	gchar *absolute_path;
	GnomeVFSResult result;
	iRiverParser *parser;
	
	/* remember to initialize GnomeVFS! */
  	if (!gnome_vfs_init ()) 
	{
    		rb_debug ("Could not initialize GnomeVFS");
    		return NULL;
  	}

	parser = g_new0 (iRiverParser, 1);	

	absolute_path = iriver_get_db_path (DEFAULT_MOUNT_PATH); 

	/* open the input file for read access */
  	result = gnome_vfs_open (&parser->read_handle, 	
				absolute_path, 
				GNOME_VFS_OPEN_READ);

	if (result != GNOME_VFS_OK)
	{
		rb_debug ("Unable to open '%s'", absolute_path);
		g_free (absolute_path);
		g_free (parser);

		return NULL;
	}

	parser->mount_path = g_strdup (mount_point);
	parser->state = IRIVER_PARSER_INIT;

	g_free (absolute_path);

	return parser;
}

void 
iriver_parser_destroy (iRiverParser *parser)
{
	g_assert (parser != NULL);

	if (parser->read_handle != NULL) 
	{

		/* Close the database */
		gnome_vfs_close (parser->read_handle);
	}

	g_free (parser->mount_path);
	g_free (parser);
}

static gboolean 
iriverdb_parse (iRiverParser *parser)
{
	char hdr1[IHP_CLUSTER_SIZE];
	char hdr2[IHP_CLUSTER_SIZE];
	char ftr[IHP_CLUSTER_SIZE];

	GnomeVFSResult vfsresult;
	GnomeVFSFileSize bytes_read;	

  	g_assert (parser != NULL);
  	g_assert (parser->read_handle != NULL);
  	g_assert (parser->state == IRIVER_PARSER_INIT);

	/* First header */
	vfsresult = gnome_vfs_read (parser->read_handle, hdr1, IHP_CLUSTER_SIZE, &bytes_read);		
	parser->header1 = g_strdup (hdr1);

	rb_debug (parser->header1);

	/* Second header */
	vfsresult = gnome_vfs_seek (parser->read_handle, GNOME_VFS_SEEK_START, IHP_HEADER2_OFFSET);
	vfsresult = gnome_vfs_read (parser->read_handle, hdr2, IHP_CLUSTER_SIZE, &bytes_read);		
	parser->header2 = g_strdup (hdr2);

	rb_debug (parser->header2);

	/* Track count */
	parser->nr_tracks = iriverDB_get_uint32 (parser, IHP_PLAYCOUNT_OFFSET);
	parser->read_count = 0;

	/* Footer */
	vfsresult = gnome_vfs_seek (parser->read_handle, GNOME_VFS_SEEK_START, IHP_FOOTER_OFFSET);
	vfsresult = gnome_vfs_read (parser->read_handle, ftr, IHP_CLUSTER_SIZE, &bytes_read);		
	parser->footer = g_strdup (ftr);

	rb_debug (parser->footer);

	/* Move on to start of track entries */
	vfsresult = gnome_vfs_seek (parser->read_handle, GNOME_VFS_SEEK_START, IHP_FAT_START_OFFSET);

  	return TRUE;
}

static iRiverItem *
iriver_item_new (iRiverItemType type, gpointer data)
{
	iRiverItem *item;

	item = g_new0 (iRiverItem, 1);
	if (item == NULL) 
	{
		rb_debug ("Couldn't allocate iRiverItem");
		return NULL;
	}

	item->data = data;
	return item;
}

static iRiverTrack *
iriver_get_entry (iRiverParser *parser)
{
  	iRiverTrack *track = NULL;
	char tmp[IHP_MAX_TRACK_SIZE];
	guint lenF, lenS, lenA, lenC, lenG;
	GnomeVFSResult result;
	GnomeVFSFileSize bytes_read;
	
	if ((parser->nr_tracks -1) != parser->read_count) 
	{
		track = g_new (iRiverTrack, 1); 
		parser->read_count ++;

		/* Get the offsets for this entry */
		lenF = iriverDB_get_uint (parser);
		lenS = iriverDB_get_uint (parser);
		lenA = iriverDB_get_uint (parser);
		lenC = iriverDB_get_uint (parser);
		lenG = iriverDB_get_uint (parser);

		result = gnome_vfs_read (parser->read_handle, tmp, lenF, &bytes_read);	
		track->filename = g_strdup (tmp);

		result = gnome_vfs_read (parser->read_handle, tmp, lenS, &bytes_read);	
		track->title = g_strdup (g_convert (tmp,  -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL));

		result = gnome_vfs_read (parser->read_handle, tmp, lenA, &bytes_read);	
		track->artist = g_strdup (g_convert (tmp,  -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL));

		result = gnome_vfs_read (parser->read_handle, tmp, lenC, &bytes_read);	
		track->album = g_strdup (g_convert (tmp,  -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL));

		result = gnome_vfs_read (parser->read_handle, tmp, lenG, &bytes_read);	
		track->genre = g_strdup (g_convert (tmp,  -1, "UTF-8", "ISO-8859-1", NULL, NULL, NULL));
	}
	
	return track;
}

static iRiverPlaylist *
iriver_get_playlists (iRiverParser *parser)
{
	/* FIXME: Process playlists */
	return NULL;
}

iRiverItem *
iriver_get_next_item (iRiverParser *parser)
{
	switch (parser->state) {
	case IRIVER_PARSER_INIT: 
	{
		gboolean res;
		res = iriverdb_parse (parser);
		if (res == FALSE) 
		{
			rb_debug ("Error parsing iRiverDB");
			parser->state = IRIVER_PARSER_ERROR;
			return NULL;
		}

		if (parser->nr_tracks == 0) 
		{
			parser->state = IRIVER_PARSER_PLAYLISTS;
		} 
		else 
		{
			parser->state = IRIVER_PARSER_SONGS;
		}
		/* break statement omitted on purpose */
	}
	case IRIVER_PARSER_SONGS: 
	{
		iRiverTrack *track;
		/* get file entry */
		track = iriver_get_entry (parser);
		if (track == NULL) 
		{
			parser->state = IRIVER_PARSER_PLAYLISTS;
		} 
		else 
		{
			return iriver_item_new (IRIVER_ITEM_SONG, track);
		}
		/* No break here, if we didn't return a song, we want to 
		 * parse and return the first playlist */
	}

	case IRIVER_PARSER_PLAYLISTS: 
	{
		iRiverPlaylist *pl;
		pl = iriver_get_playlists (parser);
		if (pl == NULL) 
		{
			parser->state = IRIVER_PARSER_END;
			break;
		} 
		else 
		{
			return iriver_item_new (IRIVER_ITEM_PLAYLIST, pl);
		}
		break;
	}
		
	case IRIVER_PARSER_END:
		break;

	case IRIVER_PARSER_ERROR: 
		/* Should not be reached */

	default: 
		g_assert_not_reached ();
	}    

	return NULL;
}

gchar *
iriverdb_get_track_name_on_iriver (const gchar *path, iRiverTrack *s)
{
    gchar *result = NULL;

    if(s && s->filename && *s->filename)
    {
	gchar *buf = g_strdup (s->filename);

	rb_debug ("iriverdb_get_track_name_on_iriver (%s)", s->filename);

	g_strdelimit (buf, "\\", G_DIR_SEPARATOR);
	result = g_build_filename (path, buf, NULL);

	g_free (buf);
    }
    return result;
}

void
iriver_item_destroy (iRiverItem *item)
{
	if (item == NULL) 
	{
		return;
	}
	if (item->type == IRIVER_ITEM_SONG) 
	{
		iRiverTrack *song = (iRiverTrack *)item->data;
		g_assert (song != NULL);

		/* g_free safely ignore NULL pointers */
		g_free (song->album);
		g_free (song->artist);
		g_free (song->title);
		g_free (song->genre);
		
	} 
	else if (item->type == IRIVER_ITEM_PLAYLIST) 
	{
		iRiverPlaylist *playlist = (iRiverPlaylist *)item->data;
		
		g_free (playlist->name);
		g_list_free (playlist->song_ids);
	} 
	else 
	{
		g_assert_not_reached ();
	}

	g_free (item->data);
	g_free (item);
}
