/** \file
 * \brief External API - Text
 *
 * See Copyright Notice in cd.h
 */

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

#include "cd.h"
#include "cd_private.h"


void cdCanvasText(cdCanvas* canvas, int x, int y, const char *s)
{
  int num_line;

  assert(canvas);
  assert(s);
  if (!_cdCheckCanvas(canvas)) return;

  if (s[0] == 0)
    return;

  if (canvas->use_origin)
  {
    x += canvas->origin.x;
    y += canvas->origin.y;
  }

  num_line = cdStrLineCount(s);
  if (num_line == 1)
  {
    if (canvas->invert_yaxis)
      y = _cdInvertYAxis(canvas, y);

    canvas->cxText(canvas->ctxcanvas, x, y, s, strlen(s));
  }
  else
  {
    int yr, i, line_height, len;
    const char *p, *q;
    double cos_theta = 0, sin_theta = 0;

    canvas->cxGetFontDim(canvas->ctxcanvas, NULL, &line_height, NULL, NULL);

    if (canvas->text_orientation)
    {
      int align = canvas->text_alignment;
      cos_theta = cos(canvas->text_orientation*CD_DEG2RAD);
      sin_theta = sin(canvas->text_orientation*CD_DEG2RAD);

      /* position vertically at the first line */
      if (align == CD_NORTH || align == CD_NORTH_EAST || align == CD_NORTH_WEST ||     /* it is relative to the full text */
          align == CD_BASE_LEFT || align == CD_BASE_CENTER || align == CD_BASE_RIGHT)  /* it is relative to the first line already */
      {
        /* Already at position */
      }
      else if (align == CD_SOUTH || align == CD_SOUTH_EAST || align == CD_SOUTH_WEST)  /* it is relative to the full text */
      {
        cdMovePoint(&x, &y, 0, (num_line-1)*line_height, sin_theta, cos_theta);
      }
      else  /* CD_CENTER || CD_EAST || CD_WEST */                                      /* it is relative to the full text */
        cdMovePoint(&x, &y, 0, (num_line-1)*line_height/2.0, sin_theta, cos_theta);
    }
    else
    {
      int align = canvas->text_alignment;

      /* position vertically at the first line */
      if (align == CD_NORTH || align == CD_NORTH_EAST || align == CD_NORTH_WEST ||     /* it is relative to the full text */
          align == CD_BASE_LEFT || align == CD_BASE_CENTER || align == CD_BASE_RIGHT)  /* it is relative to the first line already */
      {
        /* Already at position */
      }
      else if (align == CD_SOUTH || align == CD_SOUTH_EAST || align == CD_SOUTH_WEST)  /* it is relative to the full text */
      {
        y += (num_line-1)*line_height;
      }
      else  /* CD_CENTER || CD_EAST || CD_WEST */                                      /* it is relative to the full text */
        y += ((num_line-1)*line_height)/2;
    }

    p = s;
    for(i = 0; i < num_line; i++)
    {
      q = strchr(p, '\n');
      if (q) len = (int)(q-p);  /* Cut the string to contain only one line */
      else len = strlen(p);

      /* Draw the line */
      if (canvas->invert_yaxis)
        yr = _cdInvertYAxis(canvas, y);
      else
        yr = y;
      canvas->cxText(canvas->ctxcanvas, x, yr, p, len);

      /* Advance the string */
      if (q) p = q + 1;

      /* Advance a line */
      if (canvas->text_orientation)
        cdMovePoint(&x, &y, 0, -line_height, sin_theta, cos_theta);
      else
        y -= line_height;
    }
  }
}

void cdfCanvasText(cdCanvas* canvas, double x, double y, const char *s)
{
  int num_line;

  assert(canvas);
  assert(s);
  if (!_cdCheckCanvas(canvas)) return;

  if (s[0] == 0)
    return;

  if (canvas->use_origin)
  {
    x += canvas->forigin.x;
    y += canvas->forigin.y;
  }

  num_line = cdStrLineCount(s);
  if (num_line == 1)
  {
    if (canvas->invert_yaxis)
      y = _cdInvertYAxis(canvas, y);

    if (canvas->cxFText)
      canvas->cxFText(canvas->ctxcanvas, x, y, s, strlen(s));
    else
      canvas->cxText(canvas->ctxcanvas, _cdRound(x), _cdRound(y), s, strlen(s));
  }
  else
  {
    int i, line_height, len;
    const char *p, *q;
    double yr, cos_theta = 0, sin_theta = 0;

    canvas->cxGetFontDim(canvas->ctxcanvas, NULL, &line_height, NULL, NULL);

    if (canvas->text_orientation)
    {
      int align = canvas->text_alignment;
      cos_theta = cos(canvas->text_orientation*CD_DEG2RAD);
      sin_theta = sin(canvas->text_orientation*CD_DEG2RAD);

      /* position vertically at the first line */
      if (align == CD_NORTH || align == CD_NORTH_EAST || align == CD_NORTH_WEST ||     /* it is relative to the full text */
          align == CD_BASE_LEFT || align == CD_BASE_CENTER || align == CD_BASE_RIGHT)  /* it is relative to the first line already */
      {
        /* Already at position */
      }
      else if (align == CD_SOUTH || align == CD_SOUTH_EAST || align == CD_SOUTH_WEST)  /* it is relative to the full text */
      {
        cdfMovePoint(&x, &y, 0, (num_line-1)*line_height, sin_theta, cos_theta);
      }
      else  /* CD_CENTER || CD_EAST || CD_WEST */                                      /* it is relative to the full text */
        cdfMovePoint(&x, &y, 0, (num_line-1)*line_height/2.0, sin_theta, cos_theta);
    }
    else
    {
      int align = canvas->text_alignment;

      /* position vertically at the first line */
      if (align == CD_NORTH || align == CD_NORTH_EAST || align == CD_NORTH_WEST ||     /* it is relative to the full text */
          align == CD_BASE_LEFT || align == CD_BASE_CENTER || align == CD_BASE_RIGHT)  /* it is relative to the first line already */
      {
        /* Already at position */
      }
      else if (align == CD_SOUTH || align == CD_SOUTH_EAST || align == CD_SOUTH_WEST)  /* it is relative to the full text */
      {
        y += (num_line-1)*line_height;
      }
      else  /* CD_CENTER || CD_EAST || CD_WEST */                                      /* it is relative to the full text */
        y += ((num_line-1)*line_height)/2.0;
    }

    p = s;
    for(i = 0; i < num_line; i++)
    {
      q = strchr(p, '\n');
      if (q) len = (int)(q-p);  /* Cut the string to contain only one line */
      else len = strlen(p);

      /* Draw the line */
      if (canvas->invert_yaxis)
        yr = _cdInvertYAxis(canvas, y);
      else
        yr = y;
      if (canvas->cxFText)
        canvas->cxFText(canvas->ctxcanvas, x, yr, p, len);
      else
        canvas->cxText(canvas->ctxcanvas, _cdRound(x), _cdRound(yr), p, len);

      /* Advance the string */
      if (q) p = q + 1;

      /* Advance a line */
      if (canvas->text_orientation)
        cdfMovePoint(&x, &y, 0, -line_height, sin_theta, cos_theta);
      else
        y -= line_height;
    }
  }
}

int cdGetFontSizePixels(cdCanvas* canvas, int size)
{
  if (size < 0)
    size = -size;
  else
  {
    double size_mm = (double)size/CD_MM2PT;
    size = cdRound(size_mm*canvas->xres);
  }

  if (size == 0)
    size = 1;

  return size;
}

int cdGetFontSizePoints(cdCanvas* canvas, int size)
{
  if (size < 0)
  {
    double size_mm = ((double)-size)/canvas->xres;
    size = cdRound(size_mm * CD_MM2PT);
  }

  if (size == 0)
    size = 1;

  return size;
}

int cdCanvasFont(cdCanvas* canvas, const char* type_face, int style, int size)
{
  assert(canvas);
  assert(style>=-1 && style<=CD_STRIKEOUT);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  if (!type_face || type_face[0]==0)
    type_face = canvas->font_type_face;
  if (style==-1)
    style = canvas->font_style;
  if (size==0)
    size = canvas->font_size;

  if (strcmp(type_face, canvas->font_type_face)==0 && 
      style == canvas->font_style && 
      size == canvas->font_size)
    return 1;

  if (canvas->cxFont(canvas->ctxcanvas, type_face, style, size))
  {
    strcpy(canvas->font_type_face, type_face);
    canvas->font_style = style;
    canvas->font_size = size;
    canvas->native_font[0] = 0;
    return 1;
  }

  return 0;
}

void cdCanvasGetFont(cdCanvas* canvas, char *type_face, int *style, int *size)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;

  if (type_face) strcpy(type_face, canvas->font_type_face);
  if (style) *style = canvas->font_style;
  if (size) *size = canvas->font_size;
}

char* cdCanvasNativeFont(cdCanvas* canvas, const char* font)
{
  static char native_font[1024] = "";

  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return NULL;

  strcpy(native_font, canvas->native_font);

  if (font == (char*)CD_QUERY)
  {
    char style[200] = " ";
    if (canvas->font_style&CD_BOLD)
      strcat(style, "Bold ");
    if (canvas->font_style&CD_ITALIC)
      strcat(style, "Italic ");
    if (canvas->font_style&CD_UNDERLINE)
      strcat(style, "Underline ");
    if (canvas->font_style&CD_STRIKEOUT)
      strcat(style, "Strikeout ");

    sprintf(native_font, "%s,%s %d", canvas->font_type_face, style, canvas->font_size);
    return native_font;
  }

  if (!font || font[0] == 0)
    return native_font;

  if (canvas->cxNativeFont)
  {
    if (canvas->cxNativeFont(canvas->ctxcanvas, font))
      strcpy(canvas->native_font, font);
  }
  else
  {
    char type_face[1024];
    int size = 12, style = CD_PLAIN;

    if (!cdParseIupWinFont(font, type_face, &style, &size))
    {
      if (!cdParseXWinFont(font, type_face, &style, &size))
      {
        if (!cdParsePangoFont(font, type_face, &style, &size))
          return native_font;
      }
    }

    if (cdCanvasFont(canvas, type_face, style, size))
      strcpy(canvas->native_font, font);
  }

  return native_font;
}

int cdCanvasTextAlignment(cdCanvas* canvas, int alignment)
{
  int text_alignment;

  assert(canvas);
  assert(alignment==CD_QUERY || (alignment>=CD_NORTH && alignment<=CD_BASE_RIGHT));
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;
  if (alignment<CD_QUERY || alignment>CD_BASE_RIGHT);

  text_alignment = canvas->text_alignment;

  if (alignment == CD_QUERY || alignment == text_alignment)
    return text_alignment;

  if (canvas->cxTextAlignment)
    canvas->text_alignment = canvas->cxTextAlignment(canvas->ctxcanvas, alignment);
  else
    canvas->text_alignment = alignment;

  return text_alignment;
}

double cdCanvasTextOrientation(cdCanvas* canvas, double angle)
{
  double text_orientation;

  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return CD_ERROR;

  text_orientation = canvas->text_orientation;

  if (angle == CD_QUERY || angle == text_orientation)
    return text_orientation;

  if (canvas->cxTextOrientation)
    canvas->text_orientation = canvas->cxTextOrientation(canvas->ctxcanvas, angle);
  else
    canvas->text_orientation = angle;

  return text_orientation;
}

void cdCanvasGetFontDim(cdCanvas* canvas, int *max_width, int *height, int *ascent, int *descent)
{
  assert(canvas);
  if (!_cdCheckCanvas(canvas)) return;
  canvas->cxGetFontDim(canvas->ctxcanvas, max_width, height, ascent, descent);
}

void cdCanvasGetTextSize(cdCanvas* canvas, const char *s, int *width, int *height)
{
  int num_line;

  assert(canvas);
  assert(s);
  if (!_cdCheckCanvas(canvas)) return;

  num_line = cdStrLineCount(s);
  if (num_line == 1)
    canvas->cxGetTextSize(canvas->ctxcanvas, s, strlen(s), width, height);
  else
  {
    int i, line_height, max_w = 0, w, len;
    const char *p, *q;

    p = s;

    canvas->cxGetFontDim(canvas->ctxcanvas, NULL, &line_height, NULL, NULL);

    for(i = 0; i < num_line; i++)
    {
      q = strchr(p, '\n');
      if (q) len = (int)(q-p);  /* Cut the string to contain only one line */
      else len = strlen(p);

      /* Calculate line width */
      canvas->cxGetTextSize(canvas->ctxcanvas, p, len, &w, NULL);
      if (w > max_w) max_w = w;

      /* Advance the string */
      if (q) p = q + 1; /* skip line break */
    }

    if (width) *width = max_w;
    if (height) *height = num_line*line_height;
  }
}

void cdTextTranslatePoint(cdCanvas* canvas, int x, int y, int w, int h, int baseline, int *rx, int *ry)
{
  /* move to left */
  switch (canvas->text_alignment)
  {
  case CD_BASE_RIGHT:
  case CD_NORTH_EAST:
  case CD_EAST:
  case CD_SOUTH_EAST:
    *rx = x - w;    
    break;
  case CD_BASE_CENTER:
  case CD_CENTER:
  case CD_NORTH:
  case CD_SOUTH:
    *rx = x - w/2;  
    break;
  case CD_BASE_LEFT:
  case CD_NORTH_WEST:
  case CD_WEST:
  case CD_SOUTH_WEST:
    *rx = x;         
    break;
  }

  /* move to bottom */
  switch (canvas->text_alignment)
  {
  case CD_BASE_LEFT:
  case CD_BASE_CENTER:
  case CD_BASE_RIGHT:
    if (canvas->invert_yaxis)
      *ry = y + baseline;
    else
      *ry = y - baseline;
    break;
  case CD_SOUTH_EAST:
  case CD_SOUTH_WEST:
  case CD_SOUTH:
    *ry = y;
    break;
  case CD_NORTH_EAST:
  case CD_NORTH:
  case CD_NORTH_WEST:
    if (canvas->invert_yaxis)
      *ry = y + h;
    else
      *ry = y - h;
    break;
  case CD_CENTER:
  case CD_EAST:
  case CD_WEST:
    if (canvas->invert_yaxis)
      *ry = y + h/2;
    else
      *ry = y - h/2;
    break;
  }
}

void cdCanvasGetTextBounds(cdCanvas* canvas, int x, int y, const char *s, int *rect)
{
  int w, h, ascent, line_height, baseline;
  int xmin, xmax, ymin, ymax;
  int old_invert_yaxis, num_lin;

  assert(canvas);
  assert(s);
  if (!_cdCheckCanvas(canvas)) return;

  if (s[0] == 0)
    return;
  
  cdCanvasGetTextSize(canvas, s, &w, &h);
  cdCanvasGetFontDim(canvas, NULL, &line_height, &ascent, NULL);
  baseline = line_height - ascent;
  num_lin = h/line_height;
  if (num_lin > 1)
    baseline += (num_lin-1)*line_height;

  /* from here we are always upwards */
  old_invert_yaxis = canvas->invert_yaxis;
  canvas->invert_yaxis = 0;

  /* move to bottom-left */
  cdTextTranslatePoint(canvas, x, y, w, h, baseline, &xmin, &ymin);

  xmax = xmin + w-1;
  ymax = ymin + h-1;

  if (canvas->text_orientation)
  {
    double cos_theta = cos(canvas->text_orientation*CD_DEG2RAD);
    double sin_theta = sin(canvas->text_orientation*CD_DEG2RAD);

    cdRotatePoint(canvas, xmin, ymin, x, y, &rect[0], &rect[1], sin_theta, cos_theta);
    cdRotatePoint(canvas, xmax, ymin, x, y, &rect[2], &rect[3], sin_theta, cos_theta);
    cdRotatePoint(canvas, xmax, ymax, x, y, &rect[4], &rect[5], sin_theta, cos_theta);
    cdRotatePoint(canvas, xmin, ymax, x, y, &rect[6], &rect[7], sin_theta, cos_theta);
  }
  else
  {
    rect[0] = xmin; rect[1] = ymin;
    rect[2] = xmax; rect[3] = ymin;
    rect[4] = xmax; rect[5] = ymax;
    rect[6] = xmin; rect[7] = ymax;
  }

  canvas->invert_yaxis = old_invert_yaxis;
}

void cdCanvasGetTextBox(cdCanvas* canvas, int x, int y, const char *s, int *xmin, int *xmax, int *ymin, int *ymax)
{
  int rect[8];
  int _xmin, _xmax, _ymin, _ymax;

  cdCanvasGetTextBounds(canvas, x, y, s, rect);

  _xmin = rect[0];
  _ymin = rect[1];
  _xmax = rect[0];
  _ymax = rect[1];

  if(rect[2] < _xmin) _xmin = rect[2];
  if(rect[4] < _xmin) _xmin = rect[4];
  if(rect[6] < _xmin) _xmin = rect[6];

  if(rect[3] < _ymin) _ymin = rect[3];
  if(rect[5] < _ymin) _ymin = rect[5];
  if(rect[7] < _ymin) _ymin = rect[7];

  if(rect[2] > _xmax) _xmax = rect[2];
  if(rect[4] > _xmax) _xmax = rect[4];
  if(rect[6] > _xmax) _xmax = rect[6];

  if(rect[3] > _ymax) _ymax = rect[3];
  if(rect[5] > _ymax) _ymax = rect[5];
  if(rect[7] > _ymax) _ymax = rect[7];

  if (xmin) *xmin = _xmin;
  if (xmax) *xmax = _xmax;
  if (ymin) *ymin = _ymin;
  if (ymax) *ymax = _ymax;
}

/**************************************************************/
/* Native Font Format, compatible with Pango Font Description */
/**************************************************************/

/*
The string contains the font name, the style and the size. 
Style can be a free combination of some names separated by spaces.
Font name can be a list of font family names separated by comma.
*/

#define isspace(_x) (_x == ' ')

static int cd_find_style_name(const char *name, int len, int *style)
{
#define CD_STYLE_NUM_NAMES 21
  static struct { const char* name; int style; } cd_style_names[CD_STYLE_NUM_NAMES] = {
    {"Normal",         0},
    {"Oblique",        CD_ITALIC},
    {"Italic",         CD_ITALIC},
    {"Small-Caps",     0},
    {"Ultra-Light",    0},
    {"Light",          0},
    {"Medium",         0},
    {"Semi-Bold",      CD_BOLD},
    {"Bold",           CD_BOLD},
    {"Ultra-Bold",     CD_BOLD},
    {"Heavy",          0},
    {"Ultra-Condensed",0},
    {"Extra-Condensed",0},
    {"Condensed",      0},
    {"Semi-Condensed", 0},
    {"Semi-Expanded",  0},
    {"Expanded",       0},
    {"Extra-Expanded", 0},
    {"Ultra-Expanded", 0},
    {"Underline", CD_UNDERLINE},
    {"Strikeout", CD_STRIKEOUT}
  };

  int i;
  for (i = 0; i < CD_STYLE_NUM_NAMES; i++)
  {
    if (strncmp(cd_style_names[i].name, name, len)==0)
    {
      *style = cd_style_names[i].style;
      return 1;
    }
  }

  return 0;
}

static const char * cd_getword(const char *str, const char *last, int *wordlen)
{
  const char *result;
  
  while (last > str && isspace(*(last - 1)))
    last--;

  result = last;
  while (result > str && !isspace (*(result - 1)))
    result--;

  *wordlen = last - result;
  
  return result;
}

int cdParsePangoFont(const char *nativefont, char *type_face, int *style, int *size)
{
  const char *p, *last;
  int len, wordlen;

  len = (int)strlen(nativefont);
  last = nativefont + len;
  p = cd_getword(nativefont, last, &wordlen);

  /* Look for a size at the end of the string */
  if (wordlen != 0)
  {
    int new_size = atoi(p);
    if (new_size != 0)
    {
      *size = new_size;
      last = p;
    }
  }

  /* Now parse style words */
  p = cd_getword(nativefont, last, &wordlen);
  while (wordlen != 0)
  {
    int new_style = 0;

    if (!cd_find_style_name(p, wordlen, &new_style))
      break;
    else
    {
      *style |= new_style;

      last = p;
      p = cd_getword(nativefont, last, &wordlen);
    }
  }

  /* Remainder is font family list. */

  /* Trim off trailing white space */
  while (last > nativefont && isspace(*(last - 1)))
    last--;

  /* Trim off trailing commas */
  if (last > nativefont && *(last - 1) == ',')
    last--;

  /* Again, trim off trailing white space */
  while (last > nativefont && isspace(*(last - 1)))
    last--;

  /* Trim off leading white space */
  while (last > nativefont && isspace(*nativefont))
    nativefont++;

  if (nativefont != last)
  {
    len = (last - nativefont);
    strncpy(type_face, nativefont, len);
    type_face[len] = 0;
    return 1;
  }
  else
    return 0;
}

int cdParseIupWinFont(const char *nativefont, char *type_face, int *style, int *size)
{
  int c;

  if (strstr(nativefont, ":") == NULL)
    return 0;

  c = strcspn(nativefont, ":");      /* extract type_face */
  if (c == 0) return 0;
  strncpy(type_face, nativefont, c);
  type_face[c]='\0';
  nativefont += c+1;

  if(nativefont[0] == ':')  /* check for attributes */
    nativefont++;
  else
  {
    *style = 0;
    while(strlen(nativefont)) /* extract style (bold/italic etc) */
    {
      char style_str[20];

      c = strcspn(nativefont, ":,");
      if (c == 0)
        break;

      strncpy(style_str, nativefont, c);
      style_str[c] = '\0';

      if(!strcmp(style_str, "BOLD"))
        *style |= CD_BOLD;
      else if(!strcmp(style_str,"ITALIC"))
        *style |= CD_ITALIC;
      else if(!strcmp(style_str,"UNDERLINE"))
        *style |= CD_UNDERLINE;
      else if(!strcmp(style_str,"STRIKEOUT"))
        *style |= CD_STRIKEOUT;

      nativefont += c;

      if(nativefont[0] == ':')  /* end attribute list */
      {
        nativefont++;
        break;
      }

      nativefont++;   /* skip separator */
    }
  }

  /* extract size in points */
  if (sscanf(nativefont, "%d", size) != 1)
    return 0;

  if (*size == 0)
    return 0;

  return 1;
}

int cdParseXWinFont(const char *nativefont, char *type_face, int *style, int *size)
{
  char style1[10], style2[10];
  char* token;
  char font[1024];

  if (nativefont[0] != '-')
    return 0;

  strcpy(font, nativefont+1);  /* skip first '-' */

  *style = 0;

  /* fndry */
  token = strtok(font, "-");
  if (!token) return 0;

  /* fmly */
  token = strtok(NULL, "-");
  if (!token) return 0;
  strcpy(type_face, token);

  /* wght */
  token = strtok(NULL, "-");
  if (!token) return 0;
  strcpy(style1, token);
  if (strstr("bold", style1))
    *style |= CD_BOLD;

  /* slant */
  token = strtok(NULL, "-");
  if (!token) return 0;
  strcpy(style2, token);
  if (*style2 == 'i' || *style2 == 'o')
    *style |= CD_ITALIC;

  /* sWdth */
  token = strtok(NULL, "-");
  if (!token) return 0;
  /* adstyl */
  token = strtok(NULL, "-");
  if (!token) return 0;

  /* pxlsz */
  token = strtok(NULL, "-");
  if (!token) return 0;
  *size = -atoi(token); /* size in pixels */

  if (*size < 0)
    return 1;

  /* ptSz */
  token = strtok(NULL, "-");
  if (!token) return 0;
  *size = atoi(token)/10; /* size in deci-points */

  if (*size > 0)
    return 1;

  return 0;
}