/*
 * GraphApp - Cross-Platform Graphics Programming Library.
 *
 * File: drawtext.c -- cross-platform portable drawing functions.
 * Platform: Neutral  Version: 2.30  Date: 1998/01/01
 *
 * Version: 1.00  Changes: Original version by Lachlan Patrick.
 * Version: 1.50  Changes: gprintf now has internal state.
 * Version: 1.60  Changes: Major bugfixes!
 * Version: 2.00  Changes: Update to version 2.
 * Version: 2.01  Changes: Changed unnecessary longs to ints.
 * Version: 2.20  Changes: Added underlining ability.
 * Version: 2.30  Changes: Now uses getheight, getdescent.
 */

/* 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) 2022  The R Core Team
   
   avoid stack corruption on long lines  
*/

#include "internal.h"
#include <stdarg.h>

#define COMPRESS_WHITESPACE 0

static size_t max_line_size(const char *s)
{
    size_t size = 0;
    size_t i;

    for(i = 0; s[i]; i++) {
	if (s[i] == '\t') size += 8;
	else size++;
    }
    return size + 1;
}

static const char *get_next_line(char *line, int width, const char *s)
{
    const char *t; char  *k;
    int	word_width, line_width;
    int	sp, tab, nl;

    line_width  = 0;

    for (t=s, k=line; *t!='\0'; line=k, s=t)
    {
	for (nl=tab=sp=0; (*t!='\0') && isspace(*t) && !nl; t++)
	{
#if COMPRESS_WHITESPACE
	    if (*t == '\n') nl++;
	    else if (*t == '\t') tab++;
	    else sp++;
#else
	    if (*t == '\n') nl++;
	    else if (*t == '\t') { /* expand tabs */
		for (tab=4; tab; tab--)
		    *k++ = ' ';
	    }
	    else
		*k++ = *t;
#endif
	}

	s = t;

	if (nl)	{ *k++ = '\n'; break; }
	else if (tab)	{ *k++ = ' '; *k++ = ' '; }
	else if (sp)	{ *k++ = ' ';  }

	/* fetch the next word to draw */

	for (*k='\0'; (*t!='\0') && !isspace(*t); *k='\0') {
	    if (*t == '-') {
		*k++ = *t++;
		*k = '\0';
		break;
	    }
	    *k++ = *t++;
	}

	/* determine where the word should be drawn */

	while (((word_width = strwidth(current->fnt,line)) > width)
	       && (k>line))
	{
	    *(--k) = '\0';
	    --t;
	}

	if (word_width > (width - line_width)) {
	    k = line;
	    break;
	} else
	    line_width += word_width;
    }

    *k = '\0';

    return (*s == '\0') ? NULL : s;
}

int textheight(int width, const char *s)
{
    int y;
    size_t size;
    int height;
    char *line;

    size = max_line_size(s);
    line = (char *)malloc(size * sizeof(char));
    height = getheight(current->fnt);

    for(y=0; s; y+=height)
    {
	s = get_next_line(line, width, s);
    }

    free(line);
    return y;
}

static const char *draw_text_left(char *line, rect r, int line_height,
				  int underline, const char *s)
{
    char *k;
    point p;
    int width, height;
    font f;

    f = current->fnt;

    for(p=pt(r.x,r.y); (p.y<=r.y+r.height) && (s); p.y+=line_height)
    {
	s = get_next_line(line, r.width, s);

	for (k=line; *k!='\0'; k++)
	    continue;
	for (--k; (k>=line) && isspace(*k); k--)
	    *k = '\0';

	drawstr(p, line);

	if (underline) {
	    width = strwidth(f, line);
	    height = p.y+getheight(f)-getdescent(f)+2;
	    drawline(pt(p.x+1, height), pt(p.x+width-1, height));
	}
    }

    return s;
}

static const char *draw_text_right(char *line, rect r, int line_height,
			     int underline, const char *s)
{
    char *k;
    int w;
    point p;
    int width, height;
    font f;

    f = current->fnt;

    for(p=pt(r.x,r.y); (p.y<=r.y+r.height) && (s); p.y+=line_height)
    {
	s = get_next_line(line, r.width, s);

	for (k=line; *k!='\0'; k++)
	    continue;
	for (--k; (k>=line) && isspace(*k); k--)
	    *k = '\0';
	for (k=line; (*k!='\0') && isspace(*k); k++)
	    continue;

	w = strwidth(current->fnt, k);
	p.x = r.x+r.width - w;

	drawstr(p, k);

	if (underline) {
	    width = strwidth(f, k);
	    height = p.y+getheight(f)-getdescent(f)+2;
	    drawline(pt(p.x+1, height), pt(p.x+width-1, height));
	}
    }

    return s;
}

static const char *draw_text_centered(char *line, rect r, int line_height,
				      int underline, const char *s)
{
    char *k;
    int w;
    point p;
    int width, height;
    font f;

    f = current->fnt;

    for(p=pt(r.x,r.y); (p.y<=r.y+r.height) && (s); p.y+=line_height)
    {
	s = get_next_line(line, r.width, s);

	for (k=line; *k!='\0'; k++)
	    continue;
	for (--k; (k>=line) && isspace(*k); k--)
	    *k = '\0';
	for(k=line; (*k!='\0') && isspace(*k); k++)
	    continue;

	w = strwidth(current->fnt, k);
	p.x = r.x + (r.width-w)/2;

	drawstr(p, k);

	if (underline) {
	    width = strwidth(f, k);
	    height = p.y+getheight(f)-getdescent(f)+2;
	    drawline(pt(p.x+1, height), pt(p.x+width-1, height));
	}
    }

    return s;
}

static const char *draw_text_justified(char *line, rect r, int line_height,
				       int underline, const char *s)
{
    char *j, *k;
    int w, xw, nl, sc, sw, space_width;
    point p;
    int width, height;
    font f;

    space_width = strwidth(current->fnt, " ");
    f = current->fnt;

    for(p=pt(r.x,r.y); (p.y<=r.y+r.height) && (s); p.y+=line_height)
    {
	s = get_next_line(line, r.width, s);

	p.x = r.x;

	for(j=line; (*j!='\0') && isspace(*j); j++)
	    p.x += space_width;
	for (sc=0, k=j; *k!='\0'; k++)
	    if (isspace(*k))
		sc++;
	for (nl=0, --k; (k>=j) && isspace(*k); k--) {
	    if (*k == '\n')
		nl++;
	    *k = '\0';
	    sc--;
	}

	if ((sc==0) || nl || (! s)) {
	    drawstr(p, j);
	    width = strwidth(f, j);
	}
	else {
	    w = strwidth(f, j);
	    sw = space_width + (r.x+r.width-p.x-w)/sc;
	    xw = (r.x+r.width-p.x-w)%sc;

	    for(j=strtok(j," "); j; j=strtok(NULL," "))
	    {
		drawstr(p, j);
		p.x += sw + strwidth(f, j);
		if (xw) {
		    p.x++;
		    xw--;
		}
	    }
	    width = r.width;
	}
	if (underline) {
	    height = p.y+getheight(f)-getdescent(f)+2;
	    drawline(pt(p.x+1, height), pt(p.x+width-1, height));
	}
    }

    return s;
}

const char *drawtext(rect r, int alignment, const char *s)
{
    int h;
    int nlines;
    int line_height;
    int u = 0;
    rect clip;
    const char *remains;
    char *line;
    size_t size;


    initapp(0,0);
    if (! s) return (char *) NULL;
    size = max_line_size(s);
    line = (char *)malloc(size * sizeof(char));

    if (! current->fnt)
	current->fnt = SystemFont;

    clip = getcliprect();
    setcliprect(r);

    line_height = getheight(current->fnt);

    if ((alignment & VCenter) == VCenter) {
	h = textheight(r.width, s);
	if (h < r.height)
	    r.y += (r.height-h)/2;
    }
    else if ((alignment & VJustify) == VJustify) {
	h = textheight(r.width, s);
	if (h < r.height) {
	    nlines = h / line_height;
	    if (nlines > 1)
		line_height += ((r.height-h) / (nlines-1));
	}
    }
    else if ((alignment & AlignBottom) == AlignBottom) {
	h = textheight(r.width, s);
	if (h < r.height)
	    r.y += (r.height-h);
    }

    u = (alignment & Underline);

    if ((alignment & Center) == Center)
	remains = draw_text_centered(line, r, line_height, u, s);
    else if ((alignment & Justify) == Justify)
	remains = draw_text_justified(line, r, line_height, u, s);
    else if ((alignment & AlignRight) == AlignRight)
	remains = draw_text_right(line, r, line_height, u, s);
    else
	remains = draw_text_left(line, r, line_height, u, s);

    setcliprect(clip);
    free(line);
    return remains;
}

int gprintf(const char *fmt, ...)
{
    static point p = {0,0};
    int count;
    int line_height;
    char *s, *t;
    va_list argptr;
    char str[256];

    va_start(argptr, fmt);
    count = vsprintf(str, fmt, argptr);

    initapp(0,0);
    if (! current->fnt)
	current->fnt = SystemFont;
    line_height = getheight(current->fnt);

    for (s=t=str; *s!='\0'; t++) {
	if (current->p.y != p.y) {
	    /* typewriter ping! */
	    p = current->p;
	}
	if (*t == '\n') {
	    /* print everything from s to t and move point down */
	    *t = '\0';
	    drawstr(p, s);
	    current->p.y += line_height;
	    /* go past the substring just printed */
	    s = t+1;
	}
	else if (*t == '\0') {
	    /* print final string without newline */
	    p.x += drawstr(p, s);
	    /* go to end of string, signal termination */
	    s = t;
	}
    }

    va_end(argptr);

    return count;
}
