/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/graphics/video/avi_decoder.cpp $
 * $Id: avi_decoder.cpp 47896 2010-02-05 04:27:51Z mthreepwood $
 *
 */

#include "common/archive.h"
#include "common/endian.h"
#include "common/file.h"
#include "common/stream.h"
#include "common/events.h"

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

#include "graphics/video/avi_decoder.h"

// Codecs
#include "graphics/video/codecs/msvideo1.h"
#include "graphics/video/codecs/msrle.h"

namespace Graphics {

AviDecoder::AviDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : _mixer(mixer) {
	_soundType = soundType;

	_videoCodec = NULL;
	_decodedHeader = false;
	_audStream = NULL;
	_fileStream = NULL;
	_audHandle = new Audio::SoundHandle();
	memset(_palette, 0, sizeof(_palette));
	memset(&_wvInfo, 0, sizeof(PCMWAVEFORMAT));
	memset(&_bmInfo, 0, sizeof(BITMAPINFOHEADER));
	memset(&_vidsHeader, 0, sizeof(AVIStreamHeader));
	memset(&_audsHeader, 0, sizeof(AVIStreamHeader));
	memset(&_ixInfo, 0, sizeof(AVIOLDINDEX));
}

AviDecoder::~AviDecoder() {
	closeFile();
	delete _audHandle;
}

void AviDecoder::runHandle(uint32 tag) {
	assert (_fileStream);
	if (_fileStream->eos())
		return;

	debug (3, "Decoding tag %s", tag2str(tag));

	switch (tag) {
		case ID_RIFF:
			/*_filesize = */_fileStream->readUint32LE();
			if (_fileStream->readUint32BE() != ID_AVI)
				error("RIFF file is not an AVI video");
			break;
		case ID_LIST:
			handleList();
			break;
		case ID_AVIH:
			_header.size = _fileStream->readUint32LE();
			_header.microSecondsPerFrame = _fileStream->readUint32LE();
			_header.maxBytesPerSecond = _fileStream->readUint32LE();
			_header.padding = _fileStream->readUint32LE();
			_header.flags = _fileStream->readUint32LE();
			_header.totalFrames = _fileStream->readUint32LE();
			_header.initialFrames = _fileStream->readUint32LE();
			_header.streams = _fileStream->readUint32LE();
			_header.bufferSize = _fileStream->readUint32LE();
			_header.width = _fileStream->readUint32LE();
			_header.height = _fileStream->readUint32LE();
			//Ignore 16 bytes of reserved data
			_fileStream->skip(16);
			break;
		case ID_STRH:
			handleStreamHeader();
			break;
		case ID_STRD: // Extra stream info, safe to ignore
		case ID_VEDT: // Unknown, safe to ignore
		case ID_JUNK: // Alignment bytes, should be ignored
			{
			uint32 junkSize = _fileStream->readUint32LE();
			_fileStream->skip(junkSize + (junkSize & 1)); // Alignment
			} break;
		case ID_IDX1:
			_ixInfo.size = _fileStream->readUint32LE();
			_ixInfo.indices = new AVIOLDINDEX::Index[_ixInfo.size / 16];
			debug (0, "%d Indices", (_ixInfo.size / 16));
			for (uint32 i = 0; i < (_ixInfo.size / 16); i++) {
				_ixInfo.indices[i].id = _fileStream->readUint32BE();
				_ixInfo.indices[i].flags = _fileStream->readUint32LE();
				_ixInfo.indices[i].offset = _fileStream->readUint32LE();
				_ixInfo.indices[i].size = _fileStream->readUint32LE();
				debug (0, "Index %d == Tag \'%s\', Offset = %d, Size = %d", i, tag2str(_ixInfo.indices[i].id), _ixInfo.indices[i].offset, _ixInfo.indices[i].size);
			}
			break;
		default:
			error ("Unknown tag \'%s\' found", tag2str(tag));
	}
}

void AviDecoder::handleList() {
	uint32 listSize = _fileStream->readUint32LE() - 4; // Subtract away listType's 4 bytes
	uint32 listType = _fileStream->readUint32BE();
	uint32 curPos = _fileStream->pos();

	debug (0, "Found LIST of type %s", tag2str(listType));

	while ((_fileStream->pos() - curPos) < listSize)
		runHandle(_fileStream->readUint32BE());

	// We now have all the header data
	if (listType == ID_HDRL)
		_decodedHeader = true;
}

void AviDecoder::handleStreamHeader() {
	AVIStreamHeader sHeader;
	sHeader.size = _fileStream->readUint32LE();
	sHeader.streamType = _fileStream->readUint32BE();
	if (sHeader.streamType == ID_MIDS || sHeader.streamType == ID_TXTS)
		error ("Unhandled MIDI/Text stream");
	sHeader.streamHandler = _fileStream->readUint32BE();
	sHeader.flags = _fileStream->readUint32LE();
	sHeader.priority = _fileStream->readUint16LE();
	sHeader.language = _fileStream->readUint16LE();
	sHeader.initialFrames = _fileStream->readUint32LE();
	sHeader.scale = _fileStream->readUint32LE();
	sHeader.rate = _fileStream->readUint32LE();
	sHeader.start = _fileStream->readUint32LE();
	sHeader.length = _fileStream->readUint32LE();
	sHeader.bufferSize = _fileStream->readUint32LE();
	sHeader.quality = _fileStream->readUint32LE();
	sHeader.sampleSize = _fileStream->readUint32LE();
	sHeader.frame.left = _fileStream->readSint16LE();
	sHeader.frame.top = _fileStream->readSint16LE();
	sHeader.frame.right = _fileStream->readSint16LE();
	sHeader.frame.bottom = _fileStream->readSint16LE();

	if (_fileStream->readUint32BE() != ID_STRF)
		error("Could not find STRF tag");
	/* uint32 strfSize = */ _fileStream->readUint32LE();

	if (sHeader.streamType == ID_VIDS) {
		_vidsHeader = sHeader;

		_bmInfo.size = _fileStream->readUint32LE();
		_bmInfo.width = _fileStream->readUint32LE();
		assert (_header.width == _bmInfo.width);
		_bmInfo.height = _fileStream->readUint32LE();
		assert (_header.height == _bmInfo.height);
		_bmInfo.planes = _fileStream->readUint16LE();
		_bmInfo.bitCount = _fileStream->readUint16LE();
		_bmInfo.compression = _fileStream->readUint32BE();
		_bmInfo.sizeImage = _fileStream->readUint32LE();
		_bmInfo.xPelsPerMeter = _fileStream->readUint32LE();
		_bmInfo.yPelsPerMeter = _fileStream->readUint32LE();
		_bmInfo.clrUsed = _fileStream->readUint32LE();
		_bmInfo.clrImportant = _fileStream->readUint32LE();

		if (_bmInfo.bitCount == 8) {
			if (_bmInfo.clrUsed == 0)
				_bmInfo.clrUsed = 256;

			for (uint32 i = 0; i < _bmInfo.clrUsed; i++) {
				_palette[i * 3 + 2] = _fileStream->readByte();
				_palette[i * 3 + 1] = _fileStream->readByte();
				_palette[i * 3] = _fileStream->readByte();
				/*_palette[i * 4 + 3] = */_fileStream->readByte();
			}

			setPalette(_palette);
		}
	} else if (sHeader.streamType == ID_AUDS) {
		_audsHeader = sHeader;

		_wvInfo.tag = _fileStream->readUint16LE();
		_wvInfo.channels = _fileStream->readUint16LE();
		_wvInfo.samplesPerSec = _fileStream->readUint32LE();
		_wvInfo.avgBytesPerSec = _fileStream->readUint32LE();
		_wvInfo.blockAlign = _fileStream->readUint16LE();
		_wvInfo.size = _fileStream->readUint16LE();
	}
}

bool AviDecoder::loadFile(const char *fileName) {
	closeFile();

	_fileStream = SearchMan.createReadStreamForMember(fileName);
	if (!_fileStream)
		return false;

	_decodedHeader = false;
	// Seek to the first frame
	_videoInfo.currentFrame = 0;


	// Read chunks until we have decoded the header
	while (!_decodedHeader)
		runHandle(_fileStream->readUint32BE());

	_videoFrameBuffer = new byte[_header.width * _header.height];
	memset(_videoFrameBuffer, 0, _header.width * _header.height);

	uint32 nextTag = _fileStream->readUint32BE();

	// Throw out any JUNK section
	if (nextTag == ID_JUNK) {
		runHandle(ID_JUNK);
		nextTag = _fileStream->readUint32BE();
	}

	// Ignore the 'movi' LIST
	if (nextTag == ID_LIST) {
		_fileStream->readUint32BE(); // Skip size
		if (_fileStream->readUint32BE() != ID_MOVI)
			error ("Expected 'movi' LIST");
	} else
		error ("Expected 'movi' LIST");

	// Now, create the codec
	_videoCodec = createCodec();

	// Initialize the video stuff too
	_audStream = createAudioStream();
	if (_audStream)
		_mixer->playInputStream(_soundType, _audHandle, _audStream);

	debug (0, "Frames = %d, Dimensions = %d x %d", _header.totalFrames, _header.width, _header.height);
	debug (0, "Frame Rate = %d", getFrameRate());
	if ((_audsHeader.scale != 0) && (_header.flags & AVIF_ISINTERLEAVED))
		debug (0, "Sound Rate = %d", AUDIO_RATE);
	debug (0, "Video Codec = \'%s\'", tag2str(_vidsHeader.streamHandler));

	_videoInfo.firstframeOffset = _fileStream->pos();
	_videoInfo.width = _header.width;
	_videoInfo.height = _header.height;
	_videoInfo.frameCount = _header.totalFrames;
	// Our frameDelay is calculated in 1/100 ms, so we convert it here
	_videoInfo.frameDelay = _header.microSecondsPerFrame / 10;

	if (!_videoCodec)
		return false;

	return true;
}

void AviDecoder::closeFile() {
	if (!_fileStream)
		return;

	delete _fileStream;
	_fileStream = 0;

	delete[] _videoFrameBuffer;
	_videoFrameBuffer = 0;

	// Deinitialize sound
	_mixer->stopHandle(*_audHandle);
	_audStream = 0;

	_decodedHeader = false;

	delete _videoCodec;
	_videoCodec = 0;

	delete[] _ixInfo.indices;
	_ixInfo.indices = 0;

	memset(_palette, 0, sizeof(_palette));
	memset(&_wvInfo, 0, sizeof(PCMWAVEFORMAT));
	memset(&_bmInfo, 0, sizeof(BITMAPINFOHEADER));
	memset(&_vidsHeader, 0, sizeof(AVIStreamHeader));
	memset(&_audsHeader, 0, sizeof(AVIStreamHeader));
	memset(&_ixInfo, 0, sizeof(AVIOLDINDEX));
}

Surface *AviDecoder::getNextFrame() {
	uint32 nextTag = _fileStream->readUint32BE();

	if (_fileStream->eos())
		return NULL;

	if (nextTag == ID_LIST) {
		// A list of audio/video chunks
		uint32 listSize = _fileStream->readUint32LE() - 4;
		int32 startPos = _fileStream->pos();

		if (_fileStream->readUint32BE() != ID_REC)
			error ("Expected 'rec ' LIST");

		// Decode chunks in the list and see if we get a frame
		Surface *frame = NULL;
		while (_fileStream->pos() < startPos + (int32)listSize) {
			Surface *temp = getNextFrame();
			if (temp)
				frame = temp;
		}

		return frame;
	} else if (getStreamType(nextTag) == 'wb') {
		// Audio Chunk
		uint32 chunkSize = _fileStream->readUint32LE();
		byte *data = (byte *)malloc(chunkSize);
		_fileStream->read(data, chunkSize);

		byte flags = 0;
		if (_audsHeader.sampleSize == 2)
			flags |= Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
		else
			flags |= Audio::FLAG_UNSIGNED;

		_audStream->queueBuffer(data, chunkSize, DisposeAfterUse::YES, flags);
		_fileStream->skip(chunkSize & 1); // Alignment
	} else if (getStreamType(nextTag) == 'dc' || getStreamType(nextTag) == 'id' ||
	           getStreamType(nextTag) == 'AM' || getStreamType(nextTag) == '32') {
		// Compressed Frame
		_videoInfo.currentFrame++;
		uint32 chunkSize = _fileStream->readUint32LE();

		if (chunkSize == 0) // Keep last frame on screen
			return NULL;

		Common::SeekableReadStream *frameData = _fileStream->readStream(chunkSize);
		Graphics::Surface *surface = _videoCodec->decodeImage(frameData);
		delete frameData;
		_fileStream->skip(chunkSize & 1); // Alignment
		return surface;
	} else if (getStreamType(nextTag) == 'pc') {
		// Palette Change
		_fileStream->readUint32LE(); // Chunk size, not needed here
		byte firstEntry = _fileStream->readByte();
		uint16 numEntries = _fileStream->readByte();
		_fileStream->readUint16LE(); // Reserved

		// 0 entries means all colors are going to be changed
		if (numEntries == 0)
			numEntries = 256;

		for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
			_palette[i * 3] = _fileStream->readByte();
			_palette[i * 3 + 1] = _fileStream->readByte();
			_palette[i * 3 + 2] = _fileStream->readByte();
			_fileStream->readByte(); // Flags that don't serve us any purpose
		}

		setPalette(_palette);

		// No alignment necessary. It's always even.
	} else if (nextTag == ID_JUNK) {
		runHandle(ID_JUNK);
	} else if (nextTag == ID_IDX1) {
		runHandle(ID_IDX1);
	} else
		error ("Tag = \'%s\', %d", tag2str(nextTag), _fileStream->pos());

	return NULL;
}

bool AviDecoder::decodeNextFrame() {
	if (_videoInfo.currentFrame == 0)
		_videoInfo.startTime = g_system->getMillis();

	Surface *surface = NULL;

	uint32 curFrame = _videoInfo.currentFrame;

	while (!surface && _videoInfo.currentFrame < _videoInfo.frameCount && !_fileStream->eos())
		surface = getNextFrame();

	if (curFrame == _videoInfo.currentFrame) {
		warning("No video frame found");
		_videoInfo.currentFrame++;
	}

	if (surface)
		memcpy(_videoFrameBuffer, surface->pixels, _header.width * _header.height);

	return _videoInfo.currentFrame < _videoInfo.frameCount;
}

int32 AviDecoder::getAudioLag() {
	if (!_fileStream)
		return 0;

	int32 frameDelay = getFrameDelay();
	int32 videoTime = _videoInfo.currentFrame * frameDelay;
	int32 audioTime;

	if (!_audStream) {
		/* No audio.
		   Calculate the lag by how much time has gone by since the first frame
		   and how much time *should* have passed.
		*/

		audioTime = (g_system->getMillis() - _videoInfo.startTime) * 100;
	} else {
		const Audio::Timestamp ts = _mixer->getElapsedTime(*_audHandle);
		audioTime = ts.convertToFramerate(100000).totalNumberOfFrames();
	}

	return videoTime - audioTime;
}

Codec *AviDecoder::createCodec() {
	switch (_vidsHeader.streamHandler) {
		case ID_CRAM:
		case ID_MSVC:
		case ID_WHAM:
			return new MSVideo1Decoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount);
		case ID_RLE :
			return new MSRLEDecoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount);
		default:
			warning ("Unknown/Unhandled compression format \'%s\'", tag2str(_vidsHeader.streamHandler));
	}

	return NULL;
}

Audio::QueuingAudioStream *AviDecoder::createAudioStream() {

	if (_wvInfo.tag == AVI_WAVE_FORMAT_PCM) {
		return Audio::makeQueuingAudioStream(AUDIO_RATE, false);
	}

	if (_wvInfo.tag != 0) // No sound
		warning ("Unsupported AVI audio format %d", _wvInfo.tag);

	return NULL;
}

byte AviDecoder::char2num(char c) {
	return (c >= 48 && c <= 57) ? c - 48 : 0;
}

byte AviDecoder::getStreamNum(uint32 tag) {
	return char2num((char)(tag >> 24)) * 16 + char2num((char)(tag >> 16));
}

uint16 AviDecoder::getStreamType(uint32 tag) {
	return tag & 0xffff;
}

} // End of namespace Graphics
