#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <png.h>
#include <GL/glu.h>

#include "vs.h"
#include "fs.h"
#include "glextinit.c"



#define BUFFER_OFFSET(bytes) ((GLubyte *)NULL + (bytes))



typedef struct
{
	float texCoord[2];
	float position[3];
}GGL_Vertex;

typedef struct
{
	unsigned short vertexIndices[3];
}GGL_Face;

typedef struct
{
	float m[16];
}GGL_Matrix;

typedef struct
{
	GLuint size[2];
	GLuint id;
}GGL_Texture;

typedef struct tagGGL_Mesh GGL_Mesh;

typedef void (*MeshDestructor)(GGL_Mesh*);

struct tagGGL_Mesh
{
	MeshDestructor destructor;
	GLuint         faceCount;
	union
	{
		struct
		{
			GLuint buffers[2];
		}extension;

		struct
		{
			GGL_Vertex* vertexDatas;
			GGL_Face*   faceDatas;
		}base;
	}branch;
};

typedef struct
{
	GGL_Mesh super;
	GLuint   boneCount;

	union
	{
		struct
		{
			GLuint boneIndicesBuffer;
		}extension;

		struct
		{
			GGL_Vertex* invertedVertices;
			GLuint*     cvInBones;
		}base;
	}branch;
}GGL_SkinnedMesh;

typedef struct
{
	unsigned int  size[2];
	unsigned int  hasAlpha;
	unsigned char pixels[1];
}Image;

typedef struct
{
	unsigned char* data;
	unsigned int   size;
	unsigned int   position;
}PNGIOPointer;

typedef struct
{
	int left;
	int bottom;
	int width;
	int height;
}Rect;

typedef enum
{
	GGL_BLEND_DISABLE,
	GGL_BLEND_ALPHA,
	GGL_BLEND_ADD,
	GGL_BLEND_SCREEN,
}GGL_BlendMode;


static int buildShaderProgram(const void* vsdata, GLuint vssize, const void* fsdata, GLuint fssize, GLuint* lpProg);
static int compileShader(GLuint shader, const void* stringdata, GLint stringsize);
static int printShaderInfoLog(GLuint shader);
static int printProgramInfoLog(GLuint program);
static void multiply(const float* m, const float* vi, float* vo);
static Image* loadPNG(const char* path);
static void pngReadFunction(png_structp png_ptr, png_bytep buf, png_size_t size);
static Image* createImage(png_structp png_ptr, png_infop info_ptr);

static GLuint shaderProgram;
static GLuint locTexture;
static GLuint locBoneIndices;
static GLuint locBoneMatrices[32];
static double aspect = 3.0/4.0;
static float  cameraPosition[3];
static Rect   viewport;
static int    isExtensionAvailable = 0;



void GGL_initialize(int useExtension)
{
	if(useExtension)
	{
		isExtensionAvailable = !glextinit();
	}

	if(isExtensionAvailable)
	{
		if(buildShaderProgram(VS, SIZEOF_VS, FS, SIZEOF_FS, &shaderProgram) == 0)
		{
			locBoneIndices  = glGetAttribLocationARB(shaderProgram, "boneIndices");
			glEnableVertexAttribArrayARB(locBoneIndices);

			{
				unsigned int i;

				for(i=0;i<sizeof(locBoneMatrices)/sizeof(locBoneMatrices[0]);++i)
				{
					char buffer[] = "boneMatrices[99]";

					sprintf(buffer, "boneMatrices[%d]", i);

					locBoneMatrices[i] = glGetUniformLocationARB(shaderProgram, buffer);
				}
			}
			locTexture = glGetUniformLocationARB(shaderProgram, "texture");
		}
		else
		{
			isExtensionAvailable = 0;
		}
	}

	{
		const char* status = "NOT available";
		if(isExtensionAvailable)
		{
			status += 4;
		}

		printf("OpenGL Extension is %s\n", status);

		printf("OpenGL Vendor: %s\n", glGetString(GL_VENDOR));
		printf("OpenGL Renderer: %s\n",glGetString(GL_RENDERER));
		printf("OpenGL Version: %s\n", glGetString(GL_VERSION));
		printf("OpenGL Extensions: %s\n", glGetString(GL_EXTENSIONS));
	}

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glEnable(GL_TEXTURE_2D);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}


void GGL_quit(void)
{
	if(isExtensionAvailable)
	{
		glDeleteObjectARB(shaderProgram);
	}
}


static void destructMesh(GGL_Mesh* mesh)
{
	if(isExtensionAvailable)
	{
		glDeleteBuffers(sizeof(mesh->branch.extension.buffers)/sizeof(mesh->branch.extension.buffers[0]), mesh->branch.extension.buffers);
	}
	else
	{
		free(mesh->branch.base.vertexDatas);
		free(mesh->branch.base.faceDatas);
	}
}


static void constructMesh(GGL_Mesh* mesh, GGL_Vertex vertices[], unsigned int cv, GGL_Face faces[], unsigned int cf)
{
	mesh->destructor = destructMesh;

	mesh->faceCount = cf * 3;

	if(isExtensionAvailable)
	{
		glGenBuffers(2, mesh->branch.extension.buffers);

		glBindBuffer(GL_ARRAY_BUFFER, mesh->branch.extension.buffers[0]);
		glBufferData(GL_ARRAY_BUFFER, cv * sizeof(vertices[0]), vertices, GL_STATIC_DRAW);

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->branch.extension.buffers[1]);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, cf * sizeof(faces[0]), faces, GL_STATIC_DRAW);
	}
	else
	{
		unsigned int i;

		mesh->branch.base.vertexDatas = (GGL_Vertex*)calloc(cv, sizeof(GGL_Vertex));
		for(i=0;i<cv;++i)
		{
			mesh->branch.base.vertexDatas[i] = vertices[i];
		}

		mesh->branch.base.faceDatas = (GGL_Face*)calloc(cf, sizeof(GGL_Face));
		for(i=0;i<cf;++i)
		{
			mesh->branch.base.faceDatas[i] = faces[i];
		}
	}
}

GGL_Mesh* GGL_createMesh(GGL_Vertex vertices[], unsigned int cv, GGL_Face faces[], unsigned int cf)
{
	GGL_Mesh* mesh = (GGL_Mesh*)calloc(1, sizeof(*mesh));

	constructMesh(mesh, vertices, cv, faces, cf);

	return mesh;
}


static void destructSkinnedMesh(GGL_SkinnedMesh* mesh)
{
	if(isExtensionAvailable)
	{
		glDeleteBuffers(1, &mesh->branch.extension.boneIndicesBuffer);
	}
	else
	{
		free(mesh->branch.base.cvInBones);
		free(mesh->branch.base.invertedVertices);
	}

	destructMesh(&mesh->super);
}


GGL_SkinnedMesh* GGL_createSkinnedMesh(GGL_Vertex vertices[], unsigned int cv, GGL_Face faces[], unsigned int cf, unsigned int cvInBones[])
{
	GGL_SkinnedMesh* mesh = (GGL_SkinnedMesh*)calloc(1, sizeof(*mesh));

	constructMesh(&mesh->super, vertices, cv, faces, cf);

	mesh->super.destructor = (MeshDestructor)destructSkinnedMesh;

	{
		unsigned int sum = 0;

		for(mesh->boneCount=0;sum<cv;mesh->boneCount+=1)
		{
			sum += cvInBones[mesh->boneCount];
		}
		if(mesh->boneCount > sizeof(locBoneMatrices)/sizeof(locBoneMatrices[0]))
		{
			fprintf(stderr, "too many bones!\n");
			fprintf(stderr, "boneCount = %d\n", mesh->boneCount);
			fprintf(stderr, "cv = %d\n", cv);
			for(sum=0;sum<sizeof(locBoneMatrices)/sizeof(locBoneMatrices[0]);++sum)
			{
				fprintf(stderr, "cvInBones[%d] = %d\n", sum, cvInBones[sum]);
			}

			return NULL;
		}
	}

	if(isExtensionAvailable)
	{
		glGenBuffers(1, &mesh->branch.extension.boneIndicesBuffer);

		{
			unsigned char boneIndices[cv];
			unsigned int  i = 0;
			unsigned int  j = 0;
			unsigned int  k;

			while(i < cv)
			{
				for(k=0;k<cvInBones[j];++k)
				{
					boneIndices[i++] = j;
				}
				++j;
			}

			glBindBuffer(GL_ARRAY_BUFFER, mesh->branch.extension.boneIndicesBuffer);
			glBufferData(GL_ARRAY_BUFFER, cv, boneIndices, GL_STATIC_DRAW);
		}
	}
	else
	{
		mesh->branch.base.cvInBones = (GLuint*)calloc(mesh->boneCount, sizeof(GLuint));
		{
			unsigned int i;
			for(i=0;i<mesh->boneCount;++i)
			{
				mesh->branch.base.cvInBones[i] = cvInBones[i];
			}
		}
		mesh->branch.base.invertedVertices = (GGL_Vertex*)calloc(cv, sizeof(GGL_Vertex));
		{
			unsigned int i;
			for(i=0;i<cv;++i)
			{
				mesh->branch.base.invertedVertices[i] = vertices[i];
			}
		}
	}

	return mesh;
}


void GGL_releaseMesh(GGL_Mesh* mesh)
{
	if(mesh)
	{
		mesh->destructor(mesh);

		free(mesh);
	}
}


GGL_Mesh* GGL_castMesh(GGL_SkinnedMesh* mesh)
{
	return &mesh->super;
}


void GGL_drawMesh(GGL_Mesh* mesh, GGL_Texture* texture, unsigned char* color)
{
	if(texture)
	{
		glEnable(GL_TEXTURE_2D);

		glBindTexture(GL_TEXTURE_2D, texture->id);
	}
	else
	{
		glDisable(GL_TEXTURE_2D);
	}
	glColor4ubv(color);

	if(isExtensionAvailable)
	{
		glBindBuffer(GL_ARRAY_BUFFER, mesh->branch.extension.buffers[0]);
		glInterleavedArrays(GL_T2F_V3F, 0, BUFFER_OFFSET(0));

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->branch.extension.buffers[1]);
		glDrawElements(GL_TRIANGLES, mesh->faceCount, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
	}
	else
	{
		glInterleavedArrays(GL_T2F_V3F, 0, mesh->branch.base.vertexDatas);

		glDrawElements(GL_TRIANGLES, mesh->faceCount, GL_UNSIGNED_SHORT, mesh->branch.base.faceDatas);
	}
}


void GGL_drawSkinnedMesh(GGL_SkinnedMesh* mesh, GGL_Texture* texture, unsigned char* color, GGL_Matrix* matrices)
{
	if(isExtensionAvailable)
	{
		glUseProgramObjectARB(shaderProgram);

		glUniform1iARB(locTexture, 0);

		{
			unsigned int i;

			for(i=0;i<mesh->boneCount;++i)
			{
				glUniformMatrix4fvARB(locBoneMatrices[i], 1, GL_FALSE, matrices[i].m);
			}
		}

		glBindBuffer(GL_ARRAY_BUFFER, mesh->branch.extension.boneIndicesBuffer);
		glVertexAttribPointerARB(locBoneIndices, 1, GL_UNSIGNED_BYTE, GL_FALSE, 0, BUFFER_OFFSET(0));

		GGL_drawMesh(&mesh->super, texture, color);

		glUseProgramObjectARB(0);
	}
	else
	{
		unsigned int boneIndex;
		unsigned int vertexIndex = 0;

		for(boneIndex=0;boneIndex<mesh->boneCount;++boneIndex)
		{
			unsigned int e = vertexIndex + mesh->branch.base.cvInBones[boneIndex];
			for(;vertexIndex < e; ++vertexIndex)
			{
				multiply(
						matrices[boneIndex].m,
						mesh->branch.base.invertedVertices[vertexIndex].position,
						mesh->super.branch.base.vertexDatas[vertexIndex].position
					);
			}
		}

		GGL_drawMesh(&mesh->super, texture, color);
	}
}


void GGL_drawTriangleStrip(GGL_Vertex* vertices, unsigned int cv, GGL_Texture* texture)
{
	unsigned int i;

	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texture->id);
	glColor4ub(255, 255, 255, 255);

	glBegin(GL_TRIANGLE_STRIP);

	for(i=0;i<cv;++i)
	{
		glTexCoord2fv(vertices[i].texCoord);
		glVertex3fv(vertices[i].position);
	}

	glEnd();
}


void GGL_translate(float x, float y, float z)
{
	glTranslatef(x, y, z);
}


void GGL_rotate(float radian, float x, float y, float z)
{
	glRotatef(radian * 180.0 / M_PI, x, y, z);
}


void GGL_scale(float x, float y, float z)
{
	glScalef(x, y, z);
}


void GGL_multiplyMatrix(GGL_Matrix* matrix)
{
	glMultMatrixf(matrix->m);
}


void GGL_pushMatrix(void)
{
	glPushMatrix();
}


void GGL_popMatrix(void)
{
	glPopMatrix();
}


void GGL_setViewport(int left, int bottom, int width, int height)
{
	glViewport(left, bottom, width, height);
	glScissor(left, bottom, width, height);

	aspect = (double)width / (double)height;

	viewport.left   = left;
	viewport.bottom = bottom;
	viewport.width  = width;
	viewport.height = height;
}


void GGL_getViewport(int* left, int* bottom, int* width, int* height)
{
	*left   = viewport.left;
	*bottom = viewport.bottom;
	*width  = viewport.width;
	*height = viewport.height;
}


void GGL_setPerspective(double angle, double znear, double zfar)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(angle, aspect, znear, zfar);

	glMatrixMode(GL_MODELVIEW);
}


void GGL_enter2DMode(double w, double h)
{
	double a = w / h;

	if(aspect > a)
	{
		w = h * aspect;
	}
	else if(aspect < a)
	{
		h = w / aspect;
	}

	w *= 0.5;
	h *= 0.5;

	glMatrixMode( GL_PROJECTION );
	glLoadIdentity( );

	glOrtho(-w, w, -h, h, -h, h);

	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity( );
}


void GGL_enterBillBoardMode(void)
{
	float mv[16];

	glGetFloatv(GL_MODELVIEW_MATRIX, mv);

	mv[0] = mv[5] = mv[10] = 1.0;

	mv[1] = mv[2] = mv[4] = mv[6] = mv[8] = mv[9] = 0.0;

	glLoadMatrixf(mv);
}


void GGL_lookAt(float pc[], float pt[], float vu[])
{
	glLoadIdentity();

	gluLookAt(pc[0], pc[1], pc[2], pt[0], pt[1], pt[2], vu[0], vu[1], vu[2]);

	cameraPosition[0] = pc[0];
	cameraPosition[1] = pc[1];
	cameraPosition[2] = pc[2];
}


void GGL_getCameraPosition(float* p)
{
	p[0] = cameraPosition[0];
	p[1] = cameraPosition[1];
	p[2] = cameraPosition[2];
}


float GGL_getDepth(float x, float y, float z)
{
	float xd = x - cameraPosition[0];
	float yd = y - cameraPosition[1];
	float zd = z - cameraPosition[2];

	return xd*xd + yd*yd + zd*zd;
}


void GGL_enableDepthBuffer(int isEnable)
{
	if(isEnable)
	{
		glEnable(GL_DEPTH_TEST);
	}
	else
	{
		glDisable(GL_DEPTH_TEST);
	}
}


void GGL_clearDepthBuffer(void)
{
	glClear(GL_DEPTH_BUFFER_BIT);
}


void GGL_lockDepthBuffer(int isLocked)
{
	if(isLocked)
	{
		glDisable(GL_CULL_FACE);
		glDepthMask(GL_FALSE);
	}
	else
	{
		glEnable(GL_CULL_FACE);
		glDepthMask(GL_TRUE);
	}
}


void GGL_enableAlphaBlend(int isEnable)
{
	if(isEnable)
	{
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	}
	else
	{
		glDisable(GL_BLEND);
	}
}


void GGL_setBlendMode(GGL_BlendMode mode)
{
	switch(mode)
	{
	case GGL_BLEND_DISABLE:
		glDisable(GL_BLEND);
		return;

	case GGL_BLEND_ALPHA:
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		break;

	case GGL_BLEND_ADD:
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
		break;

	case GGL_BLEND_SCREEN:
		glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE);
		break;
	}
	glEnable(GL_BLEND);
}


void GGL_clearScreen(float r, float g, float b)
{
	glClearColor(r, g, b, 1.0);

	glClear(GL_COLOR_BUFFER_BIT);
}


const char* GGL_getError(void)
{
	int error = glGetError();
	if(error == GL_NO_ERROR)
	{
		return "";
	}
	else
	{
		static char message[] = "OpenGL error: 0xFFFF";

		sprintf(message, "OpenGL error: 0x%04X", error);

		return message;
	}
}


GGL_Texture* GGL_createTexture(const char* path, int compress)
{
	GGL_Texture* texture = NULL;
	Image*       image   = NULL;

	if(path)
	{
		image = loadPNG(path);
	}
	else
	{
		unsigned int i;

		image = (Image*)calloc(1, sizeof(*image) + 3 * 8*8);

		image->size[0] = image->size[1] = 8;

		image->hasAlpha = 0;

		for(i=0;i<3*8*8;i+=3)
		{
			image->pixels[i+0] = 255;
			image->pixels[i+1] = 255;
			image->pixels[i+2] = 255;
		}
	}

	if(image)
	{
		GLint dstPixelFormat = GL_RGB;
		GLint srcPixelFormat = GL_RGB;

		texture = (GGL_Texture*)calloc(1, sizeof(GGL_Texture));

		texture->size[0] = image->size[0];
		texture->size[1] = image->size[1];

		glGenTextures(1, &texture->id);
		glBindTexture(GL_TEXTURE_2D, texture->id);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

		if((!isExtensionAvailable) || (image->size[1] < 16))
		{
			compress = 0;
		}
		if(image->hasAlpha)
		{
			dstPixelFormat = srcPixelFormat = GL_RGBA;
			if(compress)
			{
				dstPixelFormat = GL_COMPRESSED_RGBA_ARB;
			}
		}
		else if(compress)
		{
			dstPixelFormat = GL_COMPRESSED_RGB_ARB;
		}

		glTexImage2D(
				GL_TEXTURE_2D,
				0,
				dstPixelFormat,
				image->size[0], image->size[1],
				0,
				srcPixelFormat,
				GL_UNSIGNED_BYTE,
				image->pixels
			);

		free(image);
	}
	else
	{
		fprintf(stderr, "%s: loadPNG() failed!\n", path);
	}

	return texture;
}


GGL_Texture* GGL_createTextureRGBA(const void* pixels, int width, int height)
{
	GGL_Texture* texture = NULL;

	texture = (GGL_Texture*)calloc(1, sizeof(GGL_Texture));

	texture->size[0] = width;
	texture->size[1] = height;

	glGenTextures(1, &texture->id);
	glBindTexture(GL_TEXTURE_2D, texture->id);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

	glTexImage2D(
			GL_TEXTURE_2D,
			0,
			GL_RGBA,
			width, height,
			0,
			GL_RGBA,
			GL_UNSIGNED_BYTE,
			pixels
		);

	return texture;
}


void GGL_releaseTexture(GGL_Texture* texture)
{
	if(texture)
	{
		glDeleteTextures(1, &texture->id);

		free(texture);
	}
}


GLuint* GGL_textureSize(GGL_Texture* texture)
{
	return texture->size;
}


void GGL_wrapTexture(GGL_Texture* texture, int isRepeated)
{
	glBindTexture(GL_TEXTURE_2D, texture->id);
	if(isRepeated)
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	}
	else
	{
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	}
}


static int buildShaderProgram(const void* vsdata, GLuint vssize, const void* fsdata, GLuint fssize, GLuint* lpProg)
{
	GLuint vtxShader;
	GLuint frgShader;
	int    result = -1;

	*lpProg = 0;

	/* シェーダオブジェクトの作成 */
	vtxShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
	frgShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);

	/* バーテックスシェーダのロードとコンパイル */
	if(compileShader(vtxShader, vsdata, vssize) == 0)
	{
		/* フラグメントシェーダのロードとコンパイル */
		if(compileShader(frgShader, fsdata, fssize) == 0)
		{
			/* プログラムオブジェクトの作成 */
			*lpProg = glCreateProgramObjectARB();

			/* シェーダオブジェクトのシェーダプログラムへの登録 */
			glAttachObjectARB(*lpProg, vtxShader);
			glAttachObjectARB(*lpProg, frgShader);

			/* シェーダプログラムのリンク */
			glLinkProgramARB(*lpProg);
			if(printProgramInfoLog(*lpProg))
			{
				result = 0;
			}
			else
			{
				fprintf(stderr, "Link error!!\n");

				glDeleteObjectARB(*lpProg);

				*lpProg = 0;
			}
		}
	}

	/* シェーダオブジェクトの削除 */
	glDeleteObjectARB(vtxShader);
	glDeleteObjectARB(frgShader);

	return result;
}


static int compileShader(GLuint shader, const void* stringdata, GLint stringsize)
{
	/* シェーダオブジェクトにプログラムをセット */
	glShaderSourceARB(shader, 1, (const GLcharARB**)&stringdata, &stringsize);
  
	/* シェーダのコンパイル */
	glCompileShaderARB(shader);
	if(printShaderInfoLog(shader))
	{
		return 0;
	}

	fprintf(stderr, "Compile error!!\n");

	return -1;
}


/* シェーダコンパイルエラーの出力 */
static int printShaderInfoLog(GLuint shader)
{
	GLint isCompiled = 0;

    glGetObjectParameterivARB(shader, GL_OBJECT_COMPILE_STATUS_ARB, &isCompiled);

    if(!isCompiled)
    {
		char str[4*1024];

        glGetInfoLogARB(shader, sizeof(str), NULL, str);

		fprintf(stderr, "%s\n", str);
    }

	return isCompiled;
}


/* シェーダリンクエラーの出力 */
static int printProgramInfoLog(GLuint program)
{
	GLint isLinked = 0;

    glGetObjectParameterivARB(program, GL_OBJECT_LINK_STATUS_ARB, &isLinked);

    if(!isLinked)
    {
		char str[4*1024];

        glGetInfoLogARB(program, sizeof(str), NULL, str);

		fprintf(stderr, "%s\n", str);
    }

	return isLinked;
}


/*
 * m[ 0] m[ 4] m[ 8] m[12]  vi[0] = vo[0]
 * m[ 1] m[ 5] m[ 9] m[13]  vi[1] = vo[1]
 * m[ 2] m[ 6] m[10] m[14]  vi[2] = vo[2]
 * m[ 3] m[ 7] m[11] m[15]      1 = ignored
 */
static void multiply(const float* m, const float* vi, float* vo)
{
	vo[0] = m[0]*vi[0] + m[4]*vi[1] + m[8]*vi[2] + m[12];
	vo[1] = m[1]*vi[0] + m[5]*vi[1] + m[9]*vi[2] + m[13];
	vo[2] = m[2]*vi[0] + m[6]*vi[1] + m[10]*vi[2] + m[14];
}


static Image* loadPNG(const char* path)
{
	Image* image = NULL;

	FILE* fp = fopen(path, "rb");
	if(fp)
	{
		unsigned char* filedata = NULL;
		unsigned int   filesize;

		fseek(fp, 0, SEEK_END);

		filesize = ftell(fp);

		rewind(fp);

		filedata = (unsigned char*)calloc(1, filesize);

		if(fread(filedata, 1, filesize, fp) == filesize)
		{
			if(png_check_sig(filedata, 8))
			{
				png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
				if(png_ptr)
				{
					png_infop info_ptr = NULL;

					if(!setjmp(png_jmpbuf(png_ptr)))
					{
						info_ptr = png_create_info_struct(png_ptr);
						if(info_ptr)
						{
							PNGIOPointer pointer;

							pointer.data = filedata;
							pointer.size = filesize;
							pointer.position = 8;

							//png_init_io(png_ptr, fp);
							png_set_read_fn(png_ptr, (png_voidp)&pointer, (png_rw_ptr)pngReadFunction);

							png_set_sig_bytes(png_ptr, 8);  

							image = createImage(png_ptr, info_ptr);
						}
						else
						{
							fprintf(stderr, "%s: png_create_info_struct() failed!\n", path);
						}
					}
					else
					{
						fprintf(stderr, "%s: setjmp() exception!\n", path);
					}

					png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				}
				else
				{
					fprintf(stderr, "%s: png_create_read_struct() failed!\n", path);
				}
			}
			else
			{
				fprintf(stderr, "%s: has no PNG signature!\n", path);
			}
		}
		else
		{
			fprintf(stderr, "%s: fread() failed!\n", path);
		}

		free(filedata);
		fclose(fp);
	}
	else
	{
		fprintf(stderr, "%s: fopen() failed!\n", path);
	}

	return image;
}


static void pngReadFunction(png_structp png_ptr, png_bytep buf, png_size_t size)
{
	PNGIOPointer* p = (PNGIOPointer*)png_get_io_ptr(png_ptr);

	if(p->position + size > p->size)
	{
		fprintf(stderr, "read position, read size, eos = %d, %d, %d\n", p->position, (int)size, p->size);

		png_error(png_ptr, "read over eos");
	}
	else
	{
		memcpy(buf, p->data + p->position, size);

		p->position += size;
	}
}


static Image* createImage(png_structp png_ptr, png_infop info_ptr)
{
	Image* image = NULL;

	png_uint_32 w;
	png_uint_32 h;
	png_byte    colorType;

	png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);

	w = png_get_image_width(png_ptr, info_ptr);
	h = png_get_image_height(png_ptr, info_ptr);

	colorType = png_get_color_type(png_ptr, info_ptr);

	switch(colorType)
	{
	case PNG_COLOR_TYPE_RGB_ALPHA:
		image = (Image*)calloc(1, sizeof(Image) + 4 * w * h);
		image->hasAlpha = 1;
		break;

	case PNG_COLOR_TYPE_RGB:
		image = (Image*)calloc(1, sizeof(Image) + 3 * w * h);
		image->hasAlpha = 0;
		break;

	default:
		fprintf(stderr, "unsupported pixel format!\n");
		return NULL;
	}
	image->size[0] = w;
	image->size[1] = h;

	{
		unsigned int i;
		unsigned int rowBytes = w * (3 + image->hasAlpha);
		png_bytepp   rowPointers = png_get_rows(png_ptr, info_ptr);

		for(i=0;i<h;++i)
		{
			memcpy(image->pixels + i * rowBytes, rowPointers[h - i - 1], rowBytes);
		}
	}

	return image;
}

