/***************************************************************************
                          server_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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

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

#include "server.h"

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

extern List *games;
extern int global_id;
extern char errbuf[128];   /* used to compile error messages */
extern char msgbuf[MAX_MSG_SIZE];   /* used to compile messages */
extern int  msglen;
extern List *levelsets;
extern char net_buffer[MAX_MSG_SIZE + PACKET_HEADER_SIZE];
extern int  server_frame_delay;
extern int msg_read_pos, net_buffer_cur_size;

extern void send_info( ServerUser *user, int type, char *format, ... );
extern void channel_hide_user( ServerChannel *channel, ServerUser *user, int hide );
extern void channel_remove_user( ServerChannel *channel, ServerUser *user );
extern void send_full_update( ServerUser *user, ServerChannel *channel );

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

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

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

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

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

#ifdef NETWORK_ENABLED

/* update the position of top paddle */
static void update_bot_paddle( Game *game, int ms )
{
	int src_x, dest_x, dir;
	Ball *ball, *min_ball = 0;
	Extra *extra, *min_extra = 0;
	Paddle *paddle = game->paddles[PADDLE_TOP];
	int move = 0;
        static int entropy = 0;
        float change;
	
	/* always fire */
	paddle->fire_left = 1;
	
	/* get nearest ball */
	list_reset( game->balls );
	while ( ( ball = list_next( game->balls ) ) ) {
		if ( ball->attached ) continue;
		if ( min_ball == 0 || ball->y < min_ball->y )
			min_ball = ball;
	}
	/* get nearest extra */
	list_reset( game->extras );
	while ( ( extra = list_next( game->extras ) ) ) {
		if ( extra->dir != -1 ) continue;
		if ( min_extra == 0 || extra->y < min_extra->y )
			min_extra = extra;
	}
	
	src_x = paddle->x + paddle->w/2;
        dest_x = paddle->x + paddle->w/2;
	if ( min_ball || min_extra ) {
		if ( min_ball && ( min_extra == 0 || min_ball->y < min_extra->y ) ) {
			dest_x = min_ball->x + 6;
			move = 1;
		}
		else
		if ( min_extra && ( min_ball == 0 || min_extra->y < min_ball->y ) ) {
			dest_x = min_extra->x + 20;
			move = 1;
		}
	}
	dir = (dest_x<src_x)?-1:(dest_x>src_x)?1:0;
	
        entropy = (rand() % 17)-8;
	if ( move && dir != 0 ) {
            change = paddle->bot_vx * ms;
            /* due to high 'ms' the change might be so much that
             * the paddle would start to jump epileptically, so
             * set position to 'dest' then */
            if ( dir < 0 && src_x-change<dest_x+entropy )
                paddle->cur_x = dest_x+entropy - paddle->w/2;
            else
                if ( dir > 0 && src_x+change>dest_x-entropy )
                    paddle->cur_x = dest_x-entropy - paddle->w/2;
                else
                    paddle->cur_x += change * dir;
            if ( paddle->cur_x < BRICK_WIDTH ) 
                paddle->cur_x = BRICK_WIDTH;
            if ( paddle->cur_x + paddle->w >= 640 - BRICK_WIDTH ) 
                paddle->cur_x = 640 - BRICK_WIDTH - paddle->w;
            paddle->x = (int)paddle->cur_x;
	}
}	

static LevelSet *find_levelset( char *name )
{
	LevelSet *set;
	
	list_reset( levelsets );
	while ( (set = list_next( levelsets ) ) )
		if ( !strcmp( set->name, name ) )
			return set;
	return 0;
}

static void send_level( Level *level, ServerUser *user, int l_pos )
{
	if ( user->bot ) return;

	msgbuf[0] = MSG_LEVEL_DATA;
	msgbuf[1] = l_pos;
	msglen = 2;
	comm_pack_level( level, (unsigned char*)msgbuf, &msglen );
	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
}

static void init_next_round( ServerGame *game )
{
	game->cur_round++;
	game->cur_level = game->cur_round / game->rounds_per_level;
	game_init( game->game, game->set->levels[game->cur_level] );
	
	/* send level and wait for ready */
	game->state = SERVER_AWAIT_READY;
	game->ready[0] = game->ready[1] = 0;
	send_level( game->set->levels[game->cur_level], 
			game->users[0], PADDLE_BOTTOM );
	if ( !game->users[1]->bot )
		send_level( game->set->levels[game->cur_level], 
			game->users[1], PADDLE_TOP );
	else
		game->ready[1] = 1; /* bot is always the challenged one */
	
        /* set up bot top paddle if any */
        if ( game->users[1]->bot )
            game->game->paddles[PADDLE_TOP]->bot_vx =
                0.001 * game->users[1]->bot_level;
        
}

static void finalize_round( ServerGame *game )
{
	/* update stats */
	game_update_stats( 0, &game->stats[0] );
	game_update_stats( 1, &game->stats[1] );

	/* finalize */
	game_finalize( game->game );
	
	/* tell clients that round is over */
	if ( game->cur_round == game->rounds-1 )
		msgbuf[0] = MSG_LAST_ROUND_OVER;
	else
		msgbuf[0] = MSG_ROUND_OVER;
	msgbuf[1] = game->game->winner;
	msglen = 2;
	socket_transmit( &game->users[0]->socket, CODE_BLUE, msglen, msgbuf );
	if ( !game->users[1]->bot )
		socket_transmit( &game->users[1]->socket, CODE_BLUE, msglen, msgbuf );

	/* if this was the last round set game_over */
	if ( game->cur_round == game->rounds-1 )
		game->game_over = 1;
}

/* send game statistics were the first stats is the user it is send
 * to and the second is the opponents stats */
static void send_stats( ServerUser *user, GameStats *stats1, GameStats *stats2 )
{
	int count;
	int kept[2] = {0,0}, bricks[2] = {0,0}, extras[2] = {0,0};

	if ( user->bot ) return;
	
	count = stats1->balls_reflected + stats1->balls_lost;
	if ( count > 0 )
		kept[0] = 100 * stats1->balls_reflected / count;
	count = stats2->balls_reflected + stats2->balls_lost;
	if ( count > 0 )
		kept[1] = 100 * stats2->balls_reflected / count;
	if ( stats1->total_brick_count > 0 )
		bricks[0] = 100 * stats1->bricks_cleared / stats1->total_brick_count;
	if ( stats2->total_brick_count > 0 )
		bricks[1] = 100 * stats2->bricks_cleared / stats2->total_brick_count;
	if ( stats1->total_extra_count > 0 )
		extras[0] = 100 * stats1->extras_collected / stats1->total_extra_count;
	if ( stats2->total_extra_count > 0 )
		extras[1] = 100 * stats2->extras_collected / stats2->total_extra_count;

	msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
	msg_write_int8( MSG_GAME_STATS );
	msg_write_int8( stats1->wins );
	msg_write_int8( stats2->wins );
	msg_write_int8( stats1->losses );
	msg_write_int8( stats2->losses );
	msg_write_int8( stats1->draws );
	msg_write_int8( stats2->draws );
	msg_write_int32( stats1->total_score );
	msg_write_int32( stats2->total_score );
	msg_write_int8( kept[0] );
	msg_write_int8( kept[1] );
	msg_write_int8( bricks[0] );
	msg_write_int8( bricks[1] );
	msg_write_int8( extras[0] );
	msg_write_int8( extras[1] );

	socket_transmit( &user->socket, CODE_BLUE, msglen, msgbuf );
}

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

/* Add a new game by the context information, hide both users
 * and send a challenge message to the challenged user. */
void server_game_add( ServerChannel *channel, ServerGameCtx *ctx )
{
	ServerGame *game = salloc( 1, sizeof( ServerGame ) );

	/* copy game data */
	game->state = SERVER_AWAIT_ACCEPT;
	game->id = global_id++;
	game->channel = channel;
	game->set = find_levelset( ctx->name );
	if ( game->set == 0 ) {
		/* should never happen... */
		sprintf( errbuf, "game_create_failed: no levelset '%s' found\n", ctx->name );
		send_info( ctx->challenger, MSG_ERROR, errbuf );
		free( game );
		return;
	}
	game->rounds_per_level = ctx->rounds;
	game->rounds = game->set->count * game->rounds_per_level;
	game->cur_round = -1; /* init_next_round will increase this to 0 */
	/* create game module */
	if ( (game->game = game_create( GT_NETWORK, ctx->diff, 100/*no rel warp*/ )) == 0 ) {
		/* send error to user */
		strncpy(errbuf,"game_create failed: out of memory",128);
		send_info( ctx->challenger, MSG_ERROR, errbuf );
		free( game );
		return;
	}
	game_set_current( game->game );
	game_set_ball_ammo( ctx->balls );
	game_set_frag_limit( ctx->frags );
	game_set_convex_paddle( 1 );
	game_set_ball_random_angle( 1 );
	
	/* set game for both users and set both users for game */
	ctx->challenger->game = game;
	ctx->challenged->game = game;
	ctx->challenger->player_id = 0;
	ctx->challenged->player_id = 1;
	game->users[0] = ctx->challenger;
	game->users[1] = ctx->challenged;
	
	/* hide both users */
	channel_hide_user( channel, ctx->challenger, 1 );
	channel_hide_user( channel, ctx->challenged, 1 );

	/* inform challenged user */
	if ( !ctx->challenged->bot ) {
		msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
		msg_write_int8( MSG_CHALLENGE );
		msg_write_string( ctx->challenger->name );
		msg_write_string( ctx->name );
		msg_write_int8( ctx->diff );
		msg_write_int8( ctx->rounds );
		msg_write_int8( ctx->frags );
		msg_write_int8( ctx->balls );
		socket_transmit( &ctx->challenged->socket, CODE_BLUE, msglen, msgbuf );
	}
	else {
		/* instantly accept as bot */
		msgbuf[0] = MSG_ACCEPT_CHALLENGE;
		socket_transmit( &game->users[0]->socket, 
			CODE_BLUE, 1, msgbuf ); 
		init_next_round( game );
	}

	list_add( games, game );
	printf( _("game added: %s (%i): %i rounds: %s vs. %s\n"), 
		game->set->name, game->id, game->rounds, 
		game->users[0]->name, game->users[1]->name );
}

/* Free game memory. */
void server_game_delete( void *ptr )
{
	ServerGame *game = (ServerGame*)ptr;
	
	if ( game ) {
		printf( _("game deleted: %s (%i)\n"), game->set->name, game->id );
		
		if ( game->game )
			game_delete( &game->game );
		free( game );
	}
}

/* unhide the users to their chat channel and delete the game.
 * if game was beyond state AWAIT_ACCEPT the game stats are send
 */
void server_game_remove( ServerGame *game )
{
	int i;
	
	/* users are not unhidden if the actual game has already started
	 * as they need time to read the error messages (if any) then */
	if ( game->state == SERVER_AWAIT_ACCEPT )
		for ( i = 0; i < 2; i++ ) {
			if ( game->users[i]->hidden )
				channel_hide_user( game->channel, game->users[i], 0 );
		}
	else {
		/* send stats */
		send_stats( game->users[0], &game->stats[0], &game->stats[1] );
		send_stats( game->users[1], &game->stats[1], &game->stats[0] );

		/* and unhide bot if any */
		if ( game->users[1]->bot )
			channel_hide_user( game->channel, game->users[1], 0 );
	}

	/* clear user game pointer */
	game->users[0]->game = 0;
	game->users[1]->game = 0;
	
	/* free memory */
	list_delete_item( games, game );
}

/* void parse_packet_game
 * IN	ServerGame	*game
 * IN	ServerUser		*user
 *
 * Check all messages in packet from user who is currently within
 * a game. The header has already been successfully processed and 
 * the read pointer is at the beginning of the first message.
 */
void parse_packet_game( ServerGame *game, ServerUser *user )
{
	ServerUser *peer;
	unsigned char type;
	int handled, i;
	
	game_set_current( game->game );

	while ( 1 ) {
		type = (unsigned)msg_read_int8(); handled = 0;
		msglen = 0; /* the extract functions require a position pointer */
		
		if ( msg_read_failed() ) break; /* no more messages */

		/* general messages */
		switch ( type ) {
			case MSG_HEARTBEAT:
				/* updates the socket information automatically
				 * so connection is not closed */
				handled = 1;
				break;
			case MSG_DISCONNECT:
				/* update stats and finalize context if playing */
				if ( game->state != SERVER_AWAIT_ACCEPT ) {
					game->game->winner = -1; /* count unfinished level as draw */
					game_update_stats( 0, &game->stats[0] );
					game_update_stats( 1, &game->stats[1] );
					game_finalize( game->game );
				}
				
				if ( user == game->users[0] )
					peer = game->users[1];
				else
					peer = game->users[0];
				send_info( peer, MSG_ERROR, _("Remote player has disconnected...") );

				server_game_remove( game );
                                printf( _("%s (%i) disconnected\n"), user->name, user->id );
				channel_remove_user( game->channel, user );
				handled = 1;
				break;
			case MSG_QUIT_GAME:
				if ( user == game->users[0] )
					peer = game->users[1];
				else
					peer = game->users[0];
				send_info( peer, MSG_ERROR, _("Remote player has left the game...") );
				
				/* update stats and finalize context */
				game->game->winner = -1; /* count unfinished level as draw */
				game_update_stats( 0, &game->stats[0] );
				game_update_stats( 1, &game->stats[1] );
				game_finalize( game->game );

				server_game_remove( game );
				handled = 1;
				break;
			case MSG_UNHIDE:
				/* it's very unlikely that the user sends this
				 * message while being in the game context but to be sure
				 * he may unhide here */
				if ( user->hidden )
					channel_hide_user( game->channel, user, 0 );
				handled = 1;
				break;
		}
	
		/* challenge */
		if ( game->state == SERVER_AWAIT_ACCEPT )
		switch ( type ) {
			case MSG_ACCEPT_CHALLENGE:
				if ( user == game->users[1] ) {
					/* inform opponent */
					msgbuf[0] = MSG_ACCEPT_CHALLENGE;
					socket_transmit( &game->users[0]->socket, 
						CODE_BLUE, 1, msgbuf ); 
					
					init_next_round( game );
					handled = 1;
				}
				break;
			case MSG_REJECT_CHALLENGE:
				if ( user == game->users[1] ) {
					/* tell challenger that you refused the offer */
					msgbuf[0] = MSG_REJECT_CHALLENGE;
					socket_transmit( &game->users[0]->socket, 
						CODE_BLUE, 1, msgbuf ); 

					server_game_remove( game );
					handled = 1;
				}
				break;
			case MSG_CANCEL_GAME:
				if ( user == game->users[0] ) {
					/* tell challenged that you cancelled the offer */
					msgbuf[0] = MSG_CANCEL_GAME;
					socket_transmit( &game->users[1]->socket, 
						CODE_BLUE, 1, msgbuf ); 

					server_game_remove( game );
					handled = 1;
				}
				break;
		}

		/* preparation */
		if ( game->state == SERVER_AWAIT_READY )
		if ( type == MSG_READY ) {
			game->ready[user->player_id] = 1;
			if ( game->ready[0] && game->ready[1] ) 
				game->state = SERVER_PLAY;
			handled = 1;
		}

		/* in-game messages */
		if ( game->state == SERVER_PLAY )
		switch ( type ) {
			case MSG_PADDLE_STATE:
				comm_unpack_paddle( game->game->paddles[user->player_id], 
					(unsigned char*)net_buffer, &msg_read_pos );
				handled = 1;
				break;
			case MSG_PAUSE:
				game->state = SERVER_PAUSE;

				msgbuf[0] = MSG_PAUSE; msglen = 1;
				if ( user == game->users[0] )
					peer = game->users[1];
				else
					peer = game->users[0];
				if ( !peer->bot )
					socket_transmit( &peer->socket, CODE_BLUE, msglen, msgbuf );
				handled = 1;
				break;
		}

		/* pause messages */
		if ( game->state == SERVER_PAUSE )
		switch ( type ) {
			case MSG_UNPAUSE:
				game->state = SERVER_PLAY;
				for ( i = 0; i < game->game->paddle_count; i++ )
					game->game->paddles[i]->last_ball_contact = SDL_GetTicks();
				
				msgbuf[0] = MSG_UNPAUSE; msglen = 1;
				if ( user == game->users[0] )
					peer = game->users[1];
				else
					peer = game->users[0];
				if ( !peer->bot )
					socket_transmit( &peer->socket, CODE_BLUE, msglen, msgbuf );
				handled = 1;
				break;
			case MSG_CHATTER:
				/* client has added <user> prefix so simply pass it
				 * to the remote user */
				msg_begin_writing( msgbuf, &msglen, MAX_MSG_SIZE );
				msg_write_int8( MSG_CHATTER );
				msg_write_string( msg_read_string() );
				if ( !msg_write_failed() ) {
					if ( user == game->users[0] )
						peer = game->users[1];
					else
						peer = game->users[0];
					if ( !peer->bot )
						socket_transmit( &peer->socket, 
								CODE_BLUE, msglen, msgbuf );
				}
				handled = 1;
				break;
		}

		if ( !handled ) {
			printf( _("game %i: %s: state %i: invalid message %x: skipping %i bytes\n"),
				game->id, net_addr_to_string( &user->socket.remote_addr ),
				game->state, type, net_buffer_cur_size - msg_read_pos );
			msg_read_pos = net_buffer_cur_size;
		}
			
	}
}

/* void update_games
 * IN	int	ms	milliseconds passed since last call
 *
 * Update the objects of all games that are actually playing.
 */
void update_games( int ms )
{
	int i;
	ServerGame *game;
	
	list_reset( games );
	while ( (game = list_next( games ) ) ) {
		if ( game->state != SERVER_PLAY ) continue;
		
		game_set_current( game->game );
		game_update( ms );

		/* send updates to remote players */

		if ( game->game->level_over ) {
			finalize_round( game );
			if ( game->game_over )
				server_game_remove( game );
			else
				init_next_round( game );
			continue;
		}
	
		/* if playing against a bot update the top paddle */
		if ( game->users[1]->bot )
			update_bot_paddle( game->game, ms );
		
		/* pack update */
		msglen = 0;

		msgbuf[msglen++] = MSG_PADDLE_STATE;
		comm_pack_paddle( game->game->paddles[1], (unsigned char*)msgbuf, &msglen );
		
		msgbuf[msglen++] = MSG_BALL_POSITIONS;
		comm_pack_balls( (unsigned char*)msgbuf, &msglen );
		
		if ( game->game->shots->count > 0 ) {
			msgbuf[msglen++] = MSG_SHOT_POSITIONS;
			comm_pack_shots( (unsigned char*)msgbuf, &msglen );
		}
		
		msgbuf[msglen++] = MSG_SCORES;
		comm_pack_scores( (unsigned char*)msgbuf, &msglen );
		
		if ( game->game->mod.brick_hit_count > 0 ) {
			msgbuf[msglen++] = MSG_BRICK_HITS;
			comm_pack_brick_hits( (unsigned char*)msgbuf, &msglen );
		}

		if ( game->game->mod.collected_extra_count[0] > 0 ||
		     game->game->mod.collected_extra_count[1] > 0 ) {
			msgbuf[msglen++] = MSG_NEW_EXTRAS;
			comm_pack_collected_extras( (unsigned char*)msgbuf, &msglen );
		}

		/* send packet */
		socket_transmit( &game->users[0]->socket, CODE_BLUE, msglen, msgbuf );
		
		/* replace paddle which has a constant size */
		i = 1;
		comm_pack_paddle( game->game->paddles[0], (unsigned char*)msgbuf, &i );
		if ( !game->users[1]->bot )
			socket_transmit( &game->users[1]->socket, CODE_BLUE, msglen, msgbuf );
	
		game_reset_mods();
	}
}

#endif
