#include "snake.h"

// main game loop
// returning 0 means quit
// anything else will continue

void do_action(int p)
{
    if (players[p]->burrow > 0)
    {
	players[p]->burrow_on = 1;
	players[p]->chainsaw_on = 0;
	players[p]->loop(sample_burrow);
    }
    else
    if (players[p]->chainsaw > 0)
    {
	players[p]->burrow_on = 0;
	players[p]->chainsaw_on = 1;
	players[p]->loop(sample_chainsaw);
    }
    else
    {
	// this is an error condition right? *evil grin*
	cerr << '\7' << '\7' << '\7';
    }
}

void special_timers()
{
    for (int p = 0; p < nr_players; p++)
    {
	if (players[p]->burrow_on)
	{
	    players[p]->burrow--;
	    if (!players[p]->burrow)
	    {
		players[p]->burrow_on = 0;
		players[p]->stop();           // stop the sound
	    }
	}

	if (players[p]->chainsaw_on)
	{
	    players[p]->chainsaw--;
	    if (!players[p]->chainsaw)
	    {
		players[p]->chainsaw_on = 0;
		players[p]->stop();           // stop the sound
	    }
	}
    }
}

void process_commands()
{
    for (int p = 0; p < nr_players; p++)
    {
	if (players[p]->left_pressed)
	    bodies[p]->turn(DELTA_DIR);
	if (players[p]->right_pressed)
	    bodies[p]->turn(-DELTA_DIR);
	if (players[p]->action_pressed)
	   do_action(p);

	// finally, clear the _pressed flags
	players[p]->clear_keys();
    }
}

void update_screen()
{
    for (int p = 0; p < nr_players; p++)
    {
	players[p]->update_list(&(players[p]->clear));
	players[p]->update_list(&(players[p]->redraw));
    }
}

int game_loop()
{
    static int glt = 0;

    // wait until the next pulse and read keys while waiting
    // do this loop at least once to read the keys even if we
    // don't have enough time to draw
    do
    {
	if (key[KEY_ESC])
	{
	    while(key[KEY_ESC]);

	    high_score();
	    
	    cerr << "buhbye!" << endl;
	    return 0;
	}

	if (key[KEY_F1])
	{
	    while (key[KEY_F1]);
	    save_bmp("snake.bmp", screen, mypal);
	}
	
	for (int p = 0; p < nr_players; p++)
	{
	    players[p]->read_keys();
	}
    }
    while (glt == game_loop_time);

    glt = game_loop_time;

    // check if we have a game over situation
    // if so, show the high score and end the game
    int alive = 0;   
    for (int p = 0; p < nr_players; p++)
    {
	if (players[p]->alive)
	    alive = 1;

	if (players[p]->score > END_SCORE)
	{
	    high_score();
	    return 0;
	}
    }
    if (!alive)
    {
	high_score();
	return 0;
    }
    
    // process player commands, this clears the _pressed flags
    process_commands();

    // generate new bonnuses if appropriate
    if (bonus_time)
    {
	generate_bonus();
	bonus_time = 0;
    }

    // reduce the 'special' timers, burrow and chainsaw
    special_timers();
    
    // clear the scratch bitmap
    // use dirty rectangles for this
    // clear_to_color(scratch, black);

    clear_scratch();
    
    // draw the border and make it dirty for the next loop
    rect(scratch, 0, 0, MAP_SIZE_X - 1, MAP_SIZE_Y - 1, gray);
    dirty_border_scratch();
    
    // draw tick marks on the scratch bitmap
    // no need to remove these : they don't move
    draw_tick_marks(scratch);

    // draw the bonusses and make them dirty for the next loop
    draw_bonusses(scratch);
    dirty_bonusses_scratch();

    // move the players
    // draw the players on the scratch bitmap
    // add dirty rectangle
    // draw to the screen at the very end, not before!

    // for each view, erase all snakes
    // erase the border once,
    for (int view = 0; view < nr_players; view++)
    {
	// make the borders dirty
	players[view]->dirty_border_clear();

	// make the bonusses dirty
	players[view]->dirty_bonusses_clear();

	// make the tickmarks dirty
	players[view]->dirty_tickmarks_clear();
	
	// draw each snake and move it
	for (int p = 0; p < nr_players; p++)
	{
	    // only draw alive snakes
	    if (!bodies[p]->alive)
		continue;

	    for (int i = bodies[p]->tail; i <= bodies[p]->head; i++)
	    {
		// don't clear burrowed bodyparts
		if (bodies[p]->get(i).burrow)
		    continue;
		
		players[view]->add_dirty_clear(bodies[p]->get(i).get_rect());
	    }
	}
    }

    // move all the players
    // check for collisions
    // if we're underground, don't collide with anything
    for (int p = 0; p < nr_players; p++)
    {
	// move the players
	bodypart bp;
	m_rect tmp;
	bonus *tmb;
	
	// don't move dead bodies
	if (!bodies[p]->alive)
	    continue;

	bp = bodies[p]->calc_new_pos();
	tmp = bp.get_rect();

	// don't collide if we are underground ourselves
	if (!players[p]->burrow_on)
	{
	    // check for collisions with other snakes or ourself
	    for (int q = 0; q < nr_players; q++)
	    {
		// don't collide with dead snakes
		if (!bodies[q]->alive)
		    continue;
		
		for (int i = bodies[q]->tail; i <= bodies[q]->head; i++)
		{
		    // can't bump into our own head
		    if (q == p && bodies[q]->head - i < 5)
			break;
		    
		    if (tmp.intersects(bodies[q]->get(i).get_rect()))
		    {
			// when using the chainsaw, break the other snake,
			// then jump out of the loop.
			if (players[p]->chainsaw_on)
			{
			    int score;

			    score = bodies[q]->cut(i);
			    players[p]->score += score;
			    players[q]->score -= score;

			    break;
			}
			    
			// don't crash if the bodypart is underground
			if (!bodies[q]->get(i).burrow)
			{
			    bodies[p]->crash();
			    players[p]->crash();
			}
		    }
		}
	    }
	    
	    // check for collisions with bonusses
	    tmb = bonusses;
	    while (tmb)
	    {
		// it is NOT guaranteed that tmb still exists at the
		// end of the loop. store the next pointer now.
		bonus *tmb_next = tmb->next;
		
		m_rect tmr = tmb->get_rect();
		
		if (tmp.intersects(tmr))
		{
		    // we hit a bonus
		    tmb->hit(players[p], bodies[p]);

		    // add the bonus to the dirty rectangle list by hand
		    players[p]->add_dirty_clear(tmr);
		    rectfill(scratch, tmr.left, tmr.top,
			     tmr.right, tmr.bot, black);
		    
		    // remove the bonus from the bonus list
		    if (!bonus_remove(&bonusses, tmb))
			cerr << "error removing bonus from the list!" << endl;

		    bonus_push(&bonus_recycled, tmb);
		}
		
		tmb = tmb_next;
	    }
	}
	
	// check if we crashed
	if (!bodies[p]->alive)
	    continue;

	// if we didn't crash we may move
	bodies[p]->move(bp, players[p]->burrow_on);
	
	// move the viewport
	players[p]->move_viewport(bodies[p]);
    }

    // for each view, draw and blit all snakes
    // draw the borders only once
    // draw all bonusses once
    for (int view = 0; view < nr_players; view++)
    {
	// make the borders dirty
	players[view]->dirty_border_redraw();

	// draw the bonusses
	players[view]->dirty_bonusses_redraw();

	// make the tickmarks dirty
	players[view]->dirty_tickmarks_redraw();
	
	// draw the player onto the scratch bitmap
	// also add the bodyparts to the dirty list again
	for (int p = 0; p < nr_players; p++)
	{
	    // don't draw dead players
	    if (!bodies[p]->alive)
		continue;

	    for (int i = bodies[p]->tail; i <= bodies[p]->head; i++)
	    {
		int col = blue;

		// don't draw burrowed bodyparts
		if (bodies[p]->get(i).burrow)
		    continue;
		
		if (i == bodies[p]->head)
		{
		    if (players[p]->chainsaw_on)
			col = red;
		    if (players[p]->burrow_on)
			col = green;
		}
		
		circlefill(scratch,
			   bodies[p]->get(i).x,
			   bodies[p]->get(i).y,
			   radius, rgbpal[players[p]->color_offset + i % 20]);

		players[view]->add_dirty_redraw(bodies[p]->get(i).get_rect());

		// add the bodyparts to the scratch dirty list,
		// but only once, not for each player
		if (view == 0)
		{
		    push (&scratch_dirty,
			  new_dirty(bodies[p]->get(i).get_rect()));
		}
	    }
	}
    }
    
    // blit all dirty rectangles to the screen
    update_screen();

    // now clear the last overhead map, then draw the new one
    clear_screen();
    draw_map();

    // finally, draw the score bitmaps and blit them
    for (int p = 0; p < nr_players; p++)
    {
	players[p]->draw_score();
	blit(players[p]->score_bitmap, players[p]->view_bitmap,
	     0, 0, 0, 0,
	     players[p]->view_port_width, 10);
    }
    
//    putpixel(screen, game_loop_time, 200, red);
    
    return 1;
}
