/*
 * Copyright (c) 2002, Peter Bentley
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 *   Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 *   Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 *   The name Peter Bentley may not be used to endorse or promote
 * products derived from this software without specific prior
 * written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "omd.h"

CVSID( "$Id: omd.c,v 1.36 2003/08/25 13:43:15 pete Exp $" );

/* Dispatch function typedef */
typedef int (*omdfunc)(int argc, char **argv,
		   omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );

/* Prototypes for all dispatch functions */
int	discinfo( int argc, char **argv ,
		  omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	play( int argc, char **argv ,
	      omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	stop( int argc, char **argv ,
	      omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	dopause( int argc, char **argv ,
		 omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	dorewind( int argc, char **argv ,
		  omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	dofforward( int argc, char **argv ,
		    omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	status( int argc, char **argv ,
		omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	list( int argc, char **argv ,
	      omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	title( int argc, char **argv ,
	       omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	rawtitle( int argc, char **argv ,
		  omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	grouplist( int argc, char **argv ,
		   omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	groupadd( int argc, char **argv ,
		  omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	groupdelete( int argc, char **argv ,
		     omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	grouprename( int argc, char **argv ,
		     omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	trackrename( int argc, char **argv , omd_unit_t *unit,
		     omd_disc_info_t *info, omd_toc_t *toc );
int	trackmove( int argc, char **argv ,
		   omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	trackdelete( int argc, char **argv ,
		     omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	toc_sync( int argc, char **argv ,
		  omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	discinit( int argc, char **argv ,
		  omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	sendraw( int argc, char **argv ,
		 omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	quickusage( int argc, char **argv ,
		    omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );
int	usage( int argc, char **argv ,
	       omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc );

/* Structure of dispatch table */
typedef struct {
    const char	*command;
    omdfunc	function;
    int		needs_unit;
    int		needs_disc;
    int		needs_toc;
    int		needs_writable;
} omd_func_t;

/* Actual dispatch table */
omd_func_t function_map[] =
{   /* First entry is default function to call if none on command line	*/
    /* 					Needs	Needs	Needs	Needs	*/
    /* Command		Function	Unit?	Info?	TOC?	Writable? */
    { "?",		quickusage,	FALSE,	FALSE,	FALSE,	FALSE },
    { "h*elp",		usage,		FALSE,	FALSE,	FALSE,	FALSE },
    { "sync",		toc_sync,	TRUE,	TRUE,	FALSE,	FALSE },
    { "st*atus",	status,		TRUE,	TRUE,	FALSE,	FALSE },
    { "pl*ay",		play,		TRUE,	TRUE,	FALSE,	FALSE },
    { "sto*p",		stop,		TRUE,	TRUE,	FALSE,	FALSE },
    { "ff*orward",	dofforward,	TRUE,	TRUE,	FALSE,	FALSE },
    { "fast*forward",	dofforward,	TRUE,	TRUE,	FALSE,	FALSE },
    { "rew*ind",	dorewind,	TRUE,	TRUE,	FALSE,	FALSE },
    { "pa*use",		dopause,	TRUE,	TRUE,	FALSE,	FALSE },
    { "inf*o",		discinfo,	TRUE,	TRUE,	FALSE,	FALSE },
    { "disc*info",	discinfo,	TRUE,	TRUE,	FALSE,	FALSE },
    { "l*ist",		list,		TRUE,	TRUE,	TRUE,	FALSE },
    { "grl*ist",	grouplist,	TRUE,	TRUE,	TRUE,	FALSE },
    { "groupl*ist",	grouplist,	TRUE,	TRUE,	TRUE,	FALSE },
    { "rawtit*le",	rawtitle,	TRUE,	TRUE,	FALSE,	TRUE },
    { "init",		discinit,	TRUE,	TRUE,	FALSE,	TRUE },
    { "discinit",	discinit,	TRUE,	TRUE,	FALSE,	TRUE },
    { "groupa*dd",	groupadd,	TRUE,	TRUE,	TRUE,	TRUE },
    { "add*group",	groupadd,	TRUE,	TRUE,	TRUE,	TRUE },
    { "addg*rp",	groupadd,	TRUE,	TRUE,	TRUE,	TRUE },
    { "newgroup",	groupadd,	TRUE,	TRUE,	TRUE,	TRUE },
    { "grnam*e",	grouprename,	TRUE,	TRUE,	TRUE,	TRUE },
    { "renamegr*oup",	grouprename,	TRUE,	TRUE,	TRUE,	TRUE },
    { "rengr*oup",	grouprename,	TRUE,	TRUE,	TRUE,	TRUE },
    { "rengr*p",	grouprename,	TRUE,	TRUE,	TRUE,	TRUE },
    { "groupren*ame",	grouprename,	TRUE,	TRUE,	TRUE,	TRUE },
    { "groupdel*ete",	groupdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "grpdel*ete",	groupdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "delg*roup",	groupdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "delg*rp",	groupdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "rmgr*oup",	groupdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "rmg*rp",		groupdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "tit*le",		title,		TRUE,	TRUE,	TRUE,	TRUE },
    { "move*track",	trackmove,	TRUE,	TRUE,	TRUE,	TRUE },
    { "trackm*ove",	trackmove,	TRUE,	TRUE,	TRUE,	TRUE },
    { "trm*ove",	trackmove,	TRUE,	TRUE,	TRUE,	TRUE },
    { "mv",		trackmove,	TRUE,	TRUE,	TRUE,	TRUE },
    { "trackdel*ete",	trackdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "trkdel*ete",	trackdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "delt*rack",	trackdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "delt*rk",	trackdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "rmt*rack",	trackdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "rm",		trackdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "del",		trackdelete,	TRUE,	TRUE,	TRUE,	TRUE },
    { "ren*ame",	trackrename,	TRUE,	TRUE,	TRUE,	TRUE },
    { "nam*e",		trackrename,	TRUE,	TRUE,	TRUE,	TRUE },
    { "raw",		sendraw,	TRUE,	FALSE,	FALSE,	FALSE },
    { NULL,		NULL,		FALSE,	FALSE,	FALSE,	FALSE },
};


/*****************************************************************************
 * Cmdmatch - match a command in the table above
 */

omd_func_t *cmdmatch( const char *userinput )
{
    int 		min;
    omd_func_t		*func;
    const char		*cmd, *user;

    for( func = function_map; func->command; func++ )
    {
	for( min=FALSE, user=userinput, cmd=func->command; *cmd; user++, cmd++)
	{
	    if( *cmd == '*' )
	    {
		min = TRUE;
		cmd++;
	    }
	    if( *user == '\0' )
	    {
		if( min )
		{
		    /* Matched the minimum amount, so return this command */
		    return func;
		}
		else
		{
		    /* No match, go on to the next one */
		    break;
		}
	    }
	    if( *cmd != *user )
	    {
		break;		/* No match */
	    }
	}
	/* If we get to the end of both strings, match */
	if( (*cmd == '\0') && (*user == '\0') )
	{
	    return func;
	}
    }
    return NULL;
}

/*****************************************************************************
 * Main()
 */
extern char *optarg;
extern int optind;

int debug = FALSE;
int quiet = FALSE;

int main( int argc, char **argv )
{
    omd_unit_t 		*unit;
    omd_unit_status_t	unit_status;
    omd_disc_info_t	*discinfo;
    omd_toc_t		*toc = NULL;
    omd_func_t		*func;
    int			rc = -1, ch;
    int			force_scan = FALSE;
    int			probed = 0;
    void		*loghandle = slog_get_handler();

    /* Default log level is INFO */
    slog_set_handler_level( loghandle, LOG_INFO );
    
    /* Look for leading arguments like -d */
    while( (ch = getopt( argc, argv, "dfhqt" )) != -1 )
    {
	switch( ch )
	{
	    case 'd':
		debug = TRUE;
		slog_set_handler_level( loghandle, LOG_DEBUG );
		break;

	    case 'f':
		force_scan = TRUE;
		break;

	    case 'h':
		quickusage( 0, NULL, NULL, NULL, NULL );
		return 0;
		
	    case 'q':
		quiet = TRUE;
		break;

	    case 't':
		debug = TRUE;
		slog_set_handler_level( loghandle, LOG_TRACE );
		break;

	    case '?':
	    default:
		printf( "Illegal argument: '%s'\n\n", argv[optind] );
		quickusage( 0, NULL, NULL, NULL, NULL );
		return -1;
	}
    }
    argc -= optind;
    argv += optind;
    
    if( argc < 1)
    {
	/* Choose first entry in map by default */
	func = function_map;
    }
    else
    {
	func = cmdmatch( argv[0] );
	argc--;
	argv++;
    }
    if( ! func )
    {
	printf( "Invalid command\n\n" );
	func = function_map;
    }


    /* If command doesn't need a netmd unit, shortcut all the init stuff */
    if( func->needs_unit == FALSE)
    {
	return func->function( argc, argv, NULL, NULL, NULL );
    }


    /* Do we have a NetMD unit at all? */
    if( ( unit = omd_open_default_unit() ) == NULL )
    {
	printf( "No NetMD devices found, use -f to force detailed USB scan\n");
	if( force_scan ) 
	{
	    if( ( probed = omd_probe_all()) > 0 )
	    {
		printf( "Found %d new unit%s\n",
			probed, probed == 1 ? "" : "s" );
		if( ( unit = omd_open_default_unit( )) == NULL )
		{
		    printf( "Cannot open device" );
		    return -1;
		}
	    }
	    else
	    {
		printf( "Nope, still no units found\n" );
		return -1;
	    }
	}
	else
	{
	    return -1;
	}
    }
    
    if( !quiet )
    {
	printf( "NetMD unit: %s, %s, %s.\nVendor ID 0x%x. Product ID 0x%x.\n",
		unit->vendor, unit->product, unit->info->model,
		unit->vendorid, unit->productid );
    }

    /* Read unit status to see if there is a disc */
    if( omd_get_unit_status( unit, &unit_status ) < 0 )
    {
	printf( "Unable to retrieve unit status\n" );
	goto errexit;
    }
    slog_hex( LOG_DEBUG, "Unit:: ",unit_status.raw, OMD_UNIT_STATUS_RAW_SIZE );

    /* If there is a disc, read the disc status */
    if( unit_status.disc_inserted )
    {
	if( ! quiet )
	{
	    printf( "Unit has a disc inserted.\n" );
	}
	discinfo = omd_disc_get_info( unit, NULL );
	if( discinfo == NULL )
	{
	    printf( "Unable to retrieve disc info\n" );
	    goto errexit;
	}
    }
    else
    {
	if( ! quiet )
	{
	    printf( "Unit is empty\n" );
	}
	discinfo = NULL;
    }

    /*
     * Read the groups part of the TOC if needed (if the func needs
     * info for tracks, it has to read them itself
     */
    if( func->needs_toc )
    {
	if( !quiet )
	{
	    printf( "Reading TOC...\n" );
	}
	if( ( toc = omd_toc_read( unit, discinfo, TRUE, FALSE )) == NULL )
	{
	    printf( "Unable to read TOC\n" );
	    return -1;
	}
    }
    else
    {
	toc = NULL;
    }
	    
    
    /* Sanity check function/disc requirements */
    if( func->needs_disc && ( discinfo == NULL ))
    {
	printf( "Unit must have a disc inserted for this function\n" );
	goto errexit;
    }
    if( func->needs_writable && !omd_disc_is_writable( discinfo ))
    {
	printf( "Disc must be writable for this function\n" );
	goto errexit;
    }

    
    /* Finally call the required function */
    rc = func->function( argc, argv, unit, discinfo, toc );

 errexit:
    if( toc )
    {
	omd_toc_free( toc );
    }
    omd_close_all_devices();
    return rc;
}    

/*****************************************************************************
 * Discinfo
 */

int discinfo( int argc, char **argv , omd_unit_t *unit,
	      omd_disc_info_t *info, omd_toc_t *toc )
{
    char *also = "";
    
    if( omd_disc_is_recordable( info ))
    {
	printf( "Disc media is recordable.\n" );
    }
    else
    {
	printf( "Disc media is pre-mastered (read-only).\n" );
	also = " as well";
    }
    if( omd_disc_is_protected( info ))
    {
	printf( "Disc is write protected%s.\n", also );
    }
    printf( "\nNumber of tracks: %d\n", info->track_count );

    printf( "Music recorded:       %10s\n",
	    omd_time_to_string( &info->time_used, NULL ));
    printf( "Disc capacity (SP):   %10s\n",
	    omd_time_to_string( &info->time_max, NULL ));
    printf( "Disc free space (SP): %10s\n",
	    omd_time_to_string( &info->time_avail, NULL ));
    return 0;
}


/*****************************************************************************
 * List tracks
 */

int list( int argc, char **argv , omd_unit_t *unit, omd_disc_info_t *info,
	  omd_toc_t *toc )
{
    omd_track_t	*track;
    int		i, thisgroup = -1, full = FALSE;
    char	*offset = "";
    omd_time_t	total;

    omd_time_zero( &total );
    
    if( argc > 0 )
    {
	if( !strcmp( argv[0], "full" ) || !strcmp( argv[0], "-l" ))
	{
	    full = TRUE;
	}
	else
	{
	    printf( "Invalid argument '%s'\n", argv[0] );
	    return -1;
	}
    }
    

/*     if( ! quiet ) */
/*     { */
/* 	printf( "Reading track info...\n" ); */
/*     } */
/*     if( omd_toc_read_all_tracks( unit, toc ) < 0 ) */
/*     { */
/* 	printf( "Unable to read track info\n" ); */
/* 	return -1; */
/*     } */


    if( toc->title )
    {
	printf( "Disc title: %s\n", toc->title );
    }
    else
    {
	printf( "Disc has no title\n" );
    }
    
    for( i = 0; i < toc->track_count; i++ )
    {
	if(( track = omd_toc_track_info( unit, toc, i )) == NULL )
	{
	    printf( "Read error!" );
	    break;
	}
	if( track->group != thisgroup )
	{
	    thisgroup = track->group;
	    if( thisgroup >= 0 )
	    {
		printf( "Group %d: %s\n", track->group + 1,
			toc->group[thisgroup].name );
		offset = "  ";
	    }
	    else
	    {
		offset = "";
	    }
	}

	printf( "%s%2d) %10s - %s\n", offset, i + 1,
		omd_time_to_string( &track->length, NULL ),
		track->title );

	if( full )
	{
	    printf( "%s               %s %s%s\n",offset,
		    track->codec_name,
		    track->bitrate_name, 
		    (track->flags == OMD_FLAG_LOCKED) ? ", Checked out" : "");
	}

	/* Keep track of total time */
	omd_time_add( &total, &track->length );
    }

    if( ! quiet )
    {
	printf( "\nTotal time: %s\n", omd_time_to_string( &total, NULL ));
    }

    return 0;
}


/*****************************************************************************
 * List groups
 */
int grouplist( int argc, char **argv , omd_unit_t *unit,
	       omd_disc_info_t *info, omd_toc_t *toc )
{
    omd_group_t	*group;
    int		i;

    if( toc->group_count == 0 )
    {
	printf( "No groups...\n" );
    }
    else
    {
	for( i = 0, group = toc->group; i < toc->group_count; i++, group++)
	{
	    printf( "%d) Start=%-2d Finish=%-2d: %s\n", i + 1,
		    group->start + 1,
		    group->finish + 1,
		    group->name );
	}
    }

    return 0;
}

/*****************************************************************************
 * Add a new group
 */
int groupadd( int argc, char **argv , omd_unit_t *unit,
	       omd_disc_info_t *info, omd_toc_t *toc )
{
    int	start, finish, tmp;

    if( argc != 3 )
    {
	printf( "Usage: groupadd <start> <finish> <name>\n" );
	return -1;
    }
    
    start = atoi( argv[0] );
    finish = atoi( argv[1] );
    if( (start <= 0) || (start > toc->track_count ))
    {
	printf( "Invalid group start\n" );
	return -1;
    }
    if( (finish <= 0) || (finish > toc->track_count ))
    {
	printf( "Invalid group finish\n" );
	return -1;
    }
    /* Convert to zero based track IDs */
    start--;
    finish--;
    if( start > finish )
    {
	printf( "Warning: Swapping group ids\n" );
	tmp = start;
	start = finish;
	finish = tmp;
    }

    /* Add it to the TOC */
    if( omd_toc_add_group( toc, start, finish, argv[2] ) < 0 )
    {
	printf( "Add group failed\n" );
	return -1;
    }

    /* Flush group info out to disc */
    if( omd_toc_write_title( unit, toc ) < 0 )
    {
	printf( "TOC write failed" );
    }
    
    if( ! quiet )
    {
	printf( "Group added.\n" );
    }
    
    return 0;
}



/*****************************************************************************
 * Rename a group
 */

int grouprename( int argc, char **argv , omd_unit_t *unit,
		 omd_disc_info_t *info, omd_toc_t *toc )
{
    int	group;

    if( argc != 2 )
    {
	printf( "Usage: grouprename <group> <name>\n" );
	return -1;
    }
    
    group = atoi( argv[0] );
    if( (group <= 0) || (group > toc->group_count ))
    {
	printf( "Invalid group\n" );
	return -1;
    }
    group--;

    /* Rename */
    if( omd_toc_rename_group( toc, group, argv[1] ) < 0 )
    {
	printf( "Rename group failed\n" );
	return -1;
    }

    /* Flush group info out to disc */
    if( omd_toc_write_title( unit, toc ) < 0 )
    {
	printf( "TOC write failed" );
    }
    
    return 0;
}


/*****************************************************************************
 * Delete a group
 */

int groupdelete( int argc, char **argv , omd_unit_t *unit,
		 omd_disc_info_t *info, omd_toc_t *toc )
{
    int	group;

    if( argc != 1 )
    {
	printf( "Usage: groupdelete <group>\n" );
	return -1;
    }
    
    group = atoi( argv[0] );
    if( (group <= 0) || (group > toc->group_count ))
    {
	printf( "Invalid group\n" );
	return -1;
    }
    group--;

    /* Rename */
    if( omd_toc_delete_group( toc, group ) < 0 )
    {
	printf( "Delete group failed\n" );
	return -1;
    }

    /* Flush group info out to disc */
    if( omd_toc_write_title( unit, toc ) < 0 )
    {
	printf( "TOC write failed" );
    }
    
    return 0;
}


/*****************************************************************************
 * Play
 * Possible command line args: track and position
 */

int play( int argc, char **argv, omd_unit_t *unit, omd_disc_info_t *info,
	  omd_toc_t *toc )
{
    int 			track;
    omd_playback_position_t	pos;

    if( argc >= 1 )
    {
	track = atoi( argv[0] );
	if( ( track < 1 ) || ( track > info->track_count ))
	{
	    printf( "Invalid track ID '%s'\n", argv[0] );
	    return -1;
	}
	if( argc >= 2 )
	{
	    pos.track = track - 1;
	    if( omd_time_from_string( &pos.position, argv[1]) < 0 )
	    {
		printf( "Invalid position value '%s'\n", argv[1] );
		return -1;
	    }

	    if( omd_set_playback_position( unit, &pos ) < 0 )
	    {
		printf( "Failed to set playback position\n" );
		return -1;
	    }
	}
	else if( omd_set_playback_track( unit, track - 1 ) < 0 )
	/* This is inefficient, but lets us exercise two functions :) */
	{
	    printf( "Failed to set track\n" );
	    return -1;
	}
    }
    
    return netmd_start_playback( unit );
}


/*****************************************************************************
 * Stop
 */

int stop( int argc, char **argv, omd_unit_t *unit, omd_disc_info_t *info,
	  omd_toc_t *toc )
{
    return netmd_stop_playback( unit );
}


/*****************************************************************************
 * Pause
 */

int dopause( int argc, char **argv, omd_unit_t *unit, omd_disc_info_t *info,
	     omd_toc_t *toc )
{
    return netmd_pause_playback( unit );
}


/*****************************************************************************
 * Rewind
 */

int dorewind( int argc, char **argv, omd_unit_t *unit, omd_disc_info_t *info,
	      omd_toc_t *toc )
{
    return netmd_start_rewind( unit );
}


/*****************************************************************************
 * Fast forward
 */

int dofforward( int argc, char **argv, omd_unit_t *unit, omd_disc_info_t *info,
		omd_toc_t *toc )
{
    return netmd_start_fforward( unit );
}


/*****************************************************************************
 * Print unit status
 */

int status( int argc, char **argv , omd_unit_t *unit, omd_disc_info_t *info,
	    omd_toc_t *toc )
{
    omd_playback_status_a_t	status_a;
    omd_playback_status_b_t	status_b;
    omd_playback_position_t	position;

    omd_get_playback_status_a( unit, &status_a );
    slog_hex( LOG_DEBUG, "A::   ", status_a.raw, 12 );
	
    omd_get_playback_status_b( unit, &status_b );
    slog_hex( LOG_DEBUG, "B::   ", status_b.raw, 6 );

    if( debug )
    {
	printf( "Playback status 0x%04x\n", status_b.playback_mode );
    }
    switch( status_b.playback_mode )
    {
	case OMD_PLAY:
	    printf( "Unit currently playing\n" );
	    break;

	case OMD_STOPPED:
	    printf( "Unit currently stopped\n" );
	    break;
		
	case OMD_PAUSED:
	    printf( "Unit currently paused\n" );
	    break;
		
	case OMD_REWIND:
	    printf( "Unit currently rewinding \n" );
	    break;
		
	case OMD_FFORWARD:
	    printf( "Unit currently fast forwarding\n" );
	    break;

	default:
	    printf( "Unknown playback mode! %04x\n", status_b.playback_mode);
	    break;
    }


    if( omd_get_playback_position( unit, &position ) < 0 )
    {
	slog( LOG_ERROR, "Get playback position failed" );
    }
    else
    {
	slog_hex( LOG_DEBUG, "Pos   ", position.raw, 11 );
	printf( "Current playback position: Track %d, %s\n",
		position.track + 1,
		omd_time_to_string( &position.position, NULL ));
    }
    return 0;
}


/*****************************************************************************
 * Rename track
 */

int trackrename( int argc, char **argv , omd_unit_t *unit,
		  omd_disc_info_t *info, omd_toc_t *toc )
{
    int		track;

    if( argc != 2 )
    {
	printf( "Usage: rename <track> <newtitle>\n" );
	return -1;
    }


    track = atoi( argv[0] );
    if( ( track < 1 ) || ( track > info->track_count ))
    {
	printf( "Invalid track ID '%s'\n", argv[0] );
	return -1;
    }

    if( omd_rename_track( unit, track - 1, NULL, argv[1] ) < 0 )
    {
	printf( "Rename failed.\n" );
	return -1;
    }

    if( ! quiet )
    {
	printf( "Rename succeeded.\n" );
    }
    return 0;
}

int netmd_const_command( omd_unit_t *unit,
			 const char *send,
			 const char *expect );

int toc_sync( int argc, char **argv ,
	      omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc )
{
    int rc;
    char *fmt = "%02x18c3 ff%02x0000 00";
    char send[64], recv[64];
    
    printf( "Don't use this function. It doesn't do what I thought it did\n" );
    
    /*
    netmd_toc_sync( unit );
    netmd_toc_cache( unit );
    netmd_toc_sync( unit );
    */
    
    if( argc < 2 )
    {
	printf( "num!" );
    }
    
    sprintf( send, fmt, 0, atoi( argv[0] ));
    sprintf( recv, fmt, 9, atoi( argv[0] ));
    
    /* Play */
    rc = netmd_const_command( unit, send, recv );
    
    printf( "rc = %d\n", rc ) ;
    
    return 0;
}


/*****************************************************************************
 * Move a track
 */

int trackmove( int argc, char **argv ,
	       omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc )
{
    int		src, dst;
    
    if( argc != 2 )
    {
	printf( "Usage: omd trackmove <src> <dst>\n" );
	return -1;
    }
    src = atoi( argv[0] );
    dst = atoi( argv[1] );
    if( ( src < 1 ) || ( src > info->track_count ) ||
	( dst < 1 ) || ( dst > info->track_count ))
    {
	printf( "Invalid track numbers\n" );
	return -1;
    }
    src--;			/* Convert to zero based numbering */
    dst--;
    
    if( src == dst )
    {
	printf( "Errrm, why?\n" );
	return 0;
    }

    /* Actually move the track and re-arrange TOC */
    if( omd_toc_move_track( unit, toc, src, dst ) < 0 )
    {
	printf( "Move failed.\n" );
	return -1;
    }

    /* Flush group info out to disc */
    if( omd_toc_write_title( unit, toc ) < 0 )
    {
	printf( "TOC write failed" );
	return -1;
    }

    if( ! quiet )
    {
	printf( "Move succeeded.\n" );
    }
    
    return 0;
}

/*****************************************************************************
 * Delete a track
 */

int trackdelete( int argc, char **argv ,
		 omd_unit_t *unit, omd_disc_info_t *info, omd_toc_t *toc )
{
    int track;
    
    if( argc != 1 )
    {
	printf( "Usage: omd trackdelete <track>\n" );
	return -1;
    }
    track = atoi( argv[0] );

    if( ( track < 1 ) || ( track > info->track_count ))
    {
	printf( "Invalid track number\n" );
	return -1;
    }
    track--;			/* Convert to zero based numbering */


    /* Actually delete the track and re-arrange TOC */
    if( omd_toc_delete_track( unit, toc, track ) < 0 )
    {
	printf( "Delete track failed.\n" );
	return -1;
    }

    /* Flush group info out to disc */
    if( omd_toc_write_title( unit, toc ) < 0 )
    {
	printf( "TOC write failed." );
	return -1;
    }

    if( ! quiet )
    {
	printf( "Delete succeeded.\n" );
    }
    
    return 0;
}


/*****************************************************************************
 * Change disc title
 */

int title( int argc, char **argv , omd_unit_t *unit, omd_disc_info_t *info,
	   omd_toc_t *toc )
{
    char	*title;

    if( argc != 1 )
    {
	printf( "Usage: omd title <new title>\n" );
	return -1;
    }
    
    if( strlen( argv[0] ) > 0 )
    {
	title = argv[0];
    }
    else
    {
	title = NULL;		/* Set title to NULL for zero length titles  */
    }

    /* Actually set the title */
    if( omd_toc_set_title( toc, title ) < 0 )
    {
	printf( "Unable to set title\n" );
	return -1;
    }

    /* Flush group info out to disc */
    if( omd_toc_write_title( unit, toc ) < 0 )
    {
	printf( "TOC write failed" );
	return -1;
    }
    
    return 0;
}


/*****************************************************************************
 * set raw disc title string (use with care!)
 */

int rawtitle( int argc, char **argv , omd_unit_t *unit, omd_disc_info_t *info,
	      omd_toc_t *toc )
{
    
    if( argc != 1 )
    {
	printf( "Usage: omd rawtitle <title>\n" );
	return -1;
    }
    
    if( omd_disc_set_raw_title( unit, argv[0] ) < 0 )
    {
	printf( "Titling Failed." );
	return -1;
    }

    if( !quiet )
    {
	printf( "Title changed.\n:" );
    }
    return 0;
}


/*****************************************************************************
 * Completely initialise a disc
 */

int discinit( int argc, char **argv , omd_unit_t *unit,
	      omd_disc_info_t *info, omd_toc_t *toc )
{
    if( ( argc != 1 ) || strcmp( argv[0], "yes" ))
    {
	printf( "Usage: omd init yes\n" );
	return -1;
    }
    
    if( !quiet )
    {
	printf( "Disc initialising...." );
	fflush( stdout );
    }

    if( netmd_disc_init( unit ) < 0 )
    {
	printf( "Failed!\n" );
	return -1;
    }

    if( !quiet )
    {
	printf( "OK\n" );
    }
    return 0;
}

/*****************************************************************************
 * Send raw hex data to the unit
 */

int sendraw( int argc, char **argv , omd_unit_t *unit,
	     omd_disc_info_t *info, omd_toc_t *toc )
{
    netmd_cmd_buf_t	*cmdbuf;
    void		*loghandle = slog_get_handler();

    /* Default log level is INFO */
    slog_set_handler_level( loghandle, LOG_TRACE );
    
    if( argc < 1 )
    {
	printf( "Usage: omd raw <hex> ...\n" );
	return -1;
    }
    
    
    cmdbuf = netmd_simple_command( unit, 255, argv[0] );
    
    return 0;
}

/*****************************************************************************
 * Usage
 */

static const char *usage_header = "\nUsage: omd <function> <args>\n";

static const char *quickage[] = {
    "Commands include: list title rename move delete play stop init",
    "",
    "Use 'omd help' to get full usage",
    "Use the Source to get all the synonyms, abbreviations etc...",
    NULL,
};

static const char *fullage[] = {
    "status                             Print player status",
    "info                               Print disc information",
    "list [full]                        List track infomation",
    "grouplist                          List group information only",
    "title <name>                       Set the disk title to <name>",
    "rename <trk#> <name>               Set track <trk#>'s name to <name>",
    "grpname <grp#> <name>              Set group <grp#>'s name to <name>",
    "addgroup <start#> <end#> <name>    Create a new group called <name>",
    "delgroup <grp#>                    Delete group <grp#> (but no tracks)",
    "del <trk#>                         Delete track <trk#>",
    "move <trk#> <dst#>                 Move track <trk#> to <dst#>",
    "init yes                           Initialise disc (deletes eveything!)",
    "rawtitle <title>                   Set raw title (use with care!)",
    "play [trk#] [pos]                  Start playback on track [trk] at [pos]",
    "stop                               Stop playback",
    "pause                              Pause playback",
    "fforward                           Start fast forward",
    "rewind                             Start rewind",
    "",
    "           <arg> denotes compulsory argument",
    "           [arg] denotes optional argument",
    "           args with a # suffix  are numeric",
    "",
    "*** NB: ALL track and group numbers start from 1 not 0 ***",
    "",
    "Use the Source to get all the synonyms, abbreviations etc...",
    NULL,
};


int quickusage( int argc, char **argv , omd_unit_t *unit,
	   omd_disc_info_t *info, omd_toc_t *toc )
{
    const char **s;
    
    printf( "%s\n", usage_header );
    for( s = quickage; *s; s++ )
    {
	printf( "%s\n", *s );
    }

    return -1;
}

int usage( int argc, char **argv , omd_unit_t *unit,
	   omd_disc_info_t *info, omd_toc_t *toc )
{
    const char **s;
    
    printf( "%s\n", usage_header );
    for( s = fullage; *s; s++ )
    {
	printf( "%s\n", *s );
    }

    return -1;
}

