
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cddb.h"
#include "cddbp.h"
#include "id3.h"
#include "misc_utils.h"
#include "select_frame_handler.h"
#include "main_window_handler.h"

#define NAME "CATraxx"
#define MAX_CDDB_FILE_SIZE 15360

enum errors { OK, REMOTE_OK, LOCAL_OK, NOT_FOUND, NO_CONNECT, CONNECT_REFUSED,
              SERVER_ERROR, PARAM_ERROR };
enum lookup_protocol { CDDBP, HTTP };
enum lookup_order { NONE, LOCAL, REMOTE, LOCAL_REMOTE, REMOTE_LOCAL };


int cddb_handle_data( const char *data, char** artist, char** dtitle, char* titles[], int* totaltracks, char** year, char** dgenre );
int do_cddb( char** result, char **disc_category, int tracknum, int duration, long int offset[], const char* server, int port, int proto );


int cddb_handle_data( const char *data, char** artist, char** dtitle, char* titles[], int* totaltracks, char** year, char** dgenre )
{
	char * row, *mark;
	const char *tmp = data;
	int i, j, counter = 0, track, previoustrack = 100, ttcounter = 0;
	char* tempstr;

	if ( strncmp( data, "# xmcd", 6 ) != 0 )
		return - 1;

	( *dtitle ) = NULL;

	while ( *tmp != 0 ) {	/* check against end of string */

		/* get the row */
		i = strcspn( tmp, "\r\n" );
		while ( tmp[ i ] == '\r' || tmp[ i ] == '\n' )
			i++;

		row = ( char* ) malloc( i + 1 );
		strncpy( row, tmp, i ); row[ i ] = 0;
		tmp += i;

		/* eval the row */
		if( strncmp( row, "DYEAR", 5 ) == 0 ) { /* CD Year */
			tempstr=malloc(MAX_YEAR_LENGTH);
			strcpy(tempstr,row);
			tempstr=strchr( row, '=' );
			tempstr++;
			j = strlen (tempstr);

			( *year )=(char* ) malloc ( j + 1 );
			
			strncpy( ( *year ),tempstr, ( j + 1) );
			remove_non_unix_chars(*year);
		} else if ( strncmp( row, "DGENRE", 6 ) == 0 ) { /* Disc Genre */
			tempstr=malloc(MAX_GENRE_LENGTH);
			strcpy(tempstr,row);
			tempstr=strchr( row, '=' );
			tempstr++;

			j = strlen (tempstr);
			( *dgenre )=(char* ) malloc( j + 1 );
			strncpy( ( *dgenre ),tempstr, ( j + 1 ));
			remove_non_unix_chars(*dgenre);
		} else if ( strncmp( row, "TTITLE", 6 ) == 0 ) { /* Track Title */
			/* get the track number before going on */
			/* skip the TTITLE */
			mark = row + 6;
			counter = 0;

			/* convert ascii -> int */
			while ( *mark != '=' ) {
				counter *= 10;
				counter += *mark - 0x30 ;
				mark++;
			}
			mark++;          /* and skip the '=' */
			track = counter;

			if ( previoustrack != track )
				ttcounter++;

			/* create the filename. Append previous title if necessary */
			tempstr = malloc( MAX_TITLE_LENGTH );
			if ( previoustrack == track )
				strcpy( tempstr, titles[ track ] );
			else
				strcpy( tempstr, "" );

			/* put in the track name */
			titles[ track ] = strcat ( tempstr, mark );

			strip_trailing_space( &titles[ track ] );
			strip_leading_space( &titles[ track ] );

			previoustrack = track;
			
			remove_non_unix_chars( titles[ track ] );

#ifdef DEBUG
			printf( "Track %d: %s\n", track, titles[ track ] );
#endif
		} else if ( ( strncmp( row, "DTITLE", 6 ) == 0 ) && ( *dtitle == NULL ) ) { /* CD Title */
			i = strcspn( row, "=" ); i++;          /* skip to the data */
			mark = row + i;

			// tm:  hack around bogus CDDB entries
			if (strstr(mark, " / ")) {
				j = strstr( mark, " / " ) - mark + 1;
				( *artist ) = ( char* ) malloc( j + 1 );
				strncpy( ( *artist ), mark, j );
				( *artist ) [ j ] = 0;
				( *dtitle ) = ( char* ) malloc ( strlen( mark ) - j );
				mark = mark + j + 1;
				strcpy( ( *dtitle ), mark );
			} else {
#ifdef DEBUG
				printf("malformed DTITLE, copying full DTITLE into both artist and dtitle\n");
#endif
				j = strlen(mark);
				(* artist ) = (char* ) malloc (j + 1);
				strncpy( ( *artist ), mark, j);
				( *artist ) [ j ] = 0;
				( *dtitle ) = (char* ) malloc (j + 1);
				strncpy( ( *dtitle ), mark, j);
				( *dtitle ) [ j ] = 0;
			}

			strip_trailing_space( artist );
			strip_leading_space( artist );
			strip_trailing_space( dtitle );
			strip_leading_space( dtitle );
			
			remove_non_unix_chars( *artist );
			remove_non_unix_chars( *dtitle );
			convert_slashes(*artist, '_'); // dc: _ Artist, - others
			convert_slashes(*dtitle, '-');
			if ( config.cddb_config.convert_spaces == TRUE ) {
				convert_spaces( *artist, '_' );
				convert_spaces( *dtitle, '_' );
			}
#ifdef DEBUG
			printf( "Artist: %s\n", ( *artist ) );
			printf( "Dtitle: %s\n", ( *dtitle ) );
#endif
		} 

		/* ignore any other results */
		free( row );
	}

	*totaltracks = ttcounter;
	return 0;
}


unsigned long int cddb_disk_id( int length, int tracknum, long int offset[] )
{
	int track;
	long int result = 0, tmp;

	for ( track = 0; track < tracknum; track++ ) {
		tmp = ( offset[ track ] + 150 ) / 75;
		do {
			result += tmp % 10;
			tmp /= 10;
		} while ( tmp != 0 );
	}

	return ( result % 0xff ) << 24 | ( length + 2 - ( ( offset[ 0 ] + 150 ) / 75 ) ) << 8 | tracknum;
}

int do_cddb( char** result, char **disc_category, int tracknum, int duration, long int offset[], const char* server, int port, int proto )
{
	FILE * sock = NULL;
	int status, matches, i;
	char **category = NULL, **title = NULL, **alt_id = NULL;

	char *final_cat, *final_id, *cd_id, *uri, *wwwserver;

	/* chop up the URL */
	wwwserver = uri = strdup( server );
	wwwserver = strsep( &uri, "/" );

	final_cat = ( char* ) malloc( 20 );
	final_id = ( char* ) malloc( 20 );
	cd_id = ( char* ) malloc( 20 );
	sprintf( cd_id, "%08lx", cddb_disk_id( duration, tracknum, offset ) );

	if ( proto == CDDBP ) {
		sock = socket_init( wwwserver, port );
		if ( !sock )
			return NO_CONNECT;

		/* connect to the cddb server */
		status = cddbp_signon( sock );
		switch ( status ) {
			case 200 : 
			case 201 : 
				break;         	/* might want to differenciate when write() is supported on the server side */
			case 432 : 
			case 433 : 
			case 434 :
				fclose( sock ); 
				return CONNECT_REFUSED;
			default :
				fclose( sock ); 
				return SERVER_ERROR;
		}

		/* do the hello handshake */
		status = cddbp_handshake( sock, NAME, VERSION );
		switch ( status ) {
			case 200 : 
			case 402 : 
				break;
			case 431 : 
				fclose( sock ); 
				return CONNECT_REFUSED;
			default : 
				fclose( sock ); 
				return SERVER_ERROR;
		}

		status = cddbp_query( sock, cd_id, tracknum, offset, duration, &matches,
		                      &category, &title, &alt_id );
	}

#if 0
	/* get cddb information from a local http server (plain text file) */
	/* must change CDDB URL to be http://localhost/testentry.txt */
	strcpy( final_cat, "test" );
	strcpy( final_id, "test" );
#else
	else {
		if ( !strcmp( config.cddb_config.proxy_server, "" ) )
			status = http_query( wwwserver, port, uri, cd_id, tracknum, offset, duration, &matches,
			                     &category, &title, &alt_id, NAME, VERSION );
		else
			status = http_query_proxy( wwwserver, port, config.cddb_config.proxy_server, config.cddb_config.proxy_port, uri, cd_id, tracknum, offset, duration, &matches,
			                           &category, &title, &alt_id, NAME, VERSION );

	}

	switch ( status ) {
case 200 : case 211 : break;
	case 999: return NO_CONNECT;
	case 202 : if ( proto == CDDBP ) cddbp_signoff( sock ); return NOT_FOUND;
	case 403 : if ( proto == CDDBP ) cddbp_signoff( sock ); return SERVER_ERROR;
	default : if ( proto == CDDBP ) fclose( sock ); return SERVER_ERROR;
	}

	/* we don't care about multiple matches, just grab the first one */
	final_cat = strdup( category[ 0 ] );
	*disc_category = final_cat;
	final_id = strdup( alt_id[ 0 ] );

	for ( i = 0; i < matches; i++ ) {
		free( category[ i ] );
		free( title[ i ] );
		free( alt_id[ i ] );
	}
	free( category );
	free( title );
	free( alt_id );
#endif

	/* now finally grab the data for the disc id and category */
	if ( proto == CDDBP )
		status = cddbp_read( sock, final_cat, final_id, result );
	else
		if ( !strcmp( config.cddb_config.proxy_server, "" ) )
			status = http_read( wwwserver, port, uri, final_cat, final_id, result, NAME, VERSION );
		else
			status = http_read_proxy( wwwserver, port, config.cddb_config.proxy_server, config.cddb_config.proxy_port, uri, final_cat, final_id, result, NAME, VERSION );

	switch ( status ) {
	case 210 : break;
	case 401 : if ( proto == CDDBP ) cddbp_signoff( sock ); return NOT_FOUND;
	case 403 : if ( proto == CDDBP ) cddbp_signoff( sock ); return SERVER_ERROR;
	default : if ( proto == CDDBP ) fclose( sock ); return SERVER_ERROR;
	}

	if ( proto == CDDBP ) cddbp_signoff( sock );

	free( final_id );

	return REMOTE_OK;
}

/*
	  check the local file system to see if the file
	  already exists by looping thru the catagories and the 
	  predefined prefix
*/
int read_local_file ( char** result, int tracknum, int duration, long int offset[] )
{
	FILE * datafile;
	int i;
	char file_check[ MAX_FILE_PATH_LENGTH + MAX_FILE_NAME_LENGTH + 1 ];
	char *cd_id;

	*result = ( char * ) malloc ( MAX_CDDB_FILE_SIZE );
	cd_id = ( char* ) malloc( 20 );
	sprintf( cd_id, "%08lx", cddb_disk_id( duration, tracknum, offset ) );
#ifdef DEBUG
	printf( "cddb_disk_id returned '%s'\n", cd_id );
#endif
	/* first, check the directory pointed to by the config file */
	strcpy( file_check, config.cddb_path );		/* start with the base path */
	strcat( file_check, "/" );
	strcat( file_check, cd_id );				/* add the filename */
	if ( ( datafile = fopen( file_check, "r" ) ) == NULL ) {
		/*  
		 *  since the file didn't exist in the directory
		 * 	check the category sub-directories
		 */
		for ( i = 0; cddb_categories[ i ].cddb_name != NULL; i++ ) {
			strcpy( file_check, config.cddb_path ); 	/*   start with the base path  */
			strcat( file_check, "/" ); 					/*   add the seperator  */
			strcat( file_check, cddb_categories[ i ].cddb_name ); 	/*   add the catagory  */
			strcat( file_check, "/" ); 					/*   add the seperator  */
			strcat( file_check, cd_id ); 				/*   add the filename  */
			if ( ( datafile = fopen( file_check, "r" ) ) != NULL )
				break;
		}
	}
	if ( datafile == NULL )
		return ( NOT_FOUND );
	else {
		fread( *result, 1, MAX_CDDB_FILE_SIZE, datafile );
		fclose( datafile );
		return ( REMOTE_OK );
	}
}

int do_cddb_proxy( char** result, char **disc_category, int tracknum, int duration, long int offset[], const char* server, int port, int proto )
{
	FILE * sock = NULL;
	int status, matches, i;
	char **category = NULL, **title = NULL, **alt_id = NULL;
	char *final_cat, *final_id, *cd_id, *uri, *wwwserver;

	/* chop up the URL */
	wwwserver = uri = strdup( server );
	wwwserver = strsep( &uri, "/" );

	final_cat = ( char* ) malloc( 20 );
	final_id = ( char* ) malloc( 20 );
	cd_id = ( char* ) malloc( 20 );

	sprintf( cd_id, "%08lx", cddb_disk_id( duration, tracknum, offset ) );
	if ( proto == CDDBP ) {
		sock = socket_init( wwwserver, port );
		if ( !sock )
			return NO_CONNECT;

		/* connect to the cddb server */
		status = cddbp_signon( sock );
		switch ( status ) {
	case 200 : case 201 : break;         	/* might want to differentiate when write() is supported on the server side */
case 432 : case 433 : case 434 :
			fclose( sock ); return CONNECT_REFUSED;
		default :
			fclose( sock ); return SERVER_ERROR;
		}

		/* do the hello handshake */
		status = cddbp_handshake( sock, NAME, VERSION );
		switch ( status ) {
	case 200 : case 402 : break;
		case 431 : fclose( sock ); return CONNECT_REFUSED;
		default : fclose( sock ); return SERVER_ERROR;
		}

		status = cddbp_query( sock, cd_id, tracknum, offset, duration, &matches,
		                      &category, &title, &alt_id );
	}

#ifdef TESTLOCALHTTP
	/* get cddb information from a local http server (plain text file) */
	/* must change CDDB URL to be http://localhost/testentry.txt */
	strcpy( final_cat, "test" );
	strcpy( final_id, "test" );
#else
	else {
		status = http_query( wwwserver, port, uri, cd_id, tracknum, offset, duration, &matches,
		                     &category, &title, &alt_id, NAME, VERSION );
	}

	switch ( status ) {
case 200 : case 211 : break;
	case 999: return NO_CONNECT;
	case 202 : if ( proto == CDDBP ) cddbp_signoff( sock ); return NOT_FOUND;
	case 403 : if ( proto == CDDBP ) cddbp_signoff( sock ); return SERVER_ERROR;
	default : if ( proto == CDDBP ) fclose( sock ); return SERVER_ERROR;
	}

	/* we don't care about multiple matches, just grab the first one */
	final_cat = strdup( category[ 0 ] );
	*disc_category = final_cat;
	final_id = strdup( alt_id[ 0 ] );

	for ( i = 0; i < matches; i++ ) {
		free( category[ i ] );
		free( title[ i ] );
		free( alt_id[ i ] );
	}
	free( category );
	free( title );
	free( alt_id );
#endif

	/* now finally grab the data for the disc id and category */
	if ( proto == CDDBP )
		status = cddbp_read( sock, final_cat, final_id, result );
	else
		status = http_read( wwwserver, port, uri, final_cat, final_id, result,
		                    NAME, VERSION );

	switch ( status ) {
	case 210 : break;
	case 401 : if ( proto == CDDBP ) cddbp_signoff( sock ); return NOT_FOUND;
	case 403 : if ( proto == CDDBP ) cddbp_signoff( sock ); return SERVER_ERROR;
	default : if ( proto == CDDBP ) fclose( sock ); return SERVER_ERROR;
	}

	if ( proto == CDDBP ) cddbp_signoff( sock );

	free( final_id );

	return REMOTE_OK;
}

int cddb_main( _main_data *main_data )
{
	int err, i, totaltracks;

	char* result = NULL;
	char* artist = NULL;
	char* dtitle = NULL;
	char* category = NULL;
	char* titles[ MAX_NUM_TRACK ];
	char* year = NULL;
	char* dgenre = NULL;

	int tracknum = main_data->num_tracks;
	int duration = main_data->total_length;
	long int offset[ MAX_NUM_TRACK ];

	main_window_handler( MW_UPDATE_STATUSBAR, "Contacting CDDB server...", NULL );
	while ( gtk_events_pending() ) gtk_main_iteration();

	/* grab the track offsets from the main_data structure */
	for ( i = 0; i < tracknum; i++ )
		offset[ i ] = main_data->track[ i ].begin;

	/* check for a local file first */
	err = read_local_file( &result, tracknum, duration, offset );

	/* connect to the cddb server and grab the results */
	if ( err != REMOTE_OK )
		err = do_cddb( &result, &category, tracknum, duration, offset, config.cddb_config.server, config.cddb_config.port, config.cddb_config.use_http );

	main_window_handler( MW_UPDATE_STATUSBAR, "Grabbing Completed...", NULL );
	while ( gtk_events_pending() ) gtk_main_iteration();

	switch ( err ) {
	case REMOTE_OK :
		/* successful lookup, now parse the returned data */
		cddb_handle_data( result, &artist, &dtitle, titles, &totaltracks, &year, &dgenre );

		if ( artist == 0 )
			strcpy( main_data->disc_artist, "" );
		else
			strcpy( main_data->disc_artist, artist );
		free( artist );
		if ( dtitle == 0 )
			strcpy( main_data->disc_title, "" );
		else
			strcpy( main_data->disc_title, dtitle );
		free( dtitle );
		if ( dgenre == 0 )
			strcpy( main_data->disc_category, "" );
		else
			strcpy( main_data->disc_category, dgenre );
		free( category );
		if ( year == 0 )
			strcpy( main_data->disc_year, "" );
		else
			strcpy( main_data->disc_year, year );
		free( year );
		for ( i = 0; i < tracknum; i++ )     /* fixed a sneaky bug here */
		{
			/* copy the data into the main_data structure */
			put_track_title(titles[i], main_data, i);
			free( titles[ i ] );
		}
		/* this is needed to update the display with the new data */
		select_frame_handler( SF_SYNC_SELECT_FRAME, 0, main_data );
		break;
	case NO_CONNECT :
		err_handler( CDDB_NO_CONNECT_ERR, NULL );
		break;
	case CONNECT_REFUSED :
		err_handler( CDDB_CONNECT_REFUSED_ERR, NULL );
		break;
	case SERVER_ERROR :
		err_handler( CDDB_SERVER_ERR, NULL );
		break;
	case NOT_FOUND :
		err_handler( CDDB_NOT_FOUND_ERR, NULL );
		break;
	}

	main_window_handler( MW_CLEAR_STATUSBAR, NULL, NULL );
	while ( gtk_events_pending() ) gtk_main_iteration();

	return 0;
}
