#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ou.h"

void bwtorgba(unsigned char *b, unsigned char *l, int n)
{
	while (n--) {
		l[0] = *b;
		l[1] = *b;
		l[2] = *b;
		l[3] = 0xff;
		l += 4;
		b++;
	}
}

void latorgba(unsigned char *b, unsigned char *a, unsigned char *l, int n)
{
	while (n--) {
		l[0] = *b;
		l[1] = *b;
		l[2] = *b;
		l[3] = *a;
		l += 4;
		b++;
		a++;
	}
}

void
rgbtorgba(unsigned char *r, unsigned char *g, unsigned char *b,
		  unsigned char *l, int n)
{
	while (n--) {
		l[0] = r[0];
		l[1] = g[0];
		l[2] = b[0];
		l[3] = 0xff;
		l += 4;
		r++;
		g++;
		b++;
	}
}

void
rgbatorgba(unsigned char *r, unsigned char *g, unsigned char *b,
		   unsigned char *a, unsigned char *l, int n)
{
	while (n--) {
		l[0] = r[0];
		l[1] = g[0];
		l[2] = b[0];
		l[3] = a[0];
		l += 4;
		r++;
		g++;
		b++;
		a++;
	}
}

typedef struct _ImageRec {
	unsigned short imagic;
	unsigned short type;
	unsigned short dim;
	unsigned short xsize, ysize, zsize;
	unsigned int min, max;
	unsigned int wasteBytes;
	char name[80];
	unsigned long colorMap;
	FILE *file;
	unsigned char *tmp, *tmpR, *tmpG, *tmpB;
	unsigned long rleEnd;
	unsigned int *rowStart;
	int *rowSize;
} ImageRec;

static void ConvertShort(unsigned short *array, long length)
{
	unsigned b1, b2;
	unsigned char *ptr;

	ptr = (unsigned char *) array;
	while (length--) {
		b1 = *ptr++;
		b2 = *ptr++;
		*array++ = (b1 << 8) | (b2);
	}
}

static void ConvertLong(unsigned *array, long length)
{
	unsigned b1, b2, b3, b4;
	unsigned char *ptr;

	ptr = (unsigned char *) array;
	while (length--) {
		b1 = *ptr++;
		b2 = *ptr++;
		b3 = *ptr++;
		b4 = *ptr++;
		*array++ = (b1 << 24) | (b2 << 16) | (b3 << 8) | (b4);
	}
}

static ImageRec *ImageOpen(const char *fileName)
{
	union {
		int testWord;
		char testByte[4];
	} endianTest;
	ImageRec *image;
	int swapFlag;
	int x;

	endianTest.testWord = 1;
	if (endianTest.testByte[0] == 1) {
		swapFlag = 1;
	} else {
		swapFlag = 0;
	}

	image = (ImageRec *) malloc(sizeof(ImageRec));
	if (image == NULL) {
		fprintf(stderr, "Out of memory!\n");
		shutdown(1);
	}
	if ((image->file = fopen(fileName, "rb")) == NULL) {
		perror(fileName);
		shutdown(1);
	}

	fread(image, 1, 12, image->file);

	if (swapFlag) {
		ConvertShort(&image->imagic, 6);
	}

	image->tmp = (unsigned char *) malloc(image->xsize * 256);
	image->tmpR = (unsigned char *) malloc(image->xsize * 256);
	image->tmpG = (unsigned char *) malloc(image->xsize * 256);
	image->tmpB = (unsigned char *) malloc(image->xsize * 256);
	if (image->tmp == NULL || image->tmpR == NULL || image->tmpG == NULL ||
		image->tmpB == NULL) {
		fprintf(stderr, "Out of memory!\n");
		shutdown(1);
	}

	if ((image->type & 0xFF00) == 0x0100) {
		x = image->ysize * image->zsize * sizeof(unsigned);
		image->rowStart = (unsigned *) malloc(x);
		image->rowSize = (int *) malloc(x);
		if (image->rowStart == NULL || image->rowSize == NULL) {
			fprintf(stderr, "Out of memory!\n");
			shutdown(1);
		}
		image->rleEnd = 512 + (2 * x);
		fseek(image->file, 512, SEEK_SET);
		fread(image->rowStart, 1, x, image->file);
		fread(image->rowSize, 1, x, image->file);
		if (swapFlag) {
			ConvertLong(image->rowStart, x / (int) sizeof(unsigned));
			ConvertLong((unsigned *) image->rowSize,
						x / (int) sizeof(int));
		}
	}
	return image;
}

static void ImageClose(ImageRec * image)
{
	fclose(image->file);
	free(image->tmp);
	free(image->tmpR);
	free(image->tmpG);
	free(image->tmpB);
	free(image);
}

static void ImageGetRow(ImageRec * image, unsigned char *buf, int y, int z)
{
	unsigned char *iPtr, *oPtr, pixel;
	int count;

	if ((image->type & 0xFF00) == 0x0100) {
		fseek(image->file, (long) image->rowStart[y + z * image->ysize],
			  SEEK_SET);
		fread(image->tmp, 1,
			  (unsigned int) image->rowSize[y + z * image->ysize],
			  image->file);

		iPtr = image->tmp;
		oPtr = buf;
		for (;;) {
			pixel = *iPtr++;
			count = (int) (pixel & 0x7F);
			if (!count) {
				return;
			}
			if (pixel & 0x80) {
				while (count--) {
					*oPtr++ = *iPtr++;
				}
			} else {
				pixel = *iPtr++;
				while (count--) {
					*oPtr++ = pixel;
				}
			}
		}
	} else {
		fseek(image->file,
			  512 + (y * image->xsize) + (z * image->xsize * image->ysize),
			  SEEK_SET);
		fread(buf, 1, image->xsize, image->file);
	}
}

unsigned *read_texture(char *name, int *width, int *height,
					   int *components)
{
	unsigned *base, *lptr;
	unsigned char *rbuf, *gbuf, *bbuf, *abuf;
	ImageRec *image;
	int y;

	image = ImageOpen(name);

	if (!image)
		return NULL;
	(*width) = image->xsize;
	(*height) = image->ysize;
	(*components) = image->zsize;
	base =
		(unsigned *) malloc(image->xsize * image->ysize *
							sizeof(unsigned));
	rbuf = (unsigned char *) malloc(image->xsize * sizeof(unsigned char));
	gbuf = (unsigned char *) malloc(image->xsize * sizeof(unsigned char));
	bbuf = (unsigned char *) malloc(image->xsize * sizeof(unsigned char));
	abuf = (unsigned char *) malloc(image->xsize * sizeof(unsigned char));
	if (!base || !rbuf || !gbuf || !bbuf)
		return NULL;
	lptr = base;
	for (y = 0; y < image->ysize; y++) {
		if (image->zsize >= 4) {
			ImageGetRow(image, rbuf, y, 0);
			ImageGetRow(image, gbuf, y, 1);
			ImageGetRow(image, bbuf, y, 2);
			ImageGetRow(image, abuf, y, 3);
			rgbatorgba(rbuf, gbuf, bbuf, abuf, (unsigned char *) lptr,
					   image->xsize);
			lptr += image->xsize;
		} else if (image->zsize == 3) {
			ImageGetRow(image, rbuf, y, 0);
			ImageGetRow(image, gbuf, y, 1);
			ImageGetRow(image, bbuf, y, 2);
			rgbtorgba(rbuf, gbuf, bbuf, (unsigned char *) lptr,
					  image->xsize);
			lptr += image->xsize;
		} else if (image->zsize == 2) {
			ImageGetRow(image, rbuf, y, 0);
			ImageGetRow(image, abuf, y, 1);
			latorgba(rbuf, abuf, (unsigned char *) lptr, image->xsize);
			lptr += image->xsize;
		} else {
			ImageGetRow(image, rbuf, y, 0);
			bwtorgba(rbuf, (unsigned char *) lptr, image->xsize);
			lptr += image->xsize;
		}
	}
	ImageClose(image);
	free(rbuf);
	free(gbuf);
	free(bbuf);
	free(abuf);

	return (unsigned *) base;
}
