/* 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/>.
 *
 */

/*
 * Based on
 * WebVenture (c) 2010, Sean Kasun
 * https://github.com/mrkite/webventure, http://seancode.com/webventure/
 *
 * Used with explicit permission from the author
 */

#include "macventure/sound.h"

#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"

namespace MacVenture {

// SoundManager
SoundManager::SoundManager(MacVentureEngine *engine, Audio::Mixer *mixer) {
	_container = nullptr;
	Common::Path filename = engine->getFilePath(kSoundPathID);
	_container = new Container(filename);
	_mixer = mixer;
	debugC(1, kMVDebugSound, "Created sound manager with file %s", filename.toString().c_str());
}

SoundManager::~SoundManager() {
	if (_container)
		delete _container;

	Common::HashMap<ObjID, SoundAsset*>::iterator it;
	Common::HashMap<ObjID, SoundAsset*>::iterator end = _assets.end();
	for (it = _assets.begin(); it != end; it++) {
		delete it->_value;
	}
}

uint32 SoundManager::playSound(ObjID sound) {
	ensureLoaded(sound);
	_assets[sound]->play(_mixer, &_handle);
	return _assets[sound]->getPlayLength();
}

void SoundManager::ensureLoaded(ObjID sound) {
	if (!_assets.contains(sound))
		_assets[sound] = new SoundAsset(_container, sound);
}

SoundAsset::SoundAsset(Container *container, ObjID id) :
	_container(container), _id(id), _length(0), _frequency(1) {
	if (_container->getItemByteSize(_id) == 0)
		warning("Trying to load an empty sound asset (%d).", _id);

	Common::SeekableReadStream *stream = _container->getItem(_id);

	stream->seek(5, SEEK_SET);
	SoundType type = (SoundType)stream->readByte();
	debugC(2, kMVDebugSound, "Decoding sound of type %x", type);
	switch(type) {
	case kSound10:
		decode10(stream);
		break;
	case kSound12:
		decode12(stream);
		break;
	case kSound18:
		decode18(stream);
		break;
	case kSound1a:
		decode1a(stream);
		break;
	case kSound44:
		decode44(stream);
		break;
	case kSound78:
		decode78(stream);
		break;
	case kSound7e:
		decode7e(stream);
		break;
	default:
		warning("Unrecognized sound type: %x", type);
		break;
	}

	delete stream;
}

SoundAsset::~SoundAsset() {
	debugC(3, kMVDebugSound, "~SoundAsset(%d)", _id);
}

void SoundAsset::play(Audio::Mixer *mixer, Audio::SoundHandle *soundHandle) {
	if (_data.size() == 0) {
		return;
	}
	Audio::AudioStream *sound = Audio::makeRawStream(&_data.front(), _length, _frequency, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
	mixer->playStream(Audio::Mixer::kPlainSoundType, soundHandle, sound);
}

uint32 SoundAsset::getPlayLength() {
	// Transform to milliseconds
	return _length * 1000 / _frequency;
}

void SoundAsset::decode10(Common::SeekableReadStream *stream) {
	warning("Decode sound 0x10 untested");
	Common::Array<byte> wavtable;
	stream->seek(0x198, SEEK_SET);
	for (uint i = 0; i < 16; i++) {
		wavtable.push_back(stream->readByte());
	}
	_length = stream->readUint32BE() * 2;
	//Unused
	stream->readUint16BE();
	_frequency = (stream->readUint32BE() * 22100 / 0x10000);
	byte ch = 0;
	for (uint i = 0; i < _length; i++) {
		if (i & 1) {
			ch >>= 4;
		} else {
			ch = stream->readByte();
		}
		_data.push_back(wavtable[ch & 0xf]);
	}
}

void SoundAsset::decode12(Common::SeekableReadStream *stream) {
	warning("Decode sound 0x12 untested");
	stream->seek(0xc, SEEK_SET);
	uint32 repeat = stream->readUint16BE();
	stream->seek(0x34, SEEK_SET);
	uint32 base = stream->readUint16BE() + 0x34;
	stream->seek(base, SEEK_SET);
	_length = stream->readUint32BE() - 6;
	stream->readUint16BE();
	_frequency = (stream->readUint32BE() * 22100 / 0x10000);
	stream->seek(0xe2, SEEK_SET);
	// TODO: Possible source of bugs, the original just assigns the seek to the scales
	uint32 scales = stream->pos() + 0xe2;
	for (uint i = 0; i < repeat; i++) {
		stream->seek(scales + i * 2, SEEK_SET);
		uint32 scale = stream->readUint16BE();
		stream->seek(base + 0xa, SEEK_SET);
		for (uint j = 0; j < _length; j++) {
			byte ch = stream->readByte();
			if (ch & 0x80) {
				ch -= 0x80;
				uint32 env = ch * scale;
				ch = (env >> 8) & 0xff;
				if (ch & 0x80) {
					ch = 0x7f;
				}
				ch += 0x80;
			} else {
				ch = (ch ^ 0xff) + 1;
				ch -= 0x80;
				uint32 env = ch * scale;
				ch = (env >> 8) & 0xff;
				if (ch & 0x80) {
					ch = 0x7f;
				}
				ch += 0x80;
				ch = (ch ^ 0xff) + 1;
			}
			_data.push_back(ch);
		}
	}
}

void SoundAsset::decode18(Common::SeekableReadStream *stream) {
	warning("Decode sound 0x18 untested");
	Common::Array<byte> wavtable;
	stream->seek(0x252, SEEK_SET);
	for (uint i = 0; i < 16; i++) {
		wavtable.push_back(stream->readByte());
	}
	_length = stream->readUint32BE() * 2;
	//Unused
	stream->readUint16BE();
	// TODO: It had `| 0` at the end of this line, possible source of bugs.
	_frequency = (stream->readUint32BE() * 22100 / 0x10000);
	byte ch = 0;
	for (uint i = 0; i < _length; i++) {
		if (i & 1) {
			ch >>= 4;
		} else {
			ch = stream->readByte();
		}
		_data.push_back(wavtable[ch & 0xf]);
	}
}

void SoundAsset::decode1a(Common::SeekableReadStream *stream) {
	warning("Decode sound 0x1a untested");
	Common::Array<byte> wavtable;
	stream->seek(0x220, SEEK_SET);
	for (uint i = 0; i < 16; i++) {
		wavtable.push_back(stream->readByte());
	}
	_length = stream->readUint32BE();
	//Unused
	stream->readUint16BE();
	_frequency = (stream->readUint32BE() * 22100 / 0x10000);
	byte ch = 0;
	for (uint i = 0; i < _length; i++) {
		if (i & 1) {
			ch >>= 4;
		} else {
			ch = stream->readByte();
		}
		_data.push_back(wavtable[ch & 0xf]);
	}
}

void SoundAsset::decode44(Common::SeekableReadStream *stream) {
	stream->seek(0x5e, SEEK_SET);
	_length = stream->readUint32BE();
	_frequency = (stream->readUint32BE() * 22100 / 0x10000);
	for (uint i = 0; i < _length; i++) {
		_data.push_back(stream->readByte());
	}
}

void SoundAsset::decode78(Common::SeekableReadStream *stream) {
	Common::Array<byte> wavtable;
	stream->seek(0xba, SEEK_SET);
	for (uint i = 0; i < 16; i++) {
		wavtable.push_back(stream->readByte());
	}
	//Unused
	stream->readUint32BE();
	_length = stream->readUint32BE();
	_frequency = (stream->readUint32BE() * 22100 / 0x10000);
	byte ch = 0;
	for (uint i = 0; i < _length; i++) {
		if (i & 1) {
			ch <<= 4;
		} else {
			ch = stream->readByte();
		}
		_data.push_back(wavtable[(ch >> 4) & 0xf]);
	}
}

void SoundAsset::decode7e(Common::SeekableReadStream *stream) {
	Common::Array<byte> wavtable;
	stream->seek(0xc2, SEEK_SET);
	for (uint i = 0; i < 16; i++) {
		wavtable.push_back(stream->readByte());
	}
	//Unused
	stream->readUint32BE();
	_length = stream->readUint32BE();
	_frequency = (stream->readUint32BE() * 22100 / 0x10000);
	uint32 last = 0x80;
	byte ch = 0;
	for (uint i = 0; i < _length; i++) {
		if (i & 1) {
			ch <<= 4;
		} else {
			ch = stream->readByte();
		}
		last += wavtable[(ch >> 4) & 0xf];
		_data.push_back(last & 0xff);
	}
}

} //End of namespace MacVenture
