/* 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 "lure/decode.h"
#include "lure/lure.h"
#include "lure/memory.h"
#include "lure/luredefs.h"
#include "common/endian.h"

namespace Lure {

/*--------------------------------------------------------------------------*/
/* PictureDecoder class                                                     */
/*                                                                          */
/* Provides the functionality for decoding screens                          */
/*--------------------------------------------------------------------------*/

/**
 * Write a single byte to the output buffer
 */
void PictureDecoder::writeByte(MemoryBlock *dest, byte v) {
	if (outputOffset == dest->size())
		error("Decoded data exceeded allocated output buffer size");
	dest->data()[outputOffset++] = v;
}

/**
 * Writes out a specified byte a given number of times to the output
 */
void PictureDecoder::writeBytes(MemoryBlock *dest, byte v, uint16 numBytes) {
	if (outputOffset + numBytes > dest->size())
		error("Decoded data exceeded allocated output buffer size");
	dest->setBytes(v, outputOffset, numBytes);
	outputOffset += numBytes;
}

/**
 * Gets a byte from the compressed source using the first data pointer
 */
byte PictureDecoder::DSSI(bool incr) {
	if (dataPos > dataIn->size())
		error("PictureDecoder went beyond end of source data");

	byte result = (dataPos == dataIn->size()) ? 0 :
		dataIn->data()[dataPos];
	if (incr) ++dataPos;
	return result;
}

/**
 * Gets a byte from the compressed source using the second data pointer
 */
byte PictureDecoder::ESBX(bool incr) {
	if (dataPos2 >= dataIn->size())
		error("PictureDecoder went beyond end of source data");

	byte result = dataIn->data()[dataPos2];
	if (incr) ++dataPos2;
	return result;
}

void PictureDecoder::decrCtr() {
	--CL;
	if (CL == 0) {
		CH = ESBX();
		CL = 8;
	}
}

bool PictureDecoder::shlCarry() {
	bool result = (CH & 0x80) != 0;
	CH <<= 1;
	return result;
}

// decode
// Decodes a compressed Lure of the Temptress screen

MemoryBlock *PictureDecoder::decode(MemoryBlock *src, uint32 maxOutputSize) {
	bool isEGA = LureEngine::getReference().isEGA();
	return isEGA ? egaDecode(src, maxOutputSize) : vgaDecode(src, maxOutputSize);
}

// egaDecode
// Takes care of decoding a compressed EGA screen

#define READ_BIT_DX { bitFlag = (dx & 0x8000) != 0; dx <<= 1; if (--bitCtr == 0) { dx = (dx & 0xff00) | DSSI(); bitCtr = 8; } }
#define READ_BITS(loops) for (int ctr = 0; ctr < loops; ++ctr) READ_BIT_DX

MemoryBlock *PictureDecoder::egaDecode(MemoryBlock *src, uint32 maxOutputSize) {
	MemoryBlock *dest = Memory::allocate(maxOutputSize);
	byte popTable[32 + 128];
	uint8 al;
	bool bitFlag;

	// Set up initial states
	dataIn = src;
	dataPos = 6;

	uint16 dx = READ_BE_UINT16(src->data() + dataPos);
	dataPos += sizeof(uint16);
	int bitCtr = 8;

	// Decode the color popularity table

	for (int nibbleCtr = 0; nibbleCtr < 32; ++nibbleCtr) {
		for (int byteCtr = 0; byteCtr < 128; byteCtr += 32) {
			popTable[nibbleCtr + byteCtr] = dx >> 11;
			READ_BITS(5);
		}
	}

	// ok, on to the real thing

	outputOffset = 0;
	al = dx >> 11;
	writeByte(dest, al);
	READ_BITS(5);

	uint16 tableOffset = al;
	uint8 v = 0;

	for (;;) {
		READ_BIT_DX

		if (!bitFlag) {
			// Get the favorite color
			v = popTable[tableOffset];

		} else {

			READ_BIT_DX

			if (bitFlag) {
				// Get another bit
				READ_BIT_DX

				if (bitFlag) {
					// We have no favorite. Could this be a repeat?
					al = dx >> 11;
					READ_BITS(5);

					if (al == popTable[tableOffset]) {
						// Repeat 16 bits
						uint16 numLoops = dx & 0xff00;
						READ_BITS(8);
						numLoops |= (dx >> 8);
						READ_BITS(8);

						if (numLoops == 0)
							// Finished decoding
							break;
						writeBytes(dest, al, numLoops);
						continue;

					} else if (al == popTable[tableOffset + 32]) {
						// Repeat 8 bits
						writeBytes(dest, tableOffset, dx >> 8);
						READ_BITS(8);
						continue;

					} else if (al == popTable[tableOffset + 64]) {
						// Repeat 6 bits
						writeBytes(dest, tableOffset, dx >> 10);
						READ_BITS(6);
						continue;

					} else if (al == popTable[tableOffset + 96]) {
						// Repeat 5 bits
						writeBytes(dest, tableOffset, dx >> 11);
						READ_BITS(5);
						continue;

					} else {
						// It's a new color
						v = al;
					}

				} else {
					// Fourth favorite
					v = popTable[tableOffset + 96];
				}

			} else {
				// Get another bit
				READ_BIT_DX

				if (bitFlag) {
					// Third favorite
					v = popTable[tableOffset + 64];
				} else {
					// Second favorite
					v = popTable[tableOffset + 32];
				}
			}
		}

		tableOffset = v;
		writeByte(dest, v);
	}

	// Resize the output to be the number of outputed bytes and return it
	if (outputOffset < dest->size()) dest->reallocate(outputOffset);

	return dest;
}

// vgaDecode
// Takes care of decoding a compressed vga screen

MemoryBlock *PictureDecoder::vgaDecode(MemoryBlock *src, uint32 maxOutputSize) {
	MemoryBlock *dest = Memory::allocate(maxOutputSize);

	// Set up initial states
	dataIn = src;
	outputOffset = 0;
	dataPos = READ_LE_UINT32(dataIn->data() + 0x400);
	dataPos2 = 0x404;

	CH = ESBX();
	CL = 9;

	// Main decoding loop
	bool loopFlag = true;
	while (loopFlag) {
		AL = DSSI();
		writeByte(dest, AL);
		BP = ((uint16) AL) << 2;

		// Inner loop
		for (;;) {
			decrCtr();
			if (shlCarry()) {
				decrCtr();
				if (shlCarry()) {
					decrCtr();
					if (shlCarry())
						break;

					AL = dataIn->data()[BP + 3];
				} else {
					decrCtr();
					if (shlCarry())
						AL = dataIn->data()[BP + 2];
					else
						AL = dataIn->data()[BP + 1];
				}
			} else {
				decrCtr();
				if (shlCarry()) {
					AL = (byte) (BP >> 2);
					AH = DSSI();
					if (AH == 0) {
						AL = DSSI();
						if (AL == 0) {
							// Finally done
							loopFlag = false;
							break;
						} else {
							// Keep going
							continue;
						}
					} else {
						// Write out byte sequence
						writeBytes(dest, AL, AH);
						continue;
					}
				} else {
					AL = dataIn->data()[BP];
				}
			}

			// Write out the next byte
			writeByte(dest, AL);
			BP = ((uint16) AL) << 2;
		}
	}

	// Resize the output to be the number of outputed bytes and return it
	if (outputOffset < dest->size()) dest->reallocate(outputOffset);
	return dest;
}

/*--------------------------------------------------------------------------*/
/* AnimationDecoder class                                                   */
/*                                                                          */
/* Provides the functionality for decoding animations                       */
/*--------------------------------------------------------------------------*/

// The code below is responsible for decompressing the pixel data
// for an animation. I'm not currently sure of the of the exact details
// of the compression format - for now I've simply copied the code
// from the executable

void AnimationDecoder::rcl(uint16 &value, bool &carry) {
	bool result = (value & 0x8000) != 0;
	value = (value << 1) + (carry ? 1 : 0);
	carry = result;
}

#define GET_BYTE currData = (currData & 0xff00) | *pSrc++
#define BX_VAL(x) *((byte *) (dest->data() + tableOffset + x))
#define SET_HI_BYTE(x,v) x = (x & 0xff) | ((v) << 8);
#define SET_LO_BYTE(x,v) x = (x & 0xff00) | (v);

void AnimationDecoder::decode_data_2(MemoryBlock *src, byte *&pSrc, uint16 &currData,
									 uint16 &bitCtr, uint16 &dx, bool &carry) {
	SET_HI_BYTE(dx, currData >> 8);

	for (int v = 0; v < 8; ++v) {
		rcl(currData, carry);
		if (--bitCtr == 0) {
			uint32 offset = (uint32) (pSrc - src->data());
			if (offset >= src->size())
				// Beyond end of source, so read in a 0 value
				currData &= 0xff00;
			else
				GET_BYTE;
			bitCtr = 8;
		}
	}
}

uint32 AnimationDecoder::decode_data(MemoryBlock *src, MemoryBlock *dest, uint32 srcPos) {
	byte *pSrc = src->data() + srcPos;
	byte *pDest = dest->data();
	uint16 v;
	bool carry = false;
	uint16 currData, bitCtr, dx;
	byte tableOffset;
	uint16 tempReg1, tempReg2;

	// Handle splitting up 16 bytes into individual nibbles
	for (int numBytes = 0; numBytes < 16; ++numBytes, ++pDest) {
		// Split up next byte to pDest and pDest+0x10
		currData = *pSrc++;
		*(pDest + 0x10) = currData & 0xf;
		*pDest = (currData >> 4) & 0xf;

		// Split up next byte to pDest+0x20 and pDest+0x30
		currData = *pSrc++;
		*(pDest + 0x30) = currData & 0xf;
		*(pDest + 0x20) = (currData >> 4) & 0xf;
	}

	pDest = (byte *) (dest->data() + 0x40);
	currData = READ_BE_UINT16(pSrc);
	pSrc += sizeof(uint16);

	bitCtr = 4;
	*pDest = (currData >> 8) & 0xf0;
	tableOffset = currData >> 12;
	currData <<= 4;
	dx = 1;

	// Main loop
	bool loopFlag = true;
	while (loopFlag) {
		for (;;) {
			carry = false;
			rcl(currData, carry);
			if (--bitCtr == 0) {
				GET_BYTE;
				bitCtr = 8;
			}
			if (!carry) {
				tableOffset = BX_VAL(0);
				break;
			}

			rcl(currData, carry);
			if (--bitCtr == 0) {
				GET_BYTE;
				bitCtr = 8;
			}
			if (!carry) {
				rcl(currData, carry);
				if (--bitCtr == 0) {
					GET_BYTE;
					bitCtr = 8;
				}

				if (!carry) {
					tableOffset = BX_VAL(0x10);
				} else {
					tableOffset = BX_VAL(0x20);
				}
				break;
			}

			rcl(currData, carry);
			if (--bitCtr == 0) {
				GET_BYTE;
				bitCtr = 8;
			}
			if (!carry) {
				tableOffset = BX_VAL(0x30);
				break;
			}

			SET_HI_BYTE(dx, currData >> 12);
			carry = false;
			for (int ctr = 0; ctr < 4; ++ctr) {
				rcl(currData, carry);
				if (--bitCtr == 0) {
					GET_BYTE;
					bitCtr = 8;
				}
			}

			byte dxHigh = dx >> 8;
			if (dxHigh == BX_VAL(0)) {
				tempReg1 = bitCtr;
				tempReg2 = dx;
				decode_data_2(src, pSrc, currData, bitCtr, dx, carry);

				SET_LO_BYTE(dx, dx >> 8);
				decode_data_2(src, pSrc, currData, bitCtr, dx, carry);
				SET_HI_BYTE(bitCtr, dx & 0xff);
				SET_LO_BYTE(bitCtr, dx >> 8);
				dx = tempReg2;

				if (bitCtr == 0) {
					// End of decompression
					loopFlag = false;
					break;
				}
			} else if (dxHigh == BX_VAL(0x10)) {
				tempReg1 = bitCtr;
				decode_data_2(src, pSrc, currData, bitCtr, dx, carry);
				bitCtr = dx >> 8;

			} else if (dxHigh == BX_VAL(0x20)) {
				SET_HI_BYTE(dx, currData >> 10);

				for (v = 0; v < 6; ++v) {
					rcl(currData, carry);
					if (--bitCtr == 0) {
						GET_BYTE;
						bitCtr = 8;
					}
				}

				tempReg1 = bitCtr;
				bitCtr = dx >> 8;

			} else if (dxHigh == BX_VAL(0x30)) {
				SET_HI_BYTE(dx, currData >> 11);

				for (v = 0; v < 5; ++v) {
					rcl(currData, carry);
					if (--bitCtr == 0) {
						GET_BYTE;
						bitCtr = 8;
					}
				}

				tempReg1 = bitCtr;
				bitCtr = dx >> 8;

			} else {
				tableOffset = dx >> 8;
				break;
			}

			if ((dx & 1) == 1) {
				*pDest++ |= tableOffset;
				--bitCtr;
				dx &= 0xfffe;
			}

			SET_HI_BYTE(dx, tableOffset << 4);
			tableOffset |= dx >> 8;

			v = bitCtr >> 1;
			while (v-- > 0) *pDest++ = tableOffset;

			bitCtr &= 1;
			if (bitCtr != 0) {
				*pDest = tableOffset & 0xf0;
				dx |= 1; //dx.l
			}

			bitCtr = tempReg1;
			tableOffset &= 0x0f;
		}

		if (loopFlag) {
			dx ^= 1;
			if ((dx & 1) != 0) {
				SET_HI_BYTE(dx, tableOffset << 4);
				*pDest = dx >> 8;
			} else {
				*pDest++ |= tableOffset;
			}
		}
	}

	// Return number of bytes written
	return pDest - dest->data();
}

} // End of namespace Lure
