/* 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/scummsys.h"

#include "zvision/scripting/effects/music_effect.h"

#include "zvision/zvision.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/sound/midi.h"
#include "zvision/sound/zork_raw.h"

#include "common/stream.h"
#include "common/file.h"
#include "audio/decoders/wave.h"

namespace ZVision {

static const uint8 dbMapLinear[256] =
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14,
14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 21, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 40, 41, 43, 45,
46, 48, 50, 52, 53, 55, 57, 60, 62, 64, 67, 69, 72, 74, 77, 80,
83, 86, 89, 92, 96, 99, 103, 107, 111, 115, 119, 123, 128, 133, 137, 143,
148, 153, 159, 165, 171, 177, 184, 191, 198, 205, 212, 220, 228, 237, 245, 255};

MusicNode::MusicNode(ZVision *engine, uint32 key, Common::String &filename, bool loop, uint8 volume)
	: MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) {
	_loop = loop;
	_volume = volume;
	_deltaVolume = 0;
	_balance = 0;
	_crossfade = false;
	_crossfadeTarget = 0;
	_crossfadeTime = 0;
	_sub = NULL;
	_stereo = false;
	_loaded = false;

	Audio::RewindableAudioStream *audioStream = NULL;

	if (filename.contains(".wav")) {
		Common::File *file = new Common::File();
		if (_engine->getSearchManager()->openFile(*file, filename)) {
			audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
		}
	} else {
		audioStream = makeRawZorkStream(filename, _engine);
	}

	if (audioStream) {
		_stereo = audioStream->isStereo();

		if (_loop) {
			Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES);
			_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, loopingAudioStream, -1, dbMapLinear[_volume]);
		} else {
			_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream, -1, dbMapLinear[_volume]);
		}

		if (_key != StateKey_NotSet)
			_engine->getScriptManager()->setStateValue(_key, 1);

		// Change filename.raw into filename.sub
		Common::String subname = filename;
		subname.setChar('s', subname.size() - 3);
		subname.setChar('u', subname.size() - 2);
		subname.setChar('b', subname.size() - 1);

		if (_engine->getSearchManager()->hasFile(subname))
			_sub = new Subtitle(_engine, subname);

		_loaded = true;
	}
}

MusicNode::~MusicNode() {
	if (_loaded)
		_engine->_mixer->stopHandle(_handle);
	if (_key != StateKey_NotSet)
		_engine->getScriptManager()->setStateValue(_key, 2);
	if (_sub)
		delete _sub;
	debug(1, "MusicNode: %d destroyed", _key);
}

void MusicNode::setDeltaVolume(uint8 volume) {
	_deltaVolume = volume;
	setVolume(_volume);
}

void MusicNode::setBalance(int8 balance) {
	_balance = balance;
	_engine->_mixer->setChannelBalance(_handle, _balance);
}

void MusicNode::setFade(int32 time, uint8 target) {
	_crossfadeTarget = target;
	_crossfadeTime = time;
	_crossfade = true;
}

bool MusicNode::process(uint32 deltaTimeInMillis) {
	if (!_loaded || ! _engine->_mixer->isSoundHandleActive(_handle))
		return stop();
	else {
		uint8 _newvol = _volume;

		if (_crossfade) {
			if (_crossfadeTime > 0) {
				if ((int32)deltaTimeInMillis > _crossfadeTime)
					deltaTimeInMillis = _crossfadeTime;
				_newvol += (int)(floor(((float)(_crossfadeTarget - _newvol) / (float)_crossfadeTime)) * (float)deltaTimeInMillis);
				_crossfadeTime -= deltaTimeInMillis;
			} else {
				_crossfade = false;
				_newvol = _crossfadeTarget;
			}
		}

		if (_volume != _newvol)
			setVolume(_newvol);

		if (_sub && _engine->getScriptManager()->getStateValue(StateKey_Subtitles) == 1)
			_sub->process(_engine->_mixer->getSoundElapsedTime(_handle) / 100);
	}
	return false;
}

void MusicNode::setVolume(uint8 newVolume) {
	if (!_loaded)
		return;

	_volume = newVolume;

	if (_deltaVolume >= _volume)
		_engine->_mixer->setChannelVolume(_handle, 0);
	else
		_engine->_mixer->setChannelVolume(_handle, dbMapLinear[_volume - _deltaVolume]);
}

uint8 MusicNode::getVolume() {
	return _volume;
}

PanTrackNode::PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos)
	: ScriptingEffect(engine, key, SCRIPTING_EFFECT_PANTRACK) {
	_slot = slot;
	_position = pos;

	// Try to set pan value for music node immediately
	process(0);
}

PanTrackNode::~PanTrackNode() {
}

bool PanTrackNode::process(uint32 deltaTimeInMillis) {
	ScriptManager * scriptManager = _engine->getScriptManager();
	ScriptingEffect *fx = scriptManager->getSideFX(_slot);
	if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO) {
		MusicNodeBASE *mus = (MusicNodeBASE *)fx;

		int curPos = scriptManager->getStateValue(StateKey_ViewPos);
		int16 _width = _engine->getRenderManager()->getBkgSize().x;
		int16 _halfWidth = _width / 2;
		int16 _quarterWidth = _width / 4;

		int tmp = 0;
		if (curPos <= _position)
			tmp = _position - curPos;
		else
			tmp = _position - curPos + _width;

		int balance = 0;

		if (tmp > _halfWidth)
			tmp -= _width;

		if (tmp > _quarterWidth) {
			balance = 1;
			tmp = _halfWidth - tmp;
		} else if (tmp < -_quarterWidth) {
			balance = -1;
			tmp = -_halfWidth - tmp;
		}

		// Originally it's value -90...90 but we use -127...127 and therefore 360 replaced by 508
		mus->setBalance( (508 * tmp) / _width );

		tmp = (360 * tmp) / _width;

		int deltaVol = balance;

		// This value sets how fast volume goes off than sound source back of you
		// By this value we can hack some "bugs" have place in originall game engine like beat sound in ZGI-dc10
		int volumeCorrection = 2;

		if (_engine->getGameId() == GID_GRANDINQUISITOR) {
			if (scriptManager->getCurrentLocation() == "dc10")
				volumeCorrection = 5;
		}

		if (deltaVol != 0)
			deltaVol = (mus->getVolume() * volumeCorrection) * (90 - tmp * balance) / 90;
		if (deltaVol > 255)
			deltaVol = 255;

		mus->setDeltaVolume(deltaVol);
	}
	return false;
}

MusicMidiNode::MusicMidiNode(ZVision *engine, uint32 key, int8 program, int8 note, int8 volume)
	: MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) {
	_volume = volume;
	_prog = program;
	_noteNumber = note;
	_pan = 0;

	_chan = _engine->getMidiManager()->getFreeChannel();

	if (_chan >= 0) {
		_engine->getMidiManager()->setVolume(_chan, _volume);
		_engine->getMidiManager()->setPan(_chan, _pan);
		_engine->getMidiManager()->setProgram(_chan, _prog);
		_engine->getMidiManager()->noteOn(_chan, _noteNumber, _volume);
	}

	if (_key != StateKey_NotSet)
		_engine->getScriptManager()->setStateValue(_key, 1);
}

MusicMidiNode::~MusicMidiNode() {
	if (_chan >= 0) {
		_engine->getMidiManager()->noteOff(_chan);
	}
	if (_key != StateKey_NotSet)
		_engine->getScriptManager()->setStateValue(_key, 2);
}

void MusicMidiNode::setDeltaVolume(uint8 volume) {
}

void MusicMidiNode::setBalance(int8 balance) {
}

void MusicMidiNode::setFade(int32 time, uint8 target) {
}

bool MusicMidiNode::process(uint32 deltaTimeInMillis) {
	return false;
}

void MusicMidiNode::setVolume(uint8 newVolume) {
	if (_chan >= 0) {
		_engine->getMidiManager()->setVolume(_chan, newVolume);
	}
	_volume = newVolume;
}

uint8 MusicMidiNode::getVolume() {
	return _volume;
}

} // End of namespace ZVision