/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "common/util.h"

#include "gob/sound/soundmixer.h"
#include "gob/sound/sounddesc.h"

#include "audio/decoders/raw.h"

namespace Gob {

SoundMixer::SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type) : _mixer(&mixer) {
	_playingSound = 0;

	_rate = _mixer->getOutputRate();
	_end = true;
	_data = nullptr;
	_length = 0;
	_freq = 0;
	_repCount = 0;

	_offset = 0;
	_offsetFrac = 0;
	_offsetInc = 0;

	_cur = 0;
	_last = 0;

	_fade = false;
	_fadeVol = 65536;
	_fadeVolStep = 0;
	_fadeSamples = 0;
	_curFadeSamples = 0;

	_mixer->playStream(type, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}

SoundMixer::~SoundMixer() {
	_mixer->stopHandle(_handle);
}

inline int16 SoundMixer::getData(int offset) {
	if (!_16bit)
		return (int16) ((int8) _data[offset]);
	else
		return (int16) READ_LE_UINT16(_data + (offset * 2));
}

bool SoundMixer::isPlaying() const {
	return !_end;
}

char SoundMixer::getPlayingSound() const {
	return _playingSound;
}

void SoundMixer::stop(int16 fadeLength) {
	Common::StackLock slock(_mutex);

	if (fadeLength <= 0) {
		_data = nullptr;
		_end = true;
		_playingSound = 0;
		return;
	}

	_fade = true;
	_fadeVol = 65536;
	_fadeSamples = (int)(fadeLength * (((double) _rate) / 10.0));
	_fadeVolStep = MAX((int32) 1, (int32) (65536 / _fadeSamples));
	_curFadeSamples = 0;
}

void SoundMixer::setRepeating(int32 repCount) {
	Common::StackLock slock(_mutex);

	_repCount = repCount;
}

void SoundMixer::setSample(SoundDesc &sndDesc, int16 repCount, int16 frequency,
		int16 fadeLength) {

	if (frequency <= 0)
		frequency = sndDesc._frequency;

	sndDesc._repCount = repCount - 1;
	sndDesc._frequency = frequency;

	_16bit = (sndDesc._mixerFlags & Audio::FLAG_16BITS) != 0;

	_data = sndDesc.getData();

	_length = sndDesc.size();
	_freq = frequency;

	_repCount = repCount;
	_end = false;
	_playingSound = 1;

	_offset = 0;
	_offsetFrac = 0;
	_offsetInc = (_freq << FRAC_BITS) / _rate;

	_last = _cur;
	_cur = getData(0);

	_curFadeSamples = 0;
	if (fadeLength == 0) {
		_fade = false;
		_fadeVol = 65536;
		_fadeSamples = 0;
		_fadeVolStep = 0;
	} else {
		_fade = true;
		_fadeVol = 0;
		_fadeSamples = (int)(fadeLength * (((double) _rate) / 10.0));
		_fadeVolStep = - MAX((int32) 1, (int32) (65536 / _fadeSamples));
	}
}

void SoundMixer::play(SoundDesc &sndDesc, int16 repCount, int16 frequency,
		int16 fadeLength) {
	Common::StackLock slock(_mutex);

	if (!_end)
		return;

	setSample(sndDesc, repCount, frequency, fadeLength);
}

void SoundMixer::checkEndSample() {
	if ((_repCount == -1) || (--_repCount > 0)) {
		_offset = 0;
		_offsetFrac = 0;
		_end = false;
		_playingSound = 1;
	} else {
		_end = true;
		_playingSound = 0;
	}
}

int SoundMixer::readBuffer(int16 *buffer, const int numSamples) {
	Common::StackLock slock(_mutex);

	for (int i = 0; i < numSamples; i++) {
		if (!_data)
			return i;
		if (_end || (_offset >= _length))
			checkEndSample();
		if (_end)
			return i;

		// Linear interpolation. See sound/rate.cpp

		int16 val = (_last + (((_cur - _last) * _offsetFrac +
					FRAC_HALF) >> FRAC_BITS)) << (_16bit ? 0 : 8);
		*buffer++ = (val * _fadeVol) >> 16;

		_offsetFrac += _offsetInc;

		// Was there an integral change?
		if (fracToInt(_offsetFrac) > 0) {
			_last = _cur;
			_cur = getData(_offset);
			_offset += fracToInt(_offsetFrac);
			_offsetFrac &= FRAC_LO_MASK;
		}

		if (_fade) {

			if (++_curFadeSamples >= _fadeSamples)
				endFade();
			else
				_fadeVol -= _fadeVolStep;

			if (_fadeVol < 0)
				_fadeVol = 0;

		}
	}
	return numSamples;
}

void SoundMixer::endFade() {
	if (_fadeVolStep > 0) {
		_data = nullptr;
		_end = true;
		_playingSound = 0;
	} else {
		_fadeVol = 65536;
		_fade = false;
	}
}

} // End of namespace Gob
