
// draw.c -- this is the only file outside the refresh that touches the
// vid buffer

#define TIMECOP

#include "quakedef.h"

typedef struct {
    vrect_t rect;
    int width;
    int height;
    byte *ptexbytes;
    int rowbytes;
} rectdesc_t;

static rectdesc_t r_rectdesc;

byte *draw_chars;		// 8*8 graphic characters
qpic_t *draw_disc;
qpic_t *draw_backtile;

byte fadetable[256];

extern qpic_t *M_CachePic(char *path);

/*
===============
Draw_Init
===============
*/
void Draw_Init(void)
{
    int i;

    draw_chars = W_GetLumpName("conchars");
    draw_disc = W_GetLumpName("disc");
    draw_backtile = W_GetLumpName("backtile");

    r_rectdesc.width = draw_backtile->width;
    r_rectdesc.height = draw_backtile->height;
    r_rectdesc.ptexbytes = draw_backtile->data;
    r_rectdesc.rowbytes = draw_backtile->width;

    for (i = 0; i < 256; i++) {
	fadetable[i] =
	    16 + (host_basepal[i * 3] + host_basepal[i * 3 + 1] +
		  host_basepal[i * 3 + 2]) / (3 * 16);
    }
}



/*
================
Draw_Character

Draws one 8*8 graphics character with 0 being transparent.
It can be clipped to the top of the screen to allow the console to be
smoothly scrolled off.
================
*/
void Draw_Character(int x, int y, int num)
{
#ifdef TIMECOP
    byte *dest;
    byte *source;
    unsigned short *pusdest;
    int drawline;
    int row, col;

    num &= 255;

    if (y <= -8)
	return;			// totally off screen

#ifdef PARANOID
    if (y > vid.height - 8 || x < 0 || x > vid.width - 8)
	Sys_Error("Con_DrawCharacter: (%i, %i)", x, y);
    if (num < 0 || num > 255)
	Sys_Error("Con_DrawCharacter: char %i", num);
#endif

    row = num >> 4;
    col = num & 15;
    source = draw_chars + (row << 10) + (col << 3);

    if (y < 0) {		// clipped
	drawline = 8 + y;
	source -= 128 * y;
	y = 0;
    } else
	drawline = 8;


    if (r_pixbytes == 1) {
	dest = vid.conbuffer + y * vid.conrowbytes + x;

	while (drawline--) {
	    if (source[0])
		dest[0] = source[0];
	    if (source[1])
		dest[1] = source[1];
	    if (source[2])
		dest[2] = source[2];
	    if (source[3])
		dest[3] = source[3];
	    if (source[4])
		dest[4] = source[4];
	    if (source[5])
		dest[5] = source[5];
	    if (source[6])
		dest[6] = source[6];
	    if (source[7])
		dest[7] = source[7];
	    source += 128;
	    dest += vid.conrowbytes;
	}
    } else {
	// FIXME: pre-expand to native format?
	pusdest = (unsigned short *)
	    ((byte *) vid.conbuffer + y * vid.conrowbytes + (x << 1));

	while (drawline--) {
	    if (source[0])
		pusdest[0] = d_8to16table[source[0]];
	    if (source[1])
		pusdest[1] = d_8to16table[source[1]];
	    if (source[2])
		pusdest[2] = d_8to16table[source[2]];
	    if (source[3])
		pusdest[3] = d_8to16table[source[3]];
	    if (source[4])
		pusdest[4] = d_8to16table[source[4]];
	    if (source[5])
		pusdest[5] = d_8to16table[source[5]];
	    if (source[6])
		pusdest[6] = d_8to16table[source[6]];
	    if (source[7])
		pusdest[7] = d_8to16table[source[7]];

	    source += 128;
	    pusdest += (vid.conrowbytes >> 1);
	}
    }
#endif
}

/*
================
Draw_String
================
*/
void Draw_String(int x, int y, char *str)
{
    while (*str) {
	Draw_Character(x, y, *str);
	str++;
	x += 8;
    }
}

/*
================
Draw_DebugChar

Draws a single character directly to the upper right corner of the screen.
This is for debugging lockups by drawing different chars in different parts
of the code.
================
*/
void Draw_DebugChar(char num)
{
    byte *dest;
    byte *source;
    int drawline;
    extern byte *draw_chars;
    int row, col;

    if (!vid.direct)
	return;			// don't have direct FB access, so no debugchars...

    drawline = 8;

    row = num >> 4;
    col = num & 15;
    source = draw_chars + (row << 10) + (col << 3);

    dest = vid.direct + 312;

    while (drawline--) {
	dest[0] = source[0];
	dest[1] = source[1];
	dest[2] = source[2];
	dest[3] = source[3];
	dest[4] = source[4];
	dest[5] = source[5];
	dest[6] = source[6];
	dest[7] = source[7];
	source += 128;
	dest += 320;
    }
}

/*
=============
Draw_Pic
=============
*/
void Draw_Pic(int x, int y, qpic_t * pic)
{
#ifdef TIMECOP
    byte *dest, *source;
    unsigned short *pusdest;
    int v, u;

    if ((x < 0) ||
	(x + pic->width > vid.width) ||
	(y < 0) || (y + pic->height > vid.height)) {
	Sys_Error("Draw_Pic: bad coordinates");
    }

    source = pic->data;

    if (r_pixbytes == 1) {
	dest = vid.buffer + y * vid.rowbytes + x;

	for (v = 0; v < pic->height; v++) {
	    Q_memcpy(dest, source, pic->width);
	    dest += vid.rowbytes;
	    source += pic->width;
	}
    } else {
	// FIXME: pretranslate at load time?
	pusdest =
	    (unsigned short *) vid.buffer + y * (vid.rowbytes >> 1) + x;

	for (v = 0; v < pic->height; v++) {
	    for (u = 0; u < pic->width; u++) {
		pusdest[u] = d_8to16table[source[u]];
	    }

	    pusdest += vid.rowbytes >> 1;
	    source += pic->width;
	}
    }
#endif
}


/*
=============
Draw_TransPic
=============
*/
void Draw_TransPic(int x, int y, qpic_t * pic)
{
#ifdef TIMECOP
    byte *dest, *source, tbyte;
    unsigned short *pusdest;
    int v, u;

    if (x < 0 || (unsigned) (x + pic->width) > vid.width || y < 0 ||
	(unsigned) (y + pic->height) > vid.height) {
	Sys_Error("Draw_TransPic: bad coordinates");
    }

    source = pic->data;

    if (r_pixbytes == 1) {
	dest = vid.buffer + y * vid.rowbytes + x;

	if (pic->width & 7) {	// general
	    for (v = 0; v < pic->height; v++) {
		for (u = 0; u < pic->width; u++)
		    if ((tbyte = source[u]) != TRANSPARENT_COLOR)
			dest[u] = tbyte;

		dest += vid.rowbytes;
		source += pic->width;
	    }
	} else {		// unwound
	    for (v = 0; v < pic->height; v++) {
		for (u = 0; u < pic->width; u += 8) {
		    if ((tbyte = source[u]) != TRANSPARENT_COLOR)
			dest[u] = tbyte;
		    if ((tbyte = source[u + 1]) != TRANSPARENT_COLOR)
			dest[u + 1] = tbyte;
		    if ((tbyte = source[u + 2]) != TRANSPARENT_COLOR)
			dest[u + 2] = tbyte;
		    if ((tbyte = source[u + 3]) != TRANSPARENT_COLOR)
			dest[u + 3] = tbyte;
		    if ((tbyte = source[u + 4]) != TRANSPARENT_COLOR)
			dest[u + 4] = tbyte;
		    if ((tbyte = source[u + 5]) != TRANSPARENT_COLOR)
			dest[u + 5] = tbyte;
		    if ((tbyte = source[u + 6]) != TRANSPARENT_COLOR)
			dest[u + 6] = tbyte;
		    if ((tbyte = source[u + 7]) != TRANSPARENT_COLOR)
			dest[u + 7] = tbyte;
		}
		dest += vid.rowbytes;
		source += pic->width;
	    }
	}
    } else {
	// FIXME: pretranslate at load time?
	pusdest =
	    (unsigned short *) vid.buffer + y * (vid.rowbytes >> 1) + x;

	for (v = 0; v < pic->height; v++) {
	    for (u = 0; u < pic->width; u++) {
		tbyte = source[u];

		if (tbyte != TRANSPARENT_COLOR) {
		    pusdest[u] = d_8to16table[tbyte];
		}
	    }

	    pusdest += vid.rowbytes >> 1;
	    source += pic->width;
	}
    }
#endif
}



/*
=============
Draw_TransPicTranslate
=============
*/
void Draw_TransPicTranslate(int x, int y, qpic_t * pic, byte * translation)
{
    byte *dest, *source, tbyte;
    unsigned short *pusdest;
    int v, u;

    if (x < 0 || (unsigned) (x + pic->width) > vid.width || y < 0 ||
	(unsigned) (y + pic->height) > vid.height) {
	Sys_Error("Draw_TransPic: bad coordinates");
    }

    source = pic->data;

    if (r_pixbytes == 1) {
	dest = vid.buffer + y * vid.rowbytes + x;

	if (pic->width & 7) {	// general
	    for (v = 0; v < pic->height; v++) {
		for (u = 0; u < pic->width; u++)
		    if ((tbyte = source[u]) != TRANSPARENT_COLOR)
			dest[u] = translation[tbyte];

		dest += vid.rowbytes;
		source += pic->width;
	    }
	} else {		// unwound
	    for (v = 0; v < pic->height; v++) {
		for (u = 0; u < pic->width; u += 8) {
		    if ((tbyte = source[u]) != TRANSPARENT_COLOR)
			dest[u] = translation[tbyte];
		    if ((tbyte = source[u + 1]) != TRANSPARENT_COLOR)
			dest[u + 1] = translation[tbyte];
		    if ((tbyte = source[u + 2]) != TRANSPARENT_COLOR)
			dest[u + 2] = translation[tbyte];
		    if ((tbyte = source[u + 3]) != TRANSPARENT_COLOR)
			dest[u + 3] = translation[tbyte];
		    if ((tbyte = source[u + 4]) != TRANSPARENT_COLOR)
			dest[u + 4] = translation[tbyte];
		    if ((tbyte = source[u + 5]) != TRANSPARENT_COLOR)
			dest[u + 5] = translation[tbyte];
		    if ((tbyte = source[u + 6]) != TRANSPARENT_COLOR)
			dest[u + 6] = translation[tbyte];
		    if ((tbyte = source[u + 7]) != TRANSPARENT_COLOR)
			dest[u + 7] = translation[tbyte];
		}
		dest += vid.rowbytes;
		source += pic->width;
	    }
	}
    } else {
	// FIXME: pretranslate at load time?
	pusdest =
	    (unsigned short *) vid.buffer + y * (vid.rowbytes >> 1) + x;

	for (v = 0; v < pic->height; v++) {
	    for (u = 0; u < pic->width; u++) {
		tbyte = source[u];

		if (tbyte != TRANSPARENT_COLOR) {
		    pusdest[u] = d_8to16table[tbyte];
		}
	    }

	    pusdest += vid.rowbytes >> 1;
	    source += pic->width;
	}
    }
}


void Draw_CharToConback(int num, byte * dest)
{
    int row, col;
    byte *source;
    int drawline;
    int x;

    row = num >> 4;
    col = num & 15;
    source = draw_chars + (row << 10) + (col << 3);

    drawline = 8;

    while (drawline--) {
	for (x = 0; x < 8; x++)
	    if (source[x])
		dest[x] = 0x60 + source[x];
	source += 128;
	dest += 320;
    }

}

/*
================
Draw_ConsoleBackground

================
*/
void Draw_ConsoleBackground(int lines)
{
    int x, y, v;
    byte *src, *dest;
    unsigned short *pusdest;
    int f, fstep;
    qpic_t *conback;
    char ver[6];

    conback = M_CachePic("gfx/conback.lmp");

// hack the version number directly into the pic
    dest = conback->data + 320 - 43 + 320 * 186;
    sprintf(ver, "%4.2f", VERSION);
    for (x = 0; x < 4; x++)
	Draw_CharToConback(ver[x], dest + (x << 3));

// draw the pic
    if (r_pixbytes == 1) {
	dest = vid.conbuffer;

	for (y = 0; y < lines; y++, dest += vid.conrowbytes) {
	    v = (vid.conheight - lines + y) * 200 / vid.conheight;
	    src = conback->data + v * 320;
	    if (vid.conwidth == 320)
		memcpy(dest, src, vid.conwidth);
	    else {
		f = 0;
		fstep = 320 * 0x10000 / vid.conwidth;
		for (x = 0; x < vid.conwidth; x += 4) {
		    dest[x] = src[f >> 16];
		    f += fstep;
		    dest[x + 1] = src[f >> 16];
		    f += fstep;
		    dest[x + 2] = src[f >> 16];
		    f += fstep;
		    dest[x + 3] = src[f >> 16];
		    f += fstep;
		}
	    }
	}
    } else {
	pusdest = (unsigned short *) vid.conbuffer;

	for (y = 0; y < lines; y++, pusdest += (vid.conrowbytes >> 1)) {
	    // FIXME: pre-expand to native format?
	    // FIXME: does the endian switching go away in production?
	    v = (vid.conheight - lines + y) * 200 / vid.conheight;
	    src = conback->data + v * 320;
	    f = 0;
	    fstep = 320 * 0x10000 / vid.conwidth;
	    for (x = 0; x < vid.conwidth; x += 4) {
		pusdest[x] = d_8to16table[src[f >> 16]];
		f += fstep;
		pusdest[x + 1] = d_8to16table[src[f >> 16]];
		f += fstep;
		pusdest[x + 2] = d_8to16table[src[f >> 16]];
		f += fstep;
		pusdest[x + 3] = d_8to16table[src[f >> 16]];
		f += fstep;
	    }
	}
    }
}


/*
==============
R_DrawRect8
==============
*/
void R_DrawRect8(vrect_t * prect, int rowbytes, byte * psrc,
		 int transparent)
{
    byte t;
    int i, j, srcdelta, destdelta;
    byte *pdest;

    pdest = vid.buffer + (prect->y * vid.rowbytes) + prect->x;

    srcdelta = rowbytes - prect->width;
    destdelta = vid.rowbytes - prect->width;

    if (transparent) {
	for (i = 0; i < prect->height; i++) {
	    for (j = 0; j < prect->width; j++) {
		t = *psrc;
		if (t != TRANSPARENT_COLOR) {
		    *pdest = t;
		}

		psrc++;
		pdest++;
	    }

	    psrc += srcdelta;
	    pdest += destdelta;
	}
    } else {
	for (i = 0; i < prect->height; i++) {
	    memcpy(pdest, psrc, prect->width);
	    psrc += rowbytes;
	    pdest += vid.rowbytes;
	}
    }
}


/*
==============
R_DrawRect16
==============
*/
void R_DrawRect16(vrect_t * prect, int rowbytes, byte * psrc,
		  int transparent)
{
    byte t;
    int i, j, srcdelta, destdelta;
    unsigned short *pdest;

// FIXME: would it be better to pre-expand native-format versions?

    pdest = (unsigned short *) vid.buffer +
	(prect->y * (vid.rowbytes >> 1)) + prect->x;

    srcdelta = rowbytes - prect->width;
    destdelta = (vid.rowbytes >> 1) - prect->width;

    if (transparent) {
	for (i = 0; i < prect->height; i++) {
	    for (j = 0; j < prect->width; j++) {
		t = *psrc;
		if (t != TRANSPARENT_COLOR) {
		    *pdest = d_8to16table[t];
		}

		psrc++;
		pdest++;
	    }

	    psrc += srcdelta;
	    pdest += destdelta;
	}
    } else {
	for (i = 0; i < prect->height; i++) {
	    for (j = 0; j < prect->width; j++) {
		*pdest = d_8to16table[*psrc];
		psrc++;
		pdest++;
	    }

	    psrc += srcdelta;
	    pdest += destdelta;
	}
    }
}


/*
=============
Draw_TileClear

This repeats a 64*64 tile graphic to fill the screen around a sized down
refresh window.
=============
*/
void Draw_TileClear(int x, int y, int w, int h)
{
    int width, height, tileoffsetx, tileoffsety;
    byte *psrc;
    vrect_t vr;

    r_rectdesc.rect.x = x;
    r_rectdesc.rect.y = y;
    r_rectdesc.rect.width = w;
    r_rectdesc.rect.height = h;

    vr.y = r_rectdesc.rect.y;
    height = r_rectdesc.rect.height;

    tileoffsety = vr.y % r_rectdesc.height;

    while (height > 0) {
	vr.x = r_rectdesc.rect.x;
	width = r_rectdesc.rect.width;

	if (tileoffsety != 0)
	    vr.height = r_rectdesc.height - tileoffsety;
	else
	    vr.height = r_rectdesc.height;

	if (vr.height > height)
	    vr.height = height;

	tileoffsetx = vr.x % r_rectdesc.width;

	while (width > 0) {
	    if (tileoffsetx != 0)
		vr.width = r_rectdesc.width - tileoffsetx;
	    else
		vr.width = r_rectdesc.width;

	    if (vr.width > width)
		vr.width = width;

	    psrc = r_rectdesc.ptexbytes +
		(tileoffsety * r_rectdesc.rowbytes) + tileoffsetx;

	    if (r_pixbytes == 1) {
		R_DrawRect8(&vr, r_rectdesc.rowbytes, psrc, 0);
	    } else {
		R_DrawRect16(&vr, r_rectdesc.rowbytes, psrc, 0);
	    }

	    vr.x += vr.width;
	    width -= vr.width;
	    tileoffsetx = 0;	// only the left tile can be left-clipped
	}

	vr.y += vr.height;
	height -= vr.height;
	tileoffsety = 0;	// only the top tile can be top-clipped
    }
}


/*
=============
Draw_Fill

Fills a box of pixels with a single color
=============
*/
void Draw_Fill(int x, int y, int w, int h, int c)
{
    byte *dest;
    unsigned short *pusdest;
    unsigned uc;
    int u, v;

    if (r_pixbytes == 1) {
	dest = vid.buffer + y * vid.rowbytes + x;
	for (v = 0; v < h; v++, dest += vid.rowbytes)
	    for (u = 0; u < w; u++)
		dest[u] = c;
    } else {
	uc = d_8to16table[c];

	pusdest =
	    (unsigned short *) vid.buffer + y * (vid.rowbytes >> 1) + x;
	for (v = 0; v < h; v++, pusdest += (vid.rowbytes >> 1))
	    for (u = 0; u < w; u++)
		pusdest[u] = uc;
    }
}

//=============================================================================

/*
================
Draw_FadeScreen

================
*/
void Draw_FadeScreen(void)
{
    int x, y, w;
    int *buf;
    unsigned quad;
    byte p1, p2, p3, p4;

    S_ExtraUpdate();

    w = vid.width >> 2;
    for (y = 0; y < vid.height; y++) {
	buf = (int *) (vid.buffer + vid.rowbytes * y);
	for (x = 0; x < w; x++) {
	    quad = buf[x];
	    p1 = fadetable[quad & 255];
	    p2 = fadetable[(quad >> 8) & 255];
	    p3 = fadetable[(quad >> 16) & 255];
	    p4 = fadetable[quad >> 24];
	    buf[x] = (p4 << 24) | (p3 << 16) | (p2 << 8) | p1;
	}
    }

    S_ExtraUpdate();
}

//=============================================================================

/*
================
Draw_BeginDisc

Draws the little blue disc in the corner of the screen.
Call before beginning any disc IO.
================
*/
void Draw_BeginDisc(void)
{

    D_BeginDirectRect(vid.width - 24, 0, draw_disc->data, 24, 24);
}


/*
================
Draw_EndDisc

Erases the disc icon.
Call after completing any disc IO
================
*/
void Draw_EndDisc(void)
{

    D_EndDirectRect(vid.width - 24, 0, 24, 24);
}
