/*
 * VGA 16 colour mode support routines
 *
 * Copyright (C) Evan Harris, 1993, 1994, 1995.
 *
 * Permission is granted to freely redistribute and modify this code,
 * providing the author(s) get credit for having written it.
 */

#include <stdlib.h>
#include <stdio.h>
#include <vga.h>
#include "vga16.h"

#define FONT_WIDTH 8
#define FONT_LINES 16


static unsigned char *vga16mem = NULL;
static int vga16width, vga16height;
static unsigned char *vga16workmem =  NULL;
static char *font_bits;


/*
 * To be called after vga_setmode()
 */

int
vga16_init()
{
    vga_modeinfo *mode;
    FILE *f;
    int nelem;

    mode = vga_getmodeinfo(vga_getcurrentmode());
    if (mode->colors != 16) {
	return -1;
    }
    if (vga16mem != NULL) {
	free(vga16mem);
    }
    vga16mem = calloc(mode->width * mode->height / 2, sizeof(unsigned char));
    vga16workmem = malloc(mode->width * sizeof(unsigned char));
    nelem = ((FONT_WIDTH + 7) / 8) * FONT_LINES * 256;
    font_bits = malloc(nelem * sizeof(char));
    if (vga16mem == NULL || vga16workmem == NULL || font_bits == NULL) {
	return -1;
    }
    vga16width = mode->width;
    vga16height = mode->height;
    
    f = fopen(VGA16FONT, "r");
    if (f == NULL) {
	fprintf(stderr, "Cannot find '%s'\n", VGA16FONT);
	exit(1);
    }
    if (fread(font_bits, sizeof(char), nelem, f) != nelem) {
	fprintf(stderr, "Font reading failed - read error.\n");
    }
    fclose(f);

    return 0;
}


void
vga16_drawscansegment(unsigned char *colors, int x, int y, int length)
{
    unsigned char *p, *c;
    int l, changed;
    
    p = &vga16mem[(y * vga16width + x) >> 1];
    c = colors;
    l = length;
    changed = 0;
    while (l > 0) {
	if (*p != (*c << 4 | *(c + 1))) {
	    *p = *c << 4 | *(c + 1);
	    changed = 1;
	}
	p++;
	c += 2;
	l -= 2;
    }

    if (changed) {
	vga_drawscansegment(colors, x, y, length);
    }
}


void
vga16_drawscanline(int y, unsigned char *colors)
{
    vga16_drawscansegment(colors, 0, y, vga16width);
}


static void
vga16_putchar(int x, int y, char c, int fg, int bg)
{
    int i, j, mask, vp, colour;

    y -= FONT_LINES - 1;
    for (i = FONT_LINES * c; i < FONT_LINES * (c + 1); i++) {
	mask = 0x80;
	for (j = x; j < x + 8; j++) {
	    if (font_bits[i] & mask) {
		colour = fg;
	    } else {
		colour = bg;
	    }
	    
	    vp = (y * vga16width + j) / 2;

	    if (j & 1) {
		if ((vga16mem[vp] & 0x0f) != colour) {
		    vga16mem[vp] = (vga16mem[vp] & 0xf0) | colour;
		    vga_setcolor(colour);
		    vga_drawpixel(j, y);
		}
	    } else {
		if ((vga16mem[vp] >> 4) != colour) {
		    vga16mem[vp] = (colour << 4) | (vga16mem[vp] & 0x0f);
		    vga_setcolor(colour);
		    vga_drawpixel(j, y);
		}
	    }

	    mask = mask >> 1;
	}
	y++;
    }
}


void
vga16_text(int x, int y, char *s, int fg, int bg)
{
    int startx = x;

    while (*s != '\0') {
	switch (*s) {
	  case '\n':
	    y += FONT_LINES;
	    x = startx;
	    break;
	  case '\b':
	    x -= FONT_WIDTH;
	    break;
	  default:
	    vga16_putchar(x, y, *s, fg, bg);
	    x += FONT_WIDTH;
	    break;
	}
	s++;
    }
}


void
vga16_setpixel(int color, int x, int y)
{
    unsigned char *vp = &vga16mem[(y * vga16width + x) >> 1];

    if (x & 1) {
	if ((*vp & 0x0f) == color) {
	    return;
	}
	*vp = (*vp & 0xf0) | color;
    } else {
	if ((*vp >> 4) == color) {
	    return;
	}
	*vp = (color << 4) | (*vp & 0x0f);
    }
    
    vga_setcolor(color);
    vga_drawpixel(x, y);
}


int
vga16_getpixel(int x, int y)
{
    unsigned char *vp = &vga16mem[(y * vga16width + x) >> 1];

    if (x & 1) {
	return *vp & 0x0f;
    } else {
	return *vp >> 4;
    }
}


/*
 * This assumes that (x0 % 8 == 0) and (x1 % 8 == 7) due to
 * vga_drawscansegment()
 */

void
vga16_filledblock(int x0, int y0, int x1, int y1, int color)
{
    unsigned char *vp;
    int x, y, c;

    for (x = 0; x <= x1 - x0; x++) {
	vga16workmem[x] = color;
    }

    for (y = y0; y <= y1; y++) {
	x = x0;
	c = (color << 4) | color;
	vp = &vga16mem[(y * vga16width + x) >> 1];
	if (x & 1) {
	    *vp++ = (*vp & 0xf0) | color;
	    x++;
	}
	while (x <= x1 - 1) {
	    *vp++ = c;
	    x += 2;
	}
	if (x < x1) {
	    *vp++ = (*vp & 0x0f) | (color << 4);
	}
	vga_drawscansegment(vga16workmem, x0, y, x1 - x0 + 1);
    }
}
