#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
extern int errno;

#include "../include/string.h"
#include "../include/disk.h"
#include "../include/prochandle.h"

#include "midi.h"
#include "raw.h"
#include "wav.h"
#include "voc.h"

#include "midiiow.h"
#include "afw.h"
#include "options.h"


static void AFWCopyDataAlloc(
	const void *data,
	int len,
	SoundBuffer **prev_data,
	YDataLength *prev_len
);

int AFWOpen(const char *path, AFWDataStruct *af_data);
int AFWLoadSegment(
	AFWDataStruct *af_data,
	YDataPosition position,
	YDataLength length,
	Audio *audio
);
void AFWClose(AFWDataStruct *af_data, Audio *audio);


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


/*
 *	Coppies the data to the previous data pointer.
 *	Reallocating it as needed.
 *
 *	prev_data and prev_len are assumed valid.
 */
static void AFWCopyDataAlloc(
	const void *data,
	int len,
	SoundBuffer **prev_data,
	YDataLength *prev_len
)
{
	if((data == NULL) ||
	   (len < 1)
	)
	{
	    if((*prev_data) != NULL)
	    {
		free(*prev_data);
		(*prev_data) = NULL;
	    }
	    (*prev_len) = 0;

	    return;
	}
	else
	{
	    if((*prev_len) != len)
	    {
		(*prev_len) = len;

		(*prev_data) = (SoundBuffer *)realloc(
		    *prev_data,
		    (*prev_len) * sizeof(SoundBuffer)
		);
		if((*prev_data) == NULL)
		{
		    (*prev_len) = 0;
		    return;
		}
	    }

	    memcpy(
		*prev_data,	/* Destination. */
		data,		/* Source. */
		(*prev_len) * sizeof(SoundBuffer)
	    );
	}

	return;
}

/*
 *	Reads the header of an audio file specified by path
 *	and puts its parameters and type data in the buffer
 *	pointed at af_data.
 */
int AFWOpen(const char *path, AFWDataStruct *af_data)
{
	FILE *fp;
	raw_data_struct rd, *rd_ptr;
	wav_data_struct wd, *wd_ptr;
	voc_data_struct vd, *vd_ptr;
	MidiDataStruct md, *md_ptr;
	struct stat stat_buf;


	if((path == NULL) || (af_data == NULL))
	    return(-1);

	/* Reset afw data structure. */
	memset(af_data, 0x00, sizeof(AFWDataStruct));

	/* Open file. */
	fp = fopen(path, "rb");
	if(fp == NULL)
	    return(-1);

	/* Get file statistics. */
	if(fstat(fileno(fp), &stat_buf))
	    return(-1);

	/* The object cannot be a directory. */
	if(S_ISDIR(stat_buf.st_mode))
	    return(-1);

	/* Try passing the opened fp to each of the library read
	 * header functions. If any of the header functions return
	 * success then that is the right one to use because the
	 * file types were detected to match.
	 *
	 * Also on success we should not referance our fp again.
	 */
	/* Is it a WAV file? */
	if(WavReadHeader(path, fp, &wd) == WavSuccess)
	{
	    fp = NULL;		/* Do not referance the fp again. */

	    /* Load as a WAV file. */
	    af_data->ptr = calloc(1, sizeof(wav_data_struct));
	    if(af_data->ptr == NULL)
		return(-1);
	    else
		wd_ptr = (wav_data_struct *)af_data->ptr;

	    memcpy(wd_ptr, &wd, sizeof(wav_data_struct));

	    /* Copy other data from library specific structure to
	     * our afw data structure.
	     */
	    af_data->file_format = AFWFileFormatWav;
	    af_data->sndobj_format = SndObjTypeDSP;
	    af_data->entire_length = wd.data_chunk_size;
	    af_data->buffer = NULL;
	    af_data->length = 0;

	    af_data->block_align = wd.block_align;
	    af_data->block_length = ((wd.bits_per_sample == 16) ?
		2 : 1
	    );

	    af_data->sample_size = wd.bits_per_sample;
	    af_data->channels = wd.channels;
	    af_data->sample_rate = wd.samples_per_sec;
	    af_data->bytes_per_sec = wd.bytes_per_sec;
	}
	/* Is it a VOC file? */
	else if(VocReadHeader(path, fp, &vd) == VocSuccess)
	{
	    int i;


	    fp = NULL;		/* Do not referance the fp again. */

	    /* Load as a VOC file. */
	    af_data->ptr = calloc(1, sizeof(voc_data_struct));
	    if(af_data->ptr == NULL)
		return(-1);
	    else
		vd_ptr = (voc_data_struct *)af_data->ptr;

	    memcpy(vd_ptr, &vd, sizeof(voc_data_struct));

	    /* Copy other data from library specific structure to
	     * our afw data structure.
	     */
	    af_data->file_format = AFWFileFormatVoc;
	    af_data->sndobj_format = SndObjTypeDSP;
	    af_data->entire_length = vd.total_data_len;
	    af_data->buffer = NULL;
	    af_data->length = 0;

	    /* Get block align and length. */
	    af_data->block_align = 0;
	    af_data->block_length = 1;
	    for(i = 0; i < vd.total_datablocks; i++)
	    {
		if(vd.datablock[i] == NULL)
		    continue;
		if(vd.datablock[i]->type != VocBlockTypeSoundData)
		    continue;

		af_data->block_align = 0;
		af_data->block_length =
		    ((vd.datablock[i]->bits == 16) ? 2 : 1);

		af_data->sample_size = 8;
		af_data->channels = 1;
		af_data->sample_rate =
		    vd.datablock[i]->sample_rate;
		af_data->bytes_per_sec = af_data->sample_rate;
	    }
	}
	/* Is it a MIDI file? */
	else if(MidiRead(path, fp, &md) == MidiSuccess)
	{
	    fp = NULL;          /* Do not referance the fp again. */

	    /* Load as a MIDI file. */
	    af_data->ptr = calloc(1, sizeof(MidiDataStruct));
	    if(af_data->ptr == NULL)
		return(-1);
	    else
		md_ptr = (MidiDataStruct *)af_data->ptr;

	    memcpy(md_ptr, &md, sizeof(MidiDataStruct));

	    /* Copy other data from library specific structure to   
	     * our afw data structure.
	     */
	    af_data->file_format = AFWFileFormatMidi;
	    af_data->sndobj_format = SndObjTypeMIDI;
	    af_data->entire_length = 0;
	    af_data->buffer = NULL;
	    af_data->length = 0;

	    af_data->block_align = 0;
	    af_data->block_length = 0;

	    af_data->sample_size = 0;
	    af_data->channels = 0;
	    af_data->sample_rate = 0;
	    af_data->bytes_per_sec = 0;
	}
	/* Is it a RAW file? (This needs to be checked last). */
	else if(RawReadHeader(path, fp, &rd) == RawSuccess)
	{
	    fp = NULL;		/* Do not referance the fp again. */

	    /* Load as a RAW file. */
	    af_data->ptr = calloc(1, sizeof(raw_data_struct));
	    if(af_data->ptr == NULL)
		return(-1);
	    else
		rd_ptr = (raw_data_struct *)af_data->ptr;

	    memcpy(rd_ptr, &rd, sizeof(raw_data_struct));

	    /* Copy other data from library specific structure to
	     * our afw data structure.
	     */
	    af_data->file_format = AFWFileFormatRaw;
	    af_data->sndobj_format = SndObjTypeDSP;
	    af_data->entire_length = rd.data_length;
	    af_data->buffer = NULL;
	    af_data->length = 0;

	    af_data->block_align = rd.block_align;
	    af_data->block_length = (rd.sample_size == 16) ?
		2 : 1;

	    af_data->sample_size = rd.sample_size;
	    af_data->channels = rd.channels;
	    af_data->sample_rate = rd.sample_rate;
	    af_data->bytes_per_sec = rd.sample_rate *
		((rd.sample_size == 16) ? 2 : 1);
	}

	/* If fp is not NULL, then that implies it did not match
	 * any file type supported.
	 */
	if(fp != NULL)
	{
	    fclose(fp);
	    return(-1);
	}
	else
	{
	    return(0);
	}
}


/*
 *	If af_data has a sndobj_format of SndObjTypeDSP, then this
 *	function:
 *
 *	Frees buffer on af_data (if allocated), reads a segment of
 *	data from the DSP sound object on file, and then allocates
 *	a new buffer and updates values on af_data. Returns 0 on success
 *	or -1 on error.
 *
 *
 *	If af_data has a sndobj_format of SndObjTypeMIDI, then this
 *	function:
 *
 *	Checks if the MIDI sound object is being played; If the
 *	sound object is not being played then it forks off a
 *	process to control the MIDI device to play the MIDI sound
 *	object on file, returning:
 *		0   on success
 *		-1  on error
 *		-2  another play process started thus stopping this one
 *		-3  finished play
 *	If the MIDI sound object is last known to be playing (marked
 *	by a previous call to this function), then it checks if the
 *	child process playing the MIDI sound object is still playing
 *	it, if it has stopped playing then -3 is returned or if it
 *	is still playing then 0 is returned.
 */
int AFWLoadSegment(
	AFWDataStruct *af_data,
	YDataPosition position,
	YDataLength length,
	Audio *audio
)
{
	int status;
	pid_t pid;
	raw_data_struct *raw_data;
	wav_data_struct *wav_data;
	voc_data_struct *voc_data;
	MidiDataStruct *midi_data;

	MIDIAudio *midi_audio_ptr = NULL;


	if(af_data == NULL)
	    return(-1);
	if(af_data->ptr == NULL)
	    return(-1);

	/* Check sound object format type. */
	if(af_data->sndobj_format == SndObjTypeMIDI)
	{
	    /* It is a MIDI Sound Object. */

	    status = 0;

	    /* Get pointer to MIDI Audio. */
	    if(audio == NULL)
	    {
		status = -1;
		return(status);
	    }
	    else
	    {
		midi_audio_ptr = &audio->midi_audio;
	    }

	    /* Handle by MIDI file format. */
	    switch(af_data->file_format)
	    {
	      case AFWFileFormatMidi:
	        midi_data = (MidiDataStruct *)af_data->ptr;

		/* Get pid of actual current MIDI play (if any). */
		pid = MIDIIOWIsPlaying(midi_audio_ptr);

		/* Was this MIDI being played? */
		if(af_data->pid > 0)
		{
		    /* MIDI play is _expected_ to be currently active. */

		    /* Is a MIDI currently being played? */
		    if(pid > 0)
		    {
			/*   MIDI play is active but is it the same play
			 *   as this MIDI object?
			 */
		        if(pid != af_data->pid)
			{
			    /* No it is not the same play, so mark this
			     * as stopped.
			     */
			    status = -2;
			    af_data->pid = 0;
			}
		    }
		    else
		    {
			/* Midi play process has finished. */
			status = -3;
			af_data->pid = 0;
		    }
		}
		else
		{
		    /* This MIDI was not being played, so we are going to
		     * start playing this MIDI.
		     */

		    /* Is a MIDI currently being played? */
		    if(pid > 0)
		    {
			/* An MIDI is still being played, stop it. */
			MIDIIOWStop(midi_audio_ptr);

			pid = 0;
		    }

		    /* Begin new midi play. */
		    if(midi_data->filename != NULL)
		    {
			pid = MIDIIOWPlay(
			    midi_audio_ptr,
			    midi_data->filename
			);
			if(pid == 0)
			    status = -1;

			/* Record new pid. */
			af_data->pid = pid;
		    }
		}
		break;

	      default:
		status = -1;
		break;
	    }

	    return(status);
	}
	else if(af_data->sndobj_format == SndObjTypeDSP)
	{
	    /* It is a DSP Sound Object. */

	    /* Do not free the buffer on our af_data, it is shared and
	     * will be free'ed by the call to the library function.
	     */
	    af_data->length = 0;

	    /* Load audio segment by the sound object file format. */
	    switch(af_data->file_format)
	    {
	      case AFWFileFormatRaw:
		raw_data = (raw_data_struct *)af_data->ptr;
		status = RawReadPartialData(
		    raw_data,
		    position,   
		    length,
		    RawReadSigned8
		);
		if(status == RawSuccess)
		{
		    AFWCopyDataAlloc(
			raw_data->data,
			raw_data->data_len,
			&af_data->buffer,
			&af_data->length
		    );
		}
		else
		{
		    af_data->buffer = NULL;
		}
		break;

	      case AFWFileFormatWav:
	        wav_data = (wav_data_struct *)af_data->ptr;
	        status = WavReadPartialData(
		    wav_data,
		    position,		/* Position. */
		    length,		/* How much to load. */
		    WavReadSigned8
	        );
	        if(status == WavSuccess)
	        {
		    AFWCopyDataAlloc(
			wav_data->data,
			wav_data->data_len,
			&af_data->buffer,
			&af_data->length
		    );
	        }
	        else
		{
		    af_data->buffer = NULL;
		}
		break;

	      case AFWFileFormatVoc:
		voc_data = (voc_data_struct *)af_data->ptr;
	        status = VocReadPartialData(
		    voc_data,
		    position,
		    length,
		    VocReadSigned8
	        );
		if(status == VocSuccess)
		{
		    AFWCopyDataAlloc(
			voc_data->data,
			voc_data->data_len,
			&af_data->buffer,
			&af_data->length
		    );
		}
		else
		{
		    af_data->buffer = NULL;
		}
		break;

	      case AFWFileFormatMP3:
		return(-1);
		break;
 
	      default:
	        return(-1);
		break;
	    }
	}
	else
	{
	    /* Unsupported sound object type. */
	    return(-1);
	}


	return(0);
}

/*
 *	Closes the audio file, deallocating any allocated resources.
 *
 *	If the object was a MIDI, then the MIDI play process will
 *	be stopped.
 */
void AFWClose(AFWDataStruct *af_data, Audio *audio)
{
	pid_t pid;
	MIDIAudio *midi_audio_ptr;


	if(af_data == NULL)
	    return;

	/* Deallocate library resources and memory. */
	switch(af_data->file_format)
	{
	  case AFWFileFormatRaw:
	    RawDestroyData((raw_data_struct *)af_data->ptr);
	    break;

	  case AFWFileFormatWav:
	    WavDestroyData((wav_data_struct *)af_data->ptr);
	    break;

	  case AFWFileFormatVoc:
	    VocDestroyData((voc_data_struct *)af_data->ptr);
	    break;

	  case AFWFileFormatMP3:
	    break;

	  case AFWFileFormatMidi:
	    /* Get pointer to MIDI Audio. */
	    if(audio != NULL)
	    {
		midi_audio_ptr = &audio->midi_audio;

		/* Stop MIDI play if it is playing this. */
		pid = MIDIIOWIsPlaying(midi_audio_ptr);

		if((af_data->pid == pid) &&
		   (af_data->pid > 0)
		)
		{
		    /* MIDI play is still playing this MIDI, stop it. */
		    MIDIIOWStop(midi_audio_ptr);
		}
	    }

	    /* Reset pid. */
	    af_data->pid = 0;

	    MidiDestroy((MidiDataStruct *)af_data->ptr);
	    break;

	  default:
	    break;
	}

	/* Free library structure (library does not free this). */
	free(af_data->ptr);
	af_data->ptr = NULL;

	/* Free loaded DSP data, since it's a duplicate of the data
	 * loaded by the library.
	 */
	free(af_data->buffer);
	af_data->buffer = NULL;
	af_data->length = 0;

	af_data->block_align = 0;
	af_data->block_length = 1;

	af_data->pid = 0;


	return;
}
