#include <grass/gis.h>
#include <grass/raster.h>
#include <grass/vector.h>
#include "local.h"


struct Cell_head region, page;

static union
{
    CELL **cell;
    DCELL **dcell;
} raster;

static int max_rows;
static int at_row;
static CELL cat;
static DCELL dcat;
static int cur_x, cur_y;
static int format;
static int dense;
static CELL *cell;
static DCELL *dcell;
static char **null_flags;
static char isnull;

/* function prototypes */
static int configure_plot(void);
static int cell_dot(int, int);
static int dcell_dot(int, int);
static int cont(int, int);
static int move(int, int);
static int (*dot) (int, int);


int begin_rasterization(int cache_mb, int f, int do_dense)
{
    int i;
    double row_mb;
    int pages;
    size_t size;

    dense = (do_dense != 0);

    /* otherwise get complaints about window changes */
    G_suppress_warnings(1);

    format = f;

    G_get_set_window(&region);
    G_get_set_window(&page);

    row_mb = (double) region.cols * (sizeof(char) + Rast_cell_size(f)) /
	     (1 << 20);

    max_rows = cache_mb / row_mb;
    if (max_rows < 1)
	max_rows = 4;

    pages = (region.rows + max_rows - 1) / max_rows;

    if (max_rows > region.rows)
	max_rows = region.rows;

    G_debug(1, "%d of %d rows are cached", max_rows, region.rows);

    size = (size_t) max_rows * region.cols;
    switch (format) {
    case CELL_TYPE:
	raster.cell =
	    (CELL **) G_calloc(max_rows * sizeof(char), sizeof(CELL *));
	raster.cell[0] = (CELL *) G_calloc(size * sizeof(char), sizeof(CELL));
	for (i = 1; i < max_rows; i++)
	    raster.cell[i] = raster.cell[i - 1] + region.cols;
	dot = cell_dot;
	break;

    case DCELL_TYPE:
	raster.dcell =
	    (DCELL **) G_calloc(max_rows * sizeof(char), sizeof(DCELL *));
	raster.dcell[0] =
	    (DCELL *) G_calloc(size * sizeof(char), sizeof(DCELL));
	for (i = 1; i < max_rows; i++)
	    raster.dcell[i] = raster.dcell[i - 1] + region.cols;
	dot = dcell_dot;
	break;
    }

    null_flags = (char **)G_calloc(max_rows * sizeof(char), sizeof(char *));
    null_flags[0] = (char *)G_calloc(size * sizeof(char), sizeof(char));
    for (i = 1; i < max_rows; i++)
	null_flags[i] = null_flags[i - 1] + region.cols;

    at_row = 0;
    configure_plot();

    return pages;
}


static int configure_plot(void)
{
    int i, j;
    int nrows;
    int ncols;

    nrows = region.rows - at_row;
    if (nrows <= 0)
	return 1;

    if (nrows > max_rows)
	nrows = max_rows;

    ncols = region.cols;

    /* zero the raster */
    switch (format) {
    case CELL_TYPE:
	for (i = 0; i < nrows; i++)
	    for (j = 0; j < ncols; j++)
		raster.cell[i][j] = 0;
	break;
    case DCELL_TYPE:
	for (i = 0; i < nrows; i++)
	    for (j = 0; j < ncols; j++)
		raster.dcell[i][j] = 0;
	break;
    }

    for (i = 0; i < nrows; i++)
	for (j = 0; j < ncols; j++)
	    null_flags[i][j] = 1;

    /* change the region */
    page.north = region.north - at_row * region.ns_res;
    page.south = page.north - nrows * region.ns_res;
    G_set_window(&page);

    /* configure the plot routines */
    if (dense)
	setup_plot(0, page.rows, 0, page.cols, dot);
    else
	G_setup_plot(-0.5, page.rows - 0.5, -0.5, page.cols - 0.5, move, cont);

    return 0;
}


int output_raster(int fd)
{
    int i;

    for (i = 0; i < page.rows; i++, at_row++) {
	G_percent(i, page.rows, 2);
	switch (format) {
	case CELL_TYPE:
	    cell = raster.cell[i];

	    /* insert the NULL values */
	    Rast_insert_c_null_values(cell, null_flags[i], page.cols);
	    Rast_put_c_row(fd, cell);
	    break;
	case DCELL_TYPE:
	    dcell = raster.dcell[i];

	    /* insert the NULL values */
	    Rast_insert_d_null_values(dcell, null_flags[i], page.cols);
	    Rast_put_d_row(fd, dcell);
	    break;
	}
    }
    G_percent(1, 1, 1);
    
    return configure_plot();
}

int set_cat(CELL x)
{
    cat = x;
    if ((isnull = ISNULL(&cat)))
	cat = 0;

    return 0;
}

int set_dcat(DCELL x)
{
    dcat = x;
    if ((isnull = ISDNULL(&dcat)))
	dcat = 0;

    return 0;
}

static int move(int x, int y)
{
    cur_x = x;
    cur_y = y;

    return 0;
}

static int cont(int x, int y)
{
    if (cur_x < 0 && x < 0) {
	move(x, y);
	return 0;
    }
    if (cur_y < 0 && y < 0) {
	move(x, y);
	return 0;
    }
    if (cur_x >= page.cols && x >= page.cols) {
	move(x, y);
	return 0;
    }
    if (cur_y >= page.rows && y >= page.rows) {
	move(x, y);
	return 0;
    }

    G_bresenham_line(cur_x, cur_y, x, y, dot);
    move(x, y);

    return 0;
}

static int cell_dot(int x, int y)
{
    if (x >= 0 && x < page.cols && y >= 0 && y < page.rows) {
	raster.cell[y][x] = cat;
	null_flags[y][x] = isnull;
    }

    return 0;
}

static int dcell_dot(int x, int y)
{
    if (x >= 0 && x < page.cols && y >= 0 && y < page.rows) {
	raster.dcell[y][x] = dcat;
	null_flags[y][x] = isnull;
    }

    return 0;
}
