/****************************************************************************
 * This file is part of the project AqDiagram.
 * AqDiagram (c) by 2020 Martin Preuss, all rights reserved.
 *
 * The license for this file can be found in the file COPYING which you
 * should have received along with this file.
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif


#include "context_cairo_p.h"

#include <gwenhywfar/debug.h>



GWEN_INHERIT(AQDG_DRAW_CONTEXT, AQDG_DRAW_CONTEXT_CAIRO);


/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */


static GWENHYWFAR_CB void _freeData(void *bp, void *p);

static int _penCreate(AQDG_DRAW_CONTEXT *g, uint32_t frontColour, int lineWidth, int dashType);
static int _penRelease(AQDG_DRAW_CONTEXT *g, int penId);
static int _drawLine(AQDG_DRAW_CONTEXT *g, int penId, int xStart, int yStart, int xEnd, int yEnd);
static int _drawRect(AQDG_DRAW_CONTEXT *g, int penId, int x, int y, int w, int h);
static int _drawFilledRect(AQDG_DRAW_CONTEXT *g, int penId, int x, int y, int w, int h);
static int _fontCreate(AQDG_DRAW_CONTEXT *g, const char *face, int fontSize, int fontSlant, int fontWeight);
static int _fontRelease(AQDG_DRAW_CONTEXT *g, int fontId);
static int _drawText(AQDG_DRAW_CONTEXT *g, int penId, int fontId, int direction, int x, int y, const char *text);
static int _getTextWidth(AQDG_DRAW_CONTEXT *g, int fontId, const char *text);
static int _getTextHeight(AQDG_DRAW_CONTEXT *g, int fontId, const char *text);
static int _finish(AQDG_DRAW_CONTEXT *g);

static AQDG_DRAW_CONTEXT_CAIRO *_ensureFullCairoContext(const AQDG_DRAW_CONTEXT *g);
static AQDG_DRAW_CONTEXT_CAIRO *_ensureCairoSurface(const AQDG_DRAW_CONTEXT *g);

static void _setColourAndWidthFromPen(cairo_t *cr, const AQDG_DRAW_PEN *pen);
static void _setDashType(cairo_t *cr, int dashType);
static void _setFont(cairo_t *cr, AQDG_DRAW_FONT *font);



/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */



const double AQDG_Draw_ContextCairo_DashPattern1[2]= {1.0, 1.0};
const double AQDG_Draw_ContextCairo_DashPattern2[2]= {1.0, 2.0};
const double AQDG_Draw_ContextCairo_DashPattern3[2]= {1.0, 5.0};
const double AQDG_Draw_ContextCairo_DashPattern4[2]= {1.0, 10.0};
const double AQDG_Draw_ContextCairo_DashPattern5[2]= {10.0, 2.0};






AQDG_DRAW_CONTEXT *AQDG_Draw_ContextCairo_Pdf_new(const char *fileName, int w, int h)
{
  AQDG_DRAW_CONTEXT *g;
  AQDG_DRAW_CONTEXT_CAIRO *xg;

  g=AQDG_Draw_Context_new();
  GWEN_NEW_OBJECT(AQDG_DRAW_CONTEXT_CAIRO, xg);
  assert(xg);
  GWEN_INHERIT_SETDATA(AQDG_DRAW_CONTEXT, AQDG_DRAW_CONTEXT_CAIRO, g, xg, _freeData);

  xg->fontList=AQDG_Draw_Font_List_new();
  xg->penList=AQDG_Draw_Pen_List_new();

  if (fileName)
    xg->fileName=strdup(fileName);
  xg->width=w;
  xg->height=h;

  xg->surface=cairo_pdf_surface_create(fileName, w, h);
  if (xg->surface==NULL) {
    DBG_ERROR(AQDG_LOGDOMAIN, "Unable to create surface");
  }

  xg->cr=cairo_create(xg->surface);
  if (xg->cr==NULL) {
    DBG_ERROR(AQDG_LOGDOMAIN, "Could not create cairo context");
  }

  /* set virtual functions */
  AQDG_Draw_Context_SetPenCreateFn(g, _penCreate);
  AQDG_Draw_Context_SetPenReleaseFn(g, _penRelease);
  AQDG_Draw_Context_SetDrawLineFn(g, _drawLine);
  AQDG_Draw_Context_SetDrawRectFn(g, _drawRect);
  AQDG_Draw_Context_SetDrawFilledRectFn(g, _drawFilledRect);
  AQDG_Draw_Context_SetFontCreateFn(g, _fontCreate);
  AQDG_Draw_Context_SetFontReleaseFn(g, _fontRelease);
  AQDG_Draw_Context_SetDrawTextFn(g, _drawText);
  AQDG_Draw_Context_SetGetTextWidthFn(g, _getTextWidth);
  AQDG_Draw_Context_SetGetTextHeightFn(g, _getTextHeight);
  AQDG_Draw_Context_SetFinishFn(g, _finish);

  return g;
}



AQDG_DRAW_CONTEXT *AQDG_Draw_ContextCairo_Ps_new(const char *fileName, int w, int h)
{
  AQDG_DRAW_CONTEXT *g;
  AQDG_DRAW_CONTEXT_CAIRO *xg;

  g=AQDG_Draw_Context_new();
  GWEN_NEW_OBJECT(AQDG_DRAW_CONTEXT_CAIRO, xg);
  assert(xg);
  GWEN_INHERIT_SETDATA(AQDG_DRAW_CONTEXT, AQDG_DRAW_CONTEXT_CAIRO, g, xg, _freeData);

  xg->fontList=AQDG_Draw_Font_List_new();
  xg->penList=AQDG_Draw_Pen_List_new();

  if (fileName)
    xg->fileName=strdup(fileName);
  xg->width=w;
  xg->height=h;

  xg->surface=cairo_ps_surface_create(fileName, w, h);
  if (xg->surface==NULL) {
    DBG_ERROR(AQDG_LOGDOMAIN, "Unable to create surface");
  }

  xg->cr=cairo_create(xg->surface);
  if (xg->cr==NULL) {
    DBG_ERROR(AQDG_LOGDOMAIN, "Could not create cairo context");
  }

  /* set virtual functions */
  AQDG_Draw_Context_SetPenCreateFn(g, _penCreate);
  AQDG_Draw_Context_SetPenReleaseFn(g, _penRelease);
  AQDG_Draw_Context_SetDrawLineFn(g, _drawLine);
  AQDG_Draw_Context_SetDrawRectFn(g, _drawRect);
  AQDG_Draw_Context_SetDrawFilledRectFn(g, _drawFilledRect);
  AQDG_Draw_Context_SetFontCreateFn(g, _fontCreate);
  AQDG_Draw_Context_SetFontReleaseFn(g, _fontRelease);
  AQDG_Draw_Context_SetDrawTextFn(g, _drawText);
  AQDG_Draw_Context_SetGetTextWidthFn(g, _getTextWidth);
  AQDG_Draw_Context_SetGetTextHeightFn(g, _getTextHeight);
  AQDG_Draw_Context_SetFinishFn(g, _finish);

  return g;
}



AQDG_DRAW_CONTEXT *AQDG_Draw_ContextCairo_Png_new(const char *fileName, int w, int h)
{
  AQDG_DRAW_CONTEXT *g;
  AQDG_DRAW_CONTEXT_CAIRO *xg;

  g=AQDG_Draw_Context_new();
  GWEN_NEW_OBJECT(AQDG_DRAW_CONTEXT_CAIRO, xg);
  assert(xg);
  GWEN_INHERIT_SETDATA(AQDG_DRAW_CONTEXT, AQDG_DRAW_CONTEXT_CAIRO, g, xg, _freeData);

  xg->fontList=AQDG_Draw_Font_List_new();
  xg->penList=AQDG_Draw_Pen_List_new();

  if (fileName)
    xg->fileName=strdup(fileName);
  xg->width=w;
  xg->height=h;
  xg->stride=cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w);
  xg->writePng=1;

  xg->surface=cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
  if (xg->surface==NULL) {
    DBG_ERROR(AQDG_LOGDOMAIN, "Unable to create surface");
  }

  xg->cr=cairo_create(xg->surface);
  if (xg->cr==NULL) {
    DBG_ERROR(AQDG_LOGDOMAIN, "Could not create cairo context");
  }

  /* set virtual functions */
  AQDG_Draw_Context_SetPenCreateFn(g, _penCreate);
  AQDG_Draw_Context_SetPenReleaseFn(g, _penRelease);
  AQDG_Draw_Context_SetDrawLineFn(g, _drawLine);
  AQDG_Draw_Context_SetDrawRectFn(g, _drawRect);
  AQDG_Draw_Context_SetDrawFilledRectFn(g, _drawFilledRect);
  AQDG_Draw_Context_SetFontCreateFn(g, _fontCreate);
  AQDG_Draw_Context_SetFontReleaseFn(g, _fontRelease);
  AQDG_Draw_Context_SetDrawTextFn(g, _drawText);
  AQDG_Draw_Context_SetGetTextWidthFn(g, _getTextWidth);
  AQDG_Draw_Context_SetGetTextHeightFn(g, _getTextHeight);
  AQDG_Draw_Context_SetFinishFn(g, _finish);

  return g;
}




GWENHYWFAR_CB void _freeData(void *bp, void *p)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;

  xg=(AQDG_DRAW_CONTEXT_CAIRO *) p;

  if (xg->cr) {
    cairo_destroy(xg->cr);
    xg->cr=NULL;
  }

  if (xg->surface) {
    cairo_surface_destroy(xg->surface);
    xg->surface=NULL;
  }

  free(xg->fileName);

  AQDG_Draw_Font_List_free(xg->fontList);
  xg->fontList=NULL;

  AQDG_Draw_Pen_List_free(xg->penList);
  xg->penList=NULL;

  GWEN_FREE_OBJECT(xg);
}





int _penCreate(AQDG_DRAW_CONTEXT *g, uint32_t frontColour, int lineWidth, int dashType)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_PEN *pen;

  xg=_ensureCairoSurface(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  pen=AQDG_Draw_Pen_new();
  AQDG_Draw_Pen_SetId(pen, ++(xg->lastPenId));
  AQDG_Draw_Pen_SetFrontColour(pen, frontColour);
  AQDG_Draw_Pen_SetLineWidth(pen, lineWidth);
  AQDG_Draw_Pen_SetDashType(pen, dashType);

  AQDG_Draw_Pen_List_Add(pen, xg->penList);
  DBG_DEBUG(AQDG_LOGDOMAIN, "Created pen %d", xg->lastPenId);
  return xg->lastPenId;
}



int _penRelease(AQDG_DRAW_CONTEXT *g, int penId)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_PEN *pen;

  xg=_ensureCairoSurface(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  pen=AQDG_Draw_Pen_List_GetById(xg->penList, penId);
  if (pen==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Pen %d not found", penId);
    return GWEN_ERROR_NOT_FOUND;
  }

  AQDG_Draw_Pen_List_Del(pen);
  AQDG_Draw_Pen_free(pen);
  return 0;
}



int _drawLine(AQDG_DRAW_CONTEXT *g, int penId, int xStart, int yStart, int xEnd, int yEnd)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_PEN *pen;

  xg=_ensureFullCairoContext(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  pen=AQDG_Draw_Pen_List_GetById(xg->penList, penId);
  if (pen==NULL) {
    DBG_ERROR(AQDG_LOGDOMAIN, "Pen %d not found", penId);
    return GWEN_ERROR_NOT_FOUND;
  }

  _setColourAndWidthFromPen(xg->cr, pen);
  _setDashType(xg->cr, AQDG_Draw_Pen_GetDashType(pen));

  /* draw line from start to end position */
  cairo_move_to(xg->cr, xStart, yStart);
  cairo_line_to(xg->cr, xEnd, yEnd);
  cairo_stroke(xg->cr);

  return 0;
}



int _drawRect(AQDG_DRAW_CONTEXT *g, int penId, int x, int y, int w, int h)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_PEN *pen;

  xg=_ensureFullCairoContext(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  pen=AQDG_Draw_Pen_List_GetById(xg->penList, penId);
  if (pen==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Pen %d not found", penId);
    return GWEN_ERROR_NOT_FOUND;
  }

  _setColourAndWidthFromPen(xg->cr, pen);

  cairo_set_dash(xg->cr, NULL, 0, 0.0);

  /* draw line from start to end position */
  cairo_rectangle(xg->cr, x, y, w, h);
  cairo_stroke(xg->cr);

  return 0;
}



int _drawFilledRect(AQDG_DRAW_CONTEXT *g, int penId, int x, int y, int w, int h)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_PEN *pen;

  xg=_ensureFullCairoContext(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  pen=AQDG_Draw_Pen_List_GetById(xg->penList, penId);
  if (pen==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Pen %d not found", penId);
    return GWEN_ERROR_NOT_FOUND;
  }

  _setColourAndWidthFromPen(xg->cr, pen);

  cairo_set_dash(xg->cr, NULL, 0, 0.0);

  /* draw line from start to end position */
  cairo_rectangle(xg->cr, x, y, w, h);
  cairo_stroke_preserve(xg->cr);
  cairo_fill(xg->cr);

  return 0;
}



int _fontCreate(AQDG_DRAW_CONTEXT *g, const char *face, int fontSize, int fontSlant, int fontWeight)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_FONT *font;

  xg=_ensureCairoSurface(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  font=AQDG_Draw_Font_new();
  AQDG_Draw_Font_SetId(font, ++(xg->lastFontId));
  AQDG_Draw_Font_SetFontName(font, face);
  AQDG_Draw_Font_SetFontSize(font, fontSize);
  AQDG_Draw_Font_SetFontSlant(font, fontSlant);
  AQDG_Draw_Font_SetFontWeight(font, fontWeight);

  AQDG_Draw_Font_List_Add(font, xg->fontList);
  return xg->lastFontId;

}



int _fontRelease(AQDG_DRAW_CONTEXT *g, int fontId)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_FONT *font;

  xg=_ensureCairoSurface(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  font=AQDG_Draw_Font_List_GetById(xg->fontList, fontId);
  if (font==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Font %d not found", fontId);
    return GWEN_ERROR_NOT_FOUND;
  }

  AQDG_Draw_Font_List_Del(font);
  AQDG_Draw_Font_free(font);
  return 0;
}



int _drawText(AQDG_DRAW_CONTEXT *g, int penId, int fontId, int direction, int x, int y, const char *text)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_FONT *font;
  AQDG_DRAW_PEN *pen;
  cairo_text_extents_t te;

  xg=_ensureFullCairoContext(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  /* get and handle pen */
  pen=AQDG_Draw_Pen_List_GetById(xg->penList, penId);
  if (pen==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Pen %d not found", penId);
    return GWEN_ERROR_NOT_FOUND;
  }

  _setColourAndWidthFromPen(xg->cr, pen);

  /* get and handle font */
  font=AQDG_Draw_Font_List_GetById(xg->fontList, fontId);
  if (font==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Font %d not found", fontId);
    return GWEN_ERROR_NOT_FOUND;
  }
  _setFont(xg->cr, font);

  cairo_text_extents(xg->cr, text, &te);

  /* TODO: translate pos and rotate according to given direction (use cairo_save and cairo_restore) */
  /*DBG_ERROR(AQDG_LOGDOMAIN, "te.x_bearing=%f, te.y_bearing=%f", te.x_bearing, te.y_bearing);*/
  cairo_move_to(xg->cr, x, y-te.y_bearing);
  cairo_show_text(xg->cr, text);

  return 0;
}



int _getTextWidth(AQDG_DRAW_CONTEXT *g, int fontId, const char *text)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_FONT *font;
  cairo_text_extents_t extents;

  xg=_ensureFullCairoContext(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  /* get and handle font */
  font=AQDG_Draw_Font_List_GetById(xg->fontList, fontId);
  if (font==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Font %d not found", fontId);
    return GWEN_ERROR_NOT_FOUND;
  }

  _setFont(xg->cr, font);

  cairo_text_extents(xg->cr, text, &extents);

  return (int)(extents.width);
}



int _getTextHeight(AQDG_DRAW_CONTEXT *g, int fontId, const char *text)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;
  AQDG_DRAW_FONT *font;
  cairo_text_extents_t extents;

  xg=_ensureFullCairoContext(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  /* get and handle font */
  font=AQDG_Draw_Font_List_GetById(xg->fontList, fontId);
  if (font==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "Font %d not found", fontId);
    return GWEN_ERROR_NOT_FOUND;
  }

  _setFont(xg->cr, font);

  cairo_text_extents(xg->cr, text, &extents);

  return (int)(extents.height);
}



int _finish(AQDG_DRAW_CONTEXT *g)
{
  AQDG_DRAW_CONTEXT_CAIRO *xg;

  xg=_ensureFullCairoContext(g);
  if (xg==NULL) {
    DBG_INFO(AQDG_LOGDOMAIN, "here");
    return GWEN_ERROR_INVALID;
  }

  if (xg->writePng) {
    cairo_status_t st;

    cairo_surface_flush(xg->surface);
    st=cairo_surface_write_to_png(xg->surface, xg->fileName);
    if (st!=CAIRO_STATUS_SUCCESS) {
      DBG_ERROR(AQDG_LOGDOMAIN, "Cairo Status: %d (%s)", st, cairo_status_to_string(st));
    }
  }

#if 0
  if (1) {
    cairo_status_t st;

    fprintf(stderr, "Writing to PNG\n");
    cairo_surface_flush(xg->surface);
    st=cairo_surface_write_to_png(xg->surface, "/tmp/test.png");
    if (st!=CAIRO_STATUS_SUCCESS) {
      fprintf(stderr, "Cairo Status: %d (%s)\n", st, cairo_status_to_string(st));
    }
  }
#endif

  cairo_surface_finish(xg->surface);

  return 0;
}



AQDG_DRAW_CONTEXT_CAIRO *_ensureFullCairoContext(const AQDG_DRAW_CONTEXT *g)
{
  if (g) {
    AQDG_DRAW_CONTEXT_CAIRO *xg;

    xg=_ensureCairoSurface(g);
    if (xg) {
      if (xg->cr==NULL) {
        DBG_INFO(AQDG_LOGDOMAIN, "Graphics context not active, call \"AQDG_Draw_Context_BeginDraw()\" first");
        return NULL;
      }
      return xg;
    }
  }
  return NULL;
}



AQDG_DRAW_CONTEXT_CAIRO *_ensureCairoSurface(const AQDG_DRAW_CONTEXT *g)
{
  if (g) {
    AQDG_DRAW_CONTEXT_CAIRO *xg;

    xg=GWEN_INHERIT_GETDATA(AQDG_DRAW_CONTEXT, AQDG_DRAW_CONTEXT_CAIRO, g);
    if (xg) {
      if (xg->surface==NULL) {
        DBG_ERROR(AQDG_LOGDOMAIN, "No surface created");
        return NULL;
      }
    }
    return xg;
  }
  return NULL;
}



void _setColourAndWidthFromPen(cairo_t *cr, const AQDG_DRAW_PEN *pen)
{
  uint32_t fgCol;
  double red, green, blue;

  /*  set foreground color */;
  fgCol=AQDG_Draw_Pen_GetFrontColour(pen);
  red=(double)((fgCol>>24) & 0xff)/255.0;
  green=(double)((fgCol>>16) & 0xff)/255.0;
  blue=(double)((fgCol>>8) & 0xff)/255.0;
  cairo_set_source_rgb(cr, red, green, blue);

  /* set line width */
  cairo_set_line_width(cr, AQDG_Draw_Pen_GetLineWidth(pen));
}



void _setDashType(cairo_t *cr, int dashType)
{
  switch (dashType) {
  default:
  case 0:
    cairo_set_dash(cr, NULL, 0, 0.0);
    break;
  case 1:
    cairo_set_dash(cr, AQDG_Draw_ContextCairo_DashPattern1, 2, 0.0);
    break;
  case 2:
    cairo_set_dash(cr, AQDG_Draw_ContextCairo_DashPattern2, 2, 0.0);
    break;
  case 3:
    cairo_set_dash(cr, AQDG_Draw_ContextCairo_DashPattern3, 2, 0.0);
    break;
  case 4:
    cairo_set_dash(cr, AQDG_Draw_ContextCairo_DashPattern4, 2, 0.0);
    break;
  case 5:
    cairo_set_dash(cr, AQDG_Draw_ContextCairo_DashPattern5, 2, 0.0);
    break;
  }
}


void _setFont(cairo_t *cr, AQDG_DRAW_FONT *font)
{
  int crSlant;
  int crWeight;

  switch (AQDG_Draw_Font_GetFontSlant(font)) {
  default:
  case AQDG_Slant_None:
    crSlant=CAIRO_FONT_SLANT_NORMAL;
    break;
  case AQDG_Slant_Italic:
    crSlant=CAIRO_FONT_SLANT_ITALIC;
    break;
  }

  switch (AQDG_Draw_Font_GetFontWeight(font)) {
  default:
  case AQDG_Weight_None:
    crWeight=CAIRO_FONT_WEIGHT_NORMAL;
    break;
  case AQDG_Weight_Bold:
    crWeight=CAIRO_FONT_WEIGHT_BOLD;
    break;
  }
  cairo_select_font_face(cr, AQDG_Draw_Font_GetFontName(font), crSlant, crWeight);
  cairo_set_font_size(cr, AQDG_Draw_Font_GetFontSize(font));
}



