/*
 * Copyright 2003-2009 (C) Raster Software Vigo (Sergio Costas)
 * This file is part of FBZX
 *
 * FBZX 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 3 of the License, or
 * (at your option) any later version.
 *
 * FBZX 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, see <http://www.gnu.org/licenses/>.
 *
 */
#include "llsound.hh"

#include <sstream>

#include "z80free/Z80free.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "computer.hh"
#include "emulator.hh"

#include <fcntl.h>
#include <sys/ioctl.h>

class LLSound *llsound;

#ifdef D_SOUND_ALSA
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
#include <alsa/asoundlib.h>
int        started_sound;
snd_pcm_t *_soundDevice;
#endif

#ifdef D_SOUND_PULSE
#include <pulse/simple.h>
pa_simple *pulse_s;
#endif

LLSound::LLSound(enum e_soundtype sound_type) {
	unsigned int bucle;
	int          ret2;

	this->sound_type = sound_type;
	ret2             = this->init_sound(); // check all sound systems
	if (ret2 == 0) {
		sound_aborted = false;
	} else { // if fails, run without sound
		this->sound_type = SOUND_NO;
		this->init_sound();
		sound_aborted = true;
	}
	printf("Init sound\n");

	// sound initialization
	this->sound = new uint8_t[2*this->buffer_len + 8];
	memset(this->sound, 0, 2*this->buffer_len + 8);
	this->current_buffer = this->sound;
	this->tst_sample     = 3500000 / this->freq;
	printf("Init sound 2 (%d)\n", this->tst_sample);
	printf("Set volume\n");
	set_volume(70);
	printf("Return init\n");
}

void LLSound::set_speed(bool turbo) {
	if (turbo) {
		this->tst_sample = 1000000000 / this->freq;
	} else {
		this->tst_sample = 3500000 / this->freq;
	}
}

int LLSound::init_sound() {
	if (this->sound_type != SOUND_AUTOMATIC) {
		switch (this->sound_type) {
		case SOUND_NO: // No sound; simulate 8bits mono
			this->format     = 0;
			this->channels   = 1;
			this->freq       = 48000;
			this->buffer_len = 4800; // will wait 1/10 second
			return 0;

			break;

#ifdef D_SOUND_ALSA
		case SOUND_ALSA:
			printf("Trying ALSA sound\n");
			if (0 == this->init_alsa()) {
				this->sound_type = SOUND_ALSA;
				return 0;
			} else {
				printf("Failed\n");
				return -1;
			}
			break;
#endif
#ifdef D_SOUND_PULSE
		case SOUND_PULSEAUDIO:
			printf("Trying PulseAudio\n");
			if (0 == this->init_pulse()) {
				this->sound_type = SOUND_PULSEAUDIO;
				return 0;
			} else {
				printf("Failed\n");
				return -1;
			}
			break;
#endif
		default:
			return -1;

			break;
		}
	}

#ifdef D_SOUND_PULSE
	printf("Trying PulseAudio\n");
	if (0 == this->init_pulse()) {
		this->sound_type = SOUND_PULSEAUDIO;
		return 0;
	}
#endif

#ifdef D_SOUND_ALSA
	printf("Trying ALSA sound\n");
	if (0 == this->init_alsa()) {
		this->sound_type = SOUND_ALSA;
		return 0;
	}
#endif

	return -1;
}



#ifdef D_SOUND_ALSA
int LLSound::init_alsa() {
	int err;
	snd_pcm_hw_params_t *hw_params;

	unsigned int      resample, samplerate;
	snd_pcm_uframes_t bufferSize;

	err = snd_pcm_open(&_soundDevice, "plughw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
	if (err < 0) {
		return -1;
	}

	if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
		snd_pcm_close(_soundDevice);
		return -2;
	}

	if ((err = snd_pcm_hw_params_any(_soundDevice, hw_params)) < 0) {
		snd_pcm_close(_soundDevice);
		return -2;
	}

	if ((err = snd_pcm_hw_params_set_access(_soundDevice, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
		snd_pcm_close(_soundDevice);
		return -3;
	}

	if (snd_pcm_hw_params_set_format(_soundDevice, hw_params, SND_PCM_FORMAT_S16_LE) >= 0) {
		this->format = 1;
	} else if (snd_pcm_hw_params_set_format(_soundDevice, hw_params, SND_PCM_FORMAT_U16_LE) >= 0) {
		this->format = 1;
	} else {
		snd_pcm_close(_soundDevice);
		return -3;
	}

	// Disable resampling.
	resample = 0;
	err      = snd_pcm_hw_params_set_rate_resample(_soundDevice, hw_params, resample);
	if (err < 0) {
		snd_pcm_close(_soundDevice);
		return -3;
	}

	if ((err = snd_pcm_hw_params_set_channels(_soundDevice, hw_params, 1)) >= 0) {
		this->channels = 1;
	} else if ((err = snd_pcm_hw_params_set_channels(_soundDevice, hw_params, 2)) >= 0) {
		this->channels = 2;
	} else {
		snd_pcm_close(_soundDevice);
		return -3;
	}

	samplerate = 48000;
	if ((err = snd_pcm_hw_params_set_rate_near(_soundDevice, hw_params, &samplerate, 0)) < 0) {
		snd_pcm_close(_soundDevice);
		return -3;
	}

	bufferSize = 4096;
	if (snd_pcm_hw_params_set_buffer_size_near(_soundDevice, hw_params, &bufferSize) < 0) {
		fprintf(stderr, "Error setting buffersize.\n");
		return -3;
	}

	this->freq = samplerate;

	err = snd_pcm_hw_params(_soundDevice, hw_params);
	if (err < 0) {
		return -3;
	}
	// snd_pcm_hw_params_get_buffer_size( hw_params, &bufferSize );

	this->buffer_len = bufferSize*2;

	started_sound = 0;
	return 0;

	err = snd_pcm_prepare(_soundDevice);
	if (err < 0) {
		return -5;
	}
	return 0;
}

#endif

#ifdef D_SOUND_PULSE
int LLSound::init_pulse() {
	pa_sample_spec ss;
	pa_buffer_attr buf;

	ss.format   = PA_SAMPLE_S16LE;
	ss.channels = 1;
	ss.rate     = 48000;

	buf.maxlength = 8192;
	buf.tlength   = 4096;
	buf.prebuf    = 4096;
	buf.minreq    = 4096;
	buf.fragsize  = 4096;

	pulse_s = pa_simple_new(NULL, "fbzx", PA_STREAM_PLAYBACK, NULL, "Spectrum", &ss, NULL, &buf, NULL);
	if (pulse_s == NULL) {
		return -1;
	}
	this->format     = 0;
	this->channels   = 1;
	this->freq       = 48000;
	this->buffer_len = 4096;

	return 0;
}

#endif

void LLSound::play() {
	static int retval, retval2;
	this->current_buffer = this->sound;

	switch (this->sound_type) {
	case SOUND_NO:     // no sound
		usleep(75000); // wait 1/20 second
		return;

		break;

#ifdef D_SOUND_ALSA
	case SOUND_ALSA: // ALSA
		if (started_sound == 0) {
			snd_pcm_prepare(_soundDevice);
			started_sound = 1;
		}
		retval = snd_pcm_writei(_soundDevice, sound, this->buffer_len);
		if (retval < 0) {
			retval = snd_pcm_prepare(_soundDevice);
		}
		return;

		break;
#endif
#ifdef D_SOUND_PULSE
	case SOUND_PULSEAUDIO: // PulseAudio
		// Remove the DC component to avoid losing the sound when multiplexing with other sources
		//this->remove_dc(this->sound, this->buffer_len);
		retval = pa_simple_write(pulse_s, this->sound, this->buffer_len * 2, &retval2);
		return;

		break;
#endif
	default:
		break;
	}
}

LLSound::~LLSound() {
	switch (this->sound_type) {
	case SOUND_NO:
		break;

#ifdef D_SOUND_ALSA
	case SOUND_ALSA:
		snd_pcm_drain(_soundDevice);
		snd_pcm_close(_soundDevice);
		break;
#endif
#ifdef D_SOUND_PULSE
	case SOUND_PULSEAUDIO:
		pa_simple_free(pulse_s);
		break;
#endif
	default:
		break;
	}
}

void LLSound::increase_volume() {
	if (this->volume > 3) {
		this->set_volume(this->volume - 4);
	}
	this->show_volume();
}

void LLSound::decrease_volume() {
	this->set_volume(this->volume + 4);
	this->show_volume();
}

void LLSound::show_volume() {
	std::ostringstream stringStream;
	stringStream << " Volume: " << this->volume / 4 << " ";
	osd->set_message(stringStream.str(), 1000);
}

void LLSound::set_volume(uint8_t volume) {
	if (volume > 64) {
		this->volume = 64;
	} else {
		this->volume = volume;
	}
}
