// C RunTime Header Files
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include <malloc.h>
#include <memory.h>
#include <stdint.h>
#include <time.h>

// C++ Headers
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <map>

#ifdef _WIN32
// Windows Headers
#define Rectangle WinRectangle
#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#undef Rectangle 
#include <mmsystem.h>
#endif

// Project Headers
#include "PicoPNG.h"
#include "Graphics.h"
#include "Utils.h"
#include "MemoryMapping.h"
#include "Window.h"


BEGIN_NAMESPACE


// void CUTE_LogDebugMessage(const char* a_formatString, ...);
void CUTE_LogMessage(/*int a_winId, */ CUTE_LogLevel a_level, const char* a_utf8TextFmt, ...);

// Using CUTE_Debug can be used for quickly adding debug output, and in release builds the calls to it shouldn't
// produce any code (if NDEBUG is defined which the ANSI C standard defines for disabling assert in release builds)
#ifdef NDEBUG
#define CUTE_Debug(a_utf8TextFmt, ...)           ((void)0)
#else
#define CUTE_Debug(a_utf8TextFmt, ...)           CUTE_LogMessage(CUTE_DEBUG, a_utf8TextFmt, __VA_ARGS__)
#endif


static uint32_t blendColors(uint32_t a_color1, uint32_t a_color2, uint8_t a_alpha)
{
	unsigned alpha2 = 255 - a_alpha;
	unsigned red   = ((a_color1 >> 16) & 0xff) * a_alpha + ((a_color2 >> 16) & 0xff) * alpha2;
	unsigned green = ((a_color1 >>  8) & 0xff) * a_alpha + ((a_color2 >>  8) & 0xff) * alpha2;
	unsigned blue  = ((a_color1 >>  0) & 0xff) * a_alpha + ((a_color2 >>  0) & 0xff) * alpha2;
	return 0xff000000 | ((red & 0xff00) << 8) | (green & 0xff00) | (blue >> 8);
}


static uint32_t blendColorsF(uint32_t a_color1, uint32_t a_color2, float a_alpha)
{
	float alpha2 = 1.0f - a_alpha;
	float red   = float((a_color1 >> 16) & 0xff) * a_alpha + float((a_color2 >> 16) & 0xff) * alpha2;
	float green = float((a_color1 >>  8) & 0xff) * a_alpha + float((a_color2 >>  8) & 0xff) * alpha2;
	float blue  = float((a_color1 >>  0) & 0xff) * a_alpha + float((a_color2 >>  0) & 0xff) * alpha2;
	return 0xff000000 | ((int(red) & 0xff) << 16) | ((int(green) & 0xff) << 8) | (int(blue) & 0xff);
}


/*
template <typename functor>
void CUTE_ForEachPixel(CUTE_PaintTarget* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			int x = i + a_x;
			int y = j + a_y;
			if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
				uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
				functor.func();
			}
		}
}
*/


void CUTE_DrawRectangleAlpha(CUTE_PaintTarget* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			int x = i + a_x;
			int y = j + a_y;
			if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
				uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
				*dst = blendColors(*dst, a_color, a_color >> 24);
			}
		}
}


void CUTE_DrawRectangle(CUTE_PaintTarget* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height)
{
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			int x = i + a_x;
			int y = j + a_y;
			if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
				uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
				*dst = a_color;
			}
		}
}
/*
	a_x = (a_x < 0) ? 0 : a_x;
	a_y = (a_y < 0) ? 0 : a_y;
	int lastX = ((a_width + a_x) > a_target->m_width) ? a_target->m_width : a_width + a_x;
	int lastY = ((a_height + a_y) > a_target->m_height) ? a_target->m_height : a_height + a_y;

	for (int j = a_y; j < lastY; j++)
	{
		uint32_t *dst = &(a_target->m_pixels[j*a_target->m_strideBytes/4]);
		for (int i = a_x; i < lastX; i++)
		{
			dst[i] = a_color;
		}
	}
}
*/


void CUTE_DrawEllipse(CUTE_PaintTarget* a_target, uint32_t a_color, int a_x, int a_y, int a_width, int a_height, bool a_smoothEdge)
{
	int alpha = (a_color >> 24) & 0xff;
	if ( alpha == 0 )
		return;
	int centerX = a_width/2;
	int centerY = a_height/2;
	int scale = 64;  // This is to stop overflows with 32bit ints
	if ( a_width < 256 && a_height < 256 )
		scale = 1;
	if ( alpha == 255 )
	{
		for (int j = 0; j < a_height; j++)
			for (int i = 0; i < a_width; i++)
			{
				int x = i + a_x;
				int y = j + a_y;
				if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
					uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
					int a = (i - centerX) * a_height / scale;
					int b = (j - centerY) * a_width / scale;
					int c1 = a_width * a_height / (2 * scale);
					int c2 = (a_width-1) * (a_height-1) / (2 * scale);
					//int c2 = ((a_width+1) * (a_height+1)) / (2 * scale);
					
					int innerR = c1*c1;
					int outerR = c2*c2;
					int dist = a*a + b*b;
					int diffR = outerR - innerR;
					int distR = dist - innerR;
					int alpha = ((distR * 255) / diffR);

					if ( dist < outerR )
						*dst = a_color;
					else if ( dist < innerR )
						*dst = blendColors(a_color, *dst, alpha);
					//a_antiAlias
				}
			}
	} else {
		for (int j = 0; j < a_height; j++)
			for (int i = 0; i < a_width; i++)
			{
				int x = i + a_x;
				int y = j + a_y;
				if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height ) {
					uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
					int a = (i - centerX) * a_height / scale;
					int b = (j - centerY) * a_width / scale;
					int c = a_width * a_height / (2 * scale);
					if ( (a*a + b*b) < c*c )
						*dst = (alpha<<24) | (blendColors(a_color, *dst, alpha) & 0xffffff);
				}
			}
	}
}


void CUTE_DrawGradient(CUTE_PaintTarget* a_target, CUTE_Gradient a_gradient, int a_x, int a_y, int a_width, int a_height)
{
	for ( int j = 0; j < a_height; j++ )
		for ( int i = 0; i < a_width; i++ )
		{
			int x = i + a_x;
			int y = j + a_y;
			if ( x >= 0 && x < a_target->m_width && y >= 0 && y < a_target->m_height )
			{
				float dist = 0.0;

				if (a_gradient.m_type == CUTE_RADIAL_GRADIENT)
				{
					int centerX = a_gradient.m_data.m_radial.m_centerX;
					int centerY = a_gradient.m_data.m_radial.m_centerY;
					//float errLUT[16] = { 1.1, -1.1, 2.2, -2.2, 3.3, -3.3, 4.4, -4.4, 5.5, -5.5, 6.6, -6.6, 7.7, -7.7, 8.8, -8.8 };
					//int dist = (int)(errLUT[rand() % 4] + sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) * 256 / a_gradient.m_data.m_radial.m_distance);
					dist = sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) / a_gradient.m_data.m_radial.m_distance;
					/*
					float distf = sqrt(float((centerX - i)*(centerX - i) + (centerY - j)*(centerY - j))) * 256 / a_gradient.m_data.m_radial.m_distance;
					float frac = distf - floor(distf) - 0.51;
					float frac2 = frac + 0.7;
					float frac3 = float(j*8) / a_height + float(i-90) / a_width;
					if ( frac < 0.1 && frac > -0.1 )
						frac = ( frac < 0.0 ) ? -0.081 : 0.08;
					int dist = (int)(float(frac * 64) - float(frac2 * frac3) + distf);
					//dist += ;
					*/
					//dist = (dist > 255) ? 255 : ((dist < 0) ? 0 : dist);
				}
				else if (a_gradient.m_type == CUTE_LINEAR_GRADIENT)
				{
					/*
					m = dy / dx
					y = mx + c;
					c = y - mx;
					m = (y - c) / x;
					*/
					// TODO:

					int y1 = a_gradient.m_data.m_linear.m_y1;
					int y2 = a_gradient.m_data.m_linear.m_y2;
					if ( y <= y1 )
						dist = 0;
					else if ( y >= y2 )
						dist = 1.0;
					else {
						dist = float(y2 - y) / (y2 - y1);
					}
				}

				uint32_t *dst = &(a_target->m_pixels[y*a_target->m_strideBytes/4 + x]);
				dist = (dist > 1.0f) ? 1.0f : ((dist < 0.0f) ? 0.0f : dist);
				*dst = blendColorsF(a_gradient.m_color1, a_gradient.m_color2, dist);
			}
		}
}


void CUTE_DrawLine(CUTE_PaintTarget* a_target, uint32_t a_color, int a_x1, int a_y1, int a_x2, int a_y2)
{
	float x = 0.0f;
	float y = 0.0f;
	float fdy = 1.0f;
	float fdx = 1.0f;
	int dx = a_x2 - a_x1;
	int dy = a_y2 - a_y1;
	int count = 1;
	if ( !dy && !dx ) {
		if ( a_x1 >= 0 && a_x1 < a_target->m_width && a_y1 >= 0 && a_y1 < a_target->m_height )
			a_target->m_pixels[a_y1*a_target->m_strideBytes/4 + a_x1] = a_color;
		return;
	}
	if ( dx*dx > dy*dy ) {
		count = dx;
		fdy = float(dy) / dx;
	} else {
		count = dy;
		fdx = float(dx) / dy;
	}
	if ( count < 0 ) {
		count = -count;
		fdy = -fdy;
		fdx = -fdx;
	}
	//count++;
	for (; count--; x+=fdx, y+=fdy)
	{
		int xi = int(a_x1 + x);
		int yi = int(a_y1 + y);
		if ( xi >= 0 && xi < a_target->m_width && yi >= 0 && yi < a_target->m_height )
			a_target->m_pixels[yi*a_target->m_strideBytes/4 + xi] = a_color;
	}
}


void CUTE_DrawPixels(CUTE_PaintTarget* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight)
{
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			int x = i + a_x;
			int y = j + a_y;
			if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
			{
				uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
				uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
				if ( src & 0xff000000 )
					*dst = src;
			}
		}
}


void CUTE_DrawPixelsAlpha(CUTE_PaintTarget* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight)
{
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			int x = i + a_x;
			int y = j + a_y;
			if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
			{
				uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
				uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
				if ( src & 0xff000000 )
					*dst = blendColors(src, *dst, (src>>24) & 0xff);
			}
		}
}


void CUTE_DrawPixelsAlphaBlended(CUTE_PaintTarget* a_target, uint32_t* a_bits, int a_x, int a_y, int a_width, int a_height, int a_xOffset, int a_yOffset, int a_pixelsWidth, int a_pixelsHeight, int a_alpha)
{
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			int x = i + a_x;
			int y = j + a_y;
			if ( y > 0 && y < a_target->m_height && x > 0 && x < a_target->m_width )
			{
				uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
				uint32_t src = a_bits[(j+a_yOffset)*a_pixelsWidth+(i+a_xOffset)];
				if ( src & 0xff000000 )
					*dst = blendColors(src, *dst, a_alpha);
			}
		}
}


static bool TestFileExtension(const char* a_file, const char* a_ext)
{
	size_t len = strlen(a_file);
	if (len < 4 || strlen(a_ext) != 4)
		return false;
	for (int i = 0; i < 4; i++)
		if (toupper(a_file[len-4+i]) != toupper(a_ext[i]))
			return false;
	return true;
}


static void LoadImage(const char* file, int a_w, int a_h, uint32_t*& a_pixels, bool chromaKeyed = true)
{
	if (TestFileExtension(file, ".png"))
	{
		// It's a PNG image
		MemoryMappingData* mapping = MemoryMapping_Open(file);
		// For 32bit builds, this code won't support input PNG files that are more than 4GiB, but neither can it support
		// having a buffer as large as that for storing the pixels in to, so perhaps best just to use 64bit builds if dealing with massive images.
		size_t siz = (size_t)MemoryMapping_GetSize(mapping);
		uint8_t* buf = (uint8_t*)MemoryMapping_GetAddress(mapping);
		std::vector<unsigned char> out_image;
		unsigned long image_width, image_height;		
		DecodePNG(out_image, image_width, image_height, buf, siz);
		a_pixels = new uint32_t[a_w * a_h];
		memcpy(a_pixels, out_image.data(), a_w * a_h * 4);
		for ( int i = 0; i < a_w*a_h; i++ )
			a_pixels[i] = ((a_pixels[i] << 16) & 0xff0000) | ((a_pixels[i] >> 16) & 0xff) | (a_pixels[i] & 0xff00ff00);
	}
#ifdef _WIN32
	else
	{
		HBITMAP hBMP = (HBITMAP)LoadImageA( NULL, file, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
		BITMAPINFO info = { { sizeof(BITMAPINFOHEADER), a_w, -a_h, 1, 32, BI_RGB, 0, 0 } };
		info.bmiHeader.biClrImportant = 0;
		HBITMAP hbitmap = CreateDIBSection(0, &info, DIB_RGB_COLORS, (void**)&a_pixels, 0, 0);
		HDC hdc = CreateCompatibleDC(0);
		HDC handle = CreateCompatibleDC(hdc);
		SelectObject(hdc, hbitmap);
		SelectObject(handle, hBMP);
		BitBlt(hdc, 0, 0, a_w, a_h, handle, 0, 0, SRCCOPY);
		DeleteDC(handle);
		DeleteDC(hdc);
	}
#endif

	if ( chromaKeyed )
		for ( int i = 0; i < a_w*a_h; i++ )
			if ( a_pixels[i] != 0x00ff00 )
				a_pixels[i] |= 0xff000000;
}



class Image {
public:
	Image() : pixels(0) {}
	uint32_t *pixels;
	unsigned long w, h;
};


void CUTE_DrawPixmapFile(CUTE_PaintTarget* a_target, const char* file, int x, int y, int x1, int y1, int x2, int y2)
{
	static std::map<const char*,Image> imageMap; // pixmap cache
	Image img = imageMap[file];
	if (!img.pixels) {
		LoadImage(file, img.w, img.h, img.pixels, false);
		//LoadPNGImage(file, img.w, img.h, img.pixels);
		imageMap[file] = img;
	}
	CUTE_DrawPixels(a_target, img.pixels, x, y, x2-x1+1, y2-y1+1, x1, y1, img.w, img.h);
}


/*
void CUTE_DrawNumber(CUTE_PaintTarget* a_target, uint32_t col, int x, int y, int num, int width)
{
//	LoadImage("numbers.png", g_numbersPixelsW, g_numbersPixelsH, g_numbersPixels, false);
	int divisor = 1;
	for (int i = width; i != 0; i--, divisor*=10)
		CUTE_DrawPixels(a_target, g_numbersPixels, x + (i-1)*16, y, 16, 30, 0, 30*((num/divisor)%10), g_numbersPixelsW, g_numbersPixelsH);
}
*/

/*

	Windows specific

*/

#ifndef CLEARTYPE_QUALITY
#	define CLEARTYPE_QUALITY	5
#endif


void CUTE_LogMessage(CUTE_LogLevel a_level, const char* a_formatString, ...)
{
	char buf[1024];
	va_list vaargs;
	va_start(vaargs, a_formatString);
	vsnprintf(buf, 1024, a_formatString, vaargs);
	buf[1023] = 0; // Ensure null terminated
#ifdef _WIN32
	OutputDebugStringA(buf);
#endif
	puts(buf);
	va_end(vaargs);
}


#ifdef _WIN32

void CUTE_GetTextExtents(CUTE_PaintTarget* a_target, const char* a_fontFamily, int a_size, const char* a_utf8String, int& a_width, int& a_height)
{
	HDC hdc = CreateCompatibleDC(0);
	HFONT hFont = CreateFontA(a_size, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CLEARTYPE_QUALITY, 0, a_fontFamily);
	SelectObject(hdc, hFont);
	SIZE siz;
	// std::wstring wstr = utf8_to_wstring(a_utf8String);
	std::wstring wstr = String(a_utf8String).toWString();
	if (GetTextExtentPoint32W(hdc, wstr.c_str(), (int)wstr.size(), &siz))
	{
		a_width = siz.cx;
		a_height = siz.cy;
	}
	else
	{
		a_width = (int)strlen(a_utf8String) * (a_size / 2);
		a_height = a_size;
	}
	DeleteDC(hdc);
	DeleteObject(hFont);
}


void CUTE_DrawText(CUTE_PaintTarget* a_target, uint32_t a_color, const char* a_fontFamily, int a_size, int a_x, int a_y, const char* a_utf8String, ...)
{
	const int fontSize = a_size;

	char string[1025];
	string[1024] = 0;
	va_list vaargs;
	va_start(vaargs, a_utf8String);
	vsnprintf(string, 1024, a_utf8String, vaargs); //TODO: dynamicly grow string to be as large as needed (see string_format in utils)
	va_end(vaargs);
	
	//printf("text: %s\n", string);

	// TODO: Loop over the string and break on line-breaks and call some internal function which does the body of this function

	int stringLen = (int)strlen(string);
	int bufferWidth = stringLen * fontSize * 2;
	int bufferHeight = fontSize * 2;

	/*
	SystemParametersInfo(SPI_SETFONTSMOOTHING,
                     TRUE,
                     0,
                     SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
	SystemParametersInfo(SPI_SETFONTSMOOTHINGTYPE,
                     0,
                     (PVOID)FE_FONTSMOOTHINGCLEARTYPE,
                     SPIF_UPDATEINIFILE | SPIF_SENDCHANGE); 
	*/
	HBITMAP hbitmap;
	unsigned int* a_bits = 0;
	BITMAPINFO info = { { sizeof(BITMAPINFOHEADER), bufferWidth, -bufferHeight, 1, WORD(32), BI_RGB, 0 } };
	hbitmap = CreateDIBSection(0, &info, DIB_RGB_COLORS, (void**)&(a_bits), 0, 0);	

	int a_width = bufferWidth;
	int a_height = bufferHeight;
	int a_pixelsWidth = bufferWidth;
	//int a_pixelsHeight = bufferHeight;

	//	memset(a_bits, 0, bufferWidth*bufferHeight*4);
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			if ( ((j+a_y) > 0) && ((j+a_y) < a_target->m_height) && ((i+a_x) > 0) && ((i+a_x) < a_target->m_width) )
			{
				a_bits[j*a_pixelsWidth+i] = a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)];
			}
		}

	HDC hdc = CreateCompatibleDC(0);
	SelectObject(hdc, hbitmap);
	//HFONT hFont = CreateFontA(fontSize, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CLEARTYPE_NATURAL_QUALITY, 0, a_fontFamily); // "Segoe Script"); // "Fixedsys");
	HFONT hFont = CreateFontA(fontSize, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CLEARTYPE_QUALITY, 0, a_fontFamily); // "Segoe Script"); // "Fixedsys");
	SelectObject(hdc, hFont);
	//SetTextColor(hdc, RGB(0xff,0xff,0xff));
	SetTextColor(hdc, RGB((a_color >> 16)&0xff,(a_color >> 8)&0xff,a_color&0xff));
	SetBkMode(hdc, TRANSPARENT);
	RECT rect = { 0, 0, bufferWidth, bufferHeight };
	// std::wstring wstr = utf8_to_wstring(string);
	std::wstring wstr = String(string).toWString();
	DrawTextW(hdc, wstr.c_str(), -1, &rect, DT_LEFT);
	DeleteDC(hdc);


	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			if ( ((j+a_y) > 0) && ((j+a_y) < a_target->m_height) && ((i+a_x) > 0) && ((i+a_x) < a_target->m_width) )
			{
				a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)] = a_bits[j*a_pixelsWidth+i];
			}
		}
/*
	// Make some internal function which can be called by this and CUTE_DrawPixels
	//CUTE_DrawPixels(a_target, backBuffer.m_target.m_pixels, a_x, a_y, 1000, 64, 0, 0, 1000, 64);
	for (int j = 0; j < a_height; j++)
		for (int i = 0; i < a_width; i++)
		{
			if ( ((j+a_y) > 0) && ((j+a_y) < a_target->m_height) && ((i+a_x) > 0) && ((i+a_x) < a_target->m_width) )
			{
				uint32_t *dst = &(a_target->m_pixels[(j+a_y)*a_target->m_strideBytes/4 + (i+a_x)]);
				uint32_t src = a_bits[j*a_pixelsWidth+i];
				if ( src )
					*dst = blendColors(a_color, *dst, src & 0xff);
					// *dst = a_color;
			}
		}
		*/

	DeleteObject(a_bits);
	DeleteObject(hFont);
	DeleteObject(hbitmap);	
}

#endif


END_NAMESPACE
