/*
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
 * It is copyright by its individual contributors, as recorded in the
 * project's Git history.  See COPYING.txt at the top level for license
 * terms and a link to the Git history.
 */
/*
 * Digital audio support
 * Library-independent stub for dynamic selection of sound system
 */

#include <stdexcept>
#include "dxxerror.h"
#include "vecmat.h"
#include "config.h"
#include "digi.h"
#include "sounds.h"
#include "console.h"
#include "rbaudio.h"
#include "args.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <digi.h>
#include <digi_audio.h>

#if DXX_USE_SDLMIXER
#include <digi_mixer.h>
#include <SDL_mixer.h>
#endif
#ifdef _WIN32
#include "hmp.h"
#endif

namespace dcx {

int digi_volume = SOUND_MAX_VOLUME;

/* The values for these three defines are arbitrary and can be changed,
 * provided that they remain unique with respect to each other.
 */
#define DXX_STS_MIXER_WITH_POINTER	0
#define DXX_STS_MIXER_WITH_COPY	1
#define DXX_STS_NO_MIXER	2

#if DXX_USE_SDLMIXER
#ifndef DXX_SOUND_TABLE_STYLE
#ifdef __PIE__
/* PIE -> paranoid checks
 * No PIE -> prefer speed
 */
#define DXX_SOUND_TABLE_STYLE	DXX_STS_MIXER_WITH_POINTER
#else
#define DXX_SOUND_TABLE_STYLE	DXX_STS_MIXER_WITH_COPY
#endif
#endif
#else
#define DXX_SOUND_TABLE_STYLE	DXX_STS_NO_MIXER
#endif

/* Sound system function pointers */

namespace {

struct sound_function_table_t
{
	int  (*init)();
	void (*close)();
	void (*set_channel_volume)(int, int);
	void (*set_channel_pan)(int, sound_pan);
	int  (*start_sound)(short, fix, sound_pan, int, int, int, sound_object *);
	void (*stop_sound)(int);
	void (*end_sound)(int);
	int  (*is_channel_playing)(int);
	void (*stop_all_channels)();
	void (*set_digi_volume)(int);
};

#if DXX_SOUND_TABLE_STYLE == DXX_STS_MIXER_WITH_POINTER
[[noreturn]]
__attribute_cold
void report_invalid_table()
{
	/* Out of line due to length of generated code */
	throw std::logic_error("invalid sound table pointer");
}
#endif

}

}

#if DXX_SOUND_TABLE_STYLE != DXX_STS_MIXER_WITH_COPY
namespace dsx
#else
namespace dcx
#endif
{

namespace {

class sound_function_pointers_t
{
#if DXX_SOUND_TABLE_STYLE == DXX_STS_MIXER_WITH_COPY
	sound_function_table_t table;
#elif DXX_SOUND_TABLE_STYLE == DXX_STS_MIXER_WITH_POINTER
	const sound_function_table_t *table;
#endif
public:
	inline const sound_function_table_t *operator->();
	inline sound_function_pointers_t &operator=(const sound_function_table_t &t);
};

#if DXX_SOUND_TABLE_STYLE == DXX_STS_MIXER_WITH_COPY
const sound_function_table_t *sound_function_pointers_t::operator->()
{
	return &table;
}

sound_function_pointers_t &sound_function_pointers_t::operator=(const sound_function_table_t &t)
{
	table = t;
	return *this;
}
#elif DXX_SOUND_TABLE_STYLE == DXX_STS_MIXER_WITH_POINTER
sound_function_pointers_t &sound_function_pointers_t::operator=(const sound_function_table_t &t)
{
	table = &t;
	/* Trap bad initial assignment */
	operator->();
	return *this;
}
#elif DXX_SOUND_TABLE_STYLE == DXX_STS_NO_MIXER
sound_function_pointers_t &sound_function_pointers_t::operator=(const sound_function_table_t &)
{
	return *this;
}
#endif

}

static sound_function_pointers_t fptr;

}

namespace dsx {

namespace {

	/* Some of the functions are in dsx, so the definition and
	 * initializer must be in dsx.
	 */
#if DXX_USE_SDLMIXER
constexpr sound_function_table_t digi_mixer_table{
	&digi_mixer_init,
	&digi_mixer_close,
	&digi_mixer_set_channel_volume,
	&digi_mixer_set_channel_pan,
	&digi_mixer_start_sound,
	&digi_mixer_stop_sound,
	&digi_mixer_end_sound,
	&digi_mixer_is_channel_playing,
	&digi_mixer_stop_all_channels,
	&digi_mixer_set_digi_volume,
};
#endif

constexpr sound_function_table_t digi_audio_table{
	&digi_audio_init,
	&digi_audio_close,
	&digi_audio_set_channel_volume,
	&digi_audio_set_channel_pan,
	&digi_audio_start_sound,
	&digi_audio_stop_sound,
	&digi_audio_end_sound,
	&digi_audio_is_channel_playing,
	&digi_audio_stop_all_channels,
	&digi_audio_set_digi_volume,
};

#if DXX_SOUND_TABLE_STYLE == DXX_STS_MIXER_WITH_POINTER
const sound_function_table_t *sound_function_pointers_t::operator->()
{
	if (table != &digi_audio_table && table != &digi_mixer_table)
		report_invalid_table();
	return table;
}
#elif DXX_SOUND_TABLE_STYLE == DXX_STS_NO_MIXER
const sound_function_table_t *sound_function_pointers_t::operator->()
{
	return &digi_audio_table;
}
#endif

}

void digi_select_system()
{
#if DXX_USE_SDLMIXER
	if (!CGameArg.SndDisableSdlMixer)
	{
		const auto vl = Mix_Linked_Version();
		con_printf(CON_NORMAL, "Using SDL_mixer library v%u.%u.%u", vl->major, vl->minor, vl->patch);
		fptr = digi_mixer_table;
		return;
	}
#endif
	con_puts(CON_NORMAL,"Using plain old SDL audio");
		fptr = digi_audio_table;
}

/* Common digi functions */
#if defined(DXX_BUILD_DESCENT_I)
int digi_sample_rate = SAMPLE_RATE_11K;
#endif

/* Stub functions */

int  digi_init()
{
	digi_init_sounds();
	return fptr->init();
}

void digi_close() { fptr->close(); }

void digi_set_channel_volume(int channel, int volume) { fptr->set_channel_volume(channel, volume); }
void digi_set_channel_pan(const int channel, const sound_pan pan) { fptr->set_channel_pan(channel, pan); }

int  digi_start_sound(const short soundnum, const fix volume, const sound_pan pan, const int looping, const int loop_start, const int loop_end, sound_object *const soundobj)
{
	return fptr->start_sound(soundnum, volume, pan, looping, loop_start, loop_end, soundobj);
}

void digi_stop_sound(int channel) { fptr->stop_sound(channel); }
void digi_end_sound(int channel) { fptr->end_sound(channel); }

int  digi_is_channel_playing(int channel) { return fptr->is_channel_playing(channel); }
void digi_stop_all_channels() { fptr->stop_all_channels(); }
void digi_set_digi_volume(int dvolume) { fptr->set_digi_volume(dvolume); }

#ifdef _WIN32
// Windows native-MIDI stuff.
static uint8_t digi_win32_midi_song_playing;
static uint8_t already_playing;
static std::unique_ptr<hmp_file> cur_hmp;

void digi_win32_set_midi_volume( int mvolume )
{
	hmp_setvolume(cur_hmp.get(), mvolume*MIDI_VOLUME_SCALE/8);
}

int digi_win32_play_midi_song( const char * filename, int loop )
{
	if (!already_playing)
	{
		hmp_reset();
		already_playing = 1;
	}
	digi_win32_stop_midi_song();

	if (filename == NULL)
		return 0;

	if ((cur_hmp = std::get<0>(hmp_open(filename))))
	{
		/* 
		 * FIXME: to be implemented as soon as we have some kind or checksum function - replacement for ugly hack in hmp.c for descent.hmp
		 * if (***filesize check*** && ***CRC32 or MD5 check***)
		 *	(((*cur_hmp).trks)[1]).data[6] = 0x6C;
		 */
		if (hmp_play(cur_hmp.get(),loop) != 0)
			return 0;	// error
		digi_win32_midi_song_playing = 1;
		digi_win32_set_midi_volume(GameCfg.MusicVolume);
		return 1;
	}

	return 0;
}

void digi_win32_pause_midi_song()
{
	hmp_pause(cur_hmp.get());
}

void digi_win32_resume_midi_song()
{
	hmp_resume(cur_hmp.get());
}

void digi_win32_stop_midi_song()
{
	if (!digi_win32_midi_song_playing)
		return;
	digi_win32_midi_song_playing = 0;
	cur_hmp.reset();
	hmp_reset();
}
#endif

}
