/* 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 code from xoreos https://github.com/DrMcCoy/xoreos/
 * relicensed under GPLv2+ with permission from DrMcCoy and clone2727
 */

#include "image/tga.h"

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

namespace Image {

TGADecoder::TGADecoder() {
	_colorMapSize = 0;
	_colorMapOrigin = 0;
	_colorMapLength = 0;
	_colorMapEntryLength = 0;
	_colorMap = NULL;
}

TGADecoder::~TGADecoder() {
	destroy();
}

void TGADecoder::destroy() {
	_surface.free();
	delete[] _colorMap;
}

bool TGADecoder::loadStream(Common::SeekableReadStream &tga) {
	destroy();

	byte imageType, pixelDepth;
	bool success;
	success = readHeader(tga, imageType, pixelDepth);
	if (success) {
		switch (imageType) {
		case TYPE_BW:
		case TYPE_TRUECOLOR:
			success = readData(tga, imageType, pixelDepth);
			break;
		case TYPE_RLE_BW:
		case TYPE_RLE_TRUECOLOR:
		case TYPE_RLE_CMAP:
			success = readDataRLE(tga, imageType, pixelDepth);
			break;
		case TYPE_CMAP:
			success = readDataColorMapped(tga, imageType, pixelDepth);
			break;
		default:
			success = false;
			break;
		}
	}
	if (tga.err() || !success) {
		warning("Failed reading TGA-file");
		return false;
	}
	return success;
}

bool TGADecoder::readHeader(Common::SeekableReadStream &tga, byte &imageType, byte &pixelDepth) {
	if (!tga.seek(0)) {
		warning("Failed reading TGA-file");
		return false;
	}

	// TGAs have an optional "id" string in the header
	uint32 idLength = tga.readByte();

	// Number of colors in the color map / palette
	int hasColorMap = tga.readByte();

	// Image type. See header for numeric constants
	imageType = tga.readByte();

	switch (imageType) {
	case TYPE_CMAP:
	case TYPE_TRUECOLOR:
	case TYPE_BW:
	case TYPE_RLE_CMAP:
	case TYPE_RLE_TRUECOLOR:
	case TYPE_RLE_BW:
		break;
	default:
		warning("Unsupported image type: %d", imageType);
		return false;
	}

	// Color map specifications
	if (hasColorMap == 0) {
		tga.skip(5);
	} else {
		_colorMapOrigin = tga.readUint16LE();
		_colorMapLength = tga.readUint16LE();
		_colorMapEntryLength = tga.readByte();
	}
	// Origin-defintions
	tga.skip(2 + 2);

	// Image dimensions
	_surface.w = tga.readUint16LE();
	_surface.h = tga.readUint16LE();

	// Bits per pixel
	pixelDepth = tga.readByte();
	_surface.format.bytesPerPixel = pixelDepth / 8;

	// Image descriptor
	byte imgDesc = tga.readByte();
	int attributeBits = imgDesc & 0x0F;
	assert((imgDesc & 0x10) == 0);
	_originTop = (imgDesc & 0x20);

	// Interleaving is not handled at this point
	//int interleave = (imgDesc & 0xC);
	if (imageType == TYPE_CMAP || imageType == TYPE_RLE_CMAP) {
		if (pixelDepth == 8) {
			_format = Graphics::PixelFormat::createFormatCLUT8();
		} else {
			warning("Unsupported index-depth: %d", pixelDepth);
			return false;
		}
	} else if (imageType == TYPE_TRUECOLOR || imageType == TYPE_RLE_TRUECOLOR) {
		if (pixelDepth == 24) {
			_format = Graphics::PixelFormat(3, 8, 8, 8, 0, 16, 8, 0, 0);
		} else if (pixelDepth == 32) {
			// HACK: According to the spec, attributeBits should determine the amount
			// of alpha-bits, however, as the game files that use this decoder seems
			// to ignore that fact, we force the amount to 8 for 32bpp files for now.
			_format = Graphics::PixelFormat(4, 8, 8, 8, /* attributeBits */ 8, 16, 8, 0, 24);
		} else if (pixelDepth == 16) {
			// 16bpp TGA is ARGB1555
			_format = Graphics::PixelFormat(2, 5, 5, 5, attributeBits, 10, 5, 0, 15);
		} else {
			warning("Unsupported pixel depth: %d, %d", imageType, pixelDepth);
			return false;
		}
	} else if (imageType == TYPE_BW || imageType == TYPE_RLE_BW) {
		if (pixelDepth == 8) {
			_format = Graphics::PixelFormat(4, 8, 8, 8, 0, 16, 8, 0, 0);
		} else {
			warning("Unsupported pixel depth: %d, %d", imageType, pixelDepth);
			return false;
		}

	} else {
		warning("Unsupported image type: %d", imageType);
		return false;
	}

	// Skip the id string
	tga.skip(idLength);

	if (hasColorMap) {
		return readColorMap(tga, imageType, pixelDepth);
	}
	return true;
}

bool TGADecoder::readColorMap(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
	_colorMap = new byte[3 * _colorMapLength];
	for (int i = 0; i < _colorMapLength * 3; i += 3) {
		byte r, g, b;
		if (_colorMapEntryLength == 32) {
			byte a;
			Graphics::PixelFormat format(4, 8, 8, 8, 0, 16, 8, 0, 24);
			uint32 color = tga.readUint32LE();
			format.colorToARGB(color, a, r, g, b);
		} else if (_colorMapEntryLength == 24) {
			r = tga.readByte();
			g = tga.readByte();
			b = tga.readByte();
		} else if (_colorMapEntryLength == 16) {
			byte a;
			Graphics::PixelFormat format(2, 5, 5, 5, 0, 10, 5, 0, 15);
			uint16 color = tga.readUint16LE();
			format.colorToARGB(color, a, r, g, b);
		} else {
			warning("Unsupported image type: %d", imageType);
			r = g = b = 0;
		}
#ifdef SCUMM_LITTLE_ENDIAN
		_colorMap[i] = r;
		_colorMap[i + 1] = g;
		_colorMap[i + 2] = b;
#else
		_colorMap[i] = b;
		_colorMap[i + 1] = g;
		_colorMap[i + 2] = r;
#endif
	}
	return true;
}

// Additional information found from http://paulbourke.net/dataformats/tga/
// With some details from the link referenced in the header.
bool TGADecoder::readData(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
	// TrueColor
	if (imageType == TYPE_TRUECOLOR) {
		_surface.create(_surface.w, _surface.h, _format);

		if (pixelDepth == 16) {
			for (int i = 0; i < _surface.h; i++) {
				uint16 *dst;
				if (!_originTop) {
					dst = (uint16 *)_surface.getBasePtr(0, _surface.h - i - 1);
				} else {
					dst = (uint16 *)_surface.getBasePtr(0, i);
				}
				for (int j = 0; j < _surface.w; j++) {
					*dst++ = tga.readUint16LE();
				}
			}
		} else if (pixelDepth == 32) {
			for (int i = 0; i < _surface.h; i++) {
				uint32 *dst;
				if (!_originTop) {
					dst = (uint32 *)_surface.getBasePtr(0, _surface.h - i - 1);
				} else {
					dst = (uint32 *)_surface.getBasePtr(0, i);
				}
				for (int j = 0; j < _surface.w; j++) {
					*dst++ = tga.readUint32LE();
				}
			}
		} else if (pixelDepth == 24) {
			for (int i = 0; i < _surface.h; i++) {
				byte *dst;
				if (!_originTop) {
					dst = (byte *)_surface.getBasePtr(0, _surface.h - i - 1);
				} else {
					dst = (byte *)_surface.getBasePtr(0, i);
				}
				for (int j = 0; j < _surface.w; j++) {
					byte r = tga.readByte();
					byte g = tga.readByte();
					byte b = tga.readByte();
#ifdef SCUMM_LITTLE_ENDIAN
					*dst++ = r;
					*dst++ = g;
					*dst++ = b;
#else
					*dst++ = b;
					*dst++ = g;
					*dst++ = r;
#endif
				}
			}
		}
		// Black/White
	} else if (imageType == TYPE_BW) {
		_surface.create(_surface.w, _surface.h, _format);

		byte *data  = (byte *)_surface.getPixels();
		uint32 count = _surface.w * _surface.h;

		while (count-- > 0) {
			byte g = tga.readByte();
			*data++ = g;
			*data++ = g;
			*data++ = g;
			*data++ = g;
		}
	}
	return true;
}

bool TGADecoder::readDataColorMapped(Common::SeekableReadStream &tga, byte imageType, byte indexDepth) {
	// Color-mapped
	if (imageType == TYPE_CMAP) {
		_surface.create(_surface.w, _surface.h, _format);
		if (indexDepth == 8) {
			for (int i = 0; i < _surface.h; i++) {
				byte *dst;
				if (!_originTop) {
					dst = (byte *)_surface.getBasePtr(0, _surface.h - i - 1);
				} else {
					dst = (byte *)_surface.getBasePtr(0, i);
				}
				for (int j = 0; j < _surface.w; j++) {
					byte index = tga.readByte();
					*dst++ = index;
				}
			}
		} else if (indexDepth == 16) {
			warning("16 bit indexes not supported");
			return false;
		}
	} else {
		return false;
	}
	return true;
}

bool TGADecoder::readDataRLE(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
	// RLE-TrueColor / RLE-Black/White
	if (imageType == TYPE_RLE_TRUECOLOR || imageType == TYPE_RLE_BW || imageType == TYPE_RLE_CMAP) {
		_surface.create(_surface.w, _surface.h, _format);
		uint32 count = _surface.w * _surface.h;
		byte *data = (byte *)_surface.getPixels();

		while (count > 0) {
			uint32 header = tga.readByte();
			byte type = (header & 0x80) >> 7;
			uint32 rleCount = (header & 0x7F) + 1;

			// RLE-packet
			if (type == 1) {
				if (pixelDepth == 32 && imageType == TYPE_RLE_TRUECOLOR) {
					uint32 color = tga.readUint32LE();
					while (rleCount-- > 0) {
						*((uint32 *)data) = color;
						data += 4;
						count--;
					}
				} else if (pixelDepth == 24 && imageType == TYPE_RLE_TRUECOLOR) {
					byte r = tga.readByte();
					byte g = tga.readByte();
					byte b = tga.readByte();
					while (rleCount-- > 0) {
#ifdef SCUMM_LITTLE_ENDIAN
						*data++ = r;
						*data++ = g;
						*data++ = b;
#else
						*data++ = b;
						*data++ = g;
						*data++ = r;
#endif
						count--;
					}
				} else if (pixelDepth == 16 && imageType == TYPE_RLE_TRUECOLOR) {
					const uint16 rgb = tga.readUint16LE();
					while (rleCount-- > 0) {
						*((uint16 *)data) = rgb;
						data += 2;
						count--;
					}
				} else if (pixelDepth == 8 && imageType == TYPE_RLE_BW) {
					byte color = tga.readByte();
					while (rleCount-- > 0) {
						*data++ = color;
						*data++ = color;
						*data++ = color;
						*data++ = color;
						count--;
					}
				} else if (pixelDepth == 8 && imageType == TYPE_RLE_CMAP) {
					byte index = tga.readByte();
					while (rleCount-- > 0) {
						*data++ = index;
						count--;
					}
				} else {
					warning("Unhandled pixel-depth for image-type 10");
					return false;
				}
				// Raw-packet
			} else if (type == 0) {
				if (pixelDepth == 32 && imageType == TYPE_RLE_TRUECOLOR) {
					while (rleCount-- > 0) {
						uint32 color = tga.readUint32LE();
						*((uint32 *)data) = color;
						data += 4;
						count--;
					}
				} else if (pixelDepth == 24 && imageType == TYPE_RLE_TRUECOLOR) {
					while (rleCount-- > 0) {
						byte r = tga.readByte();
						byte g = tga.readByte();
						byte b = tga.readByte();
#ifdef SCUMM_LITTLE_ENDIAN
						*data++ = r;
						*data++ = g;
						*data++ = b;
#else
						*data++ = b;
						*data++ = g;
						*data++ = r;
#endif
						count--;
					}
				} else if (pixelDepth == 16 && imageType == TYPE_RLE_TRUECOLOR) {
					while (rleCount-- > 0) {
						*((uint16 *)data) = tga.readUint16LE();
						data += 2;
						count--;
					}
				} else if (pixelDepth == 8 && imageType == TYPE_RLE_BW) {
					while (rleCount-- > 0) {
						byte color = tga.readByte();
						*data++ = color;
						*data++ = color;
						*data++ = color;
						*data++ = color;
						count--;
					}
				} else if (pixelDepth == 8 && imageType == TYPE_RLE_CMAP) {
					while (rleCount-- > 0) {
						byte index = tga.readByte();
						*data++ = index;
						count--;
					}
				} else {
					warning("Unhandled pixel-depth for image-type 10");
					return false;
				}
			} else {
				warning("Unknown header for RLE-packet %d", type);
				return false;
			}
		}
	} else {
		return false;
	}

	// If it's a bottom origin image, we need to vertically flip the image
	if (!_originTop) {
		byte *tempLine = new byte[_surface.pitch];
		byte *line1 = (byte *)_surface.getBasePtr(0, 0);
		byte *line2 = (byte *)_surface.getBasePtr(0, _surface.h - 1);

		for (int y = 0; y < (_surface.h / 2); ++y, line1 += _surface.pitch, line2 -= _surface.pitch) {
			Common::copy(line1, line1 + _surface.pitch, tempLine);
			Common::copy(line2, line2 + _surface.pitch, line1);
			Common::copy(tempLine, tempLine + _surface.pitch, line2);
		}

		delete[] tempLine;
	}

	return true;
}

} // End of namespace Image
