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

#include "midi.h"


/* static void swap32(int32_t *i); */
static void swapu32(u_int32_t *i);
/*
static int MidiFGetVarlenNumber(FILE *fp);
static int MidiGetVarlenNumber(const char *buf, int max);
 */

int MidiIsFileMidi(const char *filename);
int MidiRead(const char *filename, FILE *fp, MidiDataStruct *md);
void MidiDestroy(MidiDataStruct *md);


#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))


#if 0
void swap32(int32_t *i)
{
	int32_t tmp;
	int8_t *src, *tar;

	if(i == NULL)
	    return;

	tmp = *i;
	src = (int8_t *)&tmp;
	tar = (int8_t *)i;

	tar[3] = src[0];
	tar[2] = src[1];
	tar[1] = src[2];
	tar[0] = src[3];
}
#endif

void swapu32(u_int32_t *i)
{
	u_int32_t tmp = *i;
	u_int8_t *src = (u_int8_t *)&tmp,
		 *tar = (u_int8_t *)i;

	tar[3] = src[0];
	tar[2] = src[1];
	tar[1] = src[2];
	tar[0] = src[3];
}

#if 0
/*
 *	Reads variable-length number from FILE pointer fp,
 *	fp will be incremented to the point just after the bytes
 *	making up the variable-length number.
 */
int MidiFGetVarlenNumber(FILE *fp)
{
	int c, p = 0, val = 0;


	if(fp == NULL)
	    return(0);

	do
	{
	    c = fgetc(fp);
	    if(c == EOF)
		break;

	    val += (((u_int8_t)c & 0x7f) << (p * 8));
	    p++;
	}
	while(((u_int8_t)c & 0x80) && (p <= 4));

	return(val);
}
#endif

#if 0
/*
 *      Reads variable-length number from buffer pointer buf.
 *	Will not read more than max bytes.
 */
int MidiGetVarlenNumber(const char *buf, int max)
{
	int p = 0, val = 0;


	if(buf == NULL)
	    return(0);
 
	do
	{
	    val += (((u_int8_t)*buf & 0x7f) << (p * 8));

	    buf++;
	    p++;
	}
	while(((u_int8_t)*buf & 0x80) && (p <= max));

	return(val);
}
#endif

/*
 *	Returns MidiSuccess if filename is a MIDI file or
 *	MidiBadValue if filename is not a MIDI file.
 */
int MidiIsFileMidi(const char *filename)
{
	int i, c;
	struct stat stat_buf;
	FILE *fp;
	char chunk_type_name[MidiChunkTypeNameLen + 1];


	if(filename == NULL)
	    return(MidiBadValue);


	/* Check if file exists. */
	if(stat(filename, &stat_buf))
	    return(MidiBadValue);

	/* Open file. */
	fp = fopen(filename, "rb");
	if(fp == NULL)
	    return(MidiBadValue);


	/* Read first few bytes. */
	*chunk_type_name = '\0';
	for(i = 0; i < MidiChunkTypeNameLen; i++)
	{
	    c = fgetc(fp);
	    if(c == EOF)
		break;

	    chunk_type_name[i] = (char)c;
	}
	chunk_type_name[MidiChunkTypeNameLen] = '\0';

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

	if(!strcmp(chunk_type_name, MidiChunkTypeNameHeader) ||
	   !strcmp(chunk_type_name, MidiChunkTypeNameTrack)
	)
	    return(MidiSuccess);
	else
	    return(MidiBadValue);
}


/*
 *   Reads the header from the stream fp and initializes the given
 *   md structure if fp is valid and a midi file.
 *
 *   The given filename is used as referance purposes, it can be NULL.
 *
 *   If MidiSuccess is returned then the given fp will be transfered
 *   to the md structure and should not be referanced again. For all
 *   other return values, the calling function is responsible for
 *   closing the given fp.
 */
int MidiRead(const char *filename, FILE *fp, MidiDataStruct *md)
{
	int need_break;
	int i, c;
	struct stat stat_buf;
	off_t filepos, filesize;

	char chunk_type_name[MidiChunkTypeNameLen + 1];
	u_int32_t chunk_len;
	MidiChunkStruct *chunk;

	u_int8_t *buf_ptr;
	int buf_len;


	if(md == NULL)
	    return(MidiNoBuffers);
	if(fp == NULL)
	    return(MidiNoAccess);

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


	/* Reset values. */
	memset(md, 0x00, sizeof(MidiDataStruct));

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

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


	/* Check if this is a midi file by reading the first few bytes. */
	*chunk_type_name = '\0';
	for(i = 0; i < MidiChunkTypeNameLen; i++)
	{
	    c = fgetc(fp);
	    if(c == EOF)
		break;

	    chunk_type_name[i] = (char)c;
	}
	chunk_type_name[MidiChunkTypeNameLen] = '\0';
	if(strcmp(chunk_type_name, MidiChunkTypeNameHeader) &&
	   strcmp(chunk_type_name, MidiChunkTypeNameTrack)
	)
	    return(MidiErrorNotMidi);


	/* Begin reading midi file header. */

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


	/* Read each chunk in the file. */
	rewind(fp);
	filepos = 0;
	while(filepos < filesize)
	{
	    need_break = 0;

	    /* Chunk type (4 bytes). */
	    for(i = 0; i < MidiChunkTypeNameLen; i++)
	    {
		c = fgetc(fp);
		if(c == EOF)
		{
		    need_break = 1;
		    break;
		}

		chunk_type_name[i] = (char)c;
	    }
	    chunk_type_name[MidiChunkTypeNameLen] = '\0'; /* Safe. */
	    if(need_break)
		break;

	    /* Read chunk length (4 bytes). */
	    fread(&chunk_len, sizeof(u_int32_t), 1, fp);
	    swapu32(&chunk_len);

	    /* Chunk length specified in the file data does not include
	     * chunk type and length bytes so add 8 to chunk_len.
	     */
	    chunk_len += (MidiChunkTypeNameLen + sizeof(u_int32_t)); 

	    /* Allocate raw data buffer and read raw chunk data into it. */
	    buf_len = (int)chunk_len - 8;
	    if(buf_len > 0)
	    {
	        buf_ptr = (u_int8_t *)malloc(buf_len * sizeof(u_int8_t));
		if(buf_ptr != NULL)
		    fread(buf_ptr, sizeof(u_int8_t), buf_len, fp);
		else
		    break;
	    }
	    else
	    {
		buf_ptr = NULL;
		buf_len = 0;
	    }


	    /* Allocate a new chunk structure. */
	    i = md->total_chunks;
	    md->total_chunks++;
	    md->chunk = (MidiChunkStruct **)realloc(
		md->chunk,
		md->total_chunks * sizeof(MidiChunkStruct *)
	    );
	    if(md->chunk == NULL)
	    {
		md->total_chunks = 0;
		break;
	    }
	    md->chunk[i] = chunk = (MidiChunkStruct *)calloc(
		1, sizeof(MidiChunkStruct)
	    );
	    if(chunk == NULL)
	    {
		md->total_chunks -= 1;
		break;
	    }



	    /* Put fetched data into chunk structure. */

	    /* Chunk type. */
	    if(!strcmp(chunk_type_name, MidiChunkTypeNameHeader))
		chunk->type = MidiChunkTypeHeader;
	    else if(!strcmp(chunk_type_name, MidiChunkTypeNameTrack))
		chunk->type = MidiChunkTypeTrack;
	    else
		chunk->type = MidiChunkTypeUnknown;

	    /* Chunk length. */
	    chunk->len = chunk_len;

	    /* Raw data buffer and length of it (might be NULL). */
	    chunk->buf_len = buf_len;
	    chunk->buf = buf_ptr;


	    /* Parse by chunk type. */
	    /* Header chunk. */
	    if(chunk->type == MidiChunkTypeHeader)
	    {
		if(buf_len >= 6)
		{
		    /* Update the header information. */
		    md->format = (int16_t)(
			((u_int16_t)buf_ptr[0] << 8) +
			(u_int16_t)buf_ptr[1]
		    );

		    md->tracks = (int16_t)(
			((u_int16_t)buf_ptr[2] << 8) +
			(u_int16_t)buf_ptr[3]  
		    );

		    md->divisions = (int16_t)(
			((u_int16_t)buf_ptr[4] << 8) +
			(u_int16_t)buf_ptr[5]
		    );
		}
	    }
	    /* Track chunk. */
	    else if(chunk->type == MidiChunkTypeTrack)
	    {

/* TODO */

	    }


	    /* Increment file position. */
	    filepos += chunk_len;
	    if(fseek(fp, filepos, SEEK_SET) == -1)
		break;
	}


	/* Close file since we are returning MidiSuccess and the calling
	 * function will not referance the given fp again.
	 */
/* Note, later on if we are reading the midi file, we should record
 * the given fp on the md structure.
 */
	fclose(fp);

	return(MidiSuccess);
}

/*
 *	Deallocates all allocated substructures in md.
 */
void MidiDestroy(MidiDataStruct *md)
{
	int i, n;
	MidiChunkStruct *chunk;


	if(md == NULL)
	    return;

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


	/* Free each chunk. */
	for(i = 0; i < md->total_chunks; i++)
	{
	    chunk = md->chunk[i];
	    if(chunk == NULL)
		continue;

	    /* Free each event in the chunk. */
	    for(n = 0; n < chunk->total_events; n++)
		free(chunk->event[n]);
	    free(chunk->event);

	    /* Free raw data buffer. */
	    free(chunk->buf);

	    /* Free structure itself. */
	    free(chunk);
	}

	free(md->chunk);
	md->chunk = NULL;
	md->total_chunks = 0;
}
