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

#include "made/graphics.h"

#include "common/endian.h"
#include "common/textconsole.h"
#include "common/debug.h"
#include "common/util.h"

#include "graphics/surface.h"

namespace Made {

byte ValueReader::readPixel() {
	byte value;
	if (_nibbleMode) {
		if (_nibbleSwitch) {
			value = (_buffer[0] >> 4) & 0x0F;
			_buffer++;
		} else {
			value = _buffer[0] & 0x0F;
		}
		_nibbleSwitch = !_nibbleSwitch;
	} else {
		value = _buffer[0];
		_buffer++;
	}
	return value;
}

uint16 ValueReader::readUint16() {
	uint16 value = READ_LE_UINT16(_buffer);
	_buffer += 2;
	return value;
}

uint32 ValueReader::readUint32() {
	uint32 value = READ_LE_UINT32(_buffer);
	_buffer += 4;
	return value;
}

void ValueReader::resetNibbleSwitch() {
	_nibbleSwitch = false;
}

void decompressImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize, byte cmdFlags, byte pixelFlags, byte maskFlags, bool deltaFrame) {

	const int offsets[] = {
		0, 1, 2, 3,
		320, 321, 322, 323,
		640, 641, 642, 643,
		960, 961, 962, 963
	};

	uint16 pitch = surface.pitch;
	uint16 width = surface.w;
	uint16 height = surface.h;

	byte *cmdBuffer = source + cmdOffs;
	ValueReader maskReader(source + maskOffs, (maskFlags & 2) != 0);
	ValueReader pixelReader(source + pixelOffs, (pixelFlags & 2) != 0);

	if ((maskFlags != 0) && (maskFlags != 2) && (pixelFlags != 0) && (pixelFlags != 2) && (cmdFlags != 0))
		error("decompressImage() Unsupported flags: cmdFlags = %02X; maskFlags = %02X, pixelFlags = %02X", cmdFlags, maskFlags, pixelFlags);

	byte *destPtr = (byte *)surface.getPixels();

	byte lineBuf[640 * 4];
	byte bitBuf[40];

	int bitBufLastOfs = (((lineSize + 1) >> 1) << 1) - 2;
	int bitBufLastCount = ((width + 3) >> 2) & 7;
	if (bitBufLastCount == 0)
		bitBufLastCount = 8;

	while (height > 0) {

		int drawDestOfs = 0;

		memset(lineBuf, 0, sizeof(lineBuf));

		memcpy(bitBuf, cmdBuffer, lineSize);
		cmdBuffer += lineSize;

		for (uint16 bitBufOfs = 0; bitBufOfs < lineSize; bitBufOfs += 2) {

			uint16 bits = READ_LE_UINT16(&bitBuf[bitBufOfs]);

			int bitCount;
			if (bitBufOfs == bitBufLastOfs)
				bitCount = bitBufLastCount;
			else
				bitCount = 8;

			for (int curCmd = 0; curCmd < bitCount; curCmd++) {
				int cmd = bits & 3;
				bits >>= 2;

				byte pixels[4];
				uint32 mask;

				switch (cmd) {

				case 0:
					pixels[0] = pixelReader.readPixel();
					for (int i = 0; i < 16; i++)
						lineBuf[drawDestOfs + offsets[i]] = pixels[0];
					break;

				case 1:
					pixels[0] = pixelReader.readPixel();
					pixels[1] = pixelReader.readPixel();
					mask = maskReader.readUint16();
					for (int i = 0; i < 16; i++) {
						lineBuf[drawDestOfs + offsets[i]] = pixels[mask & 1];
						mask >>= 1;
					}
					break;

				case 2:
					pixels[0] = pixelReader.readPixel();
					pixels[1] = pixelReader.readPixel();
					pixels[2] = pixelReader.readPixel();
					pixels[3] = pixelReader.readPixel();
					mask = maskReader.readUint32();
					for (int i = 0; i < 16; i++) {
						lineBuf[drawDestOfs + offsets[i]] = pixels[mask & 3];
						mask >>= 2;
					}
					break;

				case 3:
					if (!deltaFrame) {
						// For EGA pictures: Pixels are read starting from a new byte
						maskReader.resetNibbleSwitch();
						// Yes, it reads from maskReader here
						for (int i = 0; i < 16; i++)
							lineBuf[drawDestOfs + offsets[i]] = maskReader.readPixel();
					}
					break;

				default:
					break;
				}

				drawDestOfs += 4;

			}

		}

		if (deltaFrame) {
			for (int y = 0; y < 4 && height > 0; y++, height--) {
				for (int x = 0; x < width; x++) {
					if (lineBuf[x + y * 320] != 0)
						*destPtr = lineBuf[x + y * 320];
					destPtr++;
				}
				destPtr += pitch - width;
			}
		} else {
			for (int y = 0; y < 4 && height > 0; y++, height--) {
				memcpy(destPtr, &lineBuf[y * 320], width);
				destPtr += pitch;
			}
		}

	}

}

void decompressMovieImage(byte *source, Graphics::Surface &surface, uint16 cmdOffs, uint16 pixelOffs, uint16 maskOffs, uint16 lineSize) {

	uint16 width = surface.w;
	uint16 height = surface.h;
	uint16 bx = 0, by = 0, bw = ((width + 3) / 4) * 4;

	byte *cmdBuffer = source + cmdOffs;
	byte *maskBuffer = source + maskOffs;
	byte *pixelBuffer = source + pixelOffs;

	byte *destPtr = (byte *)surface.getPixels();

	byte bitBuf[40];

	int bitBufLastOfs = (((lineSize + 1) >> 1) << 1) - 2;
	int bitBufLastCount = ((width + 3) >> 2) & 7;
	if (bitBufLastCount == 0)
		bitBufLastCount = 8;

	debug(1, "width = %d; bw = %d", width, bw);

	while (height > 0) {

		memcpy(bitBuf, cmdBuffer, lineSize);
		cmdBuffer += lineSize;

		for (uint16 bitBufOfs = 0; bitBufOfs < lineSize; bitBufOfs += 2) {

			uint16 bits = READ_LE_UINT16(&bitBuf[bitBufOfs]);

			int bitCount;
			if (bitBufOfs == bitBufLastOfs)
				bitCount = bitBufLastCount;
			else
				bitCount = 8;

			for (int curCmd = 0; curCmd < bitCount; curCmd++) {
				uint cmd = bits & 3;
				bits >>= 2;

				byte pixels[4], block[16];
				uint32 mask;

				switch (cmd) {

				case 0:
					pixels[0] = *pixelBuffer++;
					for (int i = 0; i < 16; i++)
						block[i] = pixels[0];
					break;

				case 1:
					pixels[0] = *pixelBuffer++;
					pixels[1] = *pixelBuffer++;
					mask = READ_LE_UINT16(maskBuffer);
					maskBuffer += 2;
					for (int i = 0; i < 16; i++) {
						block[i] = pixels[mask & 1];
						mask >>= 1;
					}
					break;

				case 2:
					pixels[0] = *pixelBuffer++;
					pixels[1] = *pixelBuffer++;
					pixels[2] = *pixelBuffer++;
					pixels[3] = *pixelBuffer++;
					mask = READ_LE_UINT32(maskBuffer);
					maskBuffer += 4;
					for (int i = 0; i < 16; i++) {
						block[i] = pixels[mask & 3];
						mask >>= 2;
					}
					break;

				case 3:
					break;

				default:
					break;
				}

				if (cmd != 3) {
					uint16 blockPos = 0;
					uint32 maxW = MIN(4, surface.w - bx);
					uint32 maxH = (MIN(4, surface.h - by) + by) * width;
					for (uint32 yc = by * width; yc < maxH; yc += width) {
						for (uint32 xc = 0; xc < maxW; xc++) {
							destPtr[(bx + xc) + yc] = block[xc + blockPos];
						}
						blockPos += 4;
					}
				}

				bx += 4;
				if (bx >= bw) {
					bx = 0;
					by += 4;
				}

			}

		}

		height -= 4;

	}

}

} // End of namespace Made
