/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
/*
 * $Id: wav.c,v 1.1.1.1 2003/01/22 14:21:30 rocko Exp $
 *
 * Copyright (c) 2000, 2001 by Shiman Associates Inc., Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions: The above
 * copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors or
 * copyright holders shall not be used in advertising or otherwise to
 * promote the sale, use or other dealings in this Software without
 * prior written authorization from the authors or copyright holders,
 * as applicable.
 *
 * All trademarks and registered trademarks mentioned herein are the
 * property of their respective owners. No right, title or interest in
 * or to any trademark, service mark, logo or trade name of the
 * authors or copyright holders or their licensors is granted.
 *
 */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "mas/mas.h"
#include "wav.h"

/******* PRIVATE FUNCTIONS *********************************************/

static int cmp_fourcc(FourCC , char* );
static int riff_read_chunk_header( FILE * , struct riff_chunk_header*  );
static int print_wav_file_info( struct wave_info* );
static int parse_small_chunks( FILE * , struct wave_info* , u_int32 );
static int32 alloc_and_read(FILE * , u_int32 , void** );

static struct wave_file_handle_node* new_wave_file_handle( void );
static int delete_wave_file_handle( struct wave_file_handle_node* ); 
static int append_wave_file_handle(struct wave_file_handle_node* ,
				   struct wave_file_handle_node* );
static struct wave_file_handle_node* find_wave_file_handle(
    struct wave_file_handle_node* , FILE* );

/****** PRIVATE VARIABLES *********************************************/
/*** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ******************************/
/*** !!!! THIS IS NOT THREADSAFE !!!!!! ******************************/
/*** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ******************************/
static struct wave_file_handle_node* wave_file_handle_list_head = 0;

/***********************************************************************/

/*** wave file handle linked list handling routines *******************/
struct wave_file_handle_node*
new_wave_file_handle( void )
{
    struct wave_file_handle_node* node;

    if ( (node = masc_rtalloc(sizeof(struct wave_file_handle_node))) == 0)
    {
	return 0;
    }

    node->fp   = 0;
    node->info = 0;
    node->next = 0;
    node->prev = 0;
    return node;
}

int32
delete_wave_file_handle( struct wave_file_handle_node* node )
{
    if (node)
    {
	if (node->info) 
	{
	    masc_rtfree(node->info);
	    node->info = 0;
	}
	
	/* connect nodes on either side in list */
	if (node->prev != 0)
	    node->prev->next = node->next;
	if (node->next != 0)
	    node->next->prev = node->prev;

	masc_rtfree(node);
    }
    else return MERR_MEMORY;

    return 0;
}

int32
append_wave_file_handle(struct wave_file_handle_node* head, struct
			    wave_file_handle_node* node)
{
    struct wave_file_handle_node* tail = head;

    while( tail->next != NULL ) tail = tail->next;

    tail->next = node;
    node->prev = tail;

    return 0;
}

struct wave_file_handle_node*
find_wave_file_handle( struct wave_file_handle_node* head, FILE *fp )
{
    struct wave_file_handle_node* node = head;
    
    while ( node != NULL )
    {
	if ( node->fp == fp) return node;

	node = node->next;
    }
    
    return 0;
}


/***********************************************************************/

int32
alloc_and_read(FILE *fp, u_int32 size, void** object)
{
    if (size == 0)
	return MERR_INVALID;

    /* allocate space for the object */
    if ( (*object = masc_rtalloc( size )) == 0)
    {
	fclose(fp);
	return MERR_MEMORY;
    }
    
    /* read in the data */
    if ( fread(*object, 1, size, fp) != size )
    {
	fclose(fp);
	return MERR_IO;
    }
   
    return size;
}

int32
cmp_fourcc(FourCC id, char* test_string)
{
    char id_string[FOURCC_SIZE+1];
    int  i;

    /* Cast each byte of the "id" into a char in a string... */
    for (i=0; i<FOURCC_SIZE; i++)
    {
	id_string[i] = ((char *) &id)[i];
    }
    id_string[FOURCC_SIZE] = 0; /* ...and null-terminate it. */
    
    return strcmp(id_string, test_string); /* test for equivalence */
}


/* returns the number of bytes read */
int32
riff_read_chunk_header( FILE *fp, struct riff_chunk_header* header )
{
    int32 error = 0;

    error = fread(header, 1, SIZEOF_RIFF_CHUNK_HEADER, fp);
    if (error == SIZEOF_RIFF_CHUNK_HEADER)
	header->length = mas_letohl(header->length);

    return error;
}

int32
riff_begin_read( char* fname, FILE** fp_addr )
{
    FILE*                    fp;
    struct riff_chunk_header header;
    int32                    error;
    struct wave_info*        info;
    FourCC                   wave_id;
    struct wave_file_handle_node* node;

    if ( (fp = fopen(fname, "rb")) == NULL)
	return MERR_FILE_CANNOT_OPEN;

    *fp_addr = fp;

    /** Check RIFF header */
    if ( (error = riff_read_chunk_header( fp, &header )) == 0)
    {
	fclose(fp);
	return error;
    }
    
    if (cmp_fourcc(header.id, "RIFF") != 0)
    {
	fclose(fp);
	return MERR_FILE_TYPE;
    }
    
    if ( (info = masc_rtalloc( sizeof(struct wave_info) )) == 0)
    {
	fclose(fp);
	return MERR_MEMORY;
    }
    
    if ( (info->name = masc_rtalloc( strlen(fname)+1 ) ) == 0)
    {
	fclose(fp);
	return MERR_MEMORY;
    }
    
    /** Initialize wave_info data structure */
    strcpy(info->name, fname);
    info->length = header.length;
    info->fmt               = 0;
    info->cue_header        = 0;
    info->cue_points        = 0;
    info->playlist_header   = 0;
    info->play_segments     = 0;
    info->label_header      = 0;
    info->label_data        = 0;
    info->note_header       = 0;
    info->note_data         = 0;
    info->label_text_header = 0;
    info->label_text_data   = 0;
    info->file_header       = 0;
    info->file_data         = 0;
    info->pcm_fields        = 0;
    info->offset_data_start = 0;
    info->data_length       = 0;
    /***************************************/

    if ( (error = fread(&wave_id, 1, FOURCC_SIZE, fp)) == 0 )
    {
	fclose(fp);
	return MERR_IO;
    }
        
    if (cmp_fourcc(wave_id, WAV_WAVE_ID) != 0)
    {
	fclose(fp);
	return MERR_FILE_TYPE;
    }
    
    if ( ( error = parse_small_chunks(fp, info, header.length) ) < 0)
    {
	fclose(fp);
	return MERR_IO;
    }
    print_wav_file_info( info );

    /* seek to start of data */
    if (info->offset_data_start)
    {
	if (fseek(fp, (long)info->offset_data_start, SEEK_SET) < 0)
	{
	    fclose(fp);
	    return MERR_IO;
	}
    }

    node       = new_wave_file_handle();
    if (node == 0) return MERR_MEMORY;
    node->fp   = fp;
    node->info = info;
    
    if (wave_file_handle_list_head == 0)
    {
	wave_file_handle_list_head = node;
    }
    else
    {
	append_wave_file_handle(wave_file_handle_list_head, node);
    }
    
    return 0;
}

int32
print_wav_file_info( struct wave_info* info )
{
    int i;

    printf("RIFF chunk length: %d\n", info->length);
    
    if (info->fmt)
    {
	printf("[format]\n");
	printf("              format tag: %s\n", wav_format_string(info->fmt->format_tag));
	printf("                channels: %d\n", info->fmt->channels);
	printf("      samples per second: %d\n", info->fmt->samples_per_second);
	printf("average bytes per second: %d\n",
	       info->fmt->average_bytes_per_second);
	printf("       block align bytes: %d\n",
	       info->fmt->block_align_bytes);
	if(info->pcm_fields)
	    printf("     PCM bits per sample: %d\n",
		   info->pcm_fields->bits_per_sample);
    }
    
    if (info->offset_data_start)
    {
	printf("\n[data]\n");
	printf("data chunk begins at: %d bytes in\n", info->offset_data_start);
	printf("     data chunk size: %d bytes\n", info->data_length);
    }
    
    if (info->label_text_header)
    {
	printf("\n[label text]\n");
	printf("         name: %d\n", info->label_text_header->name);
	printf("sample length: %d\n", info->label_text_header->sample_length);
	printf("      purpose: %d\n", info->label_text_header->purpose);
	printf("      country: %d\n", info->label_text_header->country);
	printf("     language: %d\n", info->label_text_header->language);
	printf("      dialect: %d\n", info->label_text_header->dialect);
	printf("     codepage: %d\n", info->label_text_header->codepage);
	if (info->label_text_data) printf("data: %s\n", info->label_text_data);
	
    }

    if (info->label_header)
    {
	printf("\n[label]\n");
	printf("name: %d\n", info->label_header->name);
	if (info->label_data) printf("data: %s\n", info->label_data);
    }

    if (info->cue_header)
    {
	printf("\n[cue points]\n");
	printf(" number: %d\n", info->cue_header->num_cue_points);
	for (i=0; i<info->cue_header->num_cue_points; i++)
	{
	    printf("  [cue %d]\n", i);
	    printf("             name: %d\n", info->cue_points[i].name);
	    printf("         position: %d\n", info->cue_points[i].position);
	    printf("       chunk name: %c%c%c%c\n",
		   ((char *) &(info->cue_points[i].chunk_name))[0],
		   ((char *) &(info->cue_points[i].chunk_name))[1],
		   ((char *) &(info->cue_points[i].chunk_name))[2], 
		   ((char *) &(info->cue_points[i].chunk_name))[3]);
	
	    printf("      chunk start: %d\n", info->cue_points[i].chunk_start);
	    printf("      block start: %d\n", info->cue_points[i].block_start);
	    printf("    sample offset: %d\n", info->cue_points[i].sample_offset);
	}
    }
    return 0;
}


/* This function is called recursively for list-style chunks */
int32
parse_small_chunks( FILE *fp, struct wave_info* info, u_int32 chunk_length )
{
    struct riff_chunk_header header;
    int32                    read_data=1;
    int                      recurse_read_data=0;
    FourCC                   label;
    u_int32                  skip;
    u_int32                  chunk_countdown = chunk_length;
    int                      i;

    /* loop until EOF or the end of chunk*/
    while (read_data && (chunk_countdown > 0))
    {
	if ( (read_data = riff_read_chunk_header( fp, &header )) <= 0)
	    return read_data;

	printf("chunk: %c%c%c%c ", ((char *) &(header.id))[0],
	       ((char *) &(header.id))[1], ((char *) &(header.id))[2],
	       ((char *) &(header.id))[3] );  
	
	printf("%d\n", header.length);
	skip = header.length;

	/*** Parse individual chunks, if they're small enough **/
	/*** (this is everything except WAV_DATA_ID) for now   */

	/* if we can't parse a chunk, we pass over it. */

	/***** FORMAT CHUNK ********************************************/
	if ( cmp_fourcc(header.id, WAV_FORMAT_ID) == 0)
	{
	    if ( ( read_data = alloc_and_read(fp,
					      SIZEOF_FMT_COMMON_FIELDS,
					      (void **)&info->fmt) ) <= 0 )
		return read_data;

	    /* do byte-sex conversion */
	    info->fmt->format_tag = mas_letohs(info->fmt->format_tag);
	    info->fmt->channels   = mas_letohs(info->fmt->channels);
	    info->fmt->samples_per_second =
		mas_letohl(info->fmt->samples_per_second);
	    info->fmt->average_bytes_per_second =
		mas_letohl(info->fmt->average_bytes_per_second);
	    info->fmt->block_align_bytes =
		mas_letohs(info->fmt->block_align_bytes);
	    
	    skip -= SIZEOF_FMT_COMMON_FIELDS;

	    /* read in PCM-specific fields */
	    if (info->fmt->format_tag == WAVE_FORMAT_PCM)
	    {
		if ( ( read_data = 
		       alloc_and_read(fp, SIZEOF_FMT_PCM_FIELDS,
				      (void *)&info->pcm_fields) ) <= 0 )
		    return read_data;

		info->pcm_fields->bits_per_sample =
		    mas_letohs(info->pcm_fields->bits_per_sample);
		skip -= SIZEOF_FMT_PCM_FIELDS;
	    }
	}

	/***** DATA CHUNK ********************************************/
	if ( cmp_fourcc(header.id, WAV_DATA_ID) == 0)
	{
	    /* get current position, right after the data chunk header */
	    info->offset_data_start = ftell(fp);
	    info->data_length = header.length;
	    /* don't modify skip */
	}
	
	/***** CUE CHUNK ********************************************/
	if ( cmp_fourcc(header.id, WAV_CUE_ID) == 0)
	{
	    /** get number of cue points */
	    if ( ( read_data = alloc_and_read(fp, SIZEOF_CUE_CHUNK_HEADER,
					      (void *)&info->cue_header) ) <= 0 )
		return read_data;
	    info->cue_header->num_cue_points =
		mas_letohl(info->cue_header->num_cue_points);
	    skip -= read_data;

	    if ((info->cue_header->num_cue_points*SIZEOF_CUE_POINT)
		<= (header.length - read_data) )
	    {
		if ( ( read_data =
		       alloc_and_read(fp, info->cue_header->num_cue_points *
				      SIZEOF_CUE_POINT, 
				      (void *)&info->cue_points ) ) <= 0 )
		    return read_data;
		skip -= read_data;
		for (i=0; i<info->cue_header->num_cue_points; i++)
		{
		    info->cue_points[i].name =
			mas_letohl(info->cue_points[i].name); 
		    info->cue_points[i].position =
			mas_letohl(info->cue_points[i].position); 
		    info->cue_points[i].chunk_start =
			mas_letohl(info->cue_points[i].chunk_start); 
		    info->cue_points[i].block_start =
			mas_letohl(info->cue_points[i].block_start); 
		    info->cue_points[i].sample_offset =
			mas_letohl(info->cue_points[i].sample_offset); 
		}
	    }
	}
	
	/***** LABEL CHUNK ********************************************/
	if ( cmp_fourcc(header.id, WAV_LABEL_ID) == 0)
	{
	    if ( ( read_data = alloc_and_read(fp,
					      SIZEOF_LABEL_CHUNK_HEADER, 
					      (void *)&info->label_header) ) <= 0)
		return read_data;
	    if (read_data < header.length) /* is there a data section? */
	    {
		if ( ( read_data =
		       alloc_and_read(fp, 
				      header.length - SIZEOF_LABEL_CHUNK_HEADER,
				      (void *)&info->label_data) ) <= 0 )
		    return read_data;
	    }
	    skip -= header.length;
	}

	/***** NOTE CHUNK ********************************************/
	if ( cmp_fourcc(header.id, WAV_NOTE_ID) == 0)
	{
	    if ( ( read_data = alloc_and_read(fp, 
					      SIZEOF_NOTE_CHUNK_HEADER,
					      (void *)&info->note_header) ) <= 0 )
		return read_data;
	    if (read_data < header.length) /* is there a data section? */
	    {
		if ( ( read_data =
		       alloc_and_read(fp, header.length - SIZEOF_NOTE_CHUNK_HEADER,
				      (void *)&info->note_data) ) <= 0 )
		    return read_data;
	    }
	    skip -= header.length;
	}

	/***** LABEL TEXT CHUNK *****************************************/
	if ( cmp_fourcc(header.id, WAV_TEXT_ID) == 0)
	{
	    if ( ( read_data = 
		   alloc_and_read(fp, 
				  SIZEOF_LABEL_TEXT_CHUNK_HEADER,
				  (void *)&info->label_text_header) ) <= 0 )
		return read_data;
	    if (read_data < header.length) /* is there a data section? */
	    {
		if ( ( read_data = 
		       alloc_and_read(fp,
				      header.length - SIZEOF_LABEL_TEXT_CHUNK_HEADER,
				      (void *)&info->label_text_data) ) <= 0)
		    return read_data;
	    }
	    skip -= header.length;
	}

	/***** LIST CHUNK ********************************************/
	if ( cmp_fourcc(header.id, WAV_LIST_ID) == 0)
	{
	    if ( (read_data = fread(&label, 1, FOURCC_SIZE, fp)) != FOURCC_SIZE )
	    {
		fclose(fp);
		return MERR_IO;
	    }
	    skip -= FOURCC_SIZE;
 
	    printf("%c%c%c%c\n", ((char *) &(label))[0],
		   ((char *) &(label))[1], ((char *) &(label))[2],
		   ((char *) &(label))[3] );  
	    
	    /** if this is an associated data LIST chunk, we'll recurse
		into parse_small_chunks() to read this chunk's
		contents. */
	    if ( cmp_fourcc(label, "adtl") == 0 )
	    {
		recurse_read_data =
		    parse_small_chunks(fp, info, header.length - FOURCC_SIZE );
		skip -= recurse_read_data; /* should have read the
					      rest of this chunk */
	    }
	}

	/** skip ahead to the start of the next chunk, discounting the
	    number of bytes we've already read. */
	if (fseek(fp, skip, SEEK_CUR) < 0) return MERR_IO;

	/* The file position counter is now just past this chunk.  */
	chunk_countdown -= header.length;
    }

    return chunk_length - chunk_countdown; /* number of bytes read */
}

char*
wav_format_string( int format )
{
    switch (format)
    {
    case WAVE_FORMAT_UNKNOWN: return "Unknown";
    case WAVE_FORMAT_PCM: return "Microsoft PCM";
    case WAVE_FORMAT_ADPCM: return "Microsoft ADPCM";
    case WAVE_FORMAT_IEEE_FLOAT: return "IEEE Floating Point";
    case WAVE_FORMAT_ALAW: return "Microsoft A-law";
    case WAVE_FORMAT_MULAW: return "Microsoft mu-law";
    case WAVE_FORMAT_OKI_ADPCM: return "OKI ADPCM";
    case WAVE_FORMAT_IMA_ADPCM: return "IMA ADPCM";
    case WAVE_FORMAT_DIGISTD: return "Digistd";
    case WAVE_FORMAT_DIGIFIX: return "Digifix";
    case WAVE_FORMAT_DOLBY_AC2: return "Dolby AC-2";
    case WAVE_FORMAT_GSM610: return "GSM 06.10";
    case WAVE_FORMAT_ROCKWELL_ADPCM: return "Rockwell ADPCM";
    case WAVE_FORMAT_ROCKWELL_DIGITALK: return "Rockwell Digitalk";
    case WAVE_FORMAT_G721_ADPCM: return "G.721 ADPCM";
    case WAVE_FORMAT_G728_CELP: return "G.728 CELP";
    case WAVE_FORMAT_MPEG: return "MPEG";
    case WAVE_FORMAT_MPEGLAYER3: return "MPEG2 layer 3 (MP3)";
    case WAVE_FORMAT_G726_ADPCM: return "G.726 ADPCM";
    case WAVE_FORMAT_G722_ADPCM: return "G.722 ADPCM";
    default: break;
    }
    return "";
}


int32
wav_get_format(FILE *fp, u_int16* format)
{
    struct wave_file_handle_node* node;

    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	*format = node->info->fmt->format_tag;
	return 0;
    }
    else return MERR_INVALID;
}

int32
wav_get_channels(FILE *fp, u_int16* channels)
{
    struct wave_file_handle_node* node;
    
    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	*channels = node->info->fmt->channels;
	return 0;
    }
    else return MERR_INVALID;
}

int32
wav_get_samples_per_second(FILE *fp, u_int32* samples_per_second)
{
    struct wave_file_handle_node* node;
    
    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	*samples_per_second = node->info->fmt->samples_per_second;
	return 0;
    }
    else return MERR_INVALID;
}

int32
wav_get_average_bytes_per_second(FILE *fp, u_int32* average_bytes_per_second)
{
    struct wave_file_handle_node* node;
    
    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	*average_bytes_per_second =
	    node->info->fmt->average_bytes_per_second;
	return 0;
    }
    else return MERR_INVALID;
}

int32
wav_get_block_align_bytes(FILE *fp, u_int16* block_align_bytes)
{
    struct wave_file_handle_node* node;
    
    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	*block_align_bytes = node->info->fmt->block_align_bytes;
	return 0;
    }
    else return MERR_INVALID;
}

int32
wav_pcm_get_bits_per_sample(FILE *fp, u_int16* bits_per_sample)
{
    struct wave_file_handle_node* node;
    
    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	if (node->info->pcm_fields)
	{
	    *bits_per_sample =
		node->info->pcm_fields->bits_per_sample;
	    return 0;
	}
	else return MERR_INVALID;
    }
    else return MERR_INVALID;
}

int32
wav_get_length(FILE *fp, u_int32* length)
{
    struct wave_file_handle_node* node;
    
    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	*length = node->info->data_length;
	return 0;
    }
    else return MERR_INVALID;
}

int32
wav_get_wave_info(FILE *fp, struct wave_info** info)
{
    struct wave_file_handle_node* node;
    
    node = find_wave_file_handle(wave_file_handle_list_head, fp);
    if (node)
    {
	*info = node->info;
	return 0;
    }
    else return MERR_INVALID;
}

