#include "Synth.h"

#ifdef PLATFORM_POSIX
#include <SDL2/SDL.h>
#else
#include <SDL.h>
#endif

SoundGen app_sch[NUM_CHANNELS]; // to avoid lengthy locking, keep copy under different mutex
SoundGen gen_sch[NUM_CHANNELS]; // this is copied to generator and used to actually generate the sound

INITBLOCK {
	for(int i = 0; i < NUM_CHANNELS; i++) // initialize wave_tab
		for(int j = 0; j < OPCOUNT; j++) {
			app_sch[i].op[j].Comp();
			gen_sch[i].op[j].Comp();
		}
}

int      in_sch_serial;

#if 1
struct AppSoundLock {
	AppSoundLock() { SDL_LockAudio(); }
	~AppSoundLock() { SDL_UnlockAudio(); }
};

struct GenSoundLock {
	GenSoundLock() {}
	~GenSoundLock() {}
};
#else
SpinLock s_in_sch_lock;

struct AppSoundLock {
	AppSoundLock() { s_in_sch_lock.Enter(); }
	~AppSoundLock() { s_in_sch_lock.Leave(); }
};

struct GenSoundLock {
	GenSoundLock() { s_in_sch_lock.Enter(); }
	~GenSoundLock() { s_in_sch_lock.Leave(); }
};
#endif

void SetChannel(int chi, const Sound& c, int priority, int id)
{
	AppSoundLock __;
	SoundGen& ch = app_sch[chi];
	ch.Start(c);
	ch.priority = priority;
	ch.serial = ++in_sch_serial;
	ch.id = id;
}

void StopChannels(int id)
{
	AppSoundLock __;
	for(int i = 0; i < NUM_CHANNELS; i++) {
		SoundGen& ch = app_sch[i];
		if(ch.id == id) {
			ch.serial = ++in_sch_serial;
			ch.current_volume = app_sch[i].op[0].volume = 0;
		}
	}
}

void SetChannelVolume(int chi, double volume)
{
	AppSoundLock __;
	SoundGen& ch = app_sch[chi];
	ch.param_serial = ++in_sch_serial;
	ch.op[0].volume = pow(10, (volume - 100) / 40);
}

int FindChannel(int priority, int from, double new_volume)
{
	new_volume = pow(10, (new_volume - 100) / 40);
	AppSoundLock __;
	int    besti = -1;
	int    best_priority = INT_MAX;
	double best_volume = 100;
	for(int i = from; i < NUM_CHANNELS; i++) {
		SoundGen& ch = app_sch[i];
//		if(ch.current_volume < 0.001) {
//			besti = i;
//			break;
//		}
		if(/*ch.priority <= priority &&
		   (ch.priority < best_priority || ch.priority == best_priority && */ch.current_volume < best_volume) {
		       besti = i;
//		       best_priority = ch.priority;
		       best_volume = ch.current_volume;
		}
	}
//	return besti;
	return best_volume < new_volume ? besti : -1;
//	DDUMP(best_volume);
//	DDUMP(besti);
/*	if(besti < 0)
		for(int i = from; i < NUM_CHANNELS; i++)
			if(app_sch[i].current_volume < 0.01) {
				besti = i;
				break;
			}
	return besti;*/
}

extern void Sequencer(int chunk_size);

float global_volume = 1;

void SetGlobalVolume(float vol)
{
	AppSoundLock __;
	global_volume = vol;
}

void MyAudioCallback(void *, Uint8 *stream, int len)
{
	int chunk_size = len / (2 * sizeof(float));
	Sequencer(chunk_size);
	{
#if 0
		RLOG("====================================");
#endif
		GenSoundLock __;
		for(int i = 0; i < NUM_CHANNELS; i++) {
			if(app_sch[i].serial != gen_sch[i].serial)
				gen_sch[i] = app_sch[i];
			if(app_sch[i].param_serial != gen_sch[i].param_serial) {
				gen_sch[i].op[0].volume = app_sch[i].op[0].volume;
				gen_sch[i].current_volume = 1;
				app_sch[i].param_serial = gen_sch[i].param_serial;
			}
#if 0
			RLOG(i);
			RLOG(gen_sch[i]);
#endif
		}
	}
	
	float *d = (float *)stream;

	memset(d, 0, len);
	for(int j = 0; j < NUM_CHANNELS; j++) {
		SoundGen& ch = gen_sch[j];
		d = (float *)stream;
		double v = 0;
		for(int i = 0; i < chunk_size; i++) {
			float h = ch.Get();
			*d++ += ch.lpan * h;
			*d++ += ch.rpan * h;
		}
	}

	float *e = (float *)stream + 2 * chunk_size;
	for(float *d = (float *)stream; d < e; d++)
		*d = clamp(*d * global_volume, -1.0f, 1.0f);

	{
		GenSoundLock __;
		for(int i = 0; i < NUM_CHANNELS; i++) {
			app_sch[i].current_volume = gen_sch[i].current_volume;
		}
	}
}

void InitSoundSynth(bool initsdl)
{
	if(initsdl && SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) < 0)
		return;
	
	SDL_AudioSpec want;
	memset(&want, 0, sizeof(want));
	want.freq = 44100;
	want.format = AUDIO_F32SYS;
	want.channels = 2;
	want.samples = CHUNK_SIZE;
	want.callback = MyAudioCallback;
	
	if(SDL_OpenAudio(&want, NULL) < 0)
	   LOG("Failed to open audio: " + (String)SDL_GetError());
	
	SDL_PauseAudio(0);
}

void CloseSoundSynth(bool exitsdl)
{
	SDL_CloseAudio();
	if(exitsdl)
		SDL_Quit();
}
