/*
 * GraphApp - Cross-Platform Graphics Programming Library.
 *
 * File: drawing.c -- all the drawing functions are here.
 * Platform: Windows  Version: 2.40  Date: 1998/05/05
 *
 * Version: 1.00  Changes: Original version by Lachlan Patrick.
 * Version: 1.60  Changes: drawarc/fillarc(r,0,360) now encloses.
 *    New fillellipse() function replaces Windows Ellipse().
 * Version: 2.00  Changes: New class system implemented.
 * Version: 2.02  Changes: Added support for functions like MoveToEx.
 * Version: 2.15  Changes: Fixed brush origins problem.
 * Version: 2.20  Changes: Moved some arrays from context.c to here.
 * Version: 2.40  Changes: Moved drawimage to this file.
 */

/* Copyright (C) 1993-1998 Lachlan Patrick

   This file is part of GraphApp, a cross-platform C graphics library.

   GraphApp is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License.
   GraphApp is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY.

   See the file COPYLIB.TXT for details.
*/

/*  Copyright (C) 2004	The R Foundation

    Changes for R:
        Remove assumption of current->dest being non-NULL

    Copyright (C) 2023  The R Core Team

    Changes for R:
        Avoid naming conflict with LLVM     
*/

#include "internal.h"

#if (WINVER < 0x030a)

#define MoveToEx move_to_ex
#define GetCurrentPositionEx get_current_position_ex
#define GetTextExtentPoint get_text_extent_point

int move_to_ex(HDC hdc, int x, int y, POINT *p);
int get_current_position_ex(HDC hdc, POINT *p);
int get_text_extent_point(HDC hdc, const char *str, int len, SIZE *s);

static int move_to_ex(HDC hdc, int x, int y, POINT *p)
{
    DWORD result = MoveTo(hdc, x, y);
    if (p) {
	p->x = LOWORD(result);
	p->y = HIWORD(result);
    }
    return 1;
}

static int get_current_position_ex(HDC hdc, POINT *p)
{
    DWORD result = GetCurrentPosition(hdc);
    if (p) {
	p->x = LOWORD(result);
	p->y = HIWORD(result);
    }
    return 1;
}

static int get_text_extent_point(HDC hdc, const char *str, int len, SIZE *s)
{
    DWORD result = GetTextExtent(hdc, str, len);
    if (s) {
	s->cx = LOWORD(result);
	s->cy = HIWORD(result);
    }
    return 1;
}

#endif  /* WINVER < 0x030a */

/*
 *  Windows transfer modes corresponding to bitblt operations.
 */
static long copy_mode[16] = {
    BLACKNESS,	/* Zeros */
    NOTSRCERASE,	/* DnorS */
    0x00220326L,	/* DandnotS */
    NOTSRCCOPY,	/* notS */
    SRCERASE,	/* notDandS */
    DSTINVERT,	/* notD */
    SRCINVERT,	/* DxorS */
    0x007700E6L,	/* DnandS */
    SRCAND,		/* DandS */
    0x00990066L,	/* DxnorS */
    0x00AA0029L,	/* D */			/* = no-op */
    MERGEPAINT,	/* DornotS */
    SRCCOPY,	/* S */
    0x00DD0228L,	/* notDorS */
    SRCPAINT,	/* DorS */
    WHITENESS	/* Ones */
};

/*
 *  Windows transfer modes corresponding to patblt operations.
 */
static long pat_mode[16] = {
    BLACKNESS,	/* Zeros */
    0x000500A9L,	/* DnorP */
    0x000A0329L,	/* DandnotP */
    0x000F0001L,	/* notP */
    0x00500325L,	/* notDandP */
    DSTINVERT,	/* notD */
    PATINVERT,	/* DxorP */
    0x005F00E9L,	/* DnandP */
    0x00A000C9L,	/* DandP */
    0x00A50065L,	/* DxnorP */
    0x00AA0029L,	/* D */			/* = no-op */
    0x00AF0229L,	/* DornotP */
    PATCOPY,	/* P */
    0x00F50225L,	/* notDorP */
    0x00FA0089L,	/* DorP */
    WHITENESS	/* Ones */
};

/*
 *  Windows transfer modes corresponding to pen drawing.
 */
static int pen_mode[16] = {
    R2_BLACK,	/* Zeros */
    R2_NOTMERGEPEN,	/* DnorS */
    R2_MASKNOTPEN,	/* DandnotS */
    R2_NOTCOPYPEN,	/* notS */
    R2_MASKPENNOT,	/* notDandS */
    R2_NOT,		/* notD */
    R2_XORPEN,	/* DxorS */
    R2_NOTMASKPEN,	/* DnandS */
    R2_MASKPEN,	/* DandS */
    R2_NOTXORPEN,	/* DxnorS */
    R2_NOP,		/* D */			/* = no-op */
    R2_MERGENOTPEN,	/* DornotS */
    R2_COPYPEN,	/* S */
    R2_MERGEPENNOT,	/* notDorS */
    R2_MERGEPEN,	/* DorS */
    R2_WHITE	/* Ones */
};

/*
 *  Some clipping functions.
 */
rect getcliprect(void)
{
    RECT R;
    rect r;

    GetClipBox(dc, &R);
    r.x = R.left;
    r.y = R.top;
    r.width = R.right - R.left;
    r.height = R.bottom - R.top;
    return r;
}

void setcliprect(rect r)
{
    HRGN rgn;

    rgn = CreateRectRgn(r.x, r.y, r.x+r.width, r.y+r.height);
    SelectClipRgn(dc, rgn);
    DeleteObject(rgn);
}

/*
 *  Ensure that drawing is possible by creating DCs and windows
 *  as necessary.
 */
PROTECTED
window simple_window(void)
{
    window w;
    w = newwindow("Graphics", rect(0,0,0,0), StandardWindow);
    show(w);
    return w;
}

/*
 *  Fix brush origins.
 */
PROTECTED
void fix_brush(HDC dc, drawing obj, HBRUSH brush)
{
    POINT p;
    HWND hwnd;
    drawing parent;

    parent = parentwindow(obj);
    if (! parent)
	return;
    hwnd = parent->handle;
    p.x = p.y = 0;
    ClientToScreen(hwnd, &p);
    if (brush)
	UnrealizeObject(brush);
#if (WINVER <= 0x030a)
    /* Microsoft keeps changing which functions they include in GDI.DLL */
    /* But this function should work on systems before Win 95 */
    SetBrushOrg(dc, p.x, p.y);
#else
    /* And this function should work on Win 95 and NT */
    SetBrushOrgEx(dc, p.x, p.y, &p);
#endif
}

/*
 *  All drawing functions call enabledrawing.
 */
static void enable_drawing(void)
{
    if (! current->dest) {
	if (! current_window)
	    current_window = simple_window();
	show(current_window);
	drawto(current_window);
    }
    if (! dc)
	dc = get_context(current->dest);

    fix_brush(dc, current->dest, the_brush);
}

/*
 *  Drawing functions begin here.
 */
void bitblt(bitmap db, bitmap sb, point p, rect r, int mode)
{
    HDC src;
    HDC dst;

    dst = get_context((object)db);
    src = get_context((object)sb);

    BitBlt(dst, p.x, p.y, r.width, r.height, src, r.x, r.y,
	   copy_mode[mode&0x0F]);
}

void scrollrect(point dp, rect r)
{
    rect cliprect;
    if (current->dest) cliprect = getrect(current->dest);
    else return;

    enable_drawing();
    ScrollDC(dc, dp.x-r.x, dp.y-r.y,
	     rect2RECT(&r), rect2RECT(&cliprect), 0, NULL);
}

void copyrect(bitmap sb, point p, rect r)
{
    enable_drawing();
    if (current->dest) bitblt(current->dest, sb, p, r, GA_S);
}

void texturerect(bitmap sb, rect dr)
{
    long x, y, sw, sh, sdx, sdy;
    long right, bottom;
    rect sr,r;

    enable_drawing();
    sr = getrect(sb);
    sw = sr.width;
    sh = sr.height;
    right = dr.x + dr.width;
    bottom = dr.y + dr.height;

    for (y = dr.y; y <= bottom; y += sh)
	for (x = dr.x; x <= right; x += sw) {

	    /* reduce size of source rectangle for clipping */
	    if (x+sw > right)	sdx = right - x;
	    else			sdx = sw;
	    if (y+sh > bottom)	sdy = bottom - y;
	    else			sdy = sh;

	    r = rect(sr.x, sr.y, sdx, sdy);
	    copyrect(sb, pt(x,y), r);
	}
}

void invertrect(rect r)
{
    enable_drawing();
    PatBlt(dc, r.x, r.y, r.width, r.height, DSTINVERT);
}

rgb getpixel(point p)
{
    rgb c;

    enable_drawing();
    c = GetPixel(dc, p.x, p.y);
    c = ((c&0x000000FFL)<<16) | (c&0x0000FF00L) |
	((c&0x00FF0000L)>>16);
    return c;
}

void setpixel(point p, rgb c)
{
    rgb old = current->hue;

    enable_drawing();
    setrgb(c);
    SelectObject(dc, the_brush);
    PatBlt(dc, p.x, p.y, 1, 1, pat_mode[current->mode]);
    SelectObject(dc, GetStockObject(NULL_BRUSH));
    setrgb(old);
}

void moveto(point p)
{
    current->p = p;
}

void lineto(point p)
{
    drawline(current->p, p);
    moveto(p);
}

void drawpoint(point p)
{
    setpixel(p, current->hue);
}

void drawline(point p1, point p2)
{
    if ((p1.x == p2.x) && (p1.y == p2.y))
	return; /* same point so draw nothing */

    enable_drawing();
    SelectObject(dc, the_pen);
    SetROP2(dc, pen_mode[current->mode]);
    MoveToEx(dc, p1.x, p1.y, NULL);
    LineTo(dc, p2.x, p2.y);
    SelectObject(dc, GetStockObject(NULL_PEN));
}

void drawrect(rect r)
{
    enable_drawing();
    SelectObject(dc, the_pen);
    SetROP2(dc, pen_mode[current->mode]);
    Rectangle(dc, r.x, r.y, r.x+r.width, r.y+r.height);
    SelectObject(dc, GetStockObject(NULL_PEN));
}

void fillrect(rect r)
{
    enable_drawing();
    SelectObject(dc, the_brush);
    PatBlt(dc, r.x, r.y, r.width, r.height, pat_mode[current->mode]);
    SelectObject(dc, GetStockObject(NULL_BRUSH));
}

#define deg2rad(deg) ((deg)*2*Pi/360)

void drawarc(rect r, int start_angle, int end_angle)
{
    point p0, p1, p2;
    double start, end;
    int fudge;

    enable_drawing();

    if (start_angle == end_angle)
	return;
    if (((end_angle - start_angle) % 360) == 0) {
	drawarc(r, 0, 180);
	drawarc(r, 180, 360);
	return;
    }

    start = deg2rad(start_angle);
    end = deg2rad(end_angle);
    p0 = midpt(topleft(r), bottomright(r));
    p1.x = p0.x + (int)(cos(start) * 512); /* arbitrary hypotenuse */
    p1.y = p0.y - (int)(sin(start) * 512);
    p2.x = p0.x + (int)(cos(end) * 512);
    p2.y = p0.y - (int)(sin(end) * 512);

    SelectObject(dc, the_pen);
    SetROP2(dc, pen_mode[current->mode]);
    if (current->linewidth % 2) /* Ask Bill Gates why we need this */
	fudge = 0;
    else
	fudge = 1;
    Arc(dc, r.x, r.y, r.x+r.width+fudge, r.y+r.height+fudge,
	p1.x, p1.y, p2.x, p2.y);
    SelectObject(dc, GetStockObject(NULL_PEN));
}

void fillarc(rect r, int start_angle, int end_angle)
{
    point p0, p1, p2;
    double start, end;

    enable_drawing();

    if (start_angle == end_angle)
	return;
    if (((end_angle - start_angle) % 360) == 0) {
	fillellipse(r);
	return;
    }

    start = deg2rad(start_angle);
    end = deg2rad(end_angle);
    p0 = midpt(topleft(r), bottomright(r));
    p1.x = p0.x + (int)(cos(start) * 512); /* arbitrary hypotenuse */
    p1.y = p0.y - (int)(sin(start) * 512);
    p2.x = p0.x + (int)(cos(end) * 512);
    p2.y = p0.y - (int)(sin(end) * 512);

    SelectObject(dc, the_brush);
    SetROP2(dc, pen_mode[current->mode]);
    Pie(dc, r.x, r.y, r.x+r.width+1, r.y+r.height+1,
	p1.x, p1.y, p2.x, p2.y);
    SelectObject(dc, GetStockObject(NULL_BRUSH));
}

void drawellipse(rect r)
{
    enable_drawing();
    SelectObject(dc, the_pen);
    SetROP2(dc, pen_mode[current->mode]);
    Ellipse(dc, r.x, r.y, r.x+r.width, r.y+r.height);
    SelectObject(dc, GetStockObject(NULL_PEN));
}

/*
 *  The old fillellipse function.
 *
 *  This function used the inbuilt Windows Ellipse function,
 *  which is not symmetric and also produces strange results
 *  at low sizes (the 'ellipses' look egg-shaped).
 */
void oldfillellipse(rect r)
{
    enable_drawing();
    SelectObject(dc, the_brush);
    SetROP2(dc, pen_mode[current->mode]);
    Ellipse(dc, r.x, r.y, r.x+r.width+1, r.y+r.height+1);
    SelectObject(dc, GetStockObject(NULL_BRUSH));
}

/*
 *  Fill an ellipse using a stored rectangle algorithm.
 *
 *  This algorithm is horizontally and vertically symmetrical,
 *  unlike the inbuilt Windows 3.1 algorithm, and also
 *  produces better looking ellipses at small sizes.
 */
#define fastfillrect(x,y,w,h) PatBlt(dc,(x),(y),(w),(h),mode)

void fillellipse(rect r)
{			/* e(x,y) = b*b*x*x + a*a*y*y - a*a*b*b */
    register long mode = pat_mode[current->mode];

    int w_odd = (r.width & 0x0001);
    int h_odd = (r.height & 0x0001);
    int a = r.width >> 1;
    int b = r.height >> 1;
    point c = pt(r.x+a,r.y+b);
    int x = 0;
    int y = b;
    long a2 = a*a;
    long b2 = b*b;
    long xcrit = ((a2+a2+a2) >> 2) + 1;
    long ycrit = ((b2+b2+b2) >> 2) + 1;
    long t = b2 + a2 - (a2+a2)*b;	/* t = e(x+1,y-1) */
    long dxt = b2*(3+x+x);
    long dyt = a2*(3-y-y);
    int d2xt = b2+b2;
    int d2yt = a2+a2;
    int stored = 0;
    int sx = 0, sy = 0, sh = 0; /* stored values of x, y, height */

    if ((r.width > 31) && (r.height > 31)) {
	oldfillellipse(r);
	return;
    }
    if ((r.width < 3) || (r.height < 3)) {
	fillrect(r);
	return;
    }

    enable_drawing();
    SelectObject(dc, the_brush);

    if (w_odd == 0) {
	fastfillrect(c.x-1,c.y-b,2,r.height);
    }

    while (y > 0) {

	if (stored) {
	    if (sx != x) { /* output stored rect */
		fastfillrect(c.x-sx,c.y-sy,
			     sx+sx+w_odd,sh);
		fastfillrect(c.x-sx,c.y+sy+h_odd-sh,
			     sx+sx+w_odd,sh);
		stored = 0;
	    }
	    else /* increment height of stored rect */
		sh++;
	}

	if (t + a2*y < xcrit) { /* e(x+1,y-1/2) <= 0 */
	    /* move left and right to encounter edge */
	    x += 1;
	    t += dxt;
	    dxt += d2xt;
	} else if (t - b2*x >= ycrit) { /* e(x+1/2,y-1) > 0 */
	    /* drop down one line */
	    if (!stored) {
		sx = x;
		sy = y;
		sh = 1;
		stored = 1;
	    }
	    y -= 1;
	    t += dyt;
	    dyt += d2yt;
	} else {
	    /* drop diagonally down and out */
	    if (!stored) {
		sx = x;
		sy = y;
		sh = 1;
		stored = 1;
	    }

	    x += 1;
	    y -= 1;
	    t += dxt + dyt;
	    dxt += d2xt;
	    dyt += d2yt;
	}
    }
    if (stored) { /* output stored rectangle */
	fastfillrect(c.x-sx,c.y-sy,sx+sx+w_odd,sh);
	fastfillrect(c.x-sx,c.y+sy+h_odd-sh,sx+sx+w_odd,sh);
	stored = 0;
    }
    if (x <= a){
	fastfillrect(c.x-a,c.y-y,a+a+w_odd,1);
	fastfillrect(c.x-a,c.y+y-1+h_odd,a+a+w_odd,1);
    }

    SelectObject(dc, GetStockObject(NULL_BRUSH));
}

void drawroundrect(rect r)
{
    int minimum, radius;

    enable_drawing();
    SelectObject(dc, the_pen);
    SetROP2(dc, pen_mode[current->mode]);
    minimum = min(r.width, r.height);
    if ((radius = minimum/2) < 16)
	radius = 16;
    RoundRect(dc, r.x, r.y, r.x+r.width, r.y+r.height,
	      radius, radius);
    SelectObject(dc, GetStockObject(NULL_PEN));
}

void fillroundrect(rect r)
{
    int minimum, radius;

    enable_drawing();
    SelectObject(dc, the_brush);
    SetROP2(dc, pen_mode[current->mode]);
    minimum = min(r.width, r.height);
    if ((radius = minimum/2) < 16)
	radius = 16;
    RoundRect(dc, r.x, r.y, r.x+r.width+1, r.y+r.height+1,
	      radius, radius);
    SelectObject(dc, GetStockObject(NULL_BRUSH));
}

void drawpolygon(point *p, int n)
{
    enable_drawing();
    SelectObject(dc, the_pen);
    SetROP2(dc, pen_mode[current->mode]);
    Polyline(dc, (POINT FAR *) p, n);
    SelectObject(dc, GetStockObject(NULL_PEN));
}

void fillpolygon(point *p, int n)
{
    enable_drawing();
    SelectObject(dc, the_brush);
    SetROP2(dc, pen_mode[current->mode]);
    Polygon(dc, (POINT FAR *) p, n);
    SelectObject(dc, GetStockObject(NULL_BRUSH));
}

/*
 *  String drawing functions.
 */

/*
 *  Drawstr returns the width of the string drawn.
 */
int drawstr(point p, const char *s)
{
    POINT curr_pos;
    int width;
    HFONT old;

    enable_drawing();
    SetTextColor(dc, win_rgb); /* set colour */
    if (! current->fnt)
	current->fnt = SystemFont;
    old = SelectObject(dc, current->fnt->handle);
    MoveToEx(dc, p.x, p.y, NULL);
    SetBkMode(dc, TRANSPARENT);
    SetTextAlign(dc, TA_LEFT | TA_UPDATECP);

    TextOut(dc, p.x, p.y, s, strlen(s));

    GetCurrentPositionEx(dc, &curr_pos);
    width = curr_pos.x - p.x;
    SelectObject(dc, old);
    /* always leave a DC with no real font selected */

    return width;
}

rect strrect(font f, const char *s)
{
    SIZE size;
    long h;
    HFONT old;
    HDC dc;

    if (! f)
	f = SystemFont;

    h = getheight(f);

    dc = GetDC(0); /* get screen dc */
    old = SelectObject(dc, f->handle);
    GetTextExtentPoint(dc, (LPSTR) s, strlen(s), &size);
    SelectObject(dc, old);
    ReleaseDC(0, dc);

    return rect(0,0,size.cx, h);
}

point strsize(font f, const char *s)
{
    rect r = strrect(f,s);
    return pt(r.width, r.height);
}

int strwidth(font f, const char *s)
{
    rect r = strrect(f,s);
    return r.width;
}

/*
 *  Draw an image:
 */
void drawimage(image img, rect dr, rect sr)
{
    bitmap b;
    image i = img;

    if (! img)
	return;
    enable_drawing();
    dr = rcanon(dr);
    if ((dr.width != img->width) || (dr.height != img->height))
	i = scaleimage(img, rect(0,0,dr.width,dr.height), sr);
    b = imagetobitmap(i);
    copyrect(b, pt(dr.x,dr.y), getrect(b));
    del(b);
    if (i != img)
	del(i);
}
