#include "../include/zf.h"
#include <fmod.h>
/* #include <fmod_errors.h> */

struct ZfAudioMod
{
    FMUSIC_MODULE* fmusic_module;
};

struct ZfAudioSample
{
    FSOUND_SAMPLE* fsound_sample;
    int            channel;
};

struct ZfAudioStream
{
    FSOUND_STREAM* fsound_stream;
    bool paused;
    unsigned int position; //not updated frequently, only used for pausing stream
};

static bool audio_enabled = true;

static float* empty_spectrum = 0;

void
zf_audio_init(void)
{
    unsigned int i;
    /*
      if audio is disabled, we must create a spectrum full of zero values
    */
    if (!audio_enabled)
    {
	if (!empty_spectrum)
	{
	    empty_spectrum = (float*) calloc (256, sizeof(float));
	}
	return;
    }

    /* check version */
    if (FSOUND_GetVersion() < FMOD_VERSION)
    {
        printf("Error : You are using the wrong version!"
	       "You should be using FMOD %.02f\n", FMOD_VERSION);
        exit(1);
    }
    
    /* use oss */
    /*FSOUND_SetOutput(FSOUND_OUTPUT_OSS);*/
    /* FSOUND_SetOutput(FSOUND_OUTPUT_ESD); */
    FSOUND_SetOutput(FSOUND_OUTPUT_ALSA);
    
    /* print driver names */
#if 0
    printf("%s() : available drivers.\n", __FUNCTION__);    
    for (i = 0 ; i < FSOUND_GetNumDrivers() ; i++) 
    {
        printf("%d - %s\n", i, FSOUND_GetDriverName(i));
    }
#endif    

    /* use default */
    FSOUND_SetDriver(0);

    /* init fmod */
    //   printf("%s(): attempting sound init\n", __FUNCTION__);

    if (FSOUND_Init(44100, 64, 0))
    {
      //printf("%s() : initialised.\n", __FUNCTION__);
    }
    else
    {
      /*
        printf("%s() : ERROR : %s\n",
	       __FUNCTION__, FMOD_ErrorString(FSOUND_GetError()));
      */
        exit(1);
    }

    /* set the stream buffer size */
    FSOUND_Stream_SetBufferSize(500);

    /* turn on the fft unit */
    FSOUND_DSP_SetActive(FSOUND_DSP_GetFFTUnit(), '1');
}

void
zf_audio_print(void)
{
    if (!audio_enabled)
    {
	return;
    }

    /* output method */
    printf("FSOUND Output Method : ");
    switch (FSOUND_GetOutput())
    {
    case FSOUND_OUTPUT_NOSOUND: printf("FSOUND_OUTPUT_NOSOUND\n"); break;
    case FSOUND_OUTPUT_WINMM:   printf("FSOUND_OUTPUT_WINMM\n");   break;
    case FSOUND_OUTPUT_DSOUND:  printf("FSOUND_OUTPUT_DSOUND\n");  break;
    case FSOUND_OUTPUT_ASIO:    printf("FSOUND_OUTPUT_ASIO\n");    break;
    case FSOUND_OUTPUT_OSS:     printf("FSOUND_OUTPUT_OSS\n");     break;
    case FSOUND_OUTPUT_ESD:     printf("FSOUND_OUTPUT_ESD\n");     break;
    case FSOUND_OUTPUT_ALSA:    printf("FSOUND_OUTPUT_ALSA\n");    break;
    };
    
    /* mixer */
    printf("FSOUND Mixer         : ");
    switch (FSOUND_GetMixer())
    {
    case FSOUND_MIXER_BLENDMODE:      printf("FSOUND_MIXER_BLENDMODE\n");     break;
    case FSOUND_MIXER_MMXP5:          printf("FSOUND_MIXER_MMXP5\n");         break;
    case FSOUND_MIXER_MMXP6:          printf("FSOUND_MIXER_MMXP6\n");         break;
    case FSOUND_MIXER_QUALITY_FPU:    printf("FSOUND_MIXER_QUALITY_FPU\n");   break;
    case FSOUND_MIXER_QUALITY_MMXP5:  printf("FSOUND_MIXER_QUALITY_MMXP5\n"); break;
    case FSOUND_MIXER_QUALITY_MMXP6:  printf("FSOUND_MIXER_QUALITY_MMXP6\n"); break;
    };
    
    /* driver */
    printf("FSOUND Driver        : %s\n", FSOUND_GetDriverName(FSOUND_GetDriver()));
}

float*
zf_audio_get_spectrum(void)
{
    if (!audio_enabled)
    {
	return empty_spectrum;
    }

    return FSOUND_DSP_GetSpectrum();
}

/*!
  \todo get rid of hack magic number
*/
float
zf_audio_get_spectrum_average(void)
{
    float* spectrum;
    unsigned int i;
    float sum;
    
    if (!audio_enabled)
    {
	return;
    }

    spectrum = FSOUND_DSP_GetSpectrum();
    
    sum = 0.0f;

    for (i = 0 ; i < 256 ; i++)
	sum += spectrum[i];
    
    return sum / 256.0f;
}

void
zf_audio_close(void)
{

    if (!audio_enabled)
    {
	return;
    }

    FSOUND_Close();
    //printf("%s() : closed\n", __FUNCTION__);
}

ZfAudioMod*
zf_audio_mod_new(void)
{
    ZfAudioMod* mod;

    if (!audio_enabled)
    {
	return;
    }

    mod = (ZfAudioMod*) malloc (sizeof(ZfAudioMod));

    mod->fmusic_module = 0;

    return mod;
}

/*!
  \todo Should keep track of all the songs it has loaded so that if zf_audio_close() is called, it can free the songs 
 */
ZfAudioMod*
zf_audio_mod_load(const char* filename)
{
    ZfAudioMod* mod;
    
    if (!audio_enabled)
    {
	return;
    }

    mod = zf_audio_mod_new();
    
    mod->fmusic_module = FMUSIC_LoadSong(filename);
    if (mod->fmusic_module)
    {
      //printf("zf audio : mod load : %s \n", filename);
    }
    else
    {
        /*
	  printf("%s\n",
	  FMOD_ErrorString(FSOUND_GetError()));
	*/
        exit(1);
    }
    
    return mod;
}

void
zf_audio_mod_play(ZfAudioMod* mod)
{
    if (!audio_enabled)
    {
	return;
    }

    FMUSIC_PlaySong(mod->fmusic_module);
}

void
zf_audio_mod_set_looping(ZfAudioMod* mod, bool looping)
{
    if (!audio_enabled)
    {
	return;
    }

    if (looping)
	FMUSIC_SetLooping(mod->fmusic_module, '1');
    else
	FMUSIC_SetLooping(mod->fmusic_module, '0');
}

void
zf_audio_mod_set_speed(ZfAudioMod* mod, float speed)
{
    if (!audio_enabled)
    {
	return;
    }

    FMUSIC_SetMasterSpeed(mod->fmusic_module, speed);    
}

void
zf_audio_mod_print(ZfAudioMod* mod)
{
    unsigned int   i;
    FSOUND_SAMPLE* sample;
    
    if (!audio_enabled)
    {
	return;
    }

    /* name and bpm */
    printf("Name : %s, BPM  : %d\n", FMUSIC_GetName(mod->fmusic_module), FMUSIC_GetBPM(mod->fmusic_module));

    /* channels etc */
    printf("Channels : %d, Instruments : %d, Orders : %d, Patterns : %d, Samples : %d\n", FMUSIC_GetNumChannels(mod->fmusic_module), FMUSIC_GetNumInstruments(mod->fmusic_module), FMUSIC_GetNumOrders(mod->fmusic_module), FMUSIC_GetNumPatterns(mod->fmusic_module), FMUSIC_GetNumSamples(mod->fmusic_module));

    /* samples names */
    for(i = 0 ; i < FMUSIC_GetNumSamples(mod->fmusic_module) ; i++)
    {
	sample = FMUSIC_GetSample(mod->fmusic_module, i);
	printf("sample %u - %s\n", i, FSOUND_Sample_GetName(sample));
    } 
    
    /* volume */
    printf("Global Volume : %d, Master Volume : %d\n", FMUSIC_GetGlobalVolume(mod->fmusic_module), FMUSIC_GetMasterVolume(mod->fmusic_module));
    printf("Speed : %d\n", FMUSIC_GetSpeed(mod->fmusic_module));
    printf("Time since start : %d\n", FMUSIC_GetTime(mod->fmusic_module));
    printf("Type : %d\n", FMUSIC_GetType(mod->fmusic_module));    
}

void
zf_audio_mod_destroy(ZfAudioMod* mod)
{
    if (!audio_enabled)
    {
	return;
    }

    FMUSIC_FreeSong((FMUSIC_MODULE*) mod->fmusic_module); 

    free(mod);
}

ZfAudioSample*
zf_audio_sample_new(void)
{
    ZfAudioSample* sample;

    if (!audio_enabled)
    {
	return;
    }

    sample = (ZfAudioSample*) malloc (sizeof(ZfAudioSample));
    
    sample->fsound_sample = 0;

    return sample;
}

ZfAudioSample*
zf_audio_sample_load(const char* filename)
{
    ZfAudioSample* sample;
    
    if (!audio_enabled)
    {
	return;
    }

    sample = zf_audio_sample_new();
    
    /* setup the ZfAudioSample */
    sample->fsound_sample = FSOUND_Sample_Load(FSOUND_FREE, filename, FSOUND_NORMAL, 0, 0);

    if (sample->fsound_sample)
    {
      //printf("%s(%s) : loaded.\n", __FUNCTION__, filename);
    }
    else
    {
	/*
	printf("%s(%s) : Error : %s\n",
	       __FUNCTION__,
	       filename,
	       FMOD_ErrorString(FSOUND_GetError()));
	*/
    }
    
    return sample;
}

void
zf_audio_sample_play(ZfAudioSample* sample)
{
    if (!audio_enabled)
    {
	return;
    }

    //printf("%s(): PLAY\n", __FUNCTION__);

    sample->channel = FSOUND_PlaySound(FSOUND_FREE, sample->fsound_sample);
}

void
zf_audio_sample_stop(ZfAudioSample* sample)
{
    if (!audio_enabled)
    {
	return;
    }
    //printf("%s(): STOP\n", __FUNCTION__);
    FSOUND_StopSound(sample->channel);
}

void
zf_audio_sample_set_looping(ZfAudioSample* sample, bool looping)
{
    if (!audio_enabled)
    {
	return;
    }

    if (looping)
    {
	FSOUND_Sample_SetMode(sample->fsound_sample, FSOUND_LOOP_NORMAL);
    }
    else
    {
	FSOUND_Sample_SetMode(sample->fsound_sample, FSOUND_LOOP_OFF);
    }
}

/*!
  \brief Sets volume of a given sample

  \param sample The sample whose volume will be set

  \param volume The volume to set (0 = no volume, 255 = max volume)
*/
void
zf_audio_sample_set_volume(ZfAudioSample* sample, int volume)
{
    if (!audio_enabled)
    {
	return;
    }

    FSOUND_SetVolume(sample->channel, volume);
}

void
zf_audio_sample_destroy(ZfAudioSample* sample)
{
    if (!audio_enabled)
    {
	return;
    }

    FSOUND_Sample_Free(sample->fsound_sample);

    free(sample);
}

FSOUND_SAMPLE*
zf_audio_sample_get_sample(ZfAudioSample* sample)
{
    if (!audio_enabled)
    {
	return;
    }

    return sample->fsound_sample;
}

ZfAudioStream*
zf_audio_stream_new(void)
{
    ZfAudioStream* stream;

    if (!audio_enabled)
    {
	return;
    }

    stream = (ZfAudioStream*) malloc (sizeof(ZfAudioStream));
    
    stream->fsound_stream = 0;

    stream->paused = false;

    return stream;
}

ZfAudioStream*
zf_audio_stream_load(const char* filename)
{
    FILE*          in_file;
    int            length;
    char*          data;
    ZfAudioStream* stream;
    
    if (!audio_enabled)
    {
	return;
    }

    stream = zf_audio_stream_new();
    
    /* open file and check open */

    in_file = fopen(filename, "rb");
    if (in_file)
    {
      //	printf("%s() : loaded stream %s\n", __FUNCTION__, filename);
    }
    else
    {
	printf("zf audio : ERROR : can't open sample %s\n", filename);
	return 0;
    }

    /* get length of file */
    fseek(in_file, 0, SEEK_END);
    length = ftell(in_file);
    fseek(in_file, 0, SEEK_SET);
    
    /* malloc a buffer for sound data and read into buffer */
    data = (char *)malloc(length);
    fread(data, length, 1, in_file);

    /* close the file */
    fclose(in_file);
    
    /* setup the ZfAudioStream */
    stream->fsound_stream = FSOUND_Stream_Open(data, FSOUND_NORMAL | FSOUND_MPEGACCURATE | FSOUND_LOADMEMORY | FSOUND_LOOP_NORMAL, 0, length);

    return stream;
}

void
zf_audio_stream_play(ZfAudioStream* stream)
{
    if (!audio_enabled)
    {
	return;
    }

    //printf("%s() : PLAY\n", __FUNCTION__);

    FSOUND_Stream_Play(FSOUND_FREE, stream->fsound_stream);

    //zf_audio_stream_print(stream);
}

void
zf_audio_stream_stop(ZfAudioStream* stream)
{
    if (!audio_enabled)
    {
	return;
    }

    //printf("%s() : STOP\n", __FUNCTION__);

    FSOUND_Stream_Stop(stream->fsound_stream);

    //zf_audio_stream_print(stream);
}

void
zf_audio_stream_pause(ZfAudioStream* stream)
{
    if (!audio_enabled)
    {
	return;
    }

    // printf("%s() : PAUSE\n", __FUNCTION__);

    if (stream->paused)
    {
	FSOUND_Stream_SetPosition(stream->fsound_stream, stream->position);
	FSOUND_Stream_Play(FSOUND_FREE, stream->fsound_stream);       
    }
    else
    {
	stream->position = FSOUND_Stream_GetPosition(stream->fsound_stream) - 10000; //fudge
	/*printf("%d\n", stream->position);*/
	FSOUND_Stream_Stop(stream->fsound_stream);
    }

    stream->paused = !stream->paused;

    //zf_audio_stream_print(stream);
}

ZfAudioStream*
zf_audio_stream_read(FILE* file)
{
    ZfAudioStream* stream;
    char           filename[80];

    if (!audio_enabled)
    {
	return;
    }

    fscanf(file, "ZfAudioStream\n{\n");
    
    fscanf(file, "filename = %s\n", filename);

    fscanf(file, "}\n");
    
    stream = zf_audio_stream_load(filename);
    
    return stream;
}

void
zf_audio_stream_set_loop_count(ZfAudioStream* stream, int count)
{
    if (!audio_enabled)
    {
	return;
    }

    FSOUND_Stream_SetLoopCount(stream->fsound_stream, count);
}

void
zf_audio_stream_print(ZfAudioStream* stream)
{
    if (!audio_enabled)
    {
	return;
    }

    printf("pos %6d/%6d time %02d:%02d/%02d:%02d\n", FSOUND_Stream_GetPosition(stream->fsound_stream), 
	   FSOUND_Stream_GetLength(stream->fsound_stream), 
	   FSOUND_Stream_GetTime(stream->fsound_stream) / 1000 / 60, 
	   FSOUND_Stream_GetTime(stream->fsound_stream) / 1000, 
	   FSOUND_Stream_GetLengthMs(stream->fsound_stream) / 1000 / 60, 
	   FSOUND_Stream_GetLengthMs(stream->fsound_stream) / 1000 % 60);

    printf("has %d sync points\n", FSOUND_Stream_GetNumSyncPoints(stream->fsound_stream));
}

void
zf_audio_stream_destroy(ZfAudioStream* stream)
{
    if (!audio_enabled)
    {
	return;
    }

    FSOUND_Stream_Close(stream->fsound_stream);
}

void
zf_audio_disable(void)
{
    audio_enabled = false;
}
