/* 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 "titanic/sound/music_room_handler.h"
#include "titanic/sound/sound_manager.h"
#include "titanic/events.h"
#include "titanic/core/project_item.h"
#include "titanic/titanic.h"

namespace Titanic {

CMusicRoomHandler::CMusicRoomHandler(CProjectItem *project, CSoundManager *soundManager) :
		_project(project), _soundManager(soundManager), _active(false),
		_soundHandle(-1), _waveFile(nullptr), _volume(100) {
	_instrumentsActive = 0;
	_isPlaying = false;
	_startTicks = _soundStartTicks = 0;
	Common::fill(&_instruments[0], &_instruments[4], (CMusicRoomInstrument *)nullptr);
	for (int idx = 0; idx < 4; ++idx)
		_songs[idx] = new CMusicSong(idx);
	Common::fill(&_startPos[0], &_startPos[4], 0);
	Common::fill(&_animExpiryTime[0], &_animExpiryTime[4], 0.0);
	Common::fill(&_position[0], &_position[4], 0);

	_audioBuffer = new CAudioBuffer(88200);
}

CMusicRoomHandler::~CMusicRoomHandler() {
	stop();
	for (int idx = 0; idx < 4; ++idx)
		delete _songs[idx];

	delete _audioBuffer;
}

CMusicRoomInstrument *CMusicRoomHandler::createInstrument(MusicInstrument instrument, int count) {
	switch (instrument) {
	case BELLS:
		_instruments[BELLS] = new CMusicRoomInstrument(_project, _soundManager, MV_BELLS);
		break;
	case SNAKE:
		_instruments[SNAKE] = new CMusicRoomInstrument(_project, _soundManager, MV_SNAKE);
		break;
	case PIANO:
		_instruments[PIANO] = new CMusicRoomInstrument(_project, _soundManager, MV_PIANO);
		break;
	case BASS:
		_instruments[BASS] = new CMusicRoomInstrument(_project, _soundManager, MV_BASS);
		break;
	default:
		return nullptr;
	}

	_instruments[instrument]->setFilesCount(count);
	return _instruments[instrument];
}

void CMusicRoomHandler::setup(int volume) {
	_volume = volume;
	_audioBuffer->reset();

	for (int idx = 0; idx < 4; ++idx) {
		MusicRoomInstrument &ins1 = _array1[idx];
		MusicRoomInstrument &ins2 = _array2[idx];

		if (ins1._directionControl == ins2._directionControl) {
			_startPos[idx] = 0;
		} else {
			_startPos[idx] = _songs[idx]->size() - 1;
		}

		_position[idx] = _startPos[idx];
		_animExpiryTime[idx] = 0.0;
	}

	_instrumentsActive = 4;
	_isPlaying = true;
	update();

	_waveFile = _soundManager->loadMusic(_audioBuffer, DisposeAfterUse::NO);
	update();
}

void CMusicRoomHandler::stop() {
	if (_waveFile) {
		_soundManager->stopSound(_soundHandle);
		delete _waveFile;
		_waveFile = nullptr;
		_soundHandle = -1;
	}

	for (int idx = 0; idx < 4; ++idx) {
		_instruments[idx]->clear();
		if (_active && _instruments[idx])
			_instruments[idx]->stop();
	}

	_instrumentsActive = 0;
	_isPlaying = false;
	_startTicks = _soundStartTicks = 0;
}

bool CMusicRoomHandler::checkInstrument(MusicInstrument instrument) const {
	return (_array1[instrument]._speedControl + _array2[instrument]._speedControl) == 0
		&& (_array1[instrument]._pitchControl + _array2[instrument]._pitchControl) == 0
		&& _array1[instrument]._directionControl == _array2[instrument]._directionControl
		&& _array1[instrument]._inversionControl == _array2[instrument]._inversionControl
		&& _array1[instrument]._muteControl == _array2[instrument]._muteControl;
}

void CMusicRoomHandler::setSpeedControl2(MusicInstrument instrument, int value) {
	if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
		_array2[instrument]._speedControl = value;
}

void CMusicRoomHandler::setPitchControl2(MusicInstrument instrument, int value) {
	if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
		_array2[instrument]._pitchControl = value * 3;
}

void CMusicRoomHandler::setInversionControl2(MusicInstrument instrument, bool value) {
	if (instrument >= BELLS && instrument <= BASS)
		_array2[instrument]._inversionControl = value;
}

void CMusicRoomHandler::setDirectionControl2(MusicInstrument instrument, bool value) {
	if (instrument >= BELLS && instrument <= BASS)
		_array2[instrument]._directionControl = value;
}

void CMusicRoomHandler::setPitchControl(MusicInstrument instrument, int value) {
	if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
		_array1[instrument]._pitchControl = value * 3;
}

void CMusicRoomHandler::setSpeedControl(MusicInstrument instrument, int value) {
	if (instrument >= BELLS && instrument <= BASS && value >= -2 && value <= 2)
		_array1[instrument]._speedControl = value;
}

void CMusicRoomHandler::setDirectionControl(MusicInstrument instrument, bool value) {
	if (instrument >= BELLS && instrument <= BASS)
		_array1[instrument]._directionControl = value;
}

void CMusicRoomHandler::setInversionControl(MusicInstrument instrument, bool value) {
	if (instrument >= BELLS && instrument <= BASS)
		_array1[instrument]._inversionControl = value;
}

void CMusicRoomHandler::setMuteControl(MusicInstrument instrument, bool value) {
	if (instrument >= BELLS && instrument <= BASS)
		_array1[instrument]._muteControl = value;
}

void CMusicRoomHandler::start() {
	if (_active) {
		for (int idx = 0; idx < 4; ++idx)
			_instruments[idx]->start();
	}
}

bool CMusicRoomHandler::update() {
	uint currentTicks =  g_system->getMillis();

	if (!_startTicks) {
		start();
		_startTicks = currentTicks;
	} else if (!_soundStartTicks && currentTicks >= (_startTicks + 3000)) {
		if (_waveFile) {
			CProximity prox;
			prox._channelVolume = _volume;
			_soundHandle = _soundManager->playSound(*_waveFile, prox);
		}

		_soundStartTicks = currentTicks;
	}

	if (_instrumentsActive > 0) {
		updateAudio();
		updateInstruments();
	}

	return !_audioBuffer->isFinished();
}

void CMusicRoomHandler::updateAudio() {
	int size = _audioBuffer->freeSize();
	int count;
	int16 *ptr;

	if (size > 0) {
		// Create a temporary buffer for merging the instruments into
		int16 *audioData = new int16[size];
		Common::fill(audioData, audioData + size, 0);

		for (MusicInstrument instrument = BELLS; instrument <= BASS;
				instrument = (MusicInstrument)((int)instrument + 1)) {
			CMusicRoomInstrument *musicWave = _instruments[instrument];

			// Iterate through each of the four instruments and do an additive
			// read that will merge their data onto the output buffer
			for (count = size, ptr = audioData; count > 0; ) {
				int amount = musicWave->read(ptr, count * 2);
				if (amount > 0) {
					count -= amount / sizeof(uint16);
					ptr += amount / sizeof(uint16);
				} else if (!pollInstrument(instrument)) {
					--_instrumentsActive;
					break;
				}
			}
		}

		_audioBuffer->push(audioData, size);
		delete[] audioData;
	}

	if (_instrumentsActive == 0)
		// Reaching end of music
		_audioBuffer->finalize();
}

void CMusicRoomHandler::updateInstruments() {
	uint currentTicks = g_system->getMillis();

	if (_active && _soundStartTicks) {
		for (MusicInstrument instrument = BELLS; instrument <= BASS;
				instrument = (MusicInstrument)((int)instrument + 1)) {
			MusicRoomInstrument &ins1 = _array1[instrument];
			MusicRoomInstrument &ins2 = _array2[instrument];
			CMusicRoomInstrument *ins = _instruments[instrument];

			// Is this about checking playback position?
			if (_position[instrument] < 0 || ins1._muteControl || _position[instrument] >= _songs[instrument]->size()) {
				_position[instrument] = -1;
				continue;
			}

			double time = (double)(currentTicks - _soundStartTicks) / 1000.0 - 0.6;
			double threshold = _animExpiryTime[instrument] - ins->_insStartTime;

			if (time >= threshold) {
				_animExpiryTime[instrument] += getAnimDuration(instrument, _position[instrument]);

				const CValuePair &vp = (*_songs[instrument])[_position[instrument]];
				if (vp._data != 0x7FFFFFFF) {
					int amount = getPitch(instrument, _position[instrument]);
					_instruments[instrument]->update(amount);
				}

				if (ins1._directionControl == ins2._directionControl) {
					_position[instrument]++;
				} else {
					_position[instrument]--;
				}
			}
		}
	}
}

bool CMusicRoomHandler::pollInstrument(MusicInstrument instrument) {
	int &arrIndex = _startPos[instrument];
	if (arrIndex < 0) {
		_instruments[instrument]->clear();
		return false;
	}

	const CMusicSong &song = *_songs[instrument];
	if (arrIndex >= song.size()) {
		arrIndex = -1;
		_instruments[instrument]->clear();
		return false;
	}

	const CValuePair &vp = song[arrIndex];
	uint duration = static_cast<int>(getAnimDuration(instrument, arrIndex) * 44100.0) & ~1;

	if (vp._data == 0x7FFFFFFF || _array1[instrument]._muteControl)
		_instruments[instrument]->reset(duration);
	else
		_instruments[instrument]->chooseWaveFile(getPitch(instrument, arrIndex), duration);

	if (_array1[instrument]._directionControl == _array2[instrument]._directionControl) {
		++arrIndex;
	} else {
		--arrIndex;
	}

	return true;
}

double CMusicRoomHandler::getAnimDuration(MusicInstrument instrument, int arrIndex) {
	const CValuePair &vp = (*_songs[instrument])[arrIndex];

	switch (_array1[instrument]._speedControl + _array2[instrument]._speedControl + 3) {
	case 0:
		return (double)vp._length * 1.5 * 0.0625 * 0.46875;
	case 1:
		return (double)vp._length * 1.33 * 0.0625 * 0.46875;
	case 2:
		return (double)vp._length * 1.25 * 0.0625 * 0.46875;
	case 4:
		return (double)vp._length * 0.75 * 0.0625 * 0.46875;
	case 5:
		return (double)vp._length * 0.67 * 0.0625 * 0.46875;
	case 6:
		return (double)vp._length * 0.5 * 0.0625 * 0.46875;
	default:
		return (double)vp._length * 1.0 * 0.0625 * 0.46875;
	}
}

int CMusicRoomHandler::getPitch(MusicInstrument instrument, int arrIndex) {
	const CMusicSong &song = *_songs[instrument];
	const CValuePair &vp = song[arrIndex];
	int val = vp._data;
	const MusicRoomInstrument &ins1 = _array1[instrument];
	const MusicRoomInstrument &ins2 = _array2[instrument];

	if (ins1._inversionControl != ins2._inversionControl) {
		val = song._minVal * 2 + song._range - val;
	}

	val += ins1._pitchControl + ins2._pitchControl;
	return val;
}

} // End of namespace Titanic
