/*
  File name: farread.cpp
  Copyright: (C) 2005 - 2009 Tomasz Kazmierczak

  Creation date: 27.07.2005
  Last modification date: 12.06.2009

  This file is part of Cytadela

 *   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 "farread.h"
#include "CytadelaDefinitions.h"
#include "cmf/fileio.h"
#include <stdio.h>
#include <memory.h>

class TGACMInfo {
public:
	uint16_t  firstCMEntry;    //position of first color map entry
	uint16_t  cmLength;       //how many entries in color map
	uint8_t   cmEntrySize;   //size of one color map entry
};

class TGAProps {
public:
	uint16_t  originX;        //image origin coords
	uint16_t  originY;
	uint16_t  width;
	uint16_t  height;
	uint8_t   bitsPerPixel;
	uint8_t   imgDescriptor; //image desctiption offset
};

uint8_t *readTGAData(FILE *file, uint32_t &width, uint32_t &height, uint32_t &bpp, uint32_t &size)
{
	//id field length
	uint8_t idFieldLength;
	if(fread(&idFieldLength, 1, 1, file) < 1)
		return 0;

	uint8_t colorMapType;
	if(fread(&colorMapType, 1, 1, file) < 1)
		return 0;

	uint8_t imageType;
	if(fread(&imageType, 1, 1, file) < 1)
		return 0;

	//check image type
	switch(imageType) {
		//if one of these - ok
		case 2:
		case 3:
		case 10:
		case 11:
			break;

		//if any other - we don't read
		default: {
			fprintf(stderr, "Error: unsupported TGA file type\n");
			return 0;
		}
	}

	TGACMInfo cmInfo;
	fioRead(cmInfo.firstCMEntry, file);
	fioRead(cmInfo.cmLength, file);
	if(not fioRead(cmInfo.cmEntrySize, file))
		return 0;
	//check the size of a single color map entry
	if(colorMapType > 0) {
		switch(cmInfo.cmEntrySize) {
			case 15:
			case 16:
			case 24:
			case 32:
				break;

			default: {
				fprintf(stderr, "Error: unsupported TGA file type\n");
				return 0;
			}
		}
	}

	TGAProps properties;
	fioRead(properties.originX, file);
	fioRead(properties.originY, file);
	fioRead(properties.width, file);
	fioRead(properties.height, file);
	fioRead(properties.bitsPerPixel, file);
	if(not fioRead(properties.imgDescriptor, file))
		return 0;
	//check the image color depth
	switch(properties.bitsPerPixel) {
		case 8:
		case 16:
		case 24:
		case 32:
			break;

		default: {
			fprintf(stderr, "Error: unsupported TGA file type\n");
			return 0;
		}
	}

	//skip the id filed
	fseek(file, idFieldLength, SEEK_CUR);

	//skip the color map (if any)
	if(colorMapType == 1) {
		//calculate color map size
		uint32_t cmSize = cmInfo.cmLength;
		if(cmInfo.cmEntrySize == 15)
			cmSize *= 2;
		else cmSize *= (cmInfo.cmEntrySize / 8);

		fseek(file, cmSize, SEEK_CUR);
	}

	//data size (if the image is in grayscale then the bytes count is equal to the pixels count)
	uint32_t bitsSize = properties.width * properties.height;
	//if there is no color map, multiply the size by bytes per pixel count
	if((imageType == 2) or (imageType == 10))
		bitsSize *= (properties.bitsPerPixel / 8);

	//memory for pixels
	uint8_t *readData = new uint8_t[bitsSize];
	if(readData == 0) {
		fprintf(stderr, "Error: out of memory\n");
		return 0;
	}

	//read the data
	switch(imageType) {
		//uncompressed image w/o color map
		case 2:
		case 3: {
			if(fread(readData, 1, bitsSize, file) < bitsSize) {
				delete[] readData;
				return 0;
			}
			break;
		}

		//compressed (RLE) w/o color map
		case 10:
		case 11: {
			//place for a packet header
			uint8_t packetH;

			//bytes counter
			uint32_t i = 0;
			//read packets till all bytes are copied
			while(i < bitsSize) {
				//read the packet header
				if((fread(&packetH, 1, 1, file)) < 1) {
					delete[] readData;
					return 0;
				}

				//if RL packet
				if(packetH & 128) {
					//how many pixels in packet? (value of 7 LSBs + 1)
					packetH -= 127;

					//place for a pixel
					uint8_t *tmpPixel = new uint8_t[(properties.bitsPerPixel / 8)];
					if(tmpPixel == 0) {
						delete[] readData;
						fprintf(stderr, "Error: out of memory\n");
						return 0;
					}

					//read pixel bytes
					if(fread(tmpPixel, 1, (properties.bitsPerPixel / 8), file)
					   < (properties.bitsPerPixel / 8)) {
						delete[] tmpPixel;
						delete[] readData;
						return 0;
					}

					//copy the pixel to image data
					for(uint8_t j = 0; j < packetH; j++) {
						//copy each byte separately
						for(uint8_t k = 0; k < (properties.bitsPerPixel / 8); k++, i++)
							readData[i] = tmpPixel[k];
					}

					//delete temporary pixel memory
					delete[] tmpPixel;
				}
				//if non RL packet
				else {
					//pixels in packet (value of 7 LSBs + 1)
					packetH++;

					//copy pixels to the image
					if(fread(&readData[i], 1, (packetH * (properties.bitsPerPixel / 8)), file)
					   < (packetH * (properties.bitsPerPixel / 8))) {
						delete[] readData;
						return 0;
					}

					//increase bytes counter
					i += (packetH * (properties.bitsPerPixel / 8));
				}
			}
			break;
		}
	}

	//flip the image if need to
	//flip up <-> down
	if(properties.imgDescriptor & 32) {
		uint8_t *tmp = new uint8_t[bitsSize];
		if(tmp == 0) {
			delete[] readData;
			fprintf(stderr, "Error: out of memory\n");
			return 0;
		}

		//copy the image to temp memory
		memcpy(tmp, readData, bitsSize);

		//copy lines back to image memory in reversed order
		for(uint32_t i = 0; i < properties.height; i++) {
			//copy each line separately
			uint32_t linewidth = bitsSize / properties.height;
			uint32_t linebegin = i * linewidth;
			memcpy(&readData[linebegin], &tmp[bitsSize - linewidth - linebegin], linewidth);
		}
		delete[] tmp;
	}
	//flip left <-> right
	if(properties.imgDescriptor & 16) {
		uint8_t *tmp = new uint8_t[bitsSize];
		if(tmp == 0) {
			delete[] readData;
			fprintf(stderr, "Error: out of memory\n");
			return 0;
		}

		//copy the image to temp memory
		memcpy(tmp, readData, bitsSize);

		//change pixels in every line
		for(uint32_t i = 0; i < properties.height; i++) {
			//copy new value to each pixel
			for(uint32_t j = 0; j < properties.width; j++) {
				//how many bytes there are in pixel
				uint32_t bytes = (uint32_t)(bitsSize / (properties.height * properties.width));
				uint32_t linewidth = bitsSize / properties.height;
				uint32_t pixel = j * bytes;
				uint32_t to = (i * linewidth) + pixel;
				uint32_t from = ((i + 1) * linewidth) - (pixel + bytes);
				memcpy(&readData[to], &tmp[from], bytes);
			}
		}
		delete[] tmp;
	}

	//image info
	size = bitsSize;
	width = properties.width;
	height = properties.height;
	//bits per pixel count equals the image size divided by pixel count,
	//multiplied by quantity of bits in byte (8)
	bpp = (size / (width * height)) * 8;
	return readData;
}

uint8_t *farReadImgData(const char *farFileName, const char *fileName, ImgDims &dims)
{
	//open .far file
	FILE *file = fopen(farFileName, "rb");
	if(file == 0) {
		fprintf(stderr, "Error: cannot open file: \"%s\"\n", farFileName);
		return 0;
	}

	//check file size
	fseek(file, 0, SEEK_END);
	uint32_t fileSize = ftell(file);
	fseek(file, 0, SEEK_SET);

	//read .far header
	FarHeader header;
	fioRead(header.fileId, file);
	fioRead(header.fileVer, file);
	fioRead(header.fileSize, file);
	fioRead(header.filesQuantity, file);
	fioRead(header.reserved1, file);
	if(not fioRead(header.reserved2, file)) {
		fprintf(stderr, "Error: cannot read file: \"%s\"\n", farFileName);
		fclose(file);
		return 0;
	}

	//check version, id and size
	if(header.fileVer != 100 or header.fileId != 0x141 or header.fileSize != fileSize) {
		fprintf(stderr, "Error: file \"%s\" is invalid\n", farFileName);
		fclose(file);
		return 0;
	}

	//find image in .far
	FarRootEntry rootEntry;
	int32_t i;
	for(i = 0; i < header.filesQuantity; ++i) {
		//read root entry
		fioRead(rootEntry.packedFileOffset, file);
		fioRead(rootEntry.packedFileSize, file);
		fioRead(rootEntry.fileType, file);
		fioRead(rootEntry.fileNameLength, file);
		fioRead(rootEntry.reserved1, file);
		if(not fioRead(rootEntry.reserved2, file)) {
			fprintf(stderr, "Error: cannot read file: \"%s\"\n", farFileName);
			fclose(file);
			return 0;
		}

		//read packed file name
		char *packedFileName = new char[rootEntry.fileNameLength];
		if(packedFileName == 0) {
			fprintf(stderr, "Error: out of memory while reading file: \"%s\"\n", farFileName);
			fclose(file);
			return 0;
		}
		if(fread(packedFileName, 1, rootEntry.fileNameLength * sizeof(char), file)
		   < rootEntry.fileNameLength * sizeof(char)) {
			delete[] packedFileName;
			fprintf(stderr, "Error: cannot read file: \"%s\"\n", farFileName);
			fclose(file);
			return 0;
		}
		//is this the file?
		if(0 == strcmp(fileName, packedFileName)) {
			delete[] packedFileName;
			break;
		}
		delete[] packedFileName;
	}
	//if we haven't found
	if(i == header.filesQuantity) {
		fprintf(stderr, "Error: cannot find \"%s\" in \"%s\"\n", fileName, farFileName);
		fclose(file);
		return 0;
	}

	//check if it is TGA file
	if(rootEntry.fileType != FAR_FMT_TGA) {
		fprintf(stderr, "Error: reading \"%s\"'s data from \"%s\" - "
		                "format unsupported\n", fileName, farFileName);
		fclose(file);
		return 0;
	}

	//set file pointer at the image file offset
	fseek(file, rootEntry.packedFileOffset, SEEK_SET);

	uint32_t width, height, bitsPerPixel, size;
	uint8_t *data = readTGAData(file, width, height, bitsPerPixel, size);
	if(data == 0) {
		fprintf(stderr, "Error: while reading \"%s\" in \"%s\"\n", fileName, farFileName);
		fclose(file);
		return 0;
	}
	fclose(file);

	dims.width = width;
	dims.height = height;
	dims.size = size;
	dims.bitsPerPixel = bitsPerPixel;
	return data;
}
