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

#include "raw.h"


static void RawReportError(
	const char *filename, const char *reason, int how_bad
);
static FILE *RawOpenFile(
	const char *filename, const char *mode, off_t *size_rtn
);

int RawIsFileRaw(const char *filename);

int RawReadHeader(const char *filename, FILE *fp, raw_data_struct *rd);
int RawReadPartialData(
	raw_data_struct *rd,
	long offset,
	long 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))


static void RawReportError(
	const char *filename, const char *reason, int how_bad
)
{
	if(filename != NULL)
	    fprintf(stderr, "%s: ", filename);
	if(reason != NULL)
	    fprintf(stderr, "%s (Error level %i)", reason, how_bad);

	fprintf(stderr, "\n");

	return;
}


void RawDestroyData(raw_data_struct *rd)
{
	if(rd == NULL)
	    return;

	free(rd->filename);
	rd->filename = NULL;

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

	rd->data_offset = 0;
	rd->data_length = 0;

	rd->block_align = 0;
	rd->sample_size = 0;
	rd->channels = 0;
	rd->sample_rate = 0;

	free(rd->data);
	rd->data = NULL;

	rd->data_len = 0;

	return;
}


static FILE *RawOpenFile(
	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);
}


int RawIsFileRaw(const char *filename)
{
	FILE *fp;
	off_t filesize;


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

/* What do we check? */

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

	return(RawSuccess); 
}

/*
 *      Reads the header from the stream fp and initializes the given
 *      rd structure if fp is valid and a raw file.
 *
 *      The given filename is used only for referance purposes, it can be
 *      NULL.
 *
 *      If RawSuccess is returned then the given fp will be transfered
 *      to the rd structure and should not be referanced again. For all
 *      other return values, the calling function is responsible for
 *      closing the given fp.
 */
int RawReadHeader(const char *filename, FILE *fp, raw_data_struct *rd)
{
	off_t filesize;
	struct stat stat_buf;


	/* Error checks. */
	if(rd == NULL)
	    return(RawErrorNoBuffers);
	if(fp == NULL)
	    return(RawErrorBadValue);

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


	/* Reset values. */
	memset(rd, 0x00, sizeof(raw_data_struct));

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

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


	/* Here we should be checking if this is a raw file, but there
	 * really is no way to tell so just assume that it is a raw
	 * file.
	 */

	/* Begin reading raw file header. */

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

/* Check for offset? */
	/* Get offset. */
	rd->data_offset = 0;

	/* Get length of data. */
	rd->data_length = (long)filesize - (long)rd->data_offset;
	if(rd->data_length < 0)
	    rd->data_length = 0;

	/* Standard values. */
	rd->block_align = 0;
	rd->sample_size = 8;
	rd->channels = 1;
	rd->sample_rate = 8000;

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

	return(RawSuccess);
}

/*
 *      Reads a segment of data from the raw file reffered to
 *      by the filename member on the given wd structure (which
 *      should have been initialized wuth a prior call to
 *      RawReadHeader().
 *
 *      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 RawReadPartialData(
	raw_data_struct *rd,
	long offset,            /* In bytes. */
	long max_chunk_size,    /* In bytes. */
	int read_opt
)
{
	int i;
	char *buf_ptr;


	if(rd == NULL)
	    return(RawErrorNoBuffers);
	if(offset < 0)
	    return(RawErrorBadValue);

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

	    return(RawSuccess);
	}

	/* Sanitize values. */
	if(offset >= rd->data_length)
	{
	    return(RawErrorEndOfData);
	}
	if((offset + max_chunk_size) > rd->data_length)
	{
	    max_chunk_size = rd->data_length - offset;
	}
	if(max_chunk_size <= 0)
	{
	    return(RawErrorEndOfData);
	}


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

	    if(rd->filename == NULL)
		return(RawErrorBadValue);
	 
	    rd->fp = RawOpenFile(rd->filename, "rb", &filesize);
	    if(rd->fp == NULL)
		return(RawErrorNoAccess);
	    if(filesize == 0)
		return(RawErrorNoAccess);
	}


	/* Reallocate buffer for data (as needed). */
	if(rd->data_len != max_chunk_size)
	{
	    rd->data_len = max_chunk_size;

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

	    fclose(rd->fp);
	    rd->fp = NULL;

	    return(RawErrorNoBuffers);
	}

	/* Position file pointer. */
	fseek(rd->fp, rd->data_offset + offset, SEEK_SET);

	/* Read the data, raw file format stores DSP as signed 8. */
	buf_ptr = rd->data;
	switch(read_opt)
	{
	  /* Shift byte value by + 128. */
	  case RawReadUnsigned8:
	    for(i = 0; i < max_chunk_size; i++)
		*buf_ptr++ = (char)(fgetc(rd->fp) + 128);
	    break;

	  /* Get data as is (signed 8). */
	  default:
	    for(i = 0; i < max_chunk_size; i++)
		*buf_ptr++ = (char)fgetc(rd->fp);
	    break;
	}

	return(RawSuccess);
}
