#include "misc.h"
#include <stdio.h>
#include <windows.h>
#include "FreeImage.h"

#pragma comment(lib,"FreeImage.lib")

GLuint CreateBufferObject(GLenum bufferType, GLsizeiptr size, GLenum usage, void * data)
{
	GLuint object;
	glGenBuffers(1, &object);
	glBindBuffer(bufferType, object);
	glBufferData(bufferType, size, data, usage);
	glBindBuffer(bufferType, 0);

	return object;
}

char* LoadFileContent(const char *path) {
	FILE* pFile = fopen(path, "rb");
	if (pFile) {
		fseek(pFile, 0, SEEK_END);
		int nLen = ftell(pFile);
		char* buffer = nullptr;
		if (nLen != 0) {
			buffer = new char[nLen + 1];
			rewind(pFile);
			fread(buffer, nLen, 1, pFile);
			buffer[nLen] = '\0';
		}
		else {
			printf("load file %s fail, content is 0\n", path);
		}
		fclose(pFile);
		return buffer;
	}
	else {
		printf("open file %s fail\n", path);
	}
	fclose(pFile);
	return nullptr;
}

GLuint CompileShader(GLenum shaderType, const char * shaderPath)
{
	//´´½¨shader
	GLuint shader = glCreateShader(shaderType);
	if (shader == 0) {
		printf("glCreateShader false\n");
		return 0;
	}
	//¶ÁÈ¡shader´úÂë
	const char* shaderCode = LoadFileContent(shaderPath);
	if (shaderCode == nullptr) {
		printf("load shader code from file: %s false\n", shaderPath);
		return 0;
	}
	//°Ñshader´úÂë ´ÓÄÚ´æ´«µ½ÏÔ´æ
	glShaderSource(shader, 1, &shaderCode, nullptr);
	glCompileShader(shader);

	GLint compileResult = GL_TRUE;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
	if (compileResult == GL_FALSE) {
		char szLog[1024] = { 0 };
		GLsizei logLen = 0;//ʵ¼Ê´íÎóÈÕÖ¾³¤¶È
		glGetShaderInfoLog(shader, 1024, &logLen, szLog);
		printf("Compile shader fail error log is : %s \n shader code :\n %s \n ", szLog, shaderCode);
		glDeleteShader(shader);
		return 0;
	}

	return shader;
}

/**
* ´´½¨Ò»¸öGPU³ÌÐò
*/
GLuint CreateGPUProgram(const char* vsShaderPath, const char* fsShaderPath) {

	GLuint vsShader = CompileShader(GL_VERTEX_SHADER,vsShaderPath);
	GLuint fsShader = CompileShader(GL_FRAGMENT_SHADER,fsShaderPath);

	//´´½¨program
	GLuint program = glCreateProgram();

	//°ó¶¨shader
	glAttachShader(program, vsShader);
	glAttachShader(program, fsShader);

	//Á¬½Ó
	glLinkProgram(program);

	//½â°óshader
	glDetachShader(program, vsShader);
	glDetachShader(program, fsShader);

	//ɾ³ýshader
	glDeleteShader(vsShader);
	glDeleteShader(fsShader);

	//¼ì²é´íÎó
	GLint nResult;
	glGetProgramiv(program, GL_LINK_STATUS, &nResult);
	if (nResult == GL_FALSE) {
		char log[1024] = { 0 };
		GLsizei writed = 0;
		glGetProgramInfoLog(program, 1024, &writed, log);
		printf("Create CPU program fail error %s\n", log);
		glDeleteProgram(program);
		program = 0;
	}

	return program;
}

static unsigned char* DecodeBMPData(unsigned char* imgData, int &width, int &height) {
	//decode bmp
	int pixelDataOffset = *((int*)(imgData + 10));
	width = *((int*)(imgData + 18));
	height = *((int*)(imgData + 22));

	unsigned char* pixelData = imgData + pixelDataOffset;

	//bgr ת rgb
	for (int i = 0; i < width * height * 3; i += 3) {
		unsigned char tmp = pixelData[i + 2];
		pixelData[i + 2] = pixelData[i + 0];
		pixelData[i + 0] = tmp;
	}
	return pixelData;
}

GLuint CreateTextureFromBMP(const char * imagePath)
{
	unsigned char* imgData = (unsigned char*)LoadFileContent(imagePath);
	
	if (*((unsigned short*)imgData) != 0x4D42) {
		printf("cannot decode %s\n", imagePath);
		return 0;
	}

	int width, height;
	unsigned char* pixelData = DecodeBMPData(imgData, width, height);

	GLuint texture;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixelData);
	glBindTexture(GL_TEXTURE_2D, 0);

	delete imgData;
	return texture;
}



static GLuint CreateTexture(int w, int h, const void* data, GLenum type)
{
	GLuint texId;
	glGenTextures(1, &texId);
	glBindTexture(GL_TEXTURE_2D, texId);
	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, type, w, h, 0, type, GL_UNSIGNED_BYTE, data);
	return  texId;
}

GLuint CreateTextureFromFile(const char * imagePath)
{
	//1 »ñȡͼƬ¸ñʽ
	FREE_IMAGE_FORMAT fifmt = FreeImage_GetFileType(imagePath, 0);
	if (fifmt == FIF_UNKNOWN)
	{
		printf("File %s not found! ", imagePath);
		return  0;
	}
	//2 ¼ÓÔØͼƬ
	FIBITMAP    *dib = FreeImage_Load(fifmt, imagePath, 0);

	FREE_IMAGE_COLOR_TYPE type = FreeImage_GetColorType(dib);

	//! »ñÈ¡Êý¾ÝÖ¸Õë
	FIBITMAP*   temp = dib;
	dib = FreeImage_ConvertTo32Bits(dib);
	FreeImage_Unload(temp);

	BYTE*   pixels = (BYTE*)FreeImage_GetBits(dib);
	int     width = FreeImage_GetWidth(dib);
	int     height = FreeImage_GetHeight(dib);

	for (int i = 0; i < width * height * 4; i += 4)
	{
		BYTE temp = pixels[i];
		pixels[i] = pixels[i + 2];
		pixels[i + 2] = temp;
	}

	GLuint res = CreateTexture(width, height, pixels, GL_RGBA);
	FreeImage_Unload(dib);
	return      res;
}

const unsigned long FORMAT_DXT1 = 0x31545844l; // ÕâÀïÆäʵDXT1µ¹ÐðµÄasciiÂë
static unsigned char* DecodeDXT1Data(unsigned char* imgData, int &width, int &height, int &pixelSize) {
	height = *((int*)(imgData + sizeof(unsigned long) * 3));
	width = *((int*)(imgData + sizeof(unsigned long) * 4));
	pixelSize = *((int*)(imgData + sizeof(unsigned long) * 5));

	unsigned long compressFormat;
	compressFormat = *((int*)(imgData + sizeof(unsigned long) * 21));

	switch (compressFormat) {
	case FORMAT_DXT1:
		printf("decode dxt1\n");
		break;
	}
	unsigned char* pixelData = new unsigned char[pixelSize];

	memcpy(pixelData, imgData + sizeof(unsigned long) * 32, pixelSize);
		 
	return pixelData;
}

GLuint CreateTextureFromDds(const char * imagePath)
{
	unsigned char* imgData = (unsigned char*)LoadFileContent(imagePath);

	if (memcmp(imgData, "DDS ", 4) != 0) {
		printf("cannot decode %s\n", imagePath);
		return 0;
	}

	int width, height;
	int pixelSize = 0;
	unsigned char* pixelData = DecodeDXT1Data(imgData, width, height, pixelSize);

	GLuint texture;
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	//GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
	glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, width, height, 0, pixelSize, pixelData);

	glBindTexture(GL_TEXTURE_2D, 0);
	delete imgData;
	return texture;
}
void SaveImage(const char*imagePath, unsigned char*imgData, int width, int height)
{
	FILE*pFile = fopen(imagePath, "wb");
	if (pFile)
	{
		BITMAPFILEHEADER bfh;
		memset(&bfh, 0, sizeof(BITMAPFILEHEADER));
		bfh.bfType = 0x4D42;
		bfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + width*height * 3;
		bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
		fwrite(&bfh, sizeof(BITMAPFILEHEADER), 1, pFile);

		BITMAPINFOHEADER bih;
		memset(&bih, 0, sizeof(BITMAPINFOHEADER));
		bih.biWidth = width;
		bih.biHeight = height;
		bih.biBitCount = 24;
		bih.biSize = sizeof(BITMAPINFOHEADER);
		fwrite(&bih, sizeof(BITMAPINFOHEADER), 1, pFile);
		unsigned char temp = 0;
		for (int i = 0; i<width*height * 3; i += 3)
		{
			temp = imgData[i + 2];
			imgData[i + 2] = imgData[i];
			imgData[i] = temp;
		}
		fwrite(imgData, 1, width*height * 3, pFile);
		fclose(pFile);
	}
}

void CheckGLError(const char*file, int line)
{

	GLenum error = glGetError();
	if (error != GL_NO_ERROR)
	{
		switch (error)
		{
		case  GL_INVALID_ENUM:
			printf("GL Error GL_INVALID_ENUM %s : %d\n", file, line);
			break;
		case  GL_INVALID_VALUE:
			printf("GL Error GL_INVALID_VALUE %s : %d\n", file, line);
			break;
		case  GL_INVALID_OPERATION:
			printf("GL Error GL_INVALID_OPERATION %s : %d\n", file, line);
			break;
		case  GL_STACK_OVERFLOW:
			printf("GL Error GL_STACK_OVERFLOW %s : %d\n", file, line);
			break;
		case  GL_STACK_UNDERFLOW:
			printf("GL Error GL_STACK_UNDERFLOW %s : %d\n", file, line);
			break;
		case  GL_OUT_OF_MEMORY:
			printf("GL Error GL_OUT_OF_MEMORY %s : %d\n", file, line);
			break;
		default:
			printf("GL Error 0x%x %s : %d\n", error, file, line);
			break;
		}
	}
}