#include <GL/gl.h>
#include <stdio.h>      // Header file for standard file i/o.
#include <stdlib.h>     // Header file for malloc/free.
#include <math.h>

struct Image {
	unsigned long sizeX;
	unsigned long sizeY;
	char *data;
};

typedef struct Image Image;

// quick and dirty bitmap loader...for 24 bit bitmaps with 1 plane only.
// See http://www.dcs.ed.ac.uk/~mxr/gfx/2d/BMP.txt for more info.
// Loads BMPs without transparency Alpha channel, as exported from GIMP to format 24bit
int ImageLoad(char *filename, Image *image) {
	FILE *file;
	unsigned long size;                 // size of the image in bytes.
	unsigned long i;                    // standard counter.
	unsigned short int planes;          // number of planes in image (must be 1)
	unsigned short int bpp;             // number of bits per pixel (must be 24)
	char temp;                // temporary color storage for bgr-rgb conversion.
	if ((file = fopen(filename, "rb")) == NULL) {
		printf("File Not Found : %s\n", filename);
		return 0;
	}
	fseek(file, 18, SEEK_CUR);
	if ((i = fread(&image->sizeX, 4, 1, file)) != 1) {
		printf("Error reading width from %s.\n", filename);
		return 0;
	}
	printf("Width of %s: %lu\n", filename, image->sizeX);
	if ((i = fread(&image->sizeY, 4, 1, file)) != 1) {
		printf("Error reading height from %s.\n", filename);
		return 0;
	}
	printf("Height of %s: %lu\n", filename, image->sizeY);
	size = image->sizeX * image->sizeY * 3;
	if ((fread(&planes, 2, 1, file)) != 1) {
		printf("Error reading planes from %s.\n", filename);
		return 0;
	}
	if (planes != 1) {
		printf("Planes from %s is not 1: %u\n", filename, planes);
		return 0;
	}
	if ((i = fread(&bpp, 2, 1, file)) != 1) {
		printf("Error reading bpp from %s.\n", filename);
		return 0;
	}
	if (bpp != 24) {
		printf("Bpp from %s is not 24: %u\n", filename, bpp);
		return 0;
	}
	fseek(file, 24, SEEK_CUR);
	image->data = (char *) malloc(size);
	if (image->data == NULL) {
		printf("Error allocating memory for color-corrected image data");
		return 0;
	}
	if ((i = fread(image->data, size, 1, file)) != 1) {
		printf("Error reading image data from %s.\n", filename);
		return 0;
	}
	for (i = 0; i < size; i += 3) { // reverse all of the colors. (bgr -> rgb)
		temp = image->data[i];
		image->data[i] = image->data[i + 2];
		image->data[i + 2] = temp;
	}
	return 1;
}

// Loads BMPs with transparency Alpha channel as exported from GIMP to format 32bit ARGB
int ImageLoadWithAlpha(char *filename, Image *image) {
	FILE *file;
	unsigned long size;                 // size of the image in bytes.
	unsigned long i;                    // standard counter.
	unsigned short int planes;          // number of planes in image (must be 1)
	unsigned short int bpp;             // number of bits per pixel (must be 24)
	if ((file = fopen(filename, "rb")) == NULL) {
		printf("File Not Found : %s\n", filename);
		return 0;
	}
	fseek(file, 18, SEEK_CUR);
	if ((i = fread(&image->sizeX, 4, 1, file)) != 1) {
		printf("Error reading width from %s.\n", filename);
		return 0;
	}
	printf("Width of %s: %lu\n", filename, image->sizeX);
	if ((i = fread(&image->sizeY, 4, 1, file)) != 1) {
		printf("Error reading height from %s.\n", filename);
		return 0;
	}
	printf("Height of %s: %lu\n", filename, image->sizeY);
	size = image->sizeX * image->sizeY * 4;
	if ((fread(&planes, 2, 1, file)) != 1) {
		printf("Error reading planes from %s.\n", filename);
		return 0;
	}
	if (planes != 1) {
		printf("Planes from %s is not 1: %u\n", filename, planes);
		return 0;
	}
	if ((i = fread(&bpp, 2, 1, file)) != 1) {
		printf("Error reading bpp from %s.\n", filename);
		return 0;
	}
	if (bpp != 32) {
		printf("Bpp from %s is not 32: %u\n", filename, bpp);
		return 0;
	}
	fseek(file, 40, SEEK_CUR);
	image->data = (char *) malloc(size);
	if (image->data == NULL) {
		printf("Error allocating memory for color-corrected image data");
		return 0;
	}
	if ((i = fread(image->data, size, 1, file)) != 1) {
		printf("Error reading image data from %s.\n", filename);
		return 0;
	}
	for (i = 0; i < size; i += 4) { // reverse all of the colors. (bgr -> rgb)
		char temp0 = image->data[i];
		char temp1 = image->data[i+1];
		char temp2 = image->data[i+2];
		char temp3 = image->data[i+3];
		image->data[i] = temp3;
		image->data[i + 1] = temp2;
		image->data[i + 2] = temp1;
		image->data[i + 3] = temp0;
	}
	return 1;
}

// Reduces a normal vector specified as a set of three coordinates,
// to a unit normal vector of length 1.
void ReduceToUnit(float vector[3]) {
	float length;
	length = (float) sqrt(
			(vector[0] * vector[0]) + (vector[1] * vector[1])
					+ (vector[2] * vector[2]));
	if (length == 0.0f)
		length = 1.0f;
	vector[0] /= length;
	vector[1] /= length;
	vector[2] /= length;
}

// Points p1, p2, & p3 specified in counterclockwise order
void calcNormal(float v[3][3], float out[3]) {
	float v1[3], v2[3];
	static const int x = 0;
	static const int y = 1;
	static const int z = 2;
	v1[x] = v[0][x] - v[1][x];
	v1[y] = v[0][y] - v[1][y];
	v1[z] = v[0][z] - v[1][z];
	v2[x] = v[1][x] - v[2][x];
	v2[y] = v[1][y] - v[2][y];
	v2[z] = v[1][z] - v[2][z];
	out[x] = v1[y] * v2[z] - v1[z] * v2[y];
	out[y] = v1[z] * v2[x] - v1[x] * v2[z];
	out[z] = v1[x] * v2[y] - v1[y] * v2[x];
	ReduceToUnit(out);
}

// Creates a shadow projection matrix out of the plane equation
// coefficients and the position of the light. The return value is stored
// in destMat[][]
void MakeShadowMatrix(GLfloat points[3][3], GLfloat lightPos[4],
		GLfloat destMat[4][4]) {
	GLfloat planeCoeff[4];
	GLfloat dot;
	calcNormal(points, planeCoeff);
	planeCoeff[3] = -((planeCoeff[0] * points[2][0])
			+ (planeCoeff[1] * points[2][1]) + (planeCoeff[2] * points[2][2]));
	dot = planeCoeff[0] * lightPos[0] + planeCoeff[1] * lightPos[1]
			+ planeCoeff[2] * lightPos[2] + planeCoeff[3] * lightPos[3];
	destMat[0][0] = dot - lightPos[0] * planeCoeff[0];
	destMat[1][0] = 0.0f - lightPos[0] * planeCoeff[1];
	destMat[2][0] = 0.0f - lightPos[0] * planeCoeff[2];
	destMat[3][0] = 0.0f - lightPos[0] * planeCoeff[3];
	destMat[0][1] = 0.0f - lightPos[1] * planeCoeff[0];
	destMat[1][1] = dot - lightPos[1] * planeCoeff[1];
	destMat[2][1] = 0.0f - lightPos[1] * planeCoeff[2];
	destMat[3][1] = 0.0f - lightPos[1] * planeCoeff[3];
	destMat[0][2] = 0.0f - lightPos[2] * planeCoeff[0];
	destMat[1][2] = 0.0f - lightPos[2] * planeCoeff[1];
	destMat[2][2] = dot - lightPos[2] * planeCoeff[2];
	destMat[3][2] = 0.0f - lightPos[2] * planeCoeff[3];
	destMat[0][3] = 0.0f - lightPos[3] * planeCoeff[0];
	destMat[1][3] = 0.0f - lightPos[3] * planeCoeff[1];
	destMat[2][3] = 0.0f - lightPos[3] * planeCoeff[2];
	destMat[3][3] = dot - lightPos[3] * planeCoeff[3];
}
