#include <stdio.h>
#include <string.h>
#include <math.h>
#include <sys/stat.h>

#include <sndfile.h>

#define	BUFFER_LEN	1024

static void usage_exit (char *progname) ;
static int is_data_really_float (SNDFILE *sndfile) ;
static void fix_file (char *filename) ;
static off_t  file_size (char *filename) ;

static	int buffer [BUFFER_LEN] ;

int
main (int argc, char *argv [])
{	SNDFILE *sndfile ;
	SF_INFO sfinfo ;
	int k, data_is_float, converted = 0 ;

	puts ("\nCooledit Fixer.\n---------------") ;

	if (argc < 2)
		usage_exit (argv [0]) ;
		
	for (k = 1 ; k < argc ; k++)
	{	if ((sndfile = sf_open (argv [k], SFM_READ, &sfinfo)) == NULL)
		{	/*-printf ("Failed to open : %s\n", argv [k]) ;-*/
			continue ;
			} ;
		
		if (sfinfo.format != (SF_FORMAT_WAV | SF_FORMAT_PCM_32))
		{	/*-printf ("%-50s : not a 32 bit PCM WAV file.\n", argv [k]) ;-*/
			sf_close (sndfile) ;
			continue ;
			} ;

		data_is_float = is_data_really_float (sndfile) ;

		sf_close (sndfile) ;

		if (data_is_float == SF_FALSE)
		{	/*-printf ("%-50s : not a Cooledit abomination.\n", argv [k]) ;-*/
			continue ;
			} ;

		fix_file (argv [k]) ;
		converted ++ ;
		} ;

	if (converted == 0)
		puts ("\nNo files converted.") ;

	puts ("") ;

	return 0 ;
} /* main */


static void 
usage_exit (char *progname)
{	char *cptr ;

	if ((cptr = strrchr (progname, '/')))
		progname = cptr + 1 ;
	if ((cptr = strrchr (progname, '\\')))
		progname = cptr + 1 ;
		
	printf ("\n    Usage : %s <filename>\n", progname) ;
	puts ("\n"
		"Fix broken files created by Syntrillium's Cooledit. These files are \n"
		"marked as containing PCM data but actually contain floating point \n"
		"data. Only the broken files created by Cooledit are processed. All \n"
		"other files remain untouched.\n"
		"\n"
		"More than one file may be included on the command line. \n"
		) ;

	exit (1) ;
} /* usage_exit */

static int
is_data_really_float (SNDFILE *sndfile)
{	float	*fptr ;
	int 	k, readcount ;
	
	fptr = (float *) buffer ;

	while ((readcount = sf_read_int (sndfile, buffer, BUFFER_LEN)) > 0)
	{	for (k = 0 ; k < readcount ; k++)
		{	if (buffer [k] == 0)
				continue ;
			
			if (fabs (fptr [k]) > 32768.0)
				return SF_FALSE ;
			} ;
		} ;
	
	return SF_TRUE ;
} /* is_data_really_float */

static void 
fix_file (char *filename)
{	static	char	newfilename [512] ;

	SNDFILE *infile, *outfile ;
	SF_INFO	sfinfo ;
	int		readcount, k ;
	float	*fptr, normfactor ;
	char	*cptr ;
	
	printf ("\nFixing : %s\n", filename) ;

	if ((infile = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
	{	printf ("Not able to open input file %s\n", filename) ;
		exit (1) ;
		} ;
	
	if (strlen (filename) >= sizeof (newfilename) - 1)
	{	puts ("Error : Path name too long.\n") ;
		exit (1) ;	
		} ;
		
	strncpy (newfilename, filename, sizeof (newfilename)) ;
	newfilename [sizeof (newfilename) - 1] = 0 ;
	
	if ((cptr = strrchr (newfilename, '/')) == NULL)
		cptr = strrchr (newfilename, '\\') ;
	
	if (cptr)
	{	cptr [1] = 0 ;
		strncat (newfilename, "fixed.wav", sizeof (newfilename) - strlen (newfilename) - 1) ;
		}
	else
		strncpy (newfilename, "fixed.wav", sizeof (newfilename) - 1) ;
	
	newfilename [sizeof (newfilename) - 1] = 0 ;

	printf ("    Output   : %s\n", newfilename) ;
	
	sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT ;
	
	if ((outfile = sf_open (newfilename, SFM_WRITE, &sfinfo)) == NULL)
	{	printf ("Not able to output open file %s\n", filename) ;
		exit (1) ;
		} ;

	/* Find the file peak. sf-command (SFC_CALC_SIGNAL_MAX) cannot be used. */

	fptr = (float *) buffer ;

	normfactor = 0.0 ;
	
	while ((readcount = sf_read_int (infile, buffer, BUFFER_LEN)) > 0)
	{	for (k = 0 ; k < readcount ; k++)
			if (fabs (fptr [k]) > normfactor)
				normfactor = fabs (fptr [k]) ;
		} ;

	printf ("    Peak     : %g\n", normfactor) ;
	
	normfactor = 1.0 / normfactor ;
	
	sf_seek (infile, 0, SEEK_SET) ;
	
	while ((readcount = sf_read_int (infile, buffer, BUFFER_LEN)) > 0)
	{	for (k = 0 ; k < readcount ; k++)
			fptr [k] *= normfactor ;
		sf_write_float (outfile, fptr, readcount) ;
		} ;

	sf_close (infile) ;
	sf_close (outfile) ;
	
	if (abs (file_size (filename) - file_size (newfilename)) > 50)
	{	puts ("Error : file size mismatch.\n") ;
		exit (1) ;	
		} ;

	printf ("    Renaming : %s\n", filename) ;

	if (remove (filename) != 0)
	{	perror ("rename") ;
		exit (1) ;
		} ;

	if (rename (newfilename, filename) != 0)
	{	perror ("rename") ;
		exit (1) ;
		} ;

	return ;
} /* fix_file */

static off_t
file_size (char *filename)
{	struct stat buf ;

	if (stat (filename, &buf) != 0)
	{	perror ("stat") ;
		exit (1) ;
		} ;

	return buf.st_size ;
} /* file_size */
