

#define DEBUG_FONT_RENDERING  1
#if DEBUG_FONT_RENDERING
int fixedFontSize = 10;
bool drawAAText = true;
bool drawFill = true;
bool drawOutline = false;
bool drawWholeOutline = false;
bool drawControlPoints = false;
std::string fontOpt = "Arial";
#else
const bool drawAAText = true;
const bool drawFill = true;
#endif


void DrawCurveC(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;
  // Fixed precision version
  for (int i = 0; i < dx; i++)
  {
    vec2i pos1 = bezierCurveFixed(p1, p2, p3, (i * 65536) / dx);
    vec2i pos2 = bezierCurveFixed(p1, p2, p3, ((i+1) * 65536) / 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 += a_color;//0x2000000;
				//*dst = a_color;//0x2000000;
			}
		}
}

void DrawRectangleC(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;//0x2000000;
				//*dst = a_color;//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: what if the angle from p1->p2->p3 makes 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
  // Definately some edge cases there.

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

  //if (firstInContour)
  //  firstAngle = thisAngle;

  static int lastAngle = 0;
  bool drawFirst = true;
  bool drawLast = true;
  if (!firstInContour)
    drawFirst = ((thisAngle != 0) && ((thisAngle + lastAngle) == 0))
                 //|| ( (thisAngle == 0) && (dx > 0))
                 //|| ( (thisAngle > 0) && (lastAngle == 0))
                 ;//lastAngle != 0) );
  if (lastInContour)
    drawLast = (thisAngle != 0) && ((thisAngle + firstAngle) == 0);
  
//  if (firstInContour)
//    drawFirst = true;
  if (lastInContour) {
    //drawLast = (thisAngle != 0) && ((thisAngle + firstAngle) == 0);
    
    //if (thisAngle != 0 && thisAngle == firstAngle)
    //  drawLast = true;//false;
  }

  lastAngle = (thisAngle) ? thisAngle : (1 - lastAngle);

  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 lastX = -1;
    int lastY = -1;
    for (int i = 0; i <= dy;)// i++)
    {
      vec2i pos1;
      bool final = false;
      do { 
        pos1 = bezierCurveFixed(p1, p2, p3, (i << 16) / dy);
        final = pos1.y == p3.y && pos1.x == p3.x;
        i++;
      } while (!final && (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 = /*(final && lastInContour) ||*/ 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);
      
      //if (lastInContour && pos1.y == p3.y)
      //  DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);

      //else {
      //  DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
      //  DrawRectangleB(a_target, a_color, pos1.x, pos1.y, 1, 1);
      //}
      lastY = pos1.y;
      lastX = pos1.x;
      if (final)
        break;
    }
    if (drawLast) {
      DrawRectangleB(a_target, a_color, p3.x, p3.y, 1, 1);
      DrawRectangleB(a_target, a_color, p3.x, p3.y, 1, 1);
    }
  }
  else
  {
    if (p1.y != p3.y)
      exit(0);
    //if (p1.x == p3.x)
      //exit(0);

//    if (drawFirst)
      DrawRectangleC(a_target, a_color, p1.x, p1.y, 1, 1);
//    else if (p3.x > (p1.x + 2)) {
      
      //DrawRectangleC(a_target, a_color, p1.x + 1, p3.y, 1, 1);
      //DrawRectangleC(a_target, a_color, p3.x - 1, p3.y, 1, 1);
    //}

    //if (!lastInContour && drawLast)
      DrawRectangleC(a_target, a_color, p3.x, p3.y, 1, 1);
    //else //if (p1.x != p3.x)
      //DrawRectangleB(a_target, a_color, p3.x - 1, p3.y, 1, 1);
  }
}


void RasterizeCharacter(PixelBuffer* a_target, uint32_t a_color, int a_size, int a_x, int a_y, Glyph::Outline& outline)
{
  int aaShift = 3;
  int bias = 512;
  if (drawAAText)
    a_size <<= aaShift;

  Rectangle rect = glyphBounds(outline);
  rect.m_width = ((rect.m_width * a_size + bias) >> 10) + 3;
  rect.m_height = (rect.m_height * a_size + bias) >> 10;
  PixelBuffer buf = {
    new uint32_t[rect.m_width*rect.m_height],
    rect.m_width*4,
    rect.m_width,
    rect.m_height,
    PF_ARGB8888
  };
  for (int i = 0; i < rect.m_width*rect.m_height; i++)
    buf.m_pixels[i] = 0x00C0C0C0;
      
  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++)
  {
    Glyph::Outline::Curve& b = outline.m_lines[i];
    {
      vec2i pnts[3];
      for (int c = 0; c < 3; c++)
        pnts[c] = (vec2i){ int((((b.m_controlPoints[c].m_x - rect.m_x) * a_size) + bias) >> 10),
           rect.m_height - int((((b.m_controlPoints[c].m_y - rect.m_y) * a_size) + bias) >> 10) };
      const vec2i &p1 = pnts[0], &p2 = pnts[1], &p3 = pnts[2];
      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 (1) // alternative way to determine first
      {
        if (p1.x != lastPos.x || p1.y != lastPos.y)
        {
          assert(firstInContour);
        }
        lastPos = p3;
      }

      DrawCurveB(&buf, 0x02000000, p1, p2, p3, firstInContour, lastInContour, firstAngle);
    }
  }

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

#if DEBUG_FONT_RENDERING
  if (drawControlPoints) {
    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++)
    {
      Glyph::Outline::Curve& b = outline.m_lines[i];
      {
        vec2i pnts[3];
        for (int c = 0; c < 3; c++)
          pnts[c] = (vec2i){ int((((b.m_controlPoints[c].m_x - rect.m_x) * a_size) + bias) >> 10),
             rect.m_height - int((((b.m_controlPoints[c].m_y - rect.m_y) * a_size) + bias) >> 10) };
        const vec2i &p1 = pnts[0], &p2 = pnts[1], &p3 = pnts[2];
        firstInContour = lastInContour;
        if (firstInContour)
        {
          int dy = pnts[0].y - pnts[2].y;
          firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
          firstPos = pnts[0];
        }
        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;
      }
    }
  }

  if (drawOutline) {
    lastPos = vec2i{ -1, -1 };
    firstPos = vec2i{ -1, -1 };
    firstInContour = true;
    lastInContour = true;
    firstAngle = 0;
    // iterate the bezier curves
    for (size_t i = 0; i < outline.m_lines.size(); i++)
    {
      Glyph::Outline::Curve& b = outline.m_lines[i];
      {
        vec2i pnts[3];
        for (int c = 0; c < 3; c++)
          pnts[c] = (vec2i){ int((((b.m_controlPoints[c].m_x - rect.m_x) * a_size) + bias) >> 10),
             rect.m_height - int((((b.m_controlPoints[c].m_y - rect.m_y) * a_size) + bias) >> 10) };
        firstInContour = lastInContour;
        if (firstInContour)
        {
          int dy = pnts[0].y - pnts[2].y;
          firstAngle = (dy < 0) ? -1 : ((dy > 0) ? +1 : 0);
          firstPos = pnts[0];
        }
        lastInContour = pnts[2].x == firstPos.x && pnts[2].y == firstPos.y;
        DrawCurveB(&buf, 0xFF007F7F, pnts[0], pnts[1], pnts[2], firstInContour, lastInContour, firstAngle);
      }
    }
  }
#endif // DEBUG_FONT_RENDERING

  if (drawAAText)
  {
    int w = rect.m_width >> aaShift;
    int h = rect.m_height >> aaShift;
    PixelBuffer downScaled = { new uint32_t[w*h], w*4, w, h, PF_ARGB8888 };
    if (SmoothDownSample(downScaled.m_pixels, w, h, buf.m_pixels, rect.m_width, rect.m_height))
    {
      a_size >>= aaShift;
      //int fy = (((rect.m_y+511)*a_size)>>10);
      int fy = (rect.m_y*a_size + bias) >> 10;
      int fh = rect.m_height >> aaShift;
      int yOff = a_y + a_size - fh - fy;
      DrawPixelsAlpha(a_target, downScaled.m_pixels, 5 + a_x + ((rect.m_x*a_size)>>10), 
          a_y + 2 + yOff, w, h, 0, 0, w, h);
    }
    delete[] downScaled.m_pixels;
  }
  else
  {
    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;
}


