//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
//
// This program 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.
//
// DESCRIPTION:  none
//

#include <stdio.h>
#include <stdlib.h>

#ifdef ORIGCODE
#include "SDL_mixer.h"
#endif

#include "config.h"
#include "doomfeatures.h"
#include "doomtype.h"

#ifdef ORIGCODE
#include "gusconf.h"
#endif
#include "i_sound.h"
#include "i_video.h"
#include "m_argv.h"
#include "m_config.h"

// Sound sample rate to use for digital output (Hz)

int snd_samplerate = 44100;

// Maximum number of bytes to dedicate to allocated sound effects.
// (Default: 64MB)

int snd_cachesize = 64 * 1024 * 1024;

// Config variable that controls the sound buffer size.
// We default to 28ms (1000 / 35fps = 1 buffer per tic).

int snd_maxslicetime_ms = 28;

// External command to invoke to play back music.

char* snd_musiccmd = "";

// Low-level sound and music modules we are using

static sound_module_t* sound_module;
static music_module_t* music_module;

int snd_musicdevice = SNDDEVICE_SB;
int snd_sfxdevice = SNDDEVICE_SB;

// Sound modules

extern void I_InitTimidityConfig(void);
extern sound_module_t sound_sdl_module;
extern sound_module_t sound_pcsound_module;
extern music_module_t music_sdl_module;
extern music_module_t music_opl_module;

// For OPL module:

extern int opl_io_port;

// For native music module:

extern char* timidity_cfg_path;

// DOS-specific options: These are unused but should be maintained
// so that the config file can be shared between chocolate
// doom and doom.exe

#if ORIGCODE
static int snd_sbport = 0;
static int snd_sbirq = 0;
static int snd_sbdma = 0;
static int snd_mport = 0;
#endif

// Compiled-in sound modules:

static sound_module_t* sound_modules[] = {
#ifdef FEATURE_SOUND
    &sound_sdl_module,
    &sound_pcsound_module,
#endif
    NULL,
};

// Compiled-in music modules:

static music_module_t* music_modules[] = {
#ifdef FEATURE_SOUND
    &music_sdl_module,
    &music_opl_module,
#endif
    NULL,
};

// Check if a sound device is in the given list of devices

static boolean SndDeviceInList(snddevice_t device, snddevice_t* list, int len) {
    int i;

    for (i = 0; i < len; ++i) {
        if (device == list[i]) {
            return true;
        }
    }

    return false;
}

// Find and initialize a sound_module_t appropriate for the setting
// in snd_sfxdevice.

static void InitSfxModule(boolean use_sfx_prefix) {
    int i;

    sound_module = NULL;

    for (i = 0; sound_modules[i] != NULL; ++i) {
        // Is the sfx device in the list of devices supported by
        // this module?

        if (SndDeviceInList(snd_sfxdevice, sound_modules[i]->sound_devices, sound_modules[i]->num_sound_devices)) {
            // Initialize the module

            if (sound_modules[i]->Init(use_sfx_prefix)) {
                sound_module = sound_modules[i];
                return;
            }
        }
    }
}

// Initialize music according to snd_musicdevice.

static void InitMusicModule(void) {
    int i;

    music_module = NULL;

    for (i = 0; music_modules[i] != NULL; ++i) {
        // Is the music device in the list of devices supported
        // by this module?

        if (SndDeviceInList(snd_musicdevice, music_modules[i]->sound_devices, music_modules[i]->num_sound_devices)) {
            // Initialize the module

            if (music_modules[i]->Init()) {
                music_module = music_modules[i];
                return;
            }
        }
    }
}

//
// Initializes sound stuff, including volume
// Sets channels, SFX and music volume,
//  allocates channel buffer, sets S_sfx lookup.
//

void I_InitSound(boolean use_sfx_prefix) {
    boolean nosound, nosfx, nomusic;

    //!
    // @vanilla
    //
    // Disable all sound output.
    //

    nosound = M_CheckParm("-nosound") > 0;

    //!
    // @vanilla
    //
    // Disable sound effects.
    //

    nosfx = M_CheckParm("-nosfx") > 0;

    //!
    // @vanilla
    //
    // Disable music.
    //

    nomusic = M_CheckParm("-nomusic") > 0;

    // Initialize the sound and music subsystems.

    if (!nosound && !screensaver_mode) {
        // This is kind of a hack. If native MIDI is enabled, set up
        // the TIMIDITY_CFG environment variable here before SDL_mixer
        // is opened.

        if (!nomusic && (snd_musicdevice == SNDDEVICE_GENMIDI || snd_musicdevice == SNDDEVICE_GUS)) {
            I_InitTimidityConfig();
        }

        if (!nosfx) {
            InitSfxModule(use_sfx_prefix);
        }

        if (!nomusic) {
            InitMusicModule();
        }
    }
}

void I_ShutdownSound(void) {
    if (sound_module != NULL) {
        sound_module->Shutdown();
    }

    if (music_module != NULL) {
        music_module->Shutdown();
    }
}

int I_GetSfxLumpNum(sfxinfo_t* sfxinfo) {
    if (sound_module != NULL) {
        return sound_module->GetSfxLumpNum(sfxinfo);
    } else {
        return 0;
    }
}

void I_UpdateSound(void) {
    if (sound_module != NULL) {
        sound_module->Update();
    }

    if (music_module != NULL && music_module->Poll != NULL) {
        music_module->Poll();
    }
}

static void CheckVolumeSeparation(int* vol, int* sep) {
    if (*sep < 0) {
        *sep = 0;
    } else if (*sep > 254) {
        *sep = 254;
    }

    if (*vol < 0) {
        *vol = 0;
    } else if (*vol > 127) {
        *vol = 127;
    }
}

void I_UpdateSoundParams(int channel, int vol, int sep) {
    if (sound_module != NULL) {
        CheckVolumeSeparation(&vol, &sep);
        sound_module->UpdateSoundParams(channel, vol, sep);
    }
}

int I_StartSound(sfxinfo_t* sfxinfo, int channel, int vol, int sep) {
    if (sound_module != NULL) {
        CheckVolumeSeparation(&vol, &sep);
        return sound_module->StartSound(sfxinfo, channel, vol, sep);
    } else {
        return 0;
    }
}

void I_StopSound(int channel) {
    if (sound_module != NULL) {
        sound_module->StopSound(channel);
    }
}

boolean I_SoundIsPlaying(int channel) {
    if (sound_module != NULL) {
        return sound_module->SoundIsPlaying(channel);
    } else {
        return false;
    }
}

void I_PrecacheSounds(sfxinfo_t* sounds, int num_sounds) {
    if (sound_module != NULL && sound_module->CacheSounds != NULL) {
        sound_module->CacheSounds(sounds, num_sounds);
    }
}

void I_InitMusic(void) {
}

void I_ShutdownMusic(void) {
}

void I_SetMusicVolume(int volume) {
    if (music_module != NULL) {
        music_module->SetMusicVolume(volume);
    }
}

void I_PauseSong(void) {
    if (music_module != NULL) {
        music_module->PauseMusic();
    }
}

void I_ResumeSong(void) {
    if (music_module != NULL) {
        music_module->ResumeMusic();
    }
}

void* I_RegisterSong(void* data, int len) {
    if (music_module != NULL) {
        return music_module->RegisterSong(data, len);
    } else {
        return NULL;
    }
}

void I_UnRegisterSong(void* handle) {
    if (music_module != NULL) {
        music_module->UnRegisterSong(handle);
    }
}

void I_PlaySong(void* handle, boolean looping) {
    if (music_module != NULL) {
        music_module->PlaySong(handle, looping);
    }
}

void I_StopSong(void) {
    if (music_module != NULL) {
        music_module->StopSong();
    }
}

boolean I_MusicIsPlaying(void) {
    if (music_module != NULL) {
        return music_module->MusicIsPlaying();
    } else {
        return false;
    }
}

void I_BindSoundVariables(void) {
#ifdef ORIGCODE
    extern int use_libsamplerate;
    extern float libsamplerate_scale;

    M_BindVariable("snd_musicdevice", &snd_musicdevice);
    M_BindVariable("snd_sfxdevice", &snd_sfxdevice);
    M_BindVariable("snd_sbport", &snd_sbport);
    M_BindVariable("snd_sbirq", &snd_sbirq);
    M_BindVariable("snd_sbdma", &snd_sbdma);
    M_BindVariable("snd_mport", &snd_mport);
    M_BindVariable("snd_maxslicetime_ms", &snd_maxslicetime_ms);
    M_BindVariable("snd_musiccmd", &snd_musiccmd);
    M_BindVariable("snd_samplerate", &snd_samplerate);
    M_BindVariable("snd_cachesize", &snd_cachesize);
    M_BindVariable("opl_io_port", &opl_io_port);

    M_BindVariable("timidity_cfg_path", &timidity_cfg_path);
    M_BindVariable("gus_patch_path", &gus_patch_path);
    M_BindVariable("gus_ram_kb", &gus_ram_kb);

#ifdef FEATURE_SOUND
    M_BindVariable("use_libsamplerate", &use_libsamplerate);
    M_BindVariable("libsamplerate_scale", &libsamplerate_scale);
#endif

    // Before SDL_mixer version 1.2.11, MIDI music caused the game
    // to crash when it looped.  If this is an old SDL_mixer version,
    // disable MIDI.

#ifdef __MACOSX__
    {
        const SDL_version* v = Mix_Linked_Version();

        if (SDL_VERSIONNUM(v->major, v->minor, v->patch) < SDL_VERSIONNUM(1, 2, 11)) {
            snd_musicdevice = SNDDEVICE_NONE;
        }
    }
#endif
#endif
}
