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

// Based on Deniz Oezmen's code: http://oezmen.eu/

#include "lastexpress/data/sequence.h"

#include "lastexpress/debug.h"

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

namespace LastExpress {

void FrameInfo::read(Common::SeekableReadStream *in, bool isSequence) {
	// Save the current position
	int32 basePos = in->pos();

	dataOffset = in->readUint32LE();
	unknown = in->readUint32LE();
	paletteOffset = in->readUint32LE();
	xPos1 = in->readUint32LE();
	yPos1 = in->readUint32LE();
	xPos2 = in->readUint32LE();
	yPos2 = in->readUint32LE();
	initialSkip = in->readUint32LE();
	decompressedEndOffset = in->readUint32LE();

	// Read the compression type for NIS files
	if (!isSequence) {
		in->seek(basePos + 0x124);
	} else {
		hotspot.left = (int16)in->readUint16LE();
		hotspot.right = (int16)in->readUint16LE();
		hotspot.top = (int16)in->readUint16LE();
		hotspot.bottom = (int16)in->readUint16LE();
	}

	compressionType = in->readByte();
	subType = (FrameSubType)in->readByte();

	// Sequence information
	field_2E = in->readByte();
	keepPreviousFrame = in->readByte();
	field_30 = in->readByte();
	field_31 = in->readByte();
	soundAction = in->readByte();
	field_33 = in->readByte();
	position = in->readByte();
	field_35 = in->readByte();
	field_36 = in->readUint16LE();
	field_38 = in->readUint32LE();
	entityPosition = (EntityPosition)in->readUint16LE();
	location = in->readUint16LE();
	next = in->readUint32LE();
}


// AnimFrame

AnimFrame::AnimFrame(Common::SeekableReadStream *in, const FrameInfo &f) : _palette(NULL) {
	_palSize = 1;
	// TODO: use just the needed rectangle
	_image.create(640, 480, Graphics::PixelFormat::createFormatCLUT8());

	//debugC(6, kLastExpressDebugGraphics, "    Offsets: data=%d, unknown=%d, palette=%d", f.dataOffset, f.unknown, f.paletteOffset);
	//debugC(6, kLastExpressDebugGraphics, "    Position: (%d, %d) - (%d, %d)", f.xPos1, f.yPos1, f.xPos2, f.yPos2);
	//debugC(6, kLastExpressDebugGraphics, "    Initial Skip: %d", f.initialSkip);
	//debugC(6, kLastExpressDebugGraphics, "    Decompressed end offset: %d", f.decompressedEndOffset);
	//debugC(6, kLastExpressDebugGraphics, "    Hotspot: (%d, %d) x (%d, %d)\n", f.hotspot.left, f.hotspot.top, f.hotspot.right, f.hotspot.bottom);
	//debugC(6, kLastExpressDebugGraphics, "    Compression type: %u / %u", f.compressionType, f.subType);
	//debugC(6, kLastExpressDebugGraphics, "    Unknown: %u - %u - %u - %u - %u - %u - %u - %d", f.field_2E, f.field_2F, f.field_30, f.field_31, f.field_33, f.field_35, f.field_36, f.field_38);
	//debugC(6, kLastExpressDebugGraphics, "    Sound action: %u", f.soundAction);
	//debugC(6, kLastExpressDebugGraphics, "    Position: %d", f.position);
	//debugC(6, kLastExpressDebugGraphics, "    Entity Position: %d", f.entityPosition);
	//debugC(6, kLastExpressDebugGraphics, "    Location: %d", f.location);
	//debugC(6, kLastExpressDebugGraphics, "    next: %d", f.next);

	switch (f.compressionType) {
	case 0:
		// Empty frame
		break;
	case 3:
		decomp3(in, f);
		break;
	case 4:
		decomp4(in, f);
		break;
	case 5:
		decomp5(in, f);
		break;
	case 7:
		decomp7(in, f);
		break;
	case 255:
		decompFF(in, f);
		break;
	default:
		error("[AnimFrame::AnimFrame] Unknown frame compression: %d", f.compressionType);
	}

	readPalette(in, f);
	_rect = Common::Rect((int16)f.xPos1, (int16)f.yPos1, (int16)f.xPos2, (int16)f.yPos2);
	//_rect.debugPrint(0, "Frame rect:");
}

AnimFrame::~AnimFrame() {
	_image.free();
	delete[] _palette;
}

Common::Rect AnimFrame::draw(Graphics::Surface *s) {
	byte *inp = (byte *)_image.pixels;
	uint16 *outp = (uint16 *)s->pixels;
	for (int i = 0; i < 640 * 480; i++, inp++, outp++) {
		if (*inp)
			*outp = _palette[*inp];
	}
	return _rect;
}

void AnimFrame::readPalette(Common::SeekableReadStream *in, const FrameInfo &f) {
	// Read the palette
	in->seek((int)f.paletteOffset);
	_palette = new uint16[_palSize];
	for (uint32 i = 0; i < _palSize; i++) {
		_palette[i] = in->readUint16LE();
	}
}

void AnimFrame::decomp3(Common::SeekableReadStream *in, const FrameInfo &f) {
	decomp34(in, f, 0x7, 3);
}

void AnimFrame::decomp4(Common::SeekableReadStream *in, const FrameInfo &f) {
	decomp34(in, f, 0xf, 4);
}

void AnimFrame::decomp34(Common::SeekableReadStream *in, const FrameInfo &f, byte mask, byte shift) {
	byte *p = (byte *)_image.getBasePtr(0, 0);

	uint32 skip = f.initialSkip / 2;
	uint32 size = f.decompressedEndOffset / 2;
	//warning("skip: %d, %d", skip % 640, skip / 640);
	//warning("size: %d, %d", size % 640, size / 640);
	//assert (f.yPos1 == skip / 640);
	//assert (f.yPos2 == size / 640);

	uint32 numBlanks = 640 - (f.xPos2 - f.xPos1);

	in->seek((int)f.dataOffset);
	for (uint32 out = skip; out < size; ) {
		uint16 opcode = in->readByte();

		if (opcode & 0x80) {
			if (opcode & 0x40) {
				opcode &= 0x3f;
				out += numBlanks + opcode + 1;
			} else {
				opcode &= 0x3f;
				if (opcode & 0x20) {
					opcode = ((opcode & 0x1f) << 8) + in->readByte();
					if (opcode & 0x1000) {
						out += opcode & 0xfff;
						continue;
					}
				}
				out += opcode + 2;
			}
		} else {
			byte value = opcode & mask;
			opcode >>= shift;
			if (_palSize <= value)
				_palSize = value + 1;
			if (!opcode)
				opcode = in->readByte();
			for (int i = 0; i < opcode; i++, out++) {
				p[out] = value;
			}
		}
	}
}

void AnimFrame::decomp5(Common::SeekableReadStream *in, const FrameInfo &f) {
	byte *p = (byte *)_image.getBasePtr(0, 0);

	uint32 skip = f.initialSkip / 2;
	uint32 size = f.decompressedEndOffset / 2;
	//warning("skip: %d, %d", skip % 640, skip / 640);
	//warning("size: %d, %d", size % 640, size / 640);
	//assert (f.yPos1 == skip / 640);
	//assert (f.yPos2 == size / 640);

	in->seek((int)f.dataOffset);
	for (uint32 out = skip; out < size; ) {
		uint16 opcode = in->readByte();
		if (!(opcode & 0x1f)) {
			opcode = (uint16)((opcode << 3) + in->readByte());
			if (opcode & 0x400) {
				// skip these 10 bits
				out += (opcode & 0x3ff);
			} else {
				out += opcode + 2;
			}
		} else {
			byte value = opcode & 0x1f;
			opcode >>= 5;
			if (_palSize <= value)
				_palSize = value + 1;
			if (!opcode)
				opcode = in->readByte();
			for (int i = 0; i < opcode; i++, out++) {
				p[out] = value;
			}
		}
	}
}

void AnimFrame::decomp7(Common::SeekableReadStream *in, const FrameInfo &f) {
	byte *p = (byte *)_image.getBasePtr(0, 0);

	uint32 skip = f.initialSkip / 2;
	uint32 size = f.decompressedEndOffset / 2;
	//warning("skip: %d, %d", skip % 640, skip / 640);
	//warning("size: %d, %d", size % 640, size / 640);
	//assert (f.yPos1 == skip / 640);
	//assert (f.yPos2 == size / 640);

	uint32 numBlanks = 640 - (f.xPos2 - f.xPos1);

	in->seek((int)f.dataOffset);
	for (uint32 out = skip; out < size; ) {
		uint16 opcode = in->readByte();
		if (opcode & 0x80) {
			if (opcode & 0x40) {
				if (opcode & 0x20) {
					opcode &= 0x1f;
					out += numBlanks + opcode + 1;
				} else {
					opcode &= 0x1f;
					if (opcode & 0x10) {
						opcode = ((opcode & 0xf) << 8) + in->readByte();
						if (opcode & 0x800) {
							// skip these 11 bits
							out += (opcode & 0x7ff);
							continue;
						}
					}

					// skip these 4 bits
					out += opcode + 2;
				}
			} else {
				opcode &= 0x3f;
				byte value = in->readByte();
				if (_palSize <= value)
					_palSize = value + 1;
				for (int i = 0; i < opcode; i++, out++) {
					p[out] = value;
				}
			}
		} else {
			if (_palSize <= opcode)
				_palSize = opcode + 1;
			// set the given value
			p[out] = (byte)opcode;
			out++;
		}
	}
}

void AnimFrame::decompFF(Common::SeekableReadStream *in, const FrameInfo &f) {
	byte *p = (byte *)_image.getBasePtr(0, 0);

	uint32 skip = f.initialSkip / 2;
	uint32 size = f.decompressedEndOffset / 2;

	in->seek((int)f.dataOffset);
	for (uint32 out = skip; out < size; ) {
		uint16 opcode = in->readByte();

		if (opcode < 0x80) {
			if (_palSize <= opcode)
				_palSize = opcode + 1;
			// set the given value
			p[out] = (byte)opcode;
			out++;
		} else {
			if (opcode < 0xf0) {
				if (opcode < 0xe0) {
					// copy old part
					uint32 old = out + ((opcode & 0x7) << 8) + in->readByte() - 2048;
					opcode = ((opcode >> 3) & 0xf) + 3;
					for (int i = 0; i < opcode; i++, out++, old++) {
						p[out] = p[old];
					}
				} else {
					opcode = (opcode & 0xf) + 1;
					byte value = in->readByte();
					if (_palSize <= value)
						_palSize = value + 1;
					for (int i = 0; i < opcode; i++, out++) {
						p[out] = value;
					}
				}
			} else {
				out += ((opcode & 0xf) << 8) + in->readByte();
			}
		}
	}
}


//////////////////////////////////////////////////////////////////////////
//  SEQUENCE
//////////////////////////////////////////////////////////////////////////

Sequence::~Sequence() {
	reset();
}

void Sequence::reset() {
	_frames.clear();
	delete _stream;
	_stream = NULL;
}

Sequence *Sequence::load(Common::String name, Common::SeekableReadStream *stream, byte field30) {
	Sequence *sequence = new Sequence(name);

	if (!sequence->load(stream, field30)) {
		delete sequence;
		return NULL;
	}

	return sequence;
}

bool Sequence::load(Common::SeekableReadStream *stream, byte field30) {
	if (!stream)
		return false;

	// Reset data
	reset();

	_field30 = field30;

	// Keep stream for later decoding of sequence
	_stream = stream;

	// Read header to get the number of frames
	_stream->seek(0);
	uint32 numframes = _stream->readUint32LE();
	uint32 unknown = _stream->readUint32LE();
	debugC(3, kLastExpressDebugGraphics, "Number of frames in sequence: %d / unknown=0x%x", numframes, unknown);

	// Store frames information
	for (uint i = 0; i < numframes; i++) {

		// Move stream to start of frame
		_stream->seek((int32)(_sequenceHeaderSize + i * _sequenceFrameSize), SEEK_SET);
		if (_stream->eos())
			error("[Sequence::load] Couldn't seek to the current frame data");

		// Check if there is enough data
		if ((unsigned)(_stream->size() - _stream->pos()) < _sequenceFrameSize)
			error("[Sequence::load] The sequence frame does not have a valid header");

		FrameInfo info;
		info.read(_stream, true);
		_frames.push_back(info);
	}

	_isLoaded = true;

	return true;
}

FrameInfo *Sequence::getFrameInfo(uint16 index) {
	if (_frames.size() == 0)
		error("[Sequence::getFrameInfo] Trying to decode a sequence before loading its data");

	if (index > _frames.size() - 1)
		error("[Sequence::getFrameInfo] Invalid sequence frame requested: %d, max %d", index, _frames.size() - 1);

	return &_frames[index];
}

AnimFrame *Sequence::getFrame(uint16 index) {

	FrameInfo *frame = getFrameInfo(index);

	if (!frame)
		return NULL;

	// Skip "invalid" frames
	if (frame->compressionType == 0)
		return NULL;

	debugC(9, kLastExpressDebugGraphics, "Decoding sequence %s: frame %d / %d", _name.c_str(), index, _frames.size() - 1);

	return new AnimFrame(_stream, *frame);
}

//////////////////////////////////////////////////////////////////////////
// SequenceFrame
SequenceFrame::~SequenceFrame() {
	if (_dispose && _sequence) {
		delete _sequence;
	}

	_sequence = NULL;
}

Common::Rect SequenceFrame::draw(Graphics::Surface *surface) {
	if (!_sequence || _frame >= _sequence->count())
		return Common::Rect();

	AnimFrame *f = _sequence->getFrame(_frame);
	if (!f)
		return Common::Rect();

	Common::Rect rect = f->draw(surface);

	delete f;

	return rect;
}

bool SequenceFrame::setFrame(uint16 frame) {
	if (!_sequence)
		return false;

	if (frame < _sequence->count()) {
		_frame = frame;
		return true;
	} else
		return false;
}

bool SequenceFrame::nextFrame() {
	return setFrame(_frame + 1);
}

FrameInfo *SequenceFrame::getInfo() {
	if (!_sequence)
		error("[SequenceFrame::getInfo] Invalid sequence");

	return _sequence->getFrameInfo(_frame);
}

Common::String SequenceFrame::getName() {
	if (!_sequence)
		error("[SequenceFrame::getName] Invalid sequence");

	return _sequence->getName();
}

bool SequenceFrame::equal(const SequenceFrame *other) const {
	return _sequence->getName() == other->_sequence->getName() && _frame == other->_frame;
}

} // End of namespace LastExpress
