/***************************************************************************
                          game.c  -  description
                             -------------------
    begin                : 03/03/19
    copyright            : (C) 2003 by Michael Speck
    email                : kulkanie@gmx.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/***** INCLUDES ************************************************************/

#include "game.h"

/***** EXTERNAL VARIABLES **************************************************/

extern int ball_w, ball_dia;

/***** EXPORTS *************************************************************/

Game *cur_game = 0;

/***** FORWARDED DECLARATIONS **********************************************/

/***** LOCAL TYPE DEFINITIONS **********************************************/

/***** LOCAL VARIABLES *****************************************************/

/* not local anymore as src/ClientPlayer needs some values */
GameDiff diffs[DIFF_COUNT] = {
    { 9, 12,  8, 12, 20,  5, 0.10, 0.0016, 0.20, 32000, 0, 4 },
    { 6,  9,  0,  2,  8,  8, 0.24, 0.0016, 0.40, 1800,  1, 1 },
    { 5,  7,  0,  1,  6, 10, 0.27, 0.0016, 0.43, 1800,  1, 1 },
    { 4,  5,  0,  1,  4, 13, 0.30, 0.0015, 0.45, 1800,  1, 1 }
};

/* in network game the ball is slower and the paddle is bigger */
static GameDiff net_diffs[DIFF_COUNT] = {
    { 6, 9, 1, 2, 8, 8,  0.18, 0.0012, 0.30, 1800, 1, 1 },
    { 5, 7, 1, 2, 6, 10, 0.21, 0.0012, 0.33, 1800, 1, 1 },
    { 4, 5, 1, 2, 4, 13, 0.24, 0.0011, 0.35, 1800, 1, 1 }
};

/***** LOCAL FUNCTIONS *****************************************************/

static void game_setup_title_and_author( Game *game, Level *level )
{
  switch (level->type)
    {
    case LT_NORMAL:
      snprintf( game->title, 32, "%s", level->name );
      snprintf( game->author, 32, "%s", level->author );
      break;
    case LT_JUMPING_JACK:
      snprintf( game->title, 32, _("Jumping Jack") );
      snprintf( game->author, 32, _("Bonus Level") );
      break;
    case LT_OUTBREAK:
        snprintf( game->title, 32, _("Outbreak") );
        snprintf( game->author, 32, _("Bonus Level") );
        break;
    case LT_BARRIER:
        snprintf( game->title, 32, _("Barrier") );
        snprintf( game->author, 32, _("Bonus Level") );
        break;
    case LT_SITTING_DUCKS:
        snprintf( game->title, 32, _("Sitting Ducks") );
        snprintf( game->author, 32, _("Bonus Level") );
        break;
    case LT_HUNTER:
        snprintf( game->title, 32, _("Hunter") );
        snprintf( game->author, 32, _("Bonus Level") );
        break;
    case LT_DEFENDER:
        snprintf( game->title, 32, _("Defender") );
        snprintf( game->author, 32, _("Bonus Level") );
        break;
    default:
      snprintf( game->title, 32, _("Unknown Level Type") );
      snprintf( game->author, 32, "???" );
      break;
    }
}

/***** PUBLIC FUNCTIONS ****************************************************/

/* create/delete game context */
Game *game_create( int game_type, int diff, int rel_warp_limit )
{
	Game *game = salloc( 1, sizeof( Game ) );

	/* set diff and game type */
	game->game_type = game_type;
	if ( game_type == GT_LOCAL )
		game->diff = &diffs[diff];
	else
		game->diff = &net_diffs[diff];
	game->rel_warp_limit = rel_warp_limit;
	
	/* init angles for reflection */
	init_angles();

	/* create lists */
	game->shots = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );
	game->exp_bricks = list_create( LIST_NO_AUTO_DELETE, LIST_NO_CALLBACK );
	game->heal_bricks = list_create( LIST_NO_AUTO_DELETE, LIST_NO_CALLBACK );
	game->extras = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );
	game->balls = list_create( LIST_AUTO_DELETE, LIST_NO_CALLBACK );

	/* set ball speed */
	game->ball_v_min = game->diff->v_start;
	game->ball_v_max = game->diff->v_max;
	delay_set( &game->speedup_delay, game->diff->v_delay );
	
	/* create paddles */
	game->paddle_count = (game_type==GT_NETWORK)?2:1;
	/* bottom */
        game->paddles[PADDLE_BOTTOM] = paddle_create( 0, PADDLE_BOTTOM, 
		(MAP_HEIGHT-2)*BRICK_HEIGHT, 
		game->diff->paddle_size, 
		game->diff->paddle_min_size, game->diff->paddle_max_size,
		0 );
	/* top */
	if ( game_type == GT_NETWORK )
        	game->paddles[PADDLE_TOP] = paddle_create( 0, PADDLE_TOP,
			BRICK_HEIGHT+2, 
			game->diff->paddle_size, 
			game->diff->paddle_min_size, game->diff->paddle_max_size,
			0 );
	
	return game;
}
void game_delete( Game **_game )
{
	Game *game = *_game;
	int i;
	
	if ( game == 0 ) return;
	
	/* delete paddles */
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_delete( game->paddles[i] );
	
	/* delete lists */
	if ( game->shots ) list_delete( game->shots );
	if ( game->exp_bricks ) list_delete( game->exp_bricks );
	if ( game->heal_bricks ) list_delete( game->heal_bricks );
	if ( game->extras ) list_delete( game->extras );
	if ( game->balls ) list_delete( game->balls );

    /* delete bonus level stuff */
    if (game->blDuckPositions) free(game->blDuckPositions);
	if (game->blInvaders) free(game->blInvaders);
    
    free( game );
	*_game = 0;
}

/* finalize single game level. the level_type is determined by
 * counting the bricks. the data of 'level' is copied and modified
 * while playing. */
void game_init( Game *game, Level *level )
{
  int i;
  Ball *ball;

  game->level_over = 0;
  game->isBonusLevel = 0;
  game->totalBonusLevelScore = 0;

  /* set title and author */
  game_setup_title_and_author( game, level );

  /* set level type. is level::type except for normal level with no bricks: pingpong */
  if (level->type==LT_NORMAL && game->game_type == GT_NETWORK && game->brick_count == 0 )
    game->level_type = LT_PINGPONG;
  else
  {
      game->level_type = level->type;
      if (game->level_type!=LT_NORMAL) game->isBonusLevel = 1;
  }
  
  /* clear extras */
  memset( game->extra_active, 0, sizeof( game->extra_active ) );
  memset( game->extra_time, 0, sizeof( game->extra_time ) );

  /* set ball speed */
  game->ball_v = game->ball_v_min;
  game->speedup_level = 0;
	
  /* clear maxballspeed_request */
  if ( game->game_type == GT_LOCAL )
    {
      cur_game->paddles[0]->maxballspeed_request = 0;
      cur_game->paddles[0]->maxballspeed_request_old = 0;
    }
        
  /* attach one ball to each paddle */
  list_clear( game->balls );
  for ( i = 0; i < game->paddle_count; i++ ) {
    if ( game->paddles[i]->type == PADDLE_BOTTOM )
      ball = ball_create((game->paddles[i]->w - ball_w) / 2, -ball_dia );
    else
      ball = ball_create((game->paddles[i]->w - ball_w) / 2, game->paddles[i]->h );
    ball->attached = 1;
    ball->paddle = game->paddles[i];
    ball->paddle->attached_ball_count = 1;
    ball_set_random_angle( ball, game->ball_v );
    list_add( game->balls, ball );
  }
  
  /* do bricks as last to have influence on balls to keep bonus level stuff in one place */
  /* setup bricks (from level data or from special level type; this includes setting the
     bonus level data if any */
  bricks_init( game, game->game_type, level, game->diff->score_mod, game->rel_warp_limit );
    
}

/* reset level/in_game data */
void game_finalize( Game *game )
{
	int i;
	
	/* reset lists */
	list_clear( game->balls );
	list_clear( game->extras );
	list_clear( game->shots );
	list_clear( game->heal_bricks );
	list_clear( game->exp_bricks );

	/* reset paddles (and their statistics which are only for 
	 * the currently played level) */
	for ( i = 0; i < game->paddle_count; i++ )
		paddle_reset( game->paddles[i] );

	/* reset updates */
	game_reset_mods();
}

/* set the game context the subfunctions will apply their changes to */
void game_set_current( Game *game )
{
	cur_game = game;
}

/* set score of paddle 'id'. 0 is bottom paddle and 1 is top paddle */
void game_set_score( int id, int score ) 
{
	if ( id < 0 || id >= cur_game->paddle_count ) return;
	cur_game->paddles[id]->score = score;
}

/* set number of additional balls a paddle can fire (all paddles) */
void game_set_ball_ammo( int ammo )
{
	int i;
	for ( i = 0; i < cur_game->paddle_count; i++ ) {
		cur_game->paddles[i]->ball_ammo = ammo;
		cur_game->paddles[i]->start_ball_ammo = ammo;
	}
}

/* set the number of points required to win a PINGPONG level */
void game_set_frag_limit( int limit )
{
	cur_game->frag_limit = limit;
}

/* set whether to use convex paddle */
void game_set_convex_paddle( int convex )
{
	cur_game->paddle_is_convex = convex;
}

/* set whether balls are returned to a paddle by pressing fire.
 * the alternative is that they automatically return. */
void game_set_ball_auto_return( int auto_return )
{
	cur_game->balls_return_by_click = !auto_return;
}

/* set whether balls are fired at random angle or whether the
 * left/right fire keys are used */
void game_set_ball_random_angle( int random )
{
	cur_game->balls_use_random_angle = random;
}

/* set the speed of balls will have in accelerated state */
void game_set_ball_accelerated_speed( float speed )
{
    cur_game->accelerated_ball_speed = speed;
}

/* update state of a paddle. x or y may be 0 which is not a valid value.
 * in this case the property is left unchanged */
void game_set_paddle_state( int id, int x, int y, int left_fire, int right_fire, int return_key )
{
	Paddle *paddle = 0;

	if ( id < 0 || id >= cur_game->paddle_count )
		return;

	paddle = cur_game->paddles[id];
	if ( x != 0 ) {
		if (paddle->x != x && paddle->invis)
			paddle->invis_delay = PADDLE_INVIS_DELAY;
		paddle->x = x;
		if (paddle->x < BRICK_WIDTH)
			paddle->x = BRICK_WIDTH;
		if (paddle->x + paddle->w > 640 - BRICK_WIDTH)
			paddle->x = 640 - BRICK_WIDTH - paddle->w;
		paddle->cur_x = paddle->x;
	}
	if ( y != 0 )
		paddle->y = y;
	paddle->fire_left = left_fire;
	paddle->fire_right = right_fire;
	paddle->ball_return_key_pressed = return_key;
}

/* move objects, modify game data, store brick hits and collected extras.
 * return whether level has been finished and the id of the winning paddle
 * in network games. -1 is a draw. level_over and winner is saved in the
 * game struct. */
void game_update( int ms )
{
	int i;

	extras_update( ms );
	walls_update( ms );
	shots_update( ms );
	bricks_update( ms );
	for ( i = 0; i < cur_game->paddle_count; i++ )
	  {
		paddle_update( cur_game->paddles[i], ms );
		/* release all balls from paddle if invisible */
		if (!paddle_solid(cur_game->paddles[i]))
		    balls_detach_from_paddle( cur_game->paddles[i], ((rand()%2==1)?-1:1) );
	  }
		  
	balls_update( ms );

	/* level finished? */
	cur_game->level_over = 0;
	if ( cur_game->game_type == GT_LOCAL ) {
		/* local game */
		if ( cur_game->bricks_left == 0 ) cur_game->level_over = 1;
		if ( cur_game->balls->count == 0 ) cur_game->level_over = 1;
	} else {
		/* network game */
		if ( cur_game->level_type != LT_PINGPONG ) {
			if ( cur_game->bricks_left == 0 ) cur_game->level_over = 1;
		} 
		else
		if ( cur_game->paddles[PADDLE_TOP]->score >= cur_game->frag_limit ||
		     cur_game->paddles[PADDLE_BOTTOM]->score >= cur_game->frag_limit )
			cur_game->level_over = 1;
	}

	/* if so, determine winner */
	if ( cur_game->level_over ) {
		if ( cur_game->game_type == GT_LOCAL ) {
			if ( cur_game->bricks_left == 0 || cur_game->isBonusLevel )
				cur_game->winner = PADDLE_BOTTOM; /* praise */
			else
				cur_game->winner = PADDLE_TOP; /* swear */
		} else {
			cur_game->winner = PADDLE_BOTTOM;
			if ( cur_game->game_type == GT_NETWORK ) {
				if ( cur_game->paddles[PADDLE_TOP]->score >
						cur_game->paddles[PADDLE_BOTTOM]->score )
					cur_game->winner = PADDLE_TOP;
				else
					if ( cur_game->paddles[PADDLE_TOP]->score == 
							cur_game->paddles[PADDLE_BOTTOM]->score )
						cur_game->winner = -1;
			}
		}
	}
}

/* get the modifications that occured in game_update() */

/* get current score of player. return 0 if player does not exist */
int game_get_score( int id, int *score )
{
	if ( id < 0 || id >= cur_game->paddle_count ) return 0;
	*score = cur_game->paddles[id]->score;
	return 1;
}

/* get number of ball reflections */
int game_get_reflected_ball_count( void )
{
	return cur_game->mod.brick_reflected_ball_count+
	       cur_game->mod.paddle_reflected_ball_count;
}

/* get number of ball reflections on bricks */
int game_get_brick_reflected_ball_count( void )
{
	return cur_game->mod.brick_reflected_ball_count;
}

/* get number of ball reflections on paddle */
int game_get_paddle_reflected_ball_count( void )
{
	return cur_game->mod.paddle_reflected_ball_count;
}

/* get number of newly attached balls */
int game_get_attached_ball_count( void )
{
	return cur_game->mod.attached_ball_count;
}

/* get number of fired shots no matter which paddle */
int game_get_fired_shot_count( void )
{
	return cur_game->mod.fired_shot_count;
}

/* hit bricks since last call to game_update() */
BrickHit *game_get_brick_hits( int *count )
{
	*count = cur_game->mod.brick_hit_count;
	return cur_game->mod.brick_hits;
}

/* get a list of extras collected by paddle id */
int *game_get_collected_extras( int id, int *count )
{
	*count = 0;
	if ( id < 0 || id >= cur_game->paddle_count ) return 0;
	*count = cur_game->mod.collected_extra_count[id];
	return cur_game->mod.collected_extras[id];
}

/* get a snapshot of the level data which is the brick states
 * converted to the original file format. this can be used to
 * overwrite a levels data when player changes in alternative
 * game */
void game_get_level_snapshot( Level *shot )
{
	int i, j;
	int y_off;
	
	if ( cur_game->game_type == GT_NETWORK ) 
		y_off = ( MAP_HEIGHT - EDIT_HEIGHT ) / 2;
	else
		y_off = 1;

	for ( i = 0; i < EDIT_WIDTH; i++ )
	for ( j = 0; j < EDIT_HEIGHT; j++ ) {
		shot->bricks[i][j] = cur_game->bricks[i+1][j+y_off].brick_c;
		shot->extras[i][j] = cur_game->bricks[i+1][j+y_off].extra_c;
	}
}

/* reset the modification of game_update() */
void game_reset_mods( void )
{
	memset( &cur_game->mod, 0, sizeof( GameMod ) );
}

/* update a statistics struct by the level stats of a paddle.
 * updates the win/loss/draw as well. the played_rounds entry
 * is simply increased everytime this function is called */
void game_update_stats( int id, GameStats *stats )
{
	Paddle *paddle;
	if ( id < 0 || id >= cur_game->paddle_count ) return;
	
	/* this should be called before game_finalize() as the
	 * stats will be cleared there */
	paddle = cur_game->paddles[id];
	
	stats->total_score += paddle->score;
    if ( stats->total_score < 0 ) stats->total_score = 0;
	stats->balls_reflected += paddle->balls_reflected;
	stats->balls_lost += paddle->balls_lost;
	stats->bricks_cleared += paddle->bricks_cleared;
	stats->total_brick_count += cur_game->brick_count;
	stats->extras_collected += paddle->extras_collected;
	stats->total_extra_count += cur_game->extra_count;

	if ( cur_game->winner == -1 )
		stats->draws++;
	else
	if ( cur_game->winner == id )
		stats->wins++;
	else
		stats->losses++;
	stats->played_rounds++;
}
