#include "snake.h"
#include "dirty.h"

dirty *recycled;
dirty *scratch_dirty;
dirty *screen_dirty;

int tick_spacing = 100;

dirty::dirty()
    : m_rect()
{
    next = 0;
}

dirty::dirty(int _top, int _bot, int _left, int _right, dirty *_next)
    : m_rect(_top, _bot, _left, _right)
{
    next = _next;
}

void dirty::clear()
{
    top = 0;
    bot = 0;
    left = 0;
    right = 0;
    next = 0;
}

dirty *pop(dirty **list)
{
    dirty *tmp = 0;
    
    if (*list)
    {
	tmp = *list;
	*list = (*list)->next;
    }

    return tmp;
}

dirty *pop_list(dirty **list)
{
    dirty *tmp = *list;
    *list = 0;

    return tmp;
}

// check for 0 pointers in the push functions so we can blindly recycle
// lists without checking if they were empty
void push(dirty **list, dirty *elem)
{
    if (!elem)
	return;

    elem->next = *list;
    *list = elem;
}

void push_list(dirty **list, dirty *elem_list)
{
    dirty
	*last;

    if (!elem_list)
	return;

    last = elem_list;
    
    while (last->next)
	last = last->next;

    last->next = *list;
    *list = elem_list;
}

dirty *new_dirty(int top, int bot, int left, int right)
{
    dirty *ret;
    
    if (recycled)
	ret = pop(&recycled);
    else
	ret = new dirty;

    ret->clear();
    
    ret->top = top;
    ret->bot = bot;
    ret->left = left;
    ret->right = right;
    
    return ret;
}

dirty *new_dirty(m_rect const &other)
{
    dirty *ret;
    
    if (recycled)
	ret = pop(&recycled);
    else
	ret = new dirty;

    ret->clear();
    
    ret->top = other.top;
    ret->bot = other.bot;
    ret->left = other.left;
    ret->right = other.right;
    
    return ret;
}

void init_dirty()
{
    recycled = 0;
    scratch_dirty = 0;
    screen_dirty = 0;
}

void clear_scratch()
{
    dirty *tmp = scratch_dirty;

    while (tmp)
    {
	rectfill(scratch, tmp->left, tmp->top, tmp->right, tmp->bot, black);
	tmp = tmp->next;
    }

    push_list(&recycled, pop_list(&scratch_dirty));
}

void dirty_border_scratch()
{
    // top line
    push(&scratch_dirty, new_dirty(0, 0, 0, MAP_SIZE_X - 1));

    // bottom line
    push(&scratch_dirty, new_dirty(MAP_SIZE_Y - 1, MAP_SIZE_Y - 1,
				   0, MAP_SIZE_X - 1));
    
    // left line
    push(&scratch_dirty, new_dirty(0, MAP_SIZE_Y - 1, 0, 0));

    // right line
    push(&scratch_dirty, new_dirty(0, MAP_SIZE_Y - 1,
				   MAP_SIZE_X - 1, MAP_SIZE_X - 1));
}

void dirty_bonusses_scratch()
{
    bonus *tmp = bonusses;

    while (tmp)
    {
	push(&scratch_dirty, new_dirty(tmp->get_rect()));
	tmp = tmp->next;
    }
}

void draw_tick_marks(BITMAP *b)
{
    for (int x = tick_spacing; x < MAP_SIZE_X; x += tick_spacing)
    {
	for (int y = tick_spacing; y < MAP_SIZE_Y; y += tick_spacing)
	{
	    putpixel(b, x, y, tick);
	}
    }
}

// slightly suggestive name. what it does is clear the pixels of the
// screen that are in the screen_dirty list. this is to erase the
// previous map.

// do this for each player for their own view_bitmap

// dirty rectangles contain the following information:
// left, top : screen coordinates (where to plot to)
void clear_screen()
{
    dirty *tmp = screen_dirty;

    while (tmp)
    {
	// find out where to blit from
	// coordinats to use are left and top

	for (int i = 0; i < nr_players; i++)
	{
	    putpixel(players[i]->view_bitmap,
		     tmp->left, tmp->top,
		     getpixel(scratch,
			      tmp->left + players[i]->map_x,
			      tmp->top + players[i]->map_y)
		    );
	}

	tmp = tmp->next;
    }

    push_list(&recycled, pop_list(&screen_dirty));
}

void dirty_screen(int x, int y)
{
    // only top and left are used, but we might as well fill the rectangle
    // even though it's only one pixel
    push(&screen_dirty, new_dirty(y, y, x, x));
}

void draw_map()
{
    // draw the overhead map onto b
    // use ratio of player 0 for each player
    // add every pixel to screen_dirty with dirty_screen(x, y)

    int
	x,
	y,
	col;

    double
	x_ratio,
	y_ratio;

    bonus *tmp;

    x_ratio = (double)players[0]->view_port_width / (double)MAP_SIZE_X;
    y_ratio = (double)players[0]->view_port_height / (double)MAP_SIZE_Y;
    
    // plot each bodypart of a player as a pixel
    for (int p = 0; p < nr_players; p++)
    {
	if (!players[p]->alive)
	    continue;

	for (int i = bodies[p]->tail; i <= bodies[p]->head; i++)
	{
	    x = (int)(bodies[p]->get(i).x * x_ratio);
	    y = (int)(bodies[p]->get(i).y * y_ratio);
	    
	    // add it to the dirty list
	    dirty_screen(x, y);

	    // plot the pixel for each player
	    for (int q = 0; q < nr_players; q++)
	    {
		putpixel(players[q]->view_bitmap,
			 x, y, rgbpal[players[p]->color_offset + 10]);
	    }
	}
    }

    // draw all bonusses
    tmp = bonusses;
    while (tmp)
    {
	x = (int)(tmp->x * x_ratio);
	y = (int)(tmp->y * y_ratio);
	
	switch (tmp->type)
	{
	    case bonus::dead_part:
		col = gray;
		break;
	    case bonus::longer:
		col = yellow;
		break;
	    case bonus::chainsaw:
		col = red;
		break;
	    case bonus::burrow:
		col = green;
		break;
	    default:
		col = black;
		cerr << "invalid bonus type " << tmp->type << "." << endl;
		break;
	}

	// add it to the dirty list
	dirty_screen(x, y);

	// draw it for each player
	for (int q = 0; q < nr_players; q++)
	{
	    putpixel(players[q]->view_bitmap, x, y, col);
	}
	
	tmp = tmp->next;
    }
}
