// C RunTime Header Files
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <ctime>
#include <climits>

// C++ Headers
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <map>

// Project Headers
#include "Graphics.h"
#include "Common.h"
#include "MemoryMapping.h"
#include "Window.h"
#include "TrueType.h"


BEGIN_NAMESPACE


void LogMessage(LogLevel a_level, const char* a_utf8TextFmt, ...);

// Using 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 Debug(a_utf8TextFmt, ...)           ((void)0)
#else
#  define Debug(a_utf8TextFmt, ...)           LogMessage(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 ForEachPixel(PixelBuffer* 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 DrawRectangleAlpha(PixelBuffer* 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 DrawRectangle(PixelBuffer* 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;
			}
		}
}


void DrawEllipse(PixelBuffer* 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 DrawGradient(PixelBuffer* a_target, 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 == 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 == 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 DrawLine(PixelBuffer* 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 DrawPixels(PixelBuffer* 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 DrawPixelsAlpha(PixelBuffer* 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 DrawPixelsAlphaBlended(PixelBuffer* 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);
			}
		}
}


#if 0

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;
}


extern int DecodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32);


static bool LoadImage(const char* file, PixelBuffer& a_img, bool chromaKeyed = true)
{
	if (TestFileExtension(file, ".png"))
	{
		// It's a PNG image
		MemoryMappingData* mapping = MemoryMapping_Open(file);
    if (!mapping)
      return false;
		// 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 w, h;
		if (DecodePNG(out_image, w, h, buf, siz, true))
    {
      a_img.m_height = h;
      a_img.m_width = w;
      a_img.m_strideBytes = w * sizeof(uint32_t);
      a_img.m_format = PF_ARGB8888;
      a_img.m_pixels = new uint32_t[w * h];
      memcpy(a_img.m_pixels, out_image.data(), w * h * sizeof(uint32_t));
      for ( int i = 0; i < w*h; i++ )
        a_img.m_pixels[i] = ((a_img.m_pixels[i] << 16) & 0xff0000) |
          ((a_img.m_pixels[i] >> 16) & 0xff) | (a_img.m_pixels[i] & 0xff00ff00);
      if ( chromaKeyed )
        for ( int i = 0; i < w*h; i++ )
          if ( a_img.m_pixels[i] != 0x00ff00 )
            a_img.m_pixels[i] |= 0xff000000;
      return true;
    }
	}
  return false;
}


void DrawPixmapFile(PixelBuffer* a_target, const char* file, int x, int y, int x1, int y1, int x2, int y2)
{
	static std::map<const char*,PixelBuffer> imageMap; // pixmap cache
	PixelBuffer img = imageMap[file];
	if (!img.m_pixels)
  {
		if (LoadImage(file, img, false))
    {
		  imageMap[file] = img;
    }
	}
	DrawPixels(a_target, img.m_pixels, x, y, x2-x1+1, y2-y1+1, x1, y1, img.m_width, img.m_height);
}


void DrawNumber(PixelBuffer* 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)
		DrawPixels(a_target, g_numbersPixels, x + (i-1)*16, y, 16, 30, 0, 30*((num/divisor)%10), g_numbersPixelsW, g_numbersPixelsH);
}


#endif


void LogMessage(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);
}


void GetTextExtents(PixelBuffer* a_target, const char* a_fontFamily, int a_size, const char* a_utf8String, int& a_width, int& a_height)
{
  // TODO: do this properly 
  a_width = (int)strlen(a_utf8String) * (a_size / 2);
  a_height = a_size;
}


struct vec2i
{
  int x, y;
};


// Simple 3 control point bezier curve
// p1 and p3 are the 2 end points the curve starts/ends at
// p2 is another control point but not a point on the curve
// t is the ratio between the two end points to interpolate a result for
vec2i bezierCurve(const vec2i& p1, const vec2i& p2, const vec2i& p3, double t)
{
  double t2 = 1.0 - t;
  return (vec2i){ int(t2*t2*p1.x + 2.0*t2*t*p2.x + t*t*p3.x),
                  int(t2*t2*p1.y + 2.0*t2*t*p2.y + t*t*p3.y) }; 
}


vec2i bezierCurveFixed(const vec2i& p1, const vec2i& p2, const vec2i& p3, uint64_t t)   // t is 0 to 1023
{
  uint64_t t2 = 0x10000 - t;
  uint64_t t2t2 = t2*t2;
  uint64_t t2t = t2*t;
  uint64_t tt = t*t;
  return (vec2i){ int((t2t2*p1.x + 2*t2t*p2.x + tt*p3.x) >> 32),
                  int((t2t2*p1.y + 2*t2t*p2.y + tt*p3.y) >> 32) };
}


void DrawCurve(PixelBuffer* a_target, uint32_t a_color, const vec2i& p1, const vec2i& p2, const vec2i& p3)
{
  int dx = p1.x - p3.x;
  int dy = p1.y - p3.y;
  if (dx*dx > dy*dy) {
    dx = (dx < 0) ? -dx : dx;
  } else {
    dx = (dy < 0) ? -dy : dy;
  }
  dx *= 2;
  /*
  float divisor = 1.0f / float(dx);
  for (int i = 0; i < (dx-1); i++)
  {
    vec2i pos1 = bezierCurve(p1, p2, p3, float(i) * divisor);
    vec2i pos2 = bezierCurve(p1, p2, p3, float(i + 1) * divisor);
    DrawLine(a_target, a_color, pos1.x, pos1.y, pos2.x, pos2.y);
  }
  */
  // Fixed precision version
  for (int i = 0; i < dx; i++)
  {
    vec2i pos1 = bezierCurveFixed(p1, p2, p3, (i * 1024) / dx);
    vec2i pos2 = bezierCurveFixed(p1, p2, p3, (i * 1024 + 1024) / dx);
    DrawLine(a_target, a_color, pos1.x, pos1.y, pos2.x, pos2.y);
  }

}


void DrawRectangleB(PixelBuffer* 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 += 0x2000000;
			}
		}
}

void DrawCurveB(PixelBuffer* a_target, uint32_t a_color,
  const vec2i& p1, const vec2i& p2, const vec2i& p3, // 3 control points 
  bool firstInContour, bool lastInContour, int firstAngle)  // connectivity flags 
{
  // if firstInContour - always output the first point
  // if lastInContour  - don't output the last point

  // TODO: couldn't then angle from p1->p2->p3 make a  'v' or '^' shape?
  //        if so, at the apex of this bezier curve, it won't have
  //        output 2 points as required.
  // Probably some checking of angle between p1->p2  and  p2->p3  are needed

  int dy = p1.y - p3.y;
  //int dx = p1.x - p3.x;
  int thisAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);

  static int lastAngle = 0;
  bool drawFirst = true;
  bool drawLast = true;
  if (!firstInContour)
    drawFirst = (thisAngle != 0) && ((thisAngle + lastAngle) == 0);
  if (lastInContour)
    drawLast = (thisAngle != 0) && ((thisAngle + firstAngle) == 0);
  lastAngle = thisAngle;

  dy = (dy < 0) ? -dy : dy;  // dy = |dy|
  dy *= 2; // over-sampling -> we try to sample
           // the bezier with an even distribution of 't' values but this
           // doesn't mean the resulting positions are evenly spaced along
           // the curve, but because we want one sample for each y value,
           // we need to over-sample the bezier curve to actually ensure we
           // get what we want in an efficient way. Less efficient would be
           // to try to solve the linear equation to figure out the value
           // of 't' that gives a given y value. But doing the forward
           // calculation twice instead is quite efficient and practical.
 
  if (!dy) {
    dy = 0;//1024;//512;
  }

  // Fixed precision version
  if (dy)
  {
    //dy+=5;
    int lastY = -1;
    for (int i = 0; i <= dy;)// i++)
    {
      vec2i pos1;
      do { 
        pos1 = bezierCurveFixed(p1, p2, p3, (i << 16) / dy);
        i++;
      } while ((i <= dy) && (pos1.y == lastY));

      bool first = pos1.y == p1.y && pos1.x == p1.x;
      bool final = pos1.y == p3.y && pos1.x == p3.x;
      bool canDraw = pos1.y != lastY;  // do we need to output something
      if (first && !drawFirst)
        canDraw = false;
      if (final && !drawLast)
        canDraw = false;
      if (canDraw)
        DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
      lastY = pos1.y;
      if (final)
        break;
    }
  }
  else
  {
    if (drawFirst)
      DrawRectangleB(a_target, a_color, p1.x, p1.y, 1, 1);
    if (drawLast)
      DrawRectangleB(a_target, a_color, p3.x, p3.y, 1, 1);
  }
}


Rectangle glyphBounds(const GlyphOutline& outline)
{
  Rectangle ret;
  int minX = INT_MAX, minY = INT_MAX;
  int maxX = INT_MIN, maxY = INT_MIN;
  for (size_t i = 0; i < outline.m_lines.size(); i++)
  {
    const FontLineSegment& b = outline.m_lines[i];
    if (b.m_x1 < minX) minX = b.m_x1; if (b.m_x1 > maxX) maxX = b.m_x1;
    if (b.m_y1 < minY) minY = b.m_y1; if (b.m_y1 > maxY) maxY = b.m_y1;
    if (b.m_x2 < minX) minX = b.m_x2; if (b.m_x2 > maxX) maxX = b.m_x2;
    if (b.m_y2 < minY) minY = b.m_y2; if (b.m_y2 > maxY) maxY = b.m_y2;
    if (b.m_x3 < minX) minX = b.m_x3; if (b.m_x3 > maxX) maxX = b.m_x3;
    if (b.m_y3 < minY) minY = b.m_y3; if (b.m_y3 > maxY) maxY = b.m_y3;
  }
  ret.m_x = minX; ret.m_y = minY;
  ret.m_width = maxX - minX + 1; ret.m_height = maxY - minY + 1;
  return ret;
}


void DrawOutlineText(PixelBuffer* a_target, uint32_t a_color, const char* a_fontFamily, int a_size, int a_x, int a_y, const char* a_utf8String)
{
  if (!a_target || !a_fontFamily || !a_utf8String || a_size <= 0)
  {
    return;
  }
  
  TtfFont& font = getFont("Arial");
  while (*a_utf8String)
  {
    Glyph& glyph = font.getGlyph(*a_utf8String);

    GlyphOutline& outline = glyph.outline;
    GlyphMetrics& g = glyph.metrics;

    // iterate the bezier curves
    for (size_t i = 0; i < outline.m_lines.size(); i++)
    {
      FontLineSegment& b = outline.m_lines[i];
      {
        vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
        vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
        vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
        DrawCurve(a_target, a_color, p1, p2, p3);
      }
    }
    float spacing = 1.0;
    a_x += ((g.advanceWidth * a_size) >> 10) + (int)(spacing + 0.5);
    a_utf8String++;
  }
}


void RasterizeCharacter(PixelBuffer* a_target, uint32_t a_color, int a_size, int a_x, int a_y, GlyphOutline& outline)
{
  Rectangle rect = glyphBounds(outline);
  rect.m_width = (rect.m_width * a_size) >> 10;
  rect.m_height = (rect.m_height * a_size) >> 10;
  PixelBuffer buf = {
    new uint32_t[rect.m_width*rect.m_height],
    rect.m_width*4,
    rect.m_width,
    rect.m_height,
    PF_ARGB8888
  };
  memset(buf.m_pixels, 0, rect.m_width*rect.m_height*4);
      
  vec2i lastPos{ -1, -1 };
  vec2i firstPos{ -1, -1 };

  int firstAngle = 0;

  bool firstInContour = true;
  bool lastInContour = true;
  // iterate the bezier curves
  for (size_t i = 0; i < outline.m_lines.size(); i++)
  {
    FontLineSegment& b = outline.m_lines[i];
    {
      vec2i p1 = (vec2i){ int((b.m_x1 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y1 - rect.m_y) * a_size >> 10) };
      vec2i p2 = (vec2i){ int((b.m_x2 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y2 - rect.m_y) * a_size >> 10) };
      vec2i p3 = (vec2i){ int((b.m_x3 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y3 - rect.m_y) * a_size >> 10) };
      
      firstInContour = lastInContour;
      if (firstInContour)
      {
        int dy = p1.y - p3.y;
        firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
        firstPos = p1;
      }
      lastInContour = p3.x == firstPos.x && p3.y == firstPos.y;
      /*
      if (p1.x != lastPos.x || p1.y != lastPos.y)
      {
        firstPos = p1;
        firstInContour = true;
      }
      lastPos = p3;
      */
      DrawCurveB(&buf, 0xFF000000 | a_color, p1, p2, p3, firstInContour, lastInContour, firstAngle);  

#if 0 // debug control points
      DrawRectangle(&buf, 0xFF0000FF, p1.x-3, p1.y-3, 5, 5);
      DrawRectangle(&buf, 0xFF0000FF, p2.x-3, p2.y-3, 5, 5);
      DrawRectangle(&buf, 0xFF0000FF, p3.x-3, p3.y-3, 5, 5);
#endif
    }
  }

#if 1 // rasterize
  for (int j = 0; j < rect.m_height; j++)
  {
    uint8_t state = 0;  // 0 off   1 off->on   2 on   3 on->off
    uint8_t w = 0;
    for (int i = 0; i < rect.m_width; i++)
    {
      int count = buf.m_pixels[j*rect.m_width + i] >> 25;
      do
      {
        if (count)
        {
          state = 1 - state;
          count--;
        }
        else 
        {
          if (state)
            buf.m_pixels[j*rect.m_width + i] = 0xFF000000 | a_color;
        }
      }
      while (count);
    }
  }
#endif

#if 1 // debug control points
  lastPos = vec2i{ -1, -1 };
  firstPos = vec2i{ -1, -1 };
  firstInContour = true;
  lastInContour = true;
  // iterate the bezier curves
  for (size_t i = 0; i < outline.m_lines.size(); i++)
  {
    FontLineSegment& b = outline.m_lines[i];
    {
      vec2i p1 = (vec2i){ int((b.m_x1 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y1 - rect.m_y) * a_size >> 10) };
      vec2i p2 = (vec2i){ int((b.m_x2 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y2 - rect.m_y) * a_size >> 10) };
      vec2i p3 = (vec2i){ int((b.m_x3 - rect.m_x) * a_size >> 10), rect.m_height - int((b.m_y3 - rect.m_y) * a_size >> 10) };
      firstInContour = lastInContour;
      if (firstInContour)
      {
        int dy = p1.y - p3.y;
        firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
        firstPos = p1;
      }
      lastInContour = p3.x == firstPos.x && p3.y == firstPos.y;
      
      if (firstInContour)//p1.x != lastPos.x || p1.y != lastPos.y)
      {
        DrawRectangle(&buf, 0xFF00FF00, p1.x-3, p1.y-3, 5, 5);
        DrawRectangle(&buf, 0xFF0000FF, p2.x-3, p2.y-3, 5, 5);
        DrawRectangle(&buf, 0xFF0000FF, p3.x-3, p3.y-3, 5, 5);
      }
      else if (lastInContour)
      {
        DrawRectangleAlpha(&buf, 0x1FFF7F00, p1.x-3, p1.y-3, 5, 5);
        DrawRectangle(&buf, 0xFFFF4444, p2.x-3, p2.y-3, 5, 5);
        DrawRectangleAlpha(&buf, 0x1F1FFF1F, p3.x-3, p3.y-3, 5, 5);
      }
      else
      {
        DrawRectangleAlpha(&buf, 0x3F808044, p1.x-1, p1.y-1, 3, 3);
        DrawRectangleAlpha(&buf, 0x3FFF4444, p2.x-1, p2.y-1, 3, 3);
        DrawRectangleAlpha(&buf, 0x3FFF4444, p3.x-1, p3.y-1, 3, 3);
      }
      lastPos = p3;
    }
  }
#endif

  int yOff = a_size - rect.m_height;
  DrawPixels(a_target, buf.m_pixels, a_x + ((rect.m_x*a_size)>>10), 
                   a_y + yOff - ((rect.m_y*a_size)>>10), buf.m_width, buf.m_height, 0, 0, buf.m_width, buf.m_height);
  delete[] buf.m_pixels;
  
  /*
  for (size_t i = 0; i < outline.m_lines.size(); i++)
  {
    FontLineSegment& b = outline.m_lines[i];
    {
      vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
      vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
      vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
      DrawCurve(a_target, 0x00FF00, p1, p2, p3);
    }
  }
  */
}


void DrawText(PixelBuffer* a_target, uint32_t a_color, const char* a_fontFamily, int a_size, int a_x, int a_y, const char* a_utf8String)
{
  if (!a_target || !a_fontFamily || !a_utf8String || a_size <= 0)
  {
    return;
  }

  // TODO: Loop over the string and break on line-breaks and call some internal function which does the body of this function

  //TtfFont& font = getFont("Arial");
  TtfFont& font = getFont("Tahoma");
  while (*a_utf8String)
  {
    Glyph& glyph = font.getGlyph(*a_utf8String);

    GlyphOutline& outline = glyph.outline;
    GlyphMetrics& g = glyph.metrics;

    RasterizeCharacter(a_target, a_color, a_size, a_x, a_y, outline);

    /*
    Rectangle rect = glyphBounds(outline);

    // iterate the bezier curves
    for (size_t i = 0; i < outline.m_lines.size(); i++)
    {
      FontLineSegment& b = outline.m_lines[i];
      {
        vec2i p1 = (vec2i){ a_x + int(b.m_x1 * a_size >> 10), a_y + a_size - int(b.m_y1 * a_size >> 10) };
        vec2i p2 = (vec2i){ a_x + int(b.m_x2 * a_size >> 10), a_y + a_size - int(b.m_y2 * a_size >> 10) };
        vec2i p3 = (vec2i){ a_x + int(b.m_x3 * a_size >> 10), a_y + a_size - int(b.m_y3 * a_size >> 10) };
        DrawCurve(a_target, a_color, p1, p2, p3);
      }
    }
    */

    float spacing = 1.0;
    //a_x += -g.xMin + g.leftSideBearing + g.advance + (int)(spacing + 0.5);
    a_x += ((g.advanceWidth * a_size) >> 10) + (int)(spacing + 0.5);
    a_utf8String++;
  }

//  printf("done %s:%i: Here!\n", __FILE__, __LINE__);
}


/*

struct vec2f
{
  float x, y;
};

vec2f operator*(const vec2f& v, float c)
{
  return (vec2f){ v.x*c, v.y*c };
}

vec2f operator*(float c, const vec2f& v)
{
  return (vec2f){ v.x*c, v.y*c };
}

vec2f operator+(const vec2f& v1, const vec2f& v2)
{
  return (vec2f){ v1.x+v2.x, v1.y+v2.y };
}

// Generalized spline function
// a_tension       -1.0 -> 1.0     Round -> Tight
// a_bias          -1.0 -> 1.0     Pre shoot -> Post shoot
// a_continuity    -1.0 -> 1.0     Box corners -> Inverted corners
vec2f kochanekBartelsSpline(const std::vector<vec2f>& a_points, float t, float a_tension, float a_bias, float a_continuity)
{
  int pnts = a_points.size();
  if (!pnts)
    return (vec2f){ 0.0f, 0.0f };
  t = std::max(0.0f, std::min(1.0f, t)); // clamp t to be between 0->1
  int intervals = pnts - 1; // the intervals are the segments between each point 
  int n = std::max(0, std::min(pnts-1, int(intervals * t))); // figure out which interval t is between
  t = t * intervals - n; // the new t value is the ratio between the points at n and n+1
  float tmp1 = 0.5f * (1.0f - a_tension);
  float A = tmp1 * (1.0f + a_continuity) * (1.0f + a_bias); // A,B,C,D are the parameter factors
  float B = tmp1 * (1.0f - a_continuity) * (1.0f - a_bias);
  float C = tmp1 * (1.0f - a_continuity) * (1.0f + a_bias);
  float D = tmp1 * (1.0f + a_continuity) * (1.0f - a_bias);
  const vec2f& pnt0 = a_points[std::max(n - 1, 0)];
  const vec2f& pnt1 = a_points[n];
  const vec2f& pnt2 = a_points[std::min(pnts - 1, n + 1)];
  const vec2f& pnt3 = a_points[std::min(pnts - 1, n + 2)];
  vec2f vA =  -A*pnt0 +      (2+A-B-C)*pnt1 +  (-2+B+C-D)*pnt2 +  D*pnt3; // vA,vB,vC - polynomial factors
  vec2f vB = 2*A*pnt0 + (-3-2*A+2*B+C)*pnt1 + (3-2*B-C+D)*pnt2 + -D*pnt3;
  vec2f vC =  -A*pnt0 +          (A-B)*pnt1 +           B*pnt2;
  return ((vA*t + vB)*t + vC)*t + pnt1; // blends by the factors between the 4 closest control points
}

vec2f catmulRomSpline(const std::vector<vec2f>& a_points, float t)
{
  return kochanekBartelsSpline(a_points, t, 0.0f, 0.0f, 0.0f);
}

vec2f cubicSpline(const std::vector<vec2f>& a_points, float t)
{
  return kochanekBartelsSpline(a_points, t, 1.0f, 0.0f, 0.0f);
}

vec2f lineSpline(const std::vector<vec2f>& a_points, float t)
{
  return kochanekBartelsSpline(a_points, t, 0.0f, 0.0f, -1.0f);
}

*/

END_NAMESPACE

