/** \file
* \brief Draw Functions using WinDrawLib (Direct2D / GDI+)
*
* See Copyright Notice in "iup.h"
*/

#include <windows.h>

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

#include "iup.h"

#include "iup_attrib.h"
#include "iup_class.h"
#include "iup_drvdraw.h"
#include "iup_draw.h"

#include "iupwin_drv.h"
#include "iupwin_draw.h"
#include "iupwin_str.h"

#include "wdl.h"
#include "backend-gdix.h"

/* From iupwin_image_wdl.c */
IUP_DRV_API void iupwinWdlImageInit(void);
IUP_DRV_API WD_HIMAGE iupwinWdlImageGetImage(const char* name, Ihandle* ih_parent, int make_inactive, const char* bgcolor);

struct _IdrawCanvas{
  Ihandle* ih;
  int w, h;

  HWND hWnd;
  WD_HCANVAS hCanvas;
  HDC hDC;

  int clip_x1, clip_y1, clip_x2, clip_y2;

  int backend_type;  /* WD_BACKEND_D2D or WD_BACKEND_GDIPLUS */
};

/* must be the same in wdInitialize and wdTerminate */
const DWORD wdl_flags = WD_INIT_COREAPI | WD_INIT_IMAGEAPI | WD_INIT_STRINGAPI;

static WD_HSTROKESTYLE g_strokeDash = NULL;
static WD_HSTROKESTYLE g_strokeDot = NULL;
static WD_HSTROKESTYLE g_strokeDashDot = NULL;
static WD_HSTROKESTYLE g_strokeDashDotDot = NULL;

IUP_DRV_API void iupwinDrawInit(void)
{
  iupwinDrawThemeInit();

#if 0
  wdPreInitialize(NULL, NULL, WD_DISABLE_D2D);  /* to force GDI+ */
#endif

  wdInitialize(wdl_flags);

  iupwinWdlImageInit();

  if (wdBackend() == WD_BACKEND_D2D)
    gdix_init();  /* if WDL is using Direct2D must manually initialize GDI+ */
}

IUP_DRV_API void iupwinDrawFinish(void)
{
  if (g_strokeDash) { wdDestroyStrokeStyle(g_strokeDash); g_strokeDash = NULL; }
  if (g_strokeDot) { wdDestroyStrokeStyle(g_strokeDot); g_strokeDot = NULL; }
  if (g_strokeDashDot) { wdDestroyStrokeStyle(g_strokeDashDot); g_strokeDashDot = NULL; }
  if (g_strokeDashDotDot) { wdDestroyStrokeStyle(g_strokeDashDotDot); g_strokeDashDotDot = NULL; }

  if (wdBackend() == WD_BACKEND_D2D)
    gdix_fini();  /* if WDL is using Direct2D must manually terminate GDI+ */

  wdTerminate(wdl_flags);
}

IUP_SDK_API IdrawCanvas* iupdrvDrawCreateCanvas(Ihandle* ih)
{
  IdrawCanvas* dc = calloc(1, sizeof(IdrawCanvas));
  PAINTSTRUCT ps;
  RECT rect;
  int x, y, w, h;
  char *rcPaint;

  dc->ih = ih;
  dc->backend_type = wdBackend();

  dc->hWnd = (HWND)IupGetAttribute(ih, "HWND");  /* Use the attribute, so it can work with FileDlg preview area */

  if (!dc->hWnd)
  {
    free(dc);
    return NULL;
  }

  GetClientRect(dc->hWnd, &rect);
  dc->w = rect.right - rect.left;
  dc->h = rect.bottom - rect.top;

  /* valid only inside the ACTION callback of an IupCanvas */
  rcPaint = iupAttribGetStr(ih, "CLIPRECT");

  if (rcPaint)
  {
    ps.hdc = (HDC)iupAttribGet(ih, "HDC_WMPAINT");
    dc->hDC = NULL;
    sscanf(rcPaint, "%d %d %d %d", &x, &y, &w, &h);
    ps.rcPaint.left = x;
    ps.rcPaint.top = y;
    ps.rcPaint.right = x + w;
    ps.rcPaint.bottom = y + h;
  }
  else
  {
    ps.hdc = GetDC(dc->hWnd);
    dc->hDC = ps.hdc;
    ps.rcPaint.left = 0;
    ps.rcPaint.top = 0;
    ps.rcPaint.right = dc->w;
    ps.rcPaint.bottom = dc->h;
  }

  if (!ps.hdc)
  {
    free(dc);
    return NULL;
  }

  dc->hCanvas = wdCreateCanvasWithPaintStruct(dc->hWnd, &ps, WD_CANVAS_DOUBLEBUFFER | WD_CANVAS_NOGDICOMPAT);

  if (!dc->hCanvas)
  {
    if (dc->hDC)
      ReleaseDC(dc->hWnd, dc->hDC);
    free(dc);
    return NULL;
  }

  if (wdBackend() == WD_BACKEND_D2D)
    iupAttribSet(ih, "DRAWDRIVER", "D2D");
  else
    iupAttribSet(ih, "DRAWDRIVER", "GDI+");

  wdBeginPaint(dc->hCanvas);

  return dc;
}

IUP_SDK_API void iupdrvDrawKillCanvas(IdrawCanvas* dc)
{
  wdSetClip(dc->hCanvas, NULL, NULL); /* must reset clip before destroy */
  wdDestroyCanvas(dc->hCanvas);

  if (dc->hDC)
    ReleaseDC(dc->hWnd, dc->hDC);  /* to match GetDC */

  memset(dc, 0, sizeof(IdrawCanvas));
  free(dc);
}

IUP_SDK_API void iupdrvDrawUpdateSize(IdrawCanvas* dc)
{
  int w, h;
  RECT rect;

  GetClientRect(dc->hWnd, &rect);
  w = rect.right - rect.left;
  h = rect.bottom - rect.top;

  if (w != dc->w || h != dc->h)
  {
    dc->w = w;
    dc->h = h;

    wdResizeCanvas(dc->hCanvas, w, h);
  }
}

IUP_SDK_API void iupdrvDrawFlush(IdrawCanvas* dc)
{
  wdEndPaint(dc->hCanvas);
}

IUP_SDK_API void iupdrvDrawGetSize(IdrawCanvas* dc, int *w, int *h)
{
  if (w) *w = dc->w;
  if (h) *h = dc->h;
}

/* Helper macros */
#define iupInt2Float(_x) ((float)_x)
#define iupInt2FloatW(_x) ((float)_x)
#define iupColor2ARGB(_c) WD_ARGB(iupDrawAlpha(_c), iupDrawRed(_c), iupDrawGreen(_c), iupDrawBlue(_c))

/* Returns a cached stroke style, destroyed in iupwinDrawFinish */
static WD_HSTROKESTYLE iCreateStrokeStyle(int style)
{
  static float s_dash[2] = { 9.0f, 3.0f };
  static float s_dot[2] = { 1.0f, 2.0f };
  static float s_dash_dot[4] = { 7.0f, 3.0f, 1.0f, 3.0f };
  static float s_dash_dot_dot[6] = { 7.0f, 3.0f, 1.0f, 3.0f, 1.0f, 3.0f };

  switch (style)
  {
    case IUP_DRAW_STROKE_DASH:
      if (!g_strokeDash) g_strokeDash = wdCreateStrokeStyleCustom(s_dash, 2, WD_LINECAP_FLAT, WD_LINEJOIN_MITER);
      return g_strokeDash;
    case IUP_DRAW_STROKE_DOT:
      if (!g_strokeDot) g_strokeDot = wdCreateStrokeStyleCustom(s_dot, 2, WD_LINECAP_FLAT, WD_LINEJOIN_MITER);
      return g_strokeDot;
    case IUP_DRAW_STROKE_DASH_DOT:
      if (!g_strokeDashDot) g_strokeDashDot = wdCreateStrokeStyleCustom(s_dash_dot, 4, WD_LINECAP_FLAT, WD_LINEJOIN_MITER);
      return g_strokeDashDot;
    case IUP_DRAW_STROKE_DASH_DOT_DOT:
      if (!g_strokeDashDotDot) g_strokeDashDotDot = wdCreateStrokeStyleCustom(s_dash_dot_dot, 6, WD_LINECAP_FLAT, WD_LINEJOIN_MITER);
      return g_strokeDashDotDot;
    default:
      return NULL;
  }
}

IUP_SDK_API void iupdrvDrawRectangle(IdrawCanvas* dc, int x1, int y1, int x2, int y2, long color, int style, int line_width)
{
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  if (style == IUP_DRAW_FILL)
    wdFillRect(dc->hCanvas, brush, iupInt2Float(x1 - 0.5f), iupInt2Float(y1 - 0.5f), iupInt2Float(x2 + 0.5f), iupInt2Float(y2 + 0.5f));
  else
  {
    WD_HSTROKESTYLE stroke_style = iCreateStrokeStyle(style);
    wdDrawRectStyled(dc->hCanvas, brush, iupInt2Float(x1), iupInt2Float(y1), iupInt2Float(x2), iupInt2Float(y2), iupInt2FloatW(line_width), stroke_style);

  }

  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawLine(IdrawCanvas* dc, int x1, int y1, int x2, int y2, long color, int style, int line_width)
{
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));
  WD_HSTROKESTYLE stroke_style = iCreateStrokeStyle(style);

  if (dc->backend_type == WD_BACKEND_D2D && line_width == 1)
  {
    /* compensate Direct2D horizontal and vertical lines when line_width == 1 */
    if (x1 == x2)
      wdDrawLineStyled(dc->hCanvas, brush, iupInt2Float(x1), iupInt2Float(y1 - 0.5f), iupInt2Float(x2), iupInt2Float(y2 + 0.5f), iupInt2FloatW(line_width), stroke_style);
    else if (y1 == y2)
      wdDrawLineStyled(dc->hCanvas, brush, iupInt2Float(x1 - 0.5f), iupInt2Float(y1), iupInt2Float(x2 + 0.5f), iupInt2Float(y2), iupInt2FloatW(line_width), stroke_style);
    else
      wdDrawLineStyled(dc->hCanvas, brush, iupInt2Float(x1), iupInt2Float(y1), iupInt2Float(x2), iupInt2Float(y2), iupInt2FloatW(line_width), stroke_style);
  }
  else
    wdDrawLineStyled(dc->hCanvas, brush, iupInt2Float(x1), iupInt2Float(y1), iupInt2Float(x2), iupInt2Float(y2), iupInt2FloatW(line_width), stroke_style);

  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawArc(IdrawCanvas* dc, int x1, int y1, int x2, int y2, double a1, double a2, long color, int style, int line_width)
{
  float xc, yc, rx, ry;
  float baseAngle, sweepAngle;
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  rx = (x2 - x1) / 2.0f;
  ry = (y2 - y1) / 2.0f;

  xc = iupInt2Float(x1) + rx;
  yc = iupInt2Float(y1) + ry;

  baseAngle = (float)(360.0 - a2);
  sweepAngle = (float)(a2 - a1);

  if (style == IUP_DRAW_FILL)
  {
    if (sweepAngle == 360.0f)
      wdFillEllipse(dc->hCanvas, brush, xc, yc, rx, ry);
    else
      wdFillEllipsePie(dc->hCanvas, brush, xc, yc, rx, ry, baseAngle, sweepAngle);
  }
  else
  {
    WD_HSTROKESTYLE stroke_style = iCreateStrokeStyle(style);
    if (sweepAngle == 360.0f)
      wdDrawEllipseStyled(dc->hCanvas, brush, xc, yc, rx, ry, iupInt2FloatW(line_width), stroke_style);
    else
      wdDrawEllipseArcStyled(dc->hCanvas, brush, xc, yc, rx, ry, baseAngle, sweepAngle, iupInt2FloatW(line_width), stroke_style);

  }

  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawEllipse(IdrawCanvas* dc, int x1, int y1, int x2, int y2, long color, int style, int line_width)
{
  float xc, yc, rx, ry;
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  rx = (x2 - x1) / 2.0f;
  ry = (y2 - y1) / 2.0f;

  xc = iupInt2Float(x1) + rx;
  yc = iupInt2Float(y1) + ry;

  if (style == IUP_DRAW_FILL)
  {
    wdFillEllipse(dc->hCanvas, brush, xc, yc, rx, ry);
  }
  else
  {
    WD_HSTROKESTYLE stroke_style = iCreateStrokeStyle(style);
    wdDrawEllipseStyled(dc->hCanvas, brush, xc, yc, rx, ry, iupInt2FloatW(line_width), stroke_style);

  }

  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawPolygon(IdrawCanvas* dc, int* points, int count, long color, int style, int line_width)
{
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));
  WD_HPATH path;
  WD_PATHSINK sink;
  int i;

  path = wdCreatePath(dc->hCanvas);
  wdOpenPathSink(&sink, path);

  /* Begin at first point */
  wdBeginFigure(&sink, iupInt2Float(points[0]), iupInt2Float(points[1]));

  /* Add lines to all other points (starting from point 1, which is at index 2 in the array) */
  for (i = 2; i < count * 2; i = i + 2)
    wdAddLine(&sink, iupInt2Float(points[i]), iupInt2Float(points[i + 1]));

  /* Close the figure - this connects the last point back to the first */
  wdEndFigure(&sink, TRUE);
  wdClosePathSink(&sink);

  if (style == IUP_DRAW_FILL)
    wdFillPath(dc->hCanvas, brush, path);
  else
  {
    WD_HSTROKESTYLE stroke_style = iCreateStrokeStyle(style);
    wdDrawPathStyled(dc->hCanvas, brush, path, iupInt2FloatW(line_width), stroke_style);

  }

  wdDestroyPath(path);
  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawPixel(IdrawCanvas* dc, int x, int y, long color)
{
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));
  wdFillRect(dc->hCanvas, brush, iupInt2Float(x), iupInt2Float(y), iupInt2Float(x + 1), iupInt2Float(y + 1));
  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawRoundedRectangle(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int corner_radius, long color, int style, int line_width)
{
  WD_HBRUSH brush;
  int width, height;
  float x0, y0, x1f, y1f, max_radius, radius;

  brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));

  if (!brush)
    return;

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  width = x2 - x1;
  height = y2 - y1;

  x0 = iupInt2Float(x1);
  y0 = iupInt2Float(y1);
  x1f = iupInt2Float(x2);
  y1f = iupInt2Float(y2);

  /* Calculate radius - clamp to half of smaller dimension */
  max_radius = (width < height) ? (float)width / 2.0f : (float)height / 2.0f;
  radius = (float)corner_radius;
  if (radius > max_radius)
    radius = max_radius;

  if (style == IUP_DRAW_FILL)
  {
    wdFillRoundedRect(dc->hCanvas, brush, x0, y0, x1f, y1f, radius);
  }
  else
  {
    WD_HSTROKESTYLE stroke_style = iCreateStrokeStyle(style);
    wdDrawRoundedRectStyled(dc->hCanvas, brush, x0, y0, x1f, y1f, radius, iupInt2FloatW(line_width), stroke_style);

  }

  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawBezier(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, long color, int style, int line_width)
{
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));
  WD_HPATH path;
  WD_PATHSINK sink;

  if (!brush)
    return;

  path = wdCreatePath(dc->hCanvas);
  wdOpenPathSink(&sink, path);

  wdBeginFigure(&sink, iupInt2Float(x1), iupInt2Float(y1));

  wdAddBezier(&sink, iupInt2Float(x2), iupInt2Float(y2), iupInt2Float(x3), iupInt2Float(y3), iupInt2Float(x4), iupInt2Float(y4));

  wdEndFigure(&sink, FALSE);
  wdClosePathSink(&sink);

  if (style == IUP_DRAW_FILL)
  {
    wdFillPath(dc->hCanvas, brush, path);
  }
  else
  {
    WD_HSTROKESTYLE stroke_style = iCreateStrokeStyle(style);
    wdDrawPathStyled(dc->hCanvas, brush, path, iupInt2FloatW(line_width), stroke_style);

  }

  wdDestroyPath(path);
  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawQuadraticBezier(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int x3, int y3, long color, int style, int line_width)
{
  int cx1, cy1, cx2, cy2;

  /* Convert quadratic Bezier to cubic Bezier using the 2/3 formula */
  cx1 = x1 + ((2 * (x2 - x1)) / 3);
  cy1 = y1 + ((2 * (y2 - y1)) / 3);
  cx2 = x3 + ((2 * (x2 - x3)) / 3);
  cy2 = y3 + ((2 * (y2 - y3)) / 3);

  iupdrvDrawBezier(dc, x1, y1, cx1, cy1, cx2, cy2, x3, y3, color, style, line_width);
}

IUP_SDK_API void iupdrvDrawResetClip(IdrawCanvas* dc)
{
  wdSetClip(dc->hCanvas, NULL, NULL);

  dc->clip_x1 = 0;
  dc->clip_y1 = 0;
  dc->clip_x2 = 0;
  dc->clip_y2 = 0;
}

IUP_SDK_API void iupdrvDrawGetClipRect(IdrawCanvas* dc, int *x1, int *y1, int *x2, int *y2)
{
  if (x1) *x1 = dc->clip_x1;
  if (y1) *y1 = dc->clip_y1;
  if (x2) *x2 = dc->clip_x2;
  if (y2) *y2 = dc->clip_y2;
}

IUP_SDK_API void iupdrvDrawSetClipRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2)
{
  WD_RECT rect;

  if (x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0)
  {
    iupdrvDrawResetClip(dc);
    return;
  }

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  if (x1 == x2 || y1 == y2)
  {
    rect.x0 = iupInt2Float(x1);
    rect.y0 = iupInt2Float(y1);
    rect.x1 = iupInt2Float(x2);
    rect.y1 = iupInt2Float(y2);
  }
  else
  {
    rect.x0 = iupInt2Float(x1 - 0.5f);
    rect.y0 = iupInt2Float(y1 - 0.5f);
    rect.x1 = iupInt2Float(x2 + 0.5f);
    rect.y1 = iupInt2Float(y2 + 0.5f);
  }

  wdSetClip(dc->hCanvas, &rect, NULL);

  dc->clip_x1 = x1;
  dc->clip_y1 = y1;
  dc->clip_x2 = x2;
  dc->clip_y2 = y2;
}

IUP_SDK_API void iupdrvDrawSetClipRoundedRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2, int corner_radius)
{
  WD_RECT rect;
  WD_HPATH path;
  int width, height;
  float max_radius, radius;

  if (x1 == 0 && y1 == 0 && x2 == 0 && y2 == 0)
  {
    iupdrvDrawResetClip(dc);
    return;
  }

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  width = x2 - x1;
  height = y2 - y1;

  /* Calculate radius - clamp to half of smaller dimension */
  max_radius = (width < height) ? (float)width / 2.0f : (float)height / 2.0f;
  radius = (float)corner_radius;
  if (radius > max_radius)
    radius = max_radius;

  /* Set up rectangle for path */
  rect.x0 = iupInt2Float(x1);
  rect.y0 = iupInt2Float(y1);
  rect.x1 = iupInt2Float(x2);
  rect.y1 = iupInt2Float(y2);

  /* Create rounded rectangle path and set as clip */
  path = wdCreateRoundedRectPath(dc->hCanvas, &rect, radius);
  if (path)
  {
    wdSetClip(dc->hCanvas, NULL, path);
    wdDestroyPath(path);
  }

  dc->clip_x1 = x1;
  dc->clip_y1 = y1;
  dc->clip_x2 = x2;
  dc->clip_y2 = y2;
}

static int iCompensatePosX(float font_height)
{
  return iupRound(font_height / 7.);  /* 15% */
}

IUP_SDK_API void iupdrvDrawText(IdrawCanvas* dc, const char* text, int len, int x, int y, int w, int h, long color, const char* font, int flags, double text_orientation)
{
  WD_RECT rect;
  DWORD dwFlags = WD_STR_TOPALIGN;
  WCHAR *wtext = iupwinStrToSystemLen(text, &len);
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));
  int layout_center = flags & IUP_DRAW_LAYOUTCENTER;

  HFONT hFont = (HFONT)iupwinGetHFont(font);
  WD_HFONT wdFont = wdCreateFontWithGdiHandle(hFont);
  int layout_w = w, layout_h = h;

  if (text_orientation && layout_center)
    iupDrawGetTextSize(dc->ih, text, len, &layout_w, &layout_h, 0);

  rect.x0 = iupInt2Float(x);
  rect.x1 = iupInt2Float(x + layout_w);
  rect.y0 = iupInt2Float(y);
  rect.y1 = iupInt2Float(y + layout_h);

  dwFlags |= WD_STR_LEFTALIGN;
  if (flags & IUP_DRAW_RIGHT)
    dwFlags |= WD_STR_RIGHTALIGN;
  else if (flags & IUP_DRAW_CENTER)
    dwFlags |= WD_STR_CENTERALIGN;

  if (!(flags & IUP_DRAW_WRAP))
  {
    dwFlags |= WD_STR_NOWRAP;

    if (flags & IUP_DRAW_ELLIPSIS)
      dwFlags |= WD_STR_ENDELLIPSIS;
  }

  if (dc->backend_type == WD_BACKEND_GDIPLUS)
  {
    /* compensate GDI+ internal padding */
    WD_FONTMETRICS metrics;
    wdFontMetrics(wdFont, &metrics);
    rect.x0 -= iCompensatePosX(metrics.fLeading);
  }

  if (text_orientation)
  {
    if (layout_center)
    {
      wdRotateWorld(dc->hCanvas, iupInt2Float(x + layout_w / 2), iupInt2Float(y + layout_h / 2), (float)-text_orientation);  /* counterclockwise */
      wdTranslateWorld(dc->hCanvas, iupInt2Float(w - layout_w) / 2, iupInt2Float(h - layout_h) / 2);  /* append the transform */
    }
    else
      wdRotateWorld(dc->hCanvas, iupInt2Float(x), iupInt2Float(y), (float)-text_orientation);  /* counterclockwise */
  }

  if (!(flags & IUP_DRAW_CLIP))
    dwFlags |= WD_STR_NOCLIP;

  wdDrawString(dc->hCanvas, wdFont, &rect, wtext, len, brush, dwFlags);

  /* restore settings */
  if (text_orientation)
    wdResetWorld(dc->hCanvas);

  wdDestroyFont(wdFont);
  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawImage(IdrawCanvas* dc, const char* name, int make_inactive, const char* bgcolor, int x, int y, int w, int h)
{
  WD_HIMAGE hImage = iupwinWdlImageGetImage(name, dc->ih, make_inactive, bgcolor);
  if (hImage)
  {
    UINT img_w, img_h;
    WD_RECT rect;

    wdGetImageSize(hImage, &img_w, &img_h);

    if (w == -1 || w == 0) w = img_w;
    if (h == -1 || h == 0) h = img_h;

    rect.x0 = iupInt2Float(x);
    rect.y0 = iupInt2Float(y);
    rect.x1 = iupInt2Float(x + w);
    rect.y1 = iupInt2Float(y + h);

    wdBitBltImage(dc->hCanvas, hImage, &rect, NULL);
  }
}

IUP_SDK_API void iupdrvDrawSelectRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2)
{
  WD_HBRUSH brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(iupDrawColor(0, 0, 255, 153)));

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  wdFillRect(dc->hCanvas, brush, iupInt2Float(x1 - 0.5f), iupInt2Float(y1 - 0.5f), iupInt2Float(x2 + 0.5f), iupInt2Float(y2 + 0.5f));

  wdDestroyBrush(brush);
}

IUP_SDK_API void iupdrvDrawFocusRect(IdrawCanvas* dc, int x1, int y1, int x2, int y2)
{
  iupdrvDrawRectangle(dc, x1, y1, x2, y2, iupDrawColor(0, 0, 0, 224), IUP_DRAW_STROKE_DOT, 1);
}

static long iInterpolateColor(long color1, long color2, float t)
{
  unsigned char r1 = iupDrawRed(color1), g1 = iupDrawGreen(color1), b1 = iupDrawBlue(color1), a1 = iupDrawAlpha(color1);
  unsigned char r2 = iupDrawRed(color2), g2 = iupDrawGreen(color2), b2 = iupDrawBlue(color2), a2 = iupDrawAlpha(color2);
  unsigned char r = (unsigned char)(r1 + t * (r2 - r1));
  unsigned char g = (unsigned char)(g1 + t * (g2 - g1));
  unsigned char b = (unsigned char)(b1 + t * (b2 - b1));
  unsigned char a = (unsigned char)(a1 + t * (a2 - a1));
  return iupDrawColor(r, g, b, a);
}

IUP_SDK_API void iupdrvDrawLinearGradient(IdrawCanvas* dc, int x1, int y1, int x2, int y2, float angle, long color1, long color2)
{
  WD_HBRUSH brush;
  float rad, dx, dy, cx, cy, w, h;
  float x0, y0, x3, y3;

  iupDrawCheckSwapCoord(x1, x2);
  iupDrawCheckSwapCoord(y1, y2);

  rad = angle * 3.14159265359f / 180.0f;
  dx = (float)cos(rad);
  dy = (float)sin(rad);

  w = (float)(x2 - x1);
  h = (float)(y2 - y1);
  cx = (float)x1 + w / 2.0f;
  cy = (float)y1 + h / 2.0f;

  x0 = cx - (w * dx) / 2.0f;
  y0 = cy - (h * dy) / 2.0f;

  x3 = cx + (w * dx) / 2.0f;
  y3 = cy + (h * dy) / 2.0f;

  brush = wdCreateLinearGradientBrush(dc->hCanvas, x0, y0, x3, y3, iupColor2ARGB(color1), iupColor2ARGB(color2));
  if (brush)
  {
    wdFillRect(dc->hCanvas, brush, iupInt2Float(x1), iupInt2Float(y1), iupInt2Float(x2), iupInt2Float(y2));
    wdDestroyBrush(brush);
  }
  else
  {
    /* Fallback for GDI+ backend - manual color interpolation */
    int i, steps;
    float t, length;
    int px1, py1, px2, py2;

    length = (float)sqrt(w * w + h * h);
    steps = (int)length;
    if (steps < 2) steps = 2;
    if (steps > 256) steps = 256;

    for (i = 0; i < steps; i++)
    {
      t = (float)i / (float)(steps - 1);
      long color = iInterpolateColor(color1, color2, t);
      brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));

      if (fabs(dx) > fabs(dy))
      {
        px1 = x1 + (int)(t * w);
        px2 = x1 + (int)((t + 1.0f / steps) * w);
        py1 = y1;
        py2 = y2;
      }
      else
      {
        px1 = x1;
        px2 = x2;
        py1 = y1 + (int)(t * h);
        py2 = y1 + (int)((t + 1.0f / steps) * h);
      }

      wdFillRect(dc->hCanvas, brush, iupInt2Float(px1), iupInt2Float(py1), iupInt2Float(px2), iupInt2Float(py2));
      wdDestroyBrush(brush);
    }
  }
}

IUP_SDK_API void iupdrvDrawRadialGradient(IdrawCanvas* dc, int cx, int cy, int radius, long colorCenter, long colorEdge)
{
  WD_HBRUSH brush;

  brush = wdCreateRadialGradientBrush(dc->hCanvas, iupInt2Float(cx), iupInt2Float(cy),
                                       iupInt2Float(radius), iupInt2Float(radius),
                                       iupColor2ARGB(colorCenter), iupColor2ARGB(colorEdge));
  if (brush)
  {
    wdFillEllipse(dc->hCanvas, brush, iupInt2Float(cx), iupInt2Float(cy), iupInt2Float(radius), iupInt2Float(radius));
    wdDestroyBrush(brush);
  }
  else
  {
    /* Fallback for GDI+ backend - manual color interpolation */
    int i, steps;
    float t, r;

    steps = radius;
    if (steps < 2) steps = 2;
    if (steps > 256) steps = 256;

    for (i = steps - 1; i >= 0; i--)
    {
      t = (float)i / (float)(steps - 1);
      long color = iInterpolateColor(colorCenter, colorEdge, t);
      brush = wdCreateSolidBrush(dc->hCanvas, iupColor2ARGB(color));

      r = (float)radius * t;
      wdFillEllipse(dc->hCanvas, brush, iupInt2Float(cx), iupInt2Float(cy), r, r);
      wdDestroyBrush(brush);
    }
  }
}

static int iDrawGetImageDataWindowFallback(IdrawCanvas* dc, unsigned char* data)
{
  BITMAPINFOHEADER bmi;
  HBITMAP hBitmap;
  HDC hMemDC, hWndDC;
  HGDIOBJ hOld;
  unsigned char* temp;
  int x, y;

  wdEndPaint(dc->hCanvas);

  hWndDC = GetDC(dc->hWnd);
  if (!hWndDC)
  {
    wdBeginPaint(dc->hCanvas);
    return 0;
  }

  hMemDC = CreateCompatibleDC(hWndDC);
  if (!hMemDC)
  {
    ReleaseDC(dc->hWnd, hWndDC);
    wdBeginPaint(dc->hCanvas);
    return 0;
  }

  hBitmap = CreateCompatibleBitmap(hWndDC, dc->w, dc->h);
  if (!hBitmap)
  {
    DeleteDC(hMemDC);
    ReleaseDC(dc->hWnd, hWndDC);
    wdBeginPaint(dc->hCanvas);
    return 0;
  }

  hOld = SelectObject(hMemDC, hBitmap);
  BitBlt(hMemDC, 0, 0, dc->w, dc->h, hWndDC, 0, 0, SRCCOPY);
  ReleaseDC(dc->hWnd, hWndDC);

  memset(&bmi, 0, sizeof(bmi));
  bmi.biSize = sizeof(BITMAPINFOHEADER);
  bmi.biWidth = dc->w;
  bmi.biHeight = -dc->h;
  bmi.biPlanes = 1;
  bmi.biBitCount = 32;
  bmi.biCompression = BI_RGB;

  temp = (unsigned char*)malloc((size_t)dc->w * dc->h * 4);
  if (!temp)
  {
    SelectObject(hMemDC, hOld);
    DeleteObject(hBitmap);
    DeleteDC(hMemDC);
    wdBeginPaint(dc->hCanvas);
    return 0;
  }

  if (!GetDIBits(hMemDC, hBitmap, 0, dc->h, temp, (BITMAPINFO*)&bmi, DIB_RGB_COLORS))
  {
    free(temp);
    SelectObject(hMemDC, hOld);
    DeleteObject(hBitmap);
    DeleteDC(hMemDC);
    wdBeginPaint(dc->hCanvas);
    return 0;
  }

  for (y = 0; y < dc->h; y++)
  {
    unsigned char* src_line = temp + y * dc->w * 4;
    unsigned char* dst_line = data + y * dc->w * 4;
    for (x = 0; x < dc->w; x++)
    {
      dst_line[x * 4 + 0] = src_line[x * 4 + 2];
      dst_line[x * 4 + 1] = src_line[x * 4 + 1];
      dst_line[x * 4 + 2] = src_line[x * 4 + 0];
      dst_line[x * 4 + 3] = 255;
    }
  }

  free(temp);
  SelectObject(hMemDC, hOld);
  DeleteObject(hBitmap);
  DeleteDC(hMemDC);

  wdBeginPaint(dc->hCanvas);
  return 1;
}

IUP_SDK_API int iupdrvDrawGetImageData(IdrawCanvas* dc, unsigned char* data)
{
  unsigned char* temp = (unsigned char*)malloc((size_t)dc->w * dc->h * 4);
  if (!temp)
    return 0;

  if (wdCanvasGetImageData(dc->hCanvas, temp, dc->w, dc->h))
  {
    int x, y;
    for (y = 0; y < dc->h; y++)
    {
      unsigned char* src_line = temp + y * dc->w * 4;
      unsigned char* dst_line = data + y * dc->w * 4;
      for (x = 0; x < dc->w; x++)
      {
        dst_line[x * 4 + 0] = src_line[x * 4 + 2];
        dst_line[x * 4 + 1] = src_line[x * 4 + 1];
        dst_line[x * 4 + 2] = src_line[x * 4 + 0];
        dst_line[x * 4 + 3] = 255;
      }
    }
    free(temp);
    return 1;
  }

  free(temp);

  /* D2D 1.1 not available (Windows 7), fall back to window readback */
  return iDrawGetImageDataWindowFallback(dc, data);
}

static void iBgrToRgba(unsigned char* dst, const unsigned char* src, int w, int h)
{
  int x, y;
  for (y = 0; y < h; y++)
  {
    const unsigned char* src_line = src + y * w * 4;
    unsigned char* dst_line = dst + y * w * 4;
    for (x = 0; x < w; x++)
    {
      dst_line[x * 4 + 0] = src_line[x * 4 + 2];
      dst_line[x * 4 + 1] = src_line[x * 4 + 1];
      dst_line[x * 4 + 2] = src_line[x * 4 + 0];
      dst_line[x * 4 + 3] = 255;
    }
  }
}

IUP_SDK_API int iupdrvCanvasGetImageData(Ihandle* ih, unsigned char* data, int w, int h)
{
  HWND hwnd = (HWND)IupGetAttribute(ih, "HWND");
  HDC hDC, hMemDC;
  HBITMAP hBitmap;
  HGDIOBJ hOld;
  BITMAPINFOHEADER bmi;
  unsigned char* temp;

  if (!hwnd)
    return 0;

  hDC = GetDC(hwnd);
  hMemDC = CreateCompatibleDC(hDC);
  hBitmap = CreateCompatibleBitmap(hDC, w, h);
  hOld = SelectObject(hMemDC, hBitmap);

  BitBlt(hMemDC, 0, 0, w, h, hDC, 0, 0, SRCCOPY);

  memset(&bmi, 0, sizeof(bmi));
  bmi.biSize = sizeof(BITMAPINFOHEADER);
  bmi.biWidth = w;
  bmi.biHeight = -h;  /* top-down */
  bmi.biPlanes = 1;
  bmi.biBitCount = 32;
  bmi.biCompression = BI_RGB;

  temp = (unsigned char*)malloc((size_t)w * h * 4);
  if (!temp)
  {
    SelectObject(hMemDC, hOld);
    DeleteObject(hBitmap);
    DeleteDC(hMemDC);
    ReleaseDC(hwnd, hDC);
    return 0;
  }

  if (!GetDIBits(hMemDC, hBitmap, 0, h, temp, (BITMAPINFO*)&bmi, DIB_RGB_COLORS))
  {
    free(temp);
    SelectObject(hMemDC, hOld);
    DeleteObject(hBitmap);
    DeleteDC(hMemDC);
    ReleaseDC(hwnd, hDC);
    return 0;
  }

  iBgrToRgba(data, temp, w, h);

  free(temp);
  SelectObject(hMemDC, hOld);
  DeleteObject(hBitmap);
  DeleteDC(hMemDC);
  ReleaseDC(hwnd, hDC);
  return 1;
}
