/* 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 xoreos' ASF code which is in turn
// Largely based on the ASF implementation found in FFmpeg.

#include "common/textconsole.h"
#include "common/stream.h"
#include "common/util.h"

#include "audio/audiostream.h"
#include "audio/decoders/asf.h"
#include "audio/decoders/wma.h"
#include "audio/decoders/wave_types.h"

namespace Audio {

class ASFGUID {
public:
	ASFGUID(Common::SeekableReadStream &stream) {
		stream.read(id, 16);
	}

	bool operator==(const byte gid[16]) const {
		return !memcmp(gid, id, 16);
	}

	bool operator!=(const byte gid[16]) const {
		return memcmp(gid, id, 16);
	}

	Common::String toString() const {
		return Common::String::format("%02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x, %02x",
				id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]);
	}

private:
	byte id[16];
};

// GUID's that we need
static const byte s_asfHeader[16]         = { 0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C };
static const byte s_asfFileHeader[16]     = { 0xA1, 0xDC, 0xAB, 0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 };
static const byte s_asfHead1[16]          = { 0xb5, 0x03, 0xbf, 0x5f, 0x2E, 0xA9, 0xCF, 0x11, 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 };
static const byte s_asfComment[16]        = { 0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c };
static const byte s_asfStreamHeader[16]   = { 0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65 };
static const byte s_asfCodecComment[16]   = { 0x40, 0x52, 0xD1, 0x86, 0x1D, 0x31, 0xD0, 0x11, 0xA3, 0xA4, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 };
static const byte s_asfDataHeader[16]     = { 0x36, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c };
static const byte s_asfAudioStream[16]    = { 0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B };
static const byte s_asfExtendedHeader[16] = { 0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, 0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 };
static const byte s_asfStreamBitRate[16]  = { 0xce, 0x75, 0xf8, 0x7b, 0x8d, 0x46, 0xd1, 0x11, 0x8d, 0x82, 0x00, 0x60, 0x97, 0xc9, 0xa2, 0xb2 };

class ASFStream : public SeekableAudioStream {
public:
	ASFStream(Common::SeekableReadStream *stream,
			DisposeAfterUse::Flag disposeAfterUse);
	~ASFStream();

	int readBuffer(int16 *buffer, const int numSamples) override;

	bool endOfData() const override;
	bool isStereo() const override { return _channels == 2; }
	int getRate() const override { return _sampleRate; }
	Timestamp getLength() const override { return Audio::Timestamp(_duration / 10000, _sampleRate); }
	bool seek(const Timestamp &where) override;
	bool rewind() override;

private:
	// Packet data
	struct Packet {
		Packet();
		~Packet();

		byte flags;
		byte segmentType;
		uint16 packetSize;
		uint32 sendTime;
		uint16 duration;

		struct Segment {
			byte streamID;
			byte sequenceNumber;
			bool isKeyframe;
			Common::Array<Common::SeekableReadStream *> data;
		};

		Common::Array<Segment> segments;
	};

	Common::SeekableReadStream *_stream;
	DisposeAfterUse::Flag _disposeAfterUse;

	void parseStreamHeader();
	void parseFileHeader();
	Packet *readPacket();
	Codec *createCodec();
	AudioStream *createAudioStream();

	uint32 _rewindPos;
	uint64 _curPacket;
	Packet *_lastPacket;
	Codec *_codec;
	AudioStream *_curAudioStream;
	byte _curSequenceNumber;

	// Header object variables
	uint64 _packetCount;
	uint64 _duration;
	uint32 _minPacketSize, _maxPacketSize;

	// Stream object variables
	uint16 _streamID;
	uint16 _compression;
	uint16 _channels;
	int _sampleRate;
	uint32 _bitRate;
	uint16 _blockAlign;
	uint16 _bitsPerCodedSample;
	Common::SeekableReadStream *_extraData;
};

ASFStream::Packet::Packet() {
}

ASFStream::Packet::~Packet() {
	for (uint32 i = 0; i < segments.size(); i++)
		for (uint32 j = 0; j < segments[i].data.size(); j++)
				delete segments[i].data[j];
}

ASFStream::ASFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) : _stream(stream), _disposeAfterUse(disposeAfterUse) {
	_extraData = nullptr;
	_lastPacket = nullptr;
	_curPacket = 0;
	_codec = nullptr;
	_curAudioStream = nullptr;
	_curSequenceNumber = 1; // They always start at one

	ASFGUID guid = ASFGUID(*_stream);
	if (guid != s_asfHeader)
		error("ASFStream: Missing asf header");

	_stream->readUint64LE();
	_stream->readUint32LE();
	_stream->readByte();
	_stream->readByte();

	for (;;) {
		uint64 startPos = _stream->pos();
		guid = ASFGUID(*_stream);
		uint64 size = _stream->readUint64LE();

		if (_stream->eos())
			error("ASFStream: Unexpected eos");

		// TODO: Parse each chunk
		if (guid == s_asfFileHeader) {
			parseFileHeader();
		} else if (guid == s_asfHead1) {
			// Should be safe to ignore
		} else if (guid == s_asfComment) {
			// Ignored
		} else if (guid == s_asfStreamHeader) {
			parseStreamHeader();
		} else if (guid == s_asfCodecComment) {
			// TODO
		} else if (guid == s_asfDataHeader) {
			// Done parsing the header
			break;
		} else if (guid == s_asfExtendedHeader) {
			// TODO
		} else if (guid == s_asfStreamBitRate) {
			// Ignored
		} else
			warning("Found unknown ASF GUID: %s", guid.toString().c_str());

		_stream->seek(startPos + size);
	}

	// Skip to the beginning of the packets
	_stream->skip(26);
	_rewindPos = _stream->pos();
}

ASFStream::~ASFStream() {
	if (_disposeAfterUse)
		delete _stream;

	delete _lastPacket;
	delete _curAudioStream;
	delete _codec;
}

void ASFStream::parseFileHeader() {
	_stream->skip(16); // skip client GUID
	/* uint64 fileSize = */ _stream->readUint64LE();
	/* uint64 creationTime = */ _stream->readUint64LE();
	_packetCount = _stream->readUint64LE();
	/* uint64 endTimestamp = */ _stream->readUint64LE();
	_duration = _stream->readUint64LE();
	/* uint32 startTimestamp = */ _stream->readUint32LE();
	/* uint32 unknown = */ _stream->readUint32LE();
	/* uint32 flags = */ _stream->readUint32LE();
	_minPacketSize = _stream->readUint32LE();
	_maxPacketSize = _stream->readUint32LE();
	/* uint32 uncFrameSize = */ _stream->readUint32LE();

	// We only know how to support packets of one length
	if (_minPacketSize != _maxPacketSize)
		error("ASFStream::parseFileHeader(): Mismatched packet sizes: Min = %d, Max = %d", _minPacketSize, _maxPacketSize);
}

void ASFStream::parseStreamHeader() {
	ASFGUID guid = ASFGUID(*_stream);

	if (guid != s_asfAudioStream)
		error("ASFStream::parseStreamHeader(): Found non-audio stream");

	_stream->skip(16); // skip a guid
	_stream->readUint64LE(); // total size
	uint32 typeSpecificSize = _stream->readUint32LE();
	_stream->readUint32LE();
	_streamID = _stream->readUint16LE();
	_stream->readUint32LE();

	// Parse the wave header
	_compression = _stream->readUint16LE();
	_channels = _stream->readUint16LE();
	_sampleRate = _stream->readUint32LE();
	_bitRate = _stream->readUint32LE() * 8;
	_blockAlign = _stream->readUint16LE();
	_bitsPerCodedSample = (typeSpecificSize == 14) ? 8 : _stream->readUint16LE();

	if (typeSpecificSize >= 18) {
		uint32 cbSize = _stream->readUint16LE();
		cbSize = MIN<int>(cbSize, typeSpecificSize - 18);
		_extraData = _stream->readStream(cbSize);
	}

	_codec = createCodec();
}

bool ASFStream::seek(const Timestamp &where) {
	if (where == 0) {
		return rewind();
	}

	// Seeking is not supported
	return false;
}

bool ASFStream::rewind() {
	// Seek back to the start of the packets
	_stream->seek(_rewindPos);

	// Reset our packet counter
	_curPacket = 0;
	delete _lastPacket; _lastPacket = nullptr;

	// Delete a stream if we have one
	delete _curAudioStream; _curAudioStream = nullptr;

	// Reset this too
	_curSequenceNumber = 1;

	return true;
}

ASFStream::Packet *ASFStream::readPacket() {
	if (_curPacket == _packetCount)
		error("ASFStream::readPacket(): Reading too many packets");

	uint32 packetStartPos = _stream->pos();

	// Read a single ASF packet
	if (_stream->readByte() != 0x82)
		error("ASFStream::readPacket(): Missing packet header");

	if (_stream->readUint16LE() != 0)
		error("ASFStream::readPacket(): Unknown is not zero");

	Packet *packet = new Packet();
	packet->flags = _stream->readByte();
	packet->segmentType = _stream->readByte();
	packet->packetSize = (packet->flags & 0x40) ? _stream->readUint16LE() : 0;

	uint16 paddingSize = 0;
	if (packet->flags & 0x10)
		paddingSize = _stream->readUint16LE();
	else if (packet->flags & 0x08)
		paddingSize = _stream->readByte();

	packet->sendTime = _stream->readUint32LE();
	packet->duration = _stream->readUint16LE();

	byte segmentCount = (packet->flags & 0x01) ? _stream->readByte() : 1;
	packet->segments.resize(segmentCount & 0x3F);

	for (uint32 i = 0; i < packet->segments.size(); i++) {
		Packet::Segment &segment = packet->segments[i];

		segment.streamID = _stream->readByte();
		segment.sequenceNumber = _stream->readByte();
		segment.isKeyframe = (segment.streamID & 0x80) != 0;
		segment.streamID &= 0x7F;

		uint32 fragmentOffset = 0;
		if (packet->segmentType == 0x55)
			fragmentOffset = _stream->readByte();
		else if (packet->segmentType == 0x59)
			fragmentOffset = _stream->readUint16LE();
		else if (packet->segmentType == 0x5D)
			fragmentOffset = _stream->readUint32LE();
		else
			error("ASFStream::readPacket(): Unknown packet segment type 0x%02x", packet->segmentType);

		byte flags = _stream->readByte();
		if (flags == 1) {
			//uint32 objectStartTime = fragmentOffset; // reused purpose
			_stream->readByte(); // unknown

			uint32 dataLength = (packet->segments.size() == 1) ? (_maxPacketSize - (_stream->pos() - packetStartPos) - paddingSize) : _stream->readUint16LE();
			uint32 startObjectPos = _stream->pos();

			while ((uint32)_stream->pos() < dataLength + startObjectPos)
				segment.data.push_back(_stream->readStream(_stream->readByte()));
		} else if (flags == 8) {
			/* uint32 objectLength = */ _stream->readUint32LE();
			/* uint32 objectStartTime = */ _stream->readUint32LE();

			uint32 dataLength = 0;
			if (packet->segments.size() == 1)
				dataLength = _maxPacketSize - (_stream->pos() - packetStartPos) - fragmentOffset - paddingSize;
			else if (segmentCount & 0x40)
				dataLength = _stream->readByte();
			else
				dataLength = _stream->readUint16LE();

			_stream->skip(fragmentOffset);
			segment.data.push_back(_stream->readStream(dataLength));
		} else
			error("ASFStream::readPacket(): Unknown packet flags 0x%02x", flags);
	}

	// Skip any padding
	_stream->skip(paddingSize);

	// We just read a packet
	_curPacket++;

	if ((uint32)_stream->pos() != packetStartPos + _maxPacketSize)
		error("ASFStream::readPacket(): Mismatching packet pos: %d (should be %d)", (int)_stream->pos(), _maxPacketSize + packetStartPos);

	return packet;
}

Codec *ASFStream::createCodec() {
	switch (_compression) {
	case kWaveFormatWMAv2:
		return new WMACodec(2, _sampleRate, _channels, _bitRate, _blockAlign, _extraData);
	default:
		error("ASFStream::createAudioStream(): Unknown compression 0x%04x", _compression);
	}

	return nullptr;
}

AudioStream *ASFStream::createAudioStream() {
	delete _lastPacket;
	_lastPacket = readPacket();

	// TODO
	if (_lastPacket->segments.size() != 1)
		error("ASFStream::createAudioStream(): Only single segment packets supported");

	Packet::Segment &segment = _lastPacket->segments[0];

	// We should only have one stream in a ASF audio file
	if (segment.streamID != _streamID)
		error("ASFStream::createAudioStream(): Packet stream ID mismatch");

	// TODO
	if (segment.sequenceNumber != _curSequenceNumber)
		error("ASFStream::createAudioStream(): Only one sequence number per packet supported");

	// This can overflow and needs to overflow!
	_curSequenceNumber++;

	// TODO
	if (segment.data.size() != 1)
		error("ASFStream::createAudioStream(): Packet grouping not supported");

	Common::SeekableReadStream *stream = segment.data[0];
	if (_codec)
		return _codec->decodeFrame(*stream);

	return nullptr;
}

int ASFStream::readBuffer(int16 *buffer, const int numSamples) {
	int samplesDecoded = 0;

	for (;;) {
		if (_curAudioStream) {
			samplesDecoded += _curAudioStream->readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded);

			if (_curAudioStream->endOfData()) {
				delete _curAudioStream;
				_curAudioStream = nullptr;
			}
		}

		if (samplesDecoded == numSamples || endOfData())
			break;

		if (!_curAudioStream) {
			_curAudioStream = createAudioStream();
		}

	}

	return samplesDecoded;
}

bool ASFStream::endOfData() const {
	return _curPacket == _packetCount && !_curAudioStream;
}

SeekableAudioStream *makeASFStream(
	Common::SeekableReadStream *stream,
	DisposeAfterUse::Flag disposeAfterUse) {
	SeekableAudioStream *s = new ASFStream(stream, disposeAfterUse);

	if (s && s->endOfData()) {
		delete s;
		return nullptr;
	}

	return s;
}

} // End of namespace Audio
