//-------------------------------------------------------------------------
/*
Duke Nukem Copyright (C) 1996, 2003 3D Realms Entertainment

This file is part of Duke Nukem 3D version 1.5 - Atomic Edition

Duke Nukem 3D is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  FMOD AudioLib implementation by Jonathon Fowler (jonof@edgenetwk.com)
*/
//-------------------------------------------------------------------------

#include "fx_man_fmod.h"
#include "duke3d.h"

#include "fmod.h"

#define TRUE (1==1)
#define FALSE (1==0)

#define dprintOSD(...)

#ifdef WINDOWS
extern int hWindow;
#endif


void(*FX_CallBackFunc)(unsigned int) = NULL;
int FX_ErrorCode = FX_Ok;

#define FX_SetErrorCode( status ) \
   FX_ErrorCode = ( status );

FSOUND_SAMPLE * FX_Samples[MAXSOUNDS + 11];	// 11 remote ridicules

int FX_NumVoices = 0;
static char chtoggle=0;
static char *chstates=NULL, *chstatesa, *chstatesb;
static int *chcallvals=NULL;

int FX_ReadVOCInfo(char *data, int size, int *samplerate, int *channels, int *samplesize, int *datalen);
int FX_ReadVOCData(char *data, char *buf, int bufferlen, char eightbit);

int FX_SimulateCallbacks(void);


/*---------------------------------------------------------------------
   Function: FX_ErrorString

   Returns a pointer to the error message associated with an error
   number.  A -1 returns a pointer the current error.
---------------------------------------------------------------------*/

char *FX_ErrorString(int ErrorNumber)
{
    char *ErrorString;

    switch (ErrorNumber)
    {
    case FX_Warning :
    case FX_Error :
        ErrorString = FX_ErrorString(FX_ErrorCode);
        break;

    case FX_Ok :
        ErrorString = "Fx ok.";
        break;

    case FX_ASSVersion :
        ErrorString = "Apogee Sound System Version WinMM  "
                      "Programmed by Jim Dose, Ported by Jonathon Fowler\n"
                      "(c) Copyright 1995 James R. Dose.  All Rights Reserved.\n";
        break;

    case FX_FMODInit :
        ErrorString = "Failed initializing FMOD.";
        break;

    default :
        ErrorString = "Unknown Fx error code.";
        break;
    }

    return(ErrorString);
}


/*---------------------------------------------------------------------
   Function: FX_Init

   Selects which sound device to use.
---------------------------------------------------------------------*/

static char *OutputType(int a)
{
    switch (a)
    {
    case FSOUND_OUTPUT_NOSOUND: return "no-sound";
    case FSOUND_OUTPUT_WINMM: return "WinMM";
    case FSOUND_OUTPUT_DSOUND: return "DirectSound";
    case FSOUND_OUTPUT_A3D: return "Aureal3D";
    case FSOUND_OUTPUT_OSS: return "OSS";
    case FSOUND_OUTPUT_ESD: return "ESD";
    case FSOUND_OUTPUT_ALSA: return "ALSA";
    case FSOUND_OUTPUT_ASIO: return "ASIO";
    case FSOUND_OUTPUT_XBOX: return "Xbox";
    case FSOUND_OUTPUT_PS2: return "Playstation2";
    case FSOUND_OUTPUT_MAC: return "Macintosh Sound Manager";
    default: return "unknown";
    }
}

int FX_Init(int SoundCard, int numvoices, int numchannels, int samplebits, unsigned mixrate)
{
    FSOUND_Close();

    memset(FX_Samples, 0, sizeof(FX_Samples));

#ifdef WINDOWS
    if (hWindow)
    {
        //FSOUND_SetHWND(&hWindow);
    }
#endif
    if (!FSOUND_Init(mixrate, numvoices, FSOUND_INIT_GLOBALFOCUS))
    {
        FX_SetErrorCode(FX_FMODInit);
        return FX_Error;
    }

    printOSD("FX_Init(): %d voices, %d channels, %dHz samplerate\n", numvoices,numchannels,FSOUND_GetOutputRate());
    printOSD("FX_Init(): FMOD is using the %s output driver\n", OutputType(FSOUND_GetOutput()));

    chtoggle=0;
    if (chstates) free(chstates);
    chstates = (char*)malloc(numvoices*2 + sizeof(int)*numvoices);
    memset(chstates,0,numvoices*2 + sizeof(int)*numvoices);

    chcallvals = (int*)(chstates + numvoices*2);
    chstatesa = chstates;
    chstatesb = chstates + numvoices;

    FX_NumVoices = numvoices;

    FX_SetErrorCode(FX_Ok);
    return FX_Ok;
}


/*---------------------------------------------------------------------
   Function: FX_Shutdown

   Terminates use of sound device.
---------------------------------------------------------------------*/

int FX_Shutdown(void)
{
    unsigned int curalloced, maxalloced;

    if (chstates)
    {
        FSOUND_GetMemoryStats(&curalloced, &maxalloced);
        printOSD("FX_Shutdown(): allocation stats - currently %d bytes, maximum %d bytes\n",curalloced,maxalloced);
    }

    FSOUND_Close();

    if (chstates) free(chstates);
    chstates=chstatesa=chstatesb=0;

    FX_SetErrorCode(FX_Ok);
    return FX_Ok;
}


/*---------------------------------------------------------------------
   Function: FX_SetCallback

   Sets the function to call when a voice is done.
---------------------------------------------------------------------*/

int FX_SetCallBack(void(*function)(unsigned int))
{
    FX_CallBackFunc = function;
    FX_SetErrorCode(FX_Ok);
    return FX_Ok;
}


/*---------------------------------------------------------------------
   Function: FX_SetVolume

   Sets the volume of the current sound device.
---------------------------------------------------------------------*/

void FX_SetVolume(int volume)
{
    FSOUND_SetSFXMasterVolume(volume);
}


/*---------------------------------------------------------------------
   Function: FX_SetReverseStereo

   Set the orientation of the left and right channels.
---------------------------------------------------------------------*/

void FX_SetReverseStereo(int setting)
{}


/*---------------------------------------------------------------------
   Function: FX_GetReverseStereo

   Returns the orientation of the left and right channels.
---------------------------------------------------------------------*/

int FX_GetReverseStereo(void)
{
    return 0;
}


/*---------------------------------------------------------------------
   Function: FX_SetReverb

   Sets the reverb level.
---------------------------------------------------------------------*/

void FX_SetReverb(int reverb)
{}


/*---------------------------------------------------------------------
   Function: FX_SetReverbDelay

   Sets the delay level of reverb to add to mix.
---------------------------------------------------------------------*/

void FX_SetReverbDelay(int delay)
{}


/*---------------------------------------------------------------------
   Function: FX_VoiceAvailable

   Checks if a voice can be play at the specified priority.
---------------------------------------------------------------------*/

int FX_VoiceAvailable(int priority)
{
    FX_SimulateCallbacks();
    return 1;
}


/*---------------------------------------------------------------------
   Function: FX_PlayLoopedVOC

   Begin playback of sound data with the given volume and priority.
   JBF: As a hack, since Duke3D passes the sound/sample number as the
   callbackval parameter, we can use this as an index into the
   FX_Samples array to access samples if they've already been loaded.
   RemoteRidicule sounds have negative callback values, so they
   take up residence at the end of FX_Samples.
---------------------------------------------------------------------*/

int FX_PlayLoopedVOC
(
    char *ptr,
    int loopstart,
    int loopend,
    int pitchoffset,
    int vol,
    int left,
    int right,
    int priority,
    unsigned int callbackval
)
{
    return FX_PlayLoopedSound(pitchoffset, vol, callbackval);
}


/*---------------------------------------------------------------------
   Function: FX_PlayWAV

   Begin playback of sound data with the given volume and priority.
---------------------------------------------------------------------*/

int FX_PlayLoopedWAV
(
    char *ptr,
    int loopstart,
    int loopend,
    int pitchoffset,
    int vol,
    int left,
    int right,
    int priority,
    unsigned int callbackval
)
{
    return FX_PlayLoopedSound(pitchoffset, vol, callbackval);
}


/*---------------------------------------------------------------------
   Function: FX_PlayVOC3D

   Begin playback of sound data at specified angle and distance
   from listener.
---------------------------------------------------------------------*/

int FX_PlayVOC3D
(
    char *ptr,
    int pitchoffset,
    int angle,
    int distance,
    int priority,
    unsigned int callbackval
)
{
    return FX_PlayPositionedSound(pitchoffset, angle, distance, callbackval);
}


/*---------------------------------------------------------------------
   Function: FX_PlayWAV3D

   Begin playback of sound data at specified angle and distance
   from listener.
---------------------------------------------------------------------*/

int FX_PlayWAV3D
(
    char *ptr,
    int pitchoffset,
    int angle,
    int distance,
    int priority,
    unsigned int callbackval
)
{
    return FX_PlayPositionedSound(pitchoffset, angle, distance, callbackval);
}


/*---------------------------------------------------------------------
   Function: FX_Pan3D

   Set the angle and distance from the listener of the voice associated
   with the specified handle.
---------------------------------------------------------------------*/

int FX_Pan3D
(
    int handle,
    int angle,
    int distance
)
{
    return FX_Ok;
}


/*---------------------------------------------------------------------
   Function: FX_StopSound

   Halts playback of a specific voice
---------------------------------------------------------------------*/

int FX_StopSound(int handle)
{
    FX_SimulateCallbacks();
    FSOUND_StopSound(handle);

    if (handle>=0 && handle<FX_NumVoices)
        chstatesa[handle] = chstatesb[handle] = 0;	// no callback for you!

    FX_SetErrorCode(FX_Ok);
    return FX_Ok;
}


/*---------------------------------------------------------------------
   Function: FX_StopAllSounds

   Halts playback of all sounds.
---------------------------------------------------------------------*/

int FX_StopAllSounds(void)
{
    FX_SimulateCallbacks();
    FSOUND_StopSound(FSOUND_ALL);

    memset(chstates, 0, FX_NumVoices*2);		// no callbacks for any of you!

    FX_SetErrorCode(FX_Ok);
    return FX_Ok;
}


/*---------------------------------------------------------------------
   Function: FX_Play*Sound

   Internal function to load a sound file and play it, returning
   the channel number it played on.
---------------------------------------------------------------------*/
int FX_PlayLoopedSound(
    int pitchoffset,
    int vol,
    unsigned int num
)
{
    int chan;

    FX_SimulateCallbacks();

    if (!FX_Samples[num]) return -1;

    chan = FSOUND_PlaySoundEx(FSOUND_FREE, FX_Samples[num], NULL, TRUE) & 4095;
    if (chan < 0) return -1;

    // channel was already playing
    if (chstatesa[chan] && FX_CallBackFunc) FX_CallBackFunc(chcallvals[chan]);

    // set pitch
    FSOUND_SetVolume(chan, vol);
    if (FSOUND_Sample_GetMode(FX_Samples[num]) & FSOUND_STEREO)
        FSOUND_SetPan(chan, FSOUND_STEREOPAN);
    else
        FSOUND_SetPan(chan, 128);
    FSOUND_SetLoopMode(chan, FSOUND_LOOP_NORMAL);

    chcallvals[chan] = num;
    FSOUND_SetPaused(chan, FALSE);

    dprintOSD("FX_PlayLoopedSound(): Play sound %d in channel %d\n",num,chan);

    return chan;
}


int FX_PlayPositionedSound(
    int pitchoffset,
    int angle,
    int distance,
    unsigned int num
)
{
    int chan;

    FX_SimulateCallbacks();

    if (!FX_Samples[num]) return -1;

    chan = FSOUND_PlaySoundEx(FSOUND_FREE, FX_Samples[num], NULL, TRUE) & 4095;
    if (chan < 0) return -1;

    // channel was already playing
    if (chstatesa[chan] && FX_CallBackFunc) FX_CallBackFunc(chcallvals[chan]);

    if (angle<0) angle = 255-angle;	// behind us

    // set pitch
    FSOUND_SetVolume(chan,255);
    FSOUND_SetPan(chan,128);
    FSOUND_SetLoopMode(chan, FSOUND_LOOP_OFF);

    chcallvals[chan] = num;
    FSOUND_SetPaused(chan, FALSE);

    dprintOSD("FX_PlayPositionedSound(): Play sound %d in channel %d\n",num,chan);

    return chan;
}


int FX_LoadSample(char *ptr, int size, unsigned int number, int priority)
{
    FSOUND_SAMPLE *samp = NULL;
    int samplerate=0, channels=0, samplesize=0;
    int flags=0;
    int datalen=0;
    void *ptr1,*ptr2;
    int ptr1len,ptr2len;

    if (!memcmp(ptr, "Creative Voice File", 0x13))
    {
        // VOC file
        if (FX_ReadVOCInfo(ptr,size,&samplerate,&channels,&samplesize,&datalen) == 0)
        {
            flags |= (channels==2)?FSOUND_STEREO:FSOUND_MONO;
            flags |= (samplesize==16)?FSOUND_16BITS:FSOUND_8BITS;
            flags |= FSOUND_SIGNED;
            samp = FSOUND_Sample_Alloc(number, (datalen >> (channels-1)) / (samplesize>>3), flags, samplerate, -1, -1, priority);
            if (samp)
            {
                if (FSOUND_Sample_Lock(samp,0,datalen,&ptr1,&ptr2,&ptr1len,&ptr2len))
                {
                    if (FX_ReadVOCData(ptr,ptr1,datalen,(samplesize==8))) ;
                    FSOUND_Sample_Unlock(samp,ptr1,ptr2,ptr1len,ptr2len);
                }
            }
        }
    }
    else
    {
        samp = FSOUND_Sample_Load(number, ptr, FSOUND_LOADMEMORY, size);
    }

    dprintOSD("FX_LoadSample(): loaded sound %d\n",number);
    if (samp) FSOUND_Sample_SetDefaults(samp, -1, -1, -1, priority);
    FX_Samples[number] = samp;

    return (samp != NULL);
}


int FX_SampleLoaded(unsigned int number)
{
    return (FX_Samples[number] != NULL);
}


#define REGRESSTC(tc) (256000000/(65536-(tc)))
#define REGRESSSR(sr) (1000000/(256-(sr)))

int FX_ReadVOCInfo(char *data, int size, int *samplerate, int *channels, int *samplesize, int *datalen)
{
    short version,version2;
    char blocktype=0;
    int  blocklen=0;
    char *ptr=data;

    if (memcmp(ptr, "Creative Voice File\x1A", 0x14)) return -1;
    ptr += 0x14;

    ptr += 2;
    version = ((short*)ptr)[0];
    version2 = ((short*)ptr)[1];

    if (~version + 0x1234 != version2) return -1;

    ptr += 4;

    while (1)
    {
        blocktype = *(ptr++);
        if ((ptr-data)>size) return -1;	// truncated

        if (blocktype == 0)
            break;

        blocklen = *(ptr++);
        blocklen |= *(ptr++) << 8;
        blocklen |= *(ptr++) << 16;

        switch (blocktype)
        {
        case 1: /* sound data begin block */
            if (!*samplerate)
                *samplerate = REGRESSSR(ptr[0]);
            if (ptr[1] != 0)
            {
                /* only 8-bit files please */
                return -1;
            }
            if (!*channels) *channels = 1;
            *samplesize = 8;
            *datalen += blocklen-2;
            ptr += blocklen;
            break;

        case 2: /* sound continue */
            *datalen += blocklen;
            ptr += blocklen;
            break;

#if 0
        case 3: /* silence */
            kread(fh, blockprop, 3);
            /*
            length = blockprop[0] | (blockprop[1] << 8)
            sample rate = REGRESSSR(blockprop[2]))
            */
            break;

        case 4:	/* marker */
            kread(fh, &blockprop, 2);
            /*
            id = blockprop[0] | (blockprop[1] << 8))
            */
            break;

        case 5: /* ASCII data */
            klseek(fh, blocklen, SEEK_CUR);
            /*
            asciiz string
            */
            break;

        case 6: /* repeat */
            kread(fh, blockprop, 2);
            /*
            num repetitions = (blockprop[0] | (blockprop[1] << 8)) - 1
            */
            break;

        case 7: /* end repeat */
            break;
#endif

        case 8: /* sound attribute extension block */
            *samplerate = REGRESSTC(ptr[0] | (ptr[1] << 8));
            *samplesize = 8;
            if (ptr[3] == 1)
            {
                *samplerate >>= 1;
                *channels = 2;
            }
            else
                *channels = 1;
            if (ptr[2] != 0)
            {
                /* only 8-bit files please */
                return -1;
            }
            ptr += 4;
            /* a block 1 follows */
            break;

        case 9: /* sound data format 3 */
            *samplerate = *((int*)(ptr));
            *samplesize = ptr[4];
            *channels   = ptr[5];
            if ((ptr[6] | (ptr[7] << 8)) != 0 &&
                    (ptr[6] | (ptr[7] << 8)) != 4)
            {
                /* only PCM please */
                return -1;
            }
            *datalen += blocklen-12;
            ptr += blocklen;
            break;

        default:
            ptr += blocklen;
            break;
        }
    }

    return 0;
}


int FX_ReadVOCData(char *data, char *buf, int bufferlen, char eightbit)
{
    short offset;
    char blocktype=0;
    int  blocklen=0, br;

    data += 0x14 + 2 + 4;

    while (bufferlen>0)
    {
        blocktype = *(data++);

        if (blocktype == 0)
            break;

        blocklen = *(data++);
        blocklen |= *(data++) << 8;
        blocklen |= *(data++) << 16;

        switch (blocktype)
        {
        case 1: /* sound data */
            data += 2;

            br = min(blocklen-2, bufferlen);
            goto convertdata;

        case 2: /* sound continue */
            br = min(blocklen, bufferlen);
            goto convertdata;

        case 9: /* sound data format 3 */
            data += 12;

            br = min(blocklen-12, bufferlen);
            goto convertdata;

        default:
            data += blocklen;
            continue;
        }
convertdata:
        bufferlen -= br;
        if (eightbit)
        {
            // FMOD wants signed data
            for (; br>0; br--)
                *(buf++) = (char)((short)(*(data++)) - 0x80);
        }
        else
        {
            memcpy(buf,data,br);
            buf += br;
            data += br;
        }
    }

    return 0;
}


int FX_SimulateCallbacks(void)
{
    int i;

    if (!FX_CallBackFunc || !chstates) return 0;

    chstatesa = chstates + (FX_NumVoices * chtoggle);
    chstatesb = chstates + (FX_NumVoices * (chtoggle^1));

    for (i=0;i<FX_NumVoices;i++)
    {
        chstatesa[i] = FSOUND_IsPlaying(i);
        if (chstatesa[i] == chstatesb[i]) continue;	// channel is still silent/playing
        if (chstatesa[i] > chstatesb[i]) continue;	// channel has begun playing

        // channel has ended playing
        FX_CallBackFunc(chcallvals[i]);
        dprintOSD("FX_SimulateCallbacks(): channel %d ended sound %d\n",i,chcallvals[i]);
    }

    chtoggle ^= 1;

    return 0;
}