#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#include "voc.h"


static FILE *VocOpenFile(
	const char *filename, const char *mode, off_t *size_rtn
);
int VocIsFileVoc(const char *filename);
unsigned int VocGetEnvSampleRate(voc_data_struct *vd);

void VocDestroyData(voc_data_struct *vd);
int VocAddDataBlock(voc_data_struct *vd, int type);

int VocReadHeader(const char *filename, FILE *fp, voc_data_struct *vd);
int VocReadPartialData(
	voc_data_struct *vd,
	off_t offset,
	off_t max_chunk_size,
	int read_opt
);


#define MIN(a,b)        ((a) < (b) ? (a) : (b))
#define MAX(a,b)        ((a) > (b) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define ABSOLUTE(x)     (((x) < 0) ? ((x) * -1) : (x))


/*
 *	Procedure to open a voc file and return its entire file size.
 */
static FILE *VocOpenFile(
	const char *filename, const char *mode, off_t *size_rtn
)
{

	FILE *fp;
	struct stat stat_buf;


	if(filename == NULL)
	{
	    fprintf(stderr, "Cannot open file with no name.\n");
	    *size_rtn = 0;
	    return(NULL);
	}
	if(mode == NULL)
	{
	    fprintf(stderr, "%s: Open mode not givin.\n", filename);
	    *size_rtn = 0;
	    return(NULL);
	}

	if(stat(filename, &stat_buf))
	{
	    fprintf(stderr, "%s: No such file.\n", filename);
	    *size_rtn = 0;
	    return(NULL);
	}
	*size_rtn = stat_buf.st_size;

	fp = fopen(filename, mode);
	if(fp == NULL)
	{
	    fprintf(stderr, "%s: Cannot open.\n", filename);
	    *size_rtn = 0;
	    return(NULL);
	}

	return(fp);
}

/*
 *	Checks if the specified file is a VOC file.
 */
int VocIsFileVoc(const char *filename)
{
	int i;
	char magicname[0x14];
	FILE *fp;
	off_t filesize;


	/* Open file. */
	fp = VocOpenFile(filename, "rb", &filesize);
	if(fp == NULL)
	    return(VocErrorNoAccess);
	if(filesize == 0)
	    return(VocErrorNoAccess);

	/* Bytes 0x00 to 0x13: Get magic name. */
	for(i = 0; i < 0x13; i++)
	    magicname[i] = (char)fgetc(fp);

	/* Close file. */
	fclose(fp);

	/* Check magic name. */
	magicname[0x13] = '\0';
	if(strcmp(magicname, VocMagicName))
	    return(VocErrorNotVoc);
	else
	    return(VocSuccess);
}

/*
 *	Returns the sample rate of that of the first data block
 *	of type VocBlockTypeSoundData that is found on the blocks
 *	in the vd structure.
 *
 *	If vd is invalid or no blocks of type VocBlockTypeSoundData exist
 *	then the value VocDefaultSampleRate will be returned.
 */
unsigned int VocGetEnvSampleRate(voc_data_struct *vd)
{
	int i;
	unsigned int sample_rate = VocDefaultSampleRate;
	voc_block_struct **ptr, *block_ptr;


	if(vd == NULL)
	    return(sample_rate);

	/* Go through each data block. */
	for(i = 0, ptr = vd->datablock;
	    i < vd->total_datablocks;
	    i++, ptr++)
	{
	    block_ptr = *ptr;
	    if(block_ptr == NULL)
		continue;

	    /* Check if this type is a VocBlockTypeSoundData. */
	    if(block_ptr->type == VocBlockTypeSoundData)
	    {
		sample_rate = block_ptr->sample_rate;
		break;
	    }
	}

	/* Sanitize sample rate. */
	if(sample_rate == 0)
	    sample_rate = VocDefaultSampleRate;

	return(sample_rate);
}

/*
 *	Deallocates all resources and closes any opened files specified
 *	in the given vd structure and resets its values.
 */
void VocDestroyData(voc_data_struct *vd)
{
	int i;
	voc_block_struct **ptr, *block_ptr;


	if(vd == NULL)
	    return;

	/* Data blocks. */
	for(i = 0, ptr = vd->datablock;
	    i < vd->total_datablocks;
	    i++, ptr++
	)
	{
	    block_ptr = *ptr;
	    if(block_ptr == NULL)
		continue;

	    free(block_ptr->str);

	    free(block_ptr);
	}

	free(vd->datablock);
	vd->datablock = NULL;

	vd->total_datablocks = 0;


	/* Reset values. */
	free(vd->filename);
	vd->filename = NULL;
	vd->filesize = 0;

	if(vd->fp != NULL)
	{
	    fclose(vd->fp);
	    vd->fp = NULL;
	}

	free(vd->data);
	vd->data = NULL;
	vd->data_len = 0;

	vd->total_data_len = 0;

	vd->major_version = 0;
	vd->minor_version = 0;
	vd->first_data_block = 0;


	return;
}

/*
 *	Allocates a new data block on the vd structure by the specified
 *	type.
 */
int VocAddDataBlock(voc_data_struct *vd, int type)
{
	int n;


	if(vd == NULL)
	    return(-1);

	if(vd->total_datablocks < 0)
	    vd->total_datablocks = 0;

	n = vd->total_datablocks;
	vd->total_datablocks++;

	vd->datablock = (voc_block_struct **)realloc(
	    vd->datablock,
	    vd->total_datablocks * sizeof(voc_block_struct *)
	);
	if(vd->datablock == NULL)
	{
	    vd->total_datablocks = 0;
	    return(-1);
	}

	vd->datablock[n] = (voc_block_struct *)calloc(
	    1,
	    sizeof(voc_block_struct)
	);
	if(vd->datablock[n] == NULL)
	{
	    vd->total_datablocks = n;
	    return(-1);
	}

	/* Set type. */
	vd->datablock[n]->type = type;

	return(n);
}

/*
 *      Reads the header from the stream fp and initializes the given
 *      vd structure if fp is valid and a voc file.
 *
 *      The given filename is used only for referance purposes, it can be
 *	NULL.
 *
 *      If VocSuccess is returned then the given fp will be transfered
 *      to the vd structure and should not be referanced again. For all
 *      other return values, the calling function is responsible for
 *      closing the given fp.
 */
int VocReadHeader(const char *filename, FILE *fp, voc_data_struct *vd)
{
	int x, y, z;
	unsigned char type;
	off_t nextseek;

	/* To identify that this is a VOC file. */
	char magicname[0x14];

	/* For calculating that pesky 3 byte unsigned int block offset. */
	unsigned int size_x, size_y, size_z;

	int bytes_read = 0;
	struct stat stat_buf;
	off_t filesize;


	/* Error checks. */
	if(vd == NULL)
	    return(VocErrorNoBuffers);
	if(fp == NULL)
	    return(VocErrorBadValue);

	/* Rewind to beginning of voc file. */
	rewind(fp);


	/* Reset values. */
	memset(vd, 0x00, sizeof(voc_data_struct));

	/* Get size of file. */
	if(fstat(fileno(fp), &stat_buf))
	    return(VocErrorNoAccess);

	filesize = stat_buf.st_size;
	if(filesize == 0)
	    return(VocErrorNoAccess);

	/* Is this is a voc file? Check 0x00 to 0x12 for magic number. */
	for(x = 0; x < 0x13; x++)
	{
	    magicname[x] = (char)fgetc(fp);
	    bytes_read++;
	}
	magicname[0x13] = '\0';
	/* Magic name does not match? If so then this is not a voc
	 * file.
	 */
	if(strcmp(magicname, VocMagicName))
	    return(VocErrorNotVoc);


	/* Begin reading voc file header. */

	/* Record file name. */
	if(filename != NULL)
	    vd->filename = strdup(filename);

	/* Record file size. */
	vd->filesize = filesize;


	/* 0x13: Read but skip abort print byte. */
	fgetc(fp);
	bytes_read++;


	/* 0x14 to 0x15: Get offset to first data block. */
	x = fgetc(fp);
	y = fgetc(fp);
	bytes_read += 2;
	/* Calculate, LSBF. */
	vd->first_data_block = x + (y * 256);


	/* 0x16 to 0x17: Minor and major version numbers. */
	vd->minor_version = (unsigned char)fgetc(fp);
	vd->major_version = (unsigned char)fgetc(fp);
	bytes_read += 2;


	/* 0x18 to 0x19: Extended stuff. */
	x = fgetc(fp);
	y = fgetc(fp);
	bytes_read += 2;


	/* Move fp to start of first block. */
	fseek(fp, vd->first_data_block, SEEK_SET);
	nextseek = ftell(fp);

	while(nextseek < vd->filesize)
	{
	    fseek(fp, nextseek, SEEK_SET);

	    /* Read type. */
	    type = (unsigned char)fgetc(fp);
	    bytes_read++;
	    switch(type)
	    {
	      case VocBlockTypeTerminator:
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);
		/* *** Terminator type has no size! *** */

		/* Get size. */
		vd->datablock[y]->size = 1;
		/* Set nextseek. */
		nextseek += 1;

		break;


	      case VocBlockTypeSoundData:
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Calculate and set data_start_pos. */
		vd->datablock[y]->data_start_pos = nextseek + 6;

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* Get data_len. */
		if((long)vd->datablock[y]->size >= 2)
		    vd->datablock[y]->data_len = vd->datablock[y]->size - 2;
		else
		    vd->datablock[y]->data_len = 2;

		/* Get sample rate. */
		z = fgetc(fp);
		bytes_read++;
		if(z >= 256)
		    z = 255;
		vd->datablock[y]->sample_rate = (unsigned int)(
		    (double)(-1000000) / (double)(z - 256)
		);

		/* Get compresion type. */
		vd->datablock[y]->compression_type = (unsigned char)fgetc(fp);
		bytes_read++;

		/* Calculate bits. */
		switch(vd->datablock[y]->compression_type)
		{
		  case 0x00:
		    vd->datablock[y]->bits = 8;
		    break;

		  case 0x01:
		    vd->datablock[y]->bits = 4;
		    break;

		  case 0x02:
		    vd->datablock[y]->bits = 2.6;
		    break;

		  case 0x03:
		    vd->datablock[y]->bits = 2;
		    break;

		  case 0x04:	/* Multi DAC, not standard */
		    vd->datablock[y]->bits = 8;
		    break;

		  default:
		    vd->datablock[y]->bits = 8;
		    break;
		}

		break;


	      case VocBlockTypeSoundDataContinue:
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Calculate and set data_start_pos. */
		vd->datablock[y]->data_start_pos = nextseek + 4;
		    
		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* Get data_len. */
		vd->datablock[y]->data_len = vd->datablock[y]->size;
		break;


	      case VocBlockTypeSilence:
		/* Allocate data block. */  
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* Get silence length. */
		fread(&(vd->datablock[y]->silence_length), 1, 2, fp);
		bytes_read += 2;

		/* Get sample rate. */
		z = fgetc(fp);
		bytes_read++;
		if(z >= 256)
		    z = 255;
		vd->datablock[y]->sample_rate = (unsigned int)(
		    (double)(-1000000) / (double)(z - 256)
		);
		break;

	      case VocBlockTypeMarker:
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* Get marker number. */
		fread(&(vd->datablock[y]->marker_num), 1, 2, fp);
		bytes_read += 2;
		break;

	      case VocBlockTypeASCII:
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* Get string. */
		vd->datablock[y]->str_len = vd->datablock[y]->size;
		vd->datablock[y]->str = (char *)calloc(1,
		    (vd->datablock[y]->str_len + 1) * sizeof(char)
		);
		for(z = 0; z < vd->datablock[y]->size; z++)
		{
		    vd->datablock[y]->str[z] = (char)fgetc(fp);
		    bytes_read++;
		}
		vd->datablock[y]->str[z] = '\0';
		break;


	      case VocBlockTypeRepeat:
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* Get repeat count. */
		fread(&(vd->datablock[y]->repeat_count), 1, 2, fp);
		bytes_read += 2;
		break;


	      case VocBlockTypeEndRepeat:
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* There is no info for this field. */
		break;


	      case VocBlockTypeExtended: 
		/* Allocate data block. */
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		/* Get time constant. */
		fread(&(vd->datablock[y]->time_constant), 1, 2, fp);
		bytes_read += 2;

		/* Get pack. */
		vd->datablock[y]->pack = (unsigned char)fgetc(fp);
		bytes_read++;

		/* Get mode (0 = mono, 1 = stereo). */
		vd->datablock[y]->mode = (unsigned char)fgetc(fp);
		bytes_read++;
		break;


	      /* Unknown, assume field is just 1 byte line. */
	      default:
		/* Allocate data block. */  
		y = VocAddDataBlock((voc_data_struct *)vd, type);
		if(y < 0)
		    return(VocErrorNoBuffers);

		/* Get size. */
		size_x = (unsigned int)fgetc(fp);
		size_y = (unsigned int)fgetc(fp);
		size_z = (unsigned int)fgetc(fp);
		bytes_read += 3;
		vd->datablock[y]->size = size_x + (size_y << 8) +
		    (size_z << 16);
		/* Set nextseek. */
		nextseek += (vd->datablock[y]->size + 4);

		break;
	    }
	}

	/* Add audio lengths from all data blocks containing audio data. */
	vd->total_data_len = 0;
	for(x = 0; x < vd->total_datablocks; x++)
	{
	    if(vd->datablock[x] == NULL)
		continue;

	    if( (vd->datablock[x]->type != VocBlockTypeSoundData) &&
		(vd->datablock[x]->type != VocBlockTypeSoundDataContinue)
	    )
		continue;

	    vd->total_data_len += vd->datablock[x]->data_len;
	}


	/* Transfer given fp to the vd structure, the calling
	 * function should not referance it again since we are returning
	 * VocSuccess.
	 */
	vd->fp = fp;  

	return(VocSuccess);
}

/*
 *	Reads a segment of data from the VOC file reffered to
 *      by the filename member on the given wd structure (which
 *      should have been initialized wuth a prior call to
 *      VocReadHeader().
 *
 *      File will be opened as needed if the fp member is NULL.
 *
 *      The member data and data_len will be reallocated as needed
 *      if the requested max_chunk_size is different than data_len.
 */
int VocReadPartialData(
	voc_data_struct *vd,
	off_t offset,
	off_t max_chunk_size,
	int read_opt		/* Reading format. */
)
{
	int i, datablock_num;
	int total_datablocks;
	voc_block_struct *block_ptr;

	off_t src_buf_pos;
	int tar_buf_pos;

	off_t offset_count;


	if(vd == NULL)
	    return(VocErrorBadValue);
	if(offset < 0)
	    return(VocErrorBadValue);

	/* Read nothing? */
	if(max_chunk_size <= 0)
	{
	    free(vd->data);
	    vd->data = NULL;
	    vd->data_len = 0;

	    return(VocSuccess);
	}

	/* Get total_datablocks. */
	total_datablocks = vd->total_datablocks;

	/* Free and allocate data buffer as needed. */
	if(vd->data_len != max_chunk_size)
	{
	    vd->data_len = max_chunk_size;

	    vd->data = (char *)realloc(
		vd->data,
		vd->data_len * sizeof(char)
	    );
	}
	if(vd->data == NULL)
	{
	    vd->data_len = 0;
	    return(VocErrorNoBuffers);
	}

	/* Open file as needed. */
	if(vd->fp == NULL)
	{
	    off_t filesize;

	    if(vd->filename == NULL)
		return(VocErrorBadValue);
	 
	    vd->fp = VocOpenFile(vd->filename, "rb", &filesize);
	    if(vd->fp == NULL)
		return(VocErrorNoAccess);
	    if(filesize == 0)
		return(VocErrorNoAccess);
	}

	/* Go through each data block. */
	for(datablock_num = 0, tar_buf_pos = 0, offset_count = 0;
	    datablock_num < total_datablocks;
	    datablock_num++
	)
	{
	    if(tar_buf_pos >= max_chunk_size)
		break;

	    block_ptr = vd->datablock[datablock_num];
	    if(block_ptr == NULL)
		continue;

	    /* Skip data blocks not of type audio or audio continue. */
	    if((block_ptr->type != VocBlockTypeSoundData) &&
	       (block_ptr->type != VocBlockTypeSoundDataContinue)
	    )
		continue;

	    /* Skip if offset would not put us in this block. */
	    if((offset_count + block_ptr->data_len) < offset)
	    {
		offset_count += block_ptr->data_len;
		continue;
	    }

	    /* Seek beginning of audio data block in file. */
	    src_buf_pos = (off_t)((int)offset - (int)offset_count);
	    if(src_buf_pos < 0)
		src_buf_pos = 0;
	    fseek(
		vd->fp,
		block_ptr->data_start_pos + src_buf_pos,
		SEEK_SET
	    );

	    offset_count += block_ptr->data_len;
	    offset = offset_count;
	    while((tar_buf_pos < max_chunk_size) &&
		  (src_buf_pos < block_ptr->data_len)
	    )
	    {
		vd->data[tar_buf_pos] = (char)fgetc(vd->fp);

		tar_buf_pos++;
		src_buf_pos++;
	    }
	}


	/* Sanitize data length. */
	if(tar_buf_pos < vd->data_len)
	    vd->data_len = tar_buf_pos;


	/* Shift the DSP data that was read, voc file format stores the
	 * data as unsigned char.
	 */
	switch(read_opt)
	{
	  case VocReadSigned8:
	    for(i = 0; i < vd->data_len; i++)
		vd->data[i] = (char)((int)vd->data[i] - (int)128);
	    break;

	  default:
	    break;
	}

	return(VocSuccess);
}
