/* Hello, Emacs, this is -*-C-*-
 * $Id: dumb.trm,v 1.32 2011/04/18 19:56:53 broeker Exp $
 *
 */

/* GNUPLOT - dumb.trm */

/*[
 * Copyright 1991 - 1993, 1998, 2004   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

/*
 * This file is included by ../term.c.
 *
 * This terminal driver supports:
 *   DUMB terminals
 *
 * AUTHORS
 *   Francois Pinard, 91-04-03
 *           INTERNET: pinard@iro.umontreal.ca
 *
 *   Ethan A Merritt Nov 2003
 *	Added support for enhanced text mode.
 *	Yes, this is frivolous, but it serves as an example for
 *	adding enhanced text to other terminals.  You can disable
 *	it by adding a line
 *	#define NO_DUMB_ENHANCED_SUPPORT
 *
 * send your comments or suggestions to (gnuplot-info@lists.sourceforge.net).
 *
 */
#include "driver.h"

#ifdef TERM_REGISTER
register_term(dumb_driver)
#endif

#ifdef TERM_PROTO
TERM_PUBLIC void DUMB_options __PROTO((void));
TERM_PUBLIC void DUMB_init __PROTO((void));
TERM_PUBLIC void DUMB_graphics __PROTO((void));
TERM_PUBLIC void DUMB_text __PROTO((void));
TERM_PUBLIC void DUMB_reset __PROTO((void));
TERM_PUBLIC void DUMB_linetype __PROTO((int linetype));
TERM_PUBLIC void DUMB_move __PROTO((unsigned int x, unsigned int y));
TERM_PUBLIC void DUMB_point __PROTO((unsigned int x, unsigned int y,
				     int point));
TERM_PUBLIC void DUMB_vector __PROTO((unsigned int x, unsigned int y));
TERM_PUBLIC void DUMB_put_text __PROTO((unsigned int x, unsigned int y,
					const char *str));
TERM_PUBLIC void DUMB_arrow __PROTO((unsigned int sx, unsigned int sy,
				     unsigned int ex, unsigned int ey,
				     int head));

#ifndef NO_DUMB_ENHANCED_SUPPORT
/* To support "set term dumb enhanced" (don't ask why!) */
TERM_PUBLIC void ENHdumb_put_text __PROTO((unsigned int x, unsigned int y, const char str[]));
TERM_PUBLIC void ENHdumb_OPEN __PROTO((char * fontname, double fontsize,
	                            double base, TBOOLEAN widthflag, TBOOLEAN showflag,
				    int overprint));
TERM_PUBLIC void ENHdumb_FLUSH __PROTO((void));
#else
#define ENHdumb_put_text NULL
#endif


#define DUMB_XMAX 79
#define DUMB_YMAX 24

#endif /* TERM_PROTO */

#ifdef TERM_BODY

#define DUMB_AXIS_CONST '\1'
#define DUMB_BORDER_CONST '\2'

/* matrix of characters */
static char *dumb_matrix = NULL;
/* matrix of priority at each position */
static char *dumb_priority = NULL;
/* current character used to draw */
static char dumb_pen;
/* current X position */
static int dumb_x;
/* current Y position */
static int dumb_y;
static int dumb_xmax = DUMB_XMAX;
static int dumb_ymax = DUMB_YMAX;
static int dumb_feed = 1;

#define DUMB_PIXEL(x,y) dumb_matrix[dumb_xmax*(y)+(x)]

static void dumb_set_pixel __PROTO((int x, int y, int v, int p));

enum DUMB_id { DUMB_FEED, DUMB_NOFEED, DUMB_ENH, DUMB_NOENH, DUMB_SIZE, DUMB_OTHER };

static struct gen_table DUMB_opts[] =
{
    { "f$eed", DUMB_FEED },
    { "nof$eed", DUMB_NOFEED },
    { "enh$anced", DUMB_ENH },
    { "noe$nhanced", DUMB_NOENH },
    { "size", DUMB_SIZE },
    { NULL, DUMB_OTHER }
};

TERM_PUBLIC void
DUMB_options()
{
    int x, y;

    while (!END_OF_COMMAND) {
	switch(lookup_table(&DUMB_opts[0],c_token)) {
	case DUMB_FEED:
	    c_token++;
	    dumb_feed = 1;
	    break;
	case DUMB_NOFEED:
	    c_token++;
	    dumb_feed = 0;
	    break;
#ifndef NO_DUMB_ENHANCED_SUPPORT
	case DUMB_ENH:
	    c_token++;
	    term->put_text = ENHdumb_put_text;
	    term->flags |= TERM_ENHANCED_TEXT;
	    break;
	case DUMB_NOENH:
	    c_token++;
	    term->put_text = DUMB_put_text;
	    term->flags &= ~TERM_ENHANCED_TEXT;
	    break;
#endif
	case DUMB_SIZE:
	    c_token++;
	    /* Fall through */

	case DUMB_OTHER:
	default:
	    x = int_expression();
	    if (!END_OF_COMMAND) {
		if (equals(c_token,","))
		    c_token++;
		y = int_expression();
		dumb_xmax = term->xmax = x;
		dumb_ymax = term->ymax = y;
	    }
 	    break;
	}
    }

    sprintf(term_options, "%sfeed %s size %d, %d",
	    dumb_feed ? "" : "no",
	    term->put_text == ENHdumb_put_text ? "enhanced" : "",
	    dumb_xmax, dumb_ymax);
}


static void
dumb_set_pixel(int x, int y, int v, int p)
{
    if ((unsigned int) x <= dumb_xmax &&	/* ie x>=0 && x<=dumb_xmax */
	(unsigned int) y <= dumb_ymax &&
	p > dumb_priority[dumb_xmax * y + x]) {
	dumb_matrix[dumb_xmax * y + x] = v;
	dumb_priority[dumb_xmax * y + x] = p;
    }
}


TERM_PUBLIC void
DUMB_init()
{
    if (dumb_matrix)
	free(dumb_matrix);

    dumb_matrix = gp_alloc((dumb_xmax+1) * (dumb_ymax+1) * 2, "dumb terminal");

    dumb_priority = dumb_matrix + dumb_xmax * dumb_ymax;
}


TERM_PUBLIC void
DUMB_graphics()
{
    int i;
    char *pm = dumb_matrix, *pp = dumb_priority;

    for (i = dumb_xmax * dumb_ymax; i > 0; i--) {
	*pm++ = ' ';
	*pp++ = 0;
    }
}


TERM_PUBLIC void
DUMB_text()
{
    int x, y, l;

    putc('\f', gpoutfile);
    for (y = dumb_ymax - 1; y >= 0; y--) {
	for (l = dumb_xmax; l > 0 && DUMB_PIXEL(l - 1, y) == ' '; l--);
	for (x = 0; x < l; x++)
	    putc(DUMB_PIXEL(x, y), gpoutfile);
	if (dumb_feed || y > 0)
	    putc('\n', gpoutfile);
    }
    fflush(gpoutfile);
}


TERM_PUBLIC void
DUMB_reset()
{
    if (dumb_matrix)
	free(dumb_matrix);
    dumb_matrix = NULL;
}


TERM_PUBLIC void
DUMB_linetype(int linetype)
{
    static char pen_type[7] = { '*', '#', '$', '%', '@', '&', '=' };

    if (linetype == LT_BLACK)
	dumb_pen = DUMB_BORDER_CONST;
    else if (linetype == LT_AXIS)
	dumb_pen = DUMB_AXIS_CONST;
    else if (linetype <= LT_NODRAW)
	dumb_pen = ' ';
    else {
	linetype = linetype % 7;
	dumb_pen = pen_type[linetype];
    }
}


TERM_PUBLIC void
DUMB_move(unsigned int x, unsigned int y)
{
    dumb_x = x;
    dumb_y = y;
}


TERM_PUBLIC void
DUMB_point(unsigned int x, unsigned int y, int point)
{
    dumb_set_pixel(x, y, point == -1 ? '.' : point % 26 + 'A', 4);
}


TERM_PUBLIC void
DUMB_vector(unsigned int arg_x, unsigned int arg_y)
{
    int x = arg_x;		/* we need signed int, since
				 * unsigned-signed=unsigned and */
    int y = arg_y;		/* abs and cast to double wouldn't work */
    char pen, pen1;
    int priority;
    int delta;

    if (ABS(y - dumb_y) > ABS(x - dumb_x)) {
	switch (dumb_pen) {
	case DUMB_AXIS_CONST:
	    pen = ':';
	    pen1 = '+';
	    priority = 1;
	    break;

	case DUMB_BORDER_CONST:
	    pen = '|';
	    pen1 = '+';
	    priority = 2;
	    break;

	default:
	    pen = dumb_pen;
	    pen1 = dumb_pen;
	    priority = 3;
	    break;
	}
	dumb_set_pixel(dumb_x, dumb_y, pen1, priority);
	for (delta = 1; delta < ABS(y - dumb_y); delta++) {
	    dumb_set_pixel(dumb_x  + (int) ((double) (x - dumb_x) *
					    delta / ABS(y - dumb_y) + 0.5),
			   dumb_y + delta * sign(y - dumb_y), pen, priority);
	}
	dumb_set_pixel(x, y, pen1, priority);
    } else if (ABS(x - dumb_x) > ABS(y - dumb_y)) {
	switch (dumb_pen) {
	case DUMB_AXIS_CONST:
	    pen = '.';
	    pen1 = '+';
	    priority = 1;
	    break;

	case DUMB_BORDER_CONST:
	    pen = '-';
	    pen1 = '+';
	    priority = 2;
	    break;

	default:
	    pen = dumb_pen;
	    pen1 = dumb_pen;
	    priority = 3;
	    break;
	}
	dumb_set_pixel(dumb_x, dumb_y, pen1, priority);
	for (delta = 1; delta < ABS(x - dumb_x); delta++)
	    dumb_set_pixel(dumb_x + delta * sign(x - dumb_x),
			   dumb_y +
			   (int) ((double) (y - dumb_y) * delta / ABS(x - dumb_x)
				  + 0.5),
			   pen, priority);
	dumb_set_pixel(x, y, pen1, priority);
    } else {
	switch (dumb_pen) {
	case DUMB_AXIS_CONST:	/* zero length axis */
	    pen = '+';
	    priority = 1;
	    break;

	case DUMB_BORDER_CONST:	/* zero length border */
	    pen = '+';
	    priority = 2;
	    break;

	default:
	    pen = dumb_pen;
	    priority = 3;
	    break;
	}
	for (delta = 0; delta <= ABS(x - dumb_x); delta++)
	    dumb_set_pixel(dumb_x + delta * sign(x - dumb_x),
			   dumb_y + delta * sign(y - dumb_y),
			   pen, priority);
    }
    dumb_x = x;
    dumb_y = y;
}


TERM_PUBLIC void
DUMB_put_text(unsigned int x, unsigned int y, const char *str)
{
    int length;

    length = strlen(str);
    if (x + length > dumb_xmax)
	x = GPMAX(0, dumb_xmax - length);

    for (; x < dumb_xmax && *str; x++, str++)
	dumb_set_pixel(x, y, *str, 5);
}


TERM_PUBLIC void
DUMB_arrow(
    unsigned int sx, unsigned int sy,
    unsigned int ex, unsigned int ey,
    int head)		/* ignored */
{
    char saved_pen;
    char saved_x;
    char saved_y;

    (void) head;		/* dummy usage avoid compiler warnings */
    saved_pen = dumb_pen;
    saved_x = dumb_x;
    saved_y = dumb_y;

    dumb_pen = '>';
    dumb_x = sx;
    dumb_y = sy;
    DUMB_vector(ex, ey);

    dumb_pen = saved_pen;
    dumb_x = saved_x;
    dumb_y = saved_y;
}


#ifndef NO_DUMB_ENHANCED_SUPPORT
/*
 * The code from here on serves as an example of how to
 * add enhanced text mode support to even a dumb driver.
 */

static TBOOLEAN ENHdumb_opened_string;
static TBOOLEAN ENHdumb_show = TRUE;
static int ENHdumb_overprint = 0;
static TBOOLEAN ENHdumb_widthflag = TRUE;
static unsigned int ENHdumb_xsave, ENHdumb_ysave;
#define ENHdumb_fontsize 1
#define ENHdumb_font ""
static double ENHdumb_base;

TERM_PUBLIC void
ENHdumb_OPEN(
    char *fontname,
    double fontsize, double base,
    TBOOLEAN widthflag, TBOOLEAN showflag,
    int overprint)
{
    /* There are two special cases:
     * overprint = 3 means save current position
     * overprint = 4 means restore saved position
     */
    if (overprint == 3) {
	ENHdumb_xsave = dumb_x;
	ENHdumb_ysave = dumb_y;
	return;
    } else if (overprint == 4) {
	DUMB_move(ENHdumb_xsave, ENHdumb_ysave);
	return;
    }


    if (!ENHdumb_opened_string) {
	ENHdumb_opened_string = TRUE;
	/* Start new text fragment */
	    enhanced_cur_text = &enhanced_text[0];
	/* Scale fractional font height to vertical units of display */
	    ENHdumb_base = base * 2;
	/* Keep track of whether we are supposed to show this string */
	    ENHdumb_show = showflag;
	/* 0/1/2  no overprint / 1st pass / 2nd pass */
	    ENHdumb_overprint = overprint;
	/* widthflag FALSE means do not update text position after printing */
	    ENHdumb_widthflag = widthflag;
	/* Many drivers will need to do something about font selection here */
	    /* but dumb is dumb */
    }
}

TERM_PUBLIC void
ENHdumb_FLUSH()
{
    char *str = enhanced_text;	/* The fragment to print */
    int x = dumb_x;		/* The current position  */
    int len;

    if (ENHdumb_opened_string) {
	*enhanced_cur_text = '\0';
	len = strlen(str);

	/* print the string fragment, perhaps invisibly */
	/* NB: base expresses offset from current y pos */
	for (; x < dumb_xmax && *str; x++, str++)
	    if (ENHdumb_show)
        	dumb_set_pixel(x, dumb_y+ENHdumb_base, *str, 5);

	if (!ENHdumb_widthflag)
	    /* don't update position */
	    ;
	else if (ENHdumb_overprint == 1)
	    /* First pass of overprint, leave position in center of fragment */
	    dumb_x += len / 2;
	else
	    /* Normal case is to update position to end of fragment */
	    dumb_x += len;

	ENHdumb_opened_string = FALSE;
    }
}

TERM_PUBLIC void
ENHdumb_put_text(unsigned int x, unsigned int y, const char *str)
{
    /* If no enhanced text processing is needed, we can use the plain  */
    /* vanilla put_text() routine instead of this fancy recursive one. */
    if (ignore_enhanced_text || !strpbrk(str, "{}^_@&~")) {
	DUMB_put_text(x,y,str);
	return;
    }

    /* Set up global variables needed by enhanced_recursion() */
    enhanced_fontscale = 1.0;
    ENHdumb_opened_string = FALSE;
    strncpy(enhanced_escape_format,"%c",sizeof(enhanced_escape_format));

    DUMB_move(x,y);

    /* Set the recursion going. We say to keep going until a
     * closing brace, but we don't really expect to find one.
     * If the return value is not the nul-terminator of the
     * string, that can only mean that we did find an unmatched
     * closing brace in the string. We increment past it (else
     * we get stuck in an infinite loop) and try again.
     */
    while (*(str = enhanced_recursion((char *)str, TRUE,
    			ENHdumb_font, ENHdumb_fontsize,
			0.0, TRUE, TRUE, 0))) {
	(term->enhanced_flush)();

	/* I think we can only get here if *str == '}' */
	    enh_err_check(str);

	if (!*++str)
	    break; /* end of string */

	/* else carry on and process the rest of the string */
    }
}
#endif /* NO_DUMB_ENHANCED_SUPPORT */

#endif /* TERM_BODY */

#ifdef TERM_TABLE
TERM_TABLE_START(dumb_driver)
    "dumb", "ascii art for anything that prints text",
    DUMB_XMAX, DUMB_YMAX, 1, 1,
    1, 1, DUMB_options, DUMB_init, DUMB_reset,
    DUMB_text, null_scale, DUMB_graphics, DUMB_move, DUMB_vector,
    DUMB_linetype, DUMB_put_text, null_text_angle,
    null_justify_text, DUMB_point, DUMB_arrow, set_font_null,
    0,				/* pointsize */
    TERM_CAN_MULTIPLOT,
    NULL, NULL, NULL, NULL
#ifdef USE_MOUSE
    , NULL, NULL, NULL, NULL, NULL
#endif
    , NULL, NULL, NULL, NULL
    , NULL /* image */
#ifndef NO_DUMB_ENHANCED_SUPPORT
    , ENHdumb_OPEN, ENHdumb_FLUSH, do_enh_writec
#endif /* NO_DUMB_ENHANCED_SUPPORT */
TERM_TABLE_END(dumb_driver)

#undef LAST_TERM
#define LAST_TERM dumb_driver

#endif /* TERM_TABLE */

#ifdef TERM_HELP
START_HELP(dumb)
"1 dumb",
"?commands set terminal dumb",
"?set terminal dumb",
"?set term dumb",
"?terminal dumb",
"?term dumb",
"?dumb",
" The `dumb` terminal driver plots into a text block using ascii characters.",
" It has an optional size specification and a trailing linefeed flag.",
"",
" Syntax:",
"       set terminal dumb {size <xchars>,<ychars>} {[no]feed}",
#ifndef NO_DUMB_ENHANCED_SUPPORT
"                         {[no]enhanced}",
#endif
"",
" where <xchars> and <ychars> set the size of the text block. The default is",
" 79 by 24. The last newline is printed only if `feed` is enabled.",
"",
" Example:",
"       set term dumb size 60,15",
"       plot [-5:6.5] sin(x) with impulse",
"",
"            1 +-------------------------------------------------+",
"          0.8 +|||++                   ++||||++   sin(x) +----+ |",
"          0.6 +|||||+                 ++|||||||+                |",
"          0.4 +||||||+               ++|||||||||+               |",
"          0.2 +|||||||+             ++|||||||||||+             +|",
"            0 ++++++++++++++++++++++++++++++++++++++++++++++++++|",
"         -0.2 +        +|||||||||||+              +|||||||||||+ |",
"         -0.4 +         +|||||||||+                +|||||||||+  |",
"         -0.6 +          +|||||||+                  +|||||||+   |",
"         -0.8 +   +       ++||||+   +       +        ++||||+  + |",
"           -1 +---+--------+--------+-------+--------+--------+-+",
"                 -4       -2        0       2        4        6  "
END_HELP(dumb)
#endif /* TERM_HELP */
