/* Dgege, entheh and Bob */
#include <string.h>
#include <math.h>

#include "main.h"
#include "menu.h"
#include "zcontrol.h"
#include "timeloop.h"
#include "level.h"
#include "player.h"
#include "game.h"
#include "screen.h"
#include "draw.h"
#include "overlay.h"
#include "tilemap.h"
#include "s_player.h"
#include "s_camera.h"
#include "s_anim_s.h"
#include "options.h"
#include "log.h"
#include "ai.h"
#include "sound2.h"

int player_wins[N_PLAYERS] = {0, 0, 0, 0},
	player_losses[N_PLAYERS] = {0, 0, 0, 0};


/* Holds who won the game */
PLAYER *player_won = NULL;

/* Set this to a positive number to end the game after some time */
int game_time_left = -1;
int game_start_time = 0;
int game_is_draw = 0;

int game_has_started = FALSE;

int game_draw_time = 0;

/* GAME *initialise_game(LEVEL *level, OUTPUTDEV *output, PLAYER *players)
 *  Initialises a game from the specified level. Set 'output' to the
 *  default device you'd like the game to be drawn into (may be the screen,
 *  or a backbuffer, or a subbitmap even). See create_output_dev() for
 *  details. Pass the player linked list.
 *  Returns NULL on failure.
 */
GAME *initialise_game(LEVEL *level, OUTPUTDEV *output, PLAYER *players) {

	int i;
	GAME *game = malloc(sizeof(GAME));
	SPRITE *spr;

	if (!game)
		return NULL;

	memset(game, 0, sizeof(GAME));

	game->output = output;
	game->map = level_initiate_tilemap(level);
    if (!game->map) {
        free(game);
        return NULL;
    }

	game->player = players;

	player_won = NULL;
    game_time_left = -1;
    game_draw_time = 0;
	game_is_draw = 0;
	game_has_started = FALSE;
	
	for (i = 0; i < N_PLAYERS; i++) {

		if (!players)
			break;

		game->sprite = add_player_sprite(game->map, game->sprite, players,
			level->player[players->id].x, level->player[players->id].y, level->player[players->id].dir);

		players = players->next;
	}

	spr = level->portalsprite;

	while (spr) {
		game->sprite = add_portal_sprite(game->map, game->sprite, spr->x, spr->y);
		spr = spr->next;
	}

	return game;
}


/* int add_player_to_game(GAME *game, PLAYER *player)
 *  Adds a player to the game. The player must already have been initialized.
 *  This will attach the player's sprite to the game. Returs 0 on success.
 *  We need the level pointer as it contains the player's starting position.
 */
int add_player_to_game(LEVEL *level, GAME *game, PLAYER *player) {

 if (game->player)
	game->player->prev = player;
 player->next = game->player;
 game->player = player;

 game->sprite = add_player_sprite(game->map, game->sprite, player,
                                  level->player[player->id].x,
                                  level->player[player->id].y,
                                  level->player[player->id].dir);

/* camera->next = player->camera; */

 return 0;
}


/* void remove_player_from_game(GAME *game, PLAYER *player)
 *  Removes a player from the game. The player's structure will not be modified.
 *  This function will also remove the player's sprite from the game.
 *  Neither the player's memory, nor its sprite will be freed.
 */
void remove_player_from_game(GAME *game, PLAYER *player) {
 PLAYER *current = game->player;
 SPRITE *current2 = game->sprite;
 while(current!=NULL) {
    if (current->next==player) {
       current->next = current->next->next;
       break;
    }       
    current = current->next;
 }
 while(current2!=NULL) {
    if (current2->next==player->sprite) {
       current2->next = current2->next->next;
       break;
    }       
    current2 = current2->next;
 }
 
}
/* int is_player_in_game(GAME *game, PLAYER *player)
 * returns TRUE if the player is in 'game'. Player's status
 * (dead, outside map, etc) is irrelevant.
 */
int is_player_in_game(GAME *game, PLAYER *player) {
 PLAYER *current = game->player;
 while(current!=NULL) {
    if (current==player)
       return 1;
    current = current->next;
 }
 return 0;
}

/* void shut_down_game(GAME *game)
 *  Call when the game has ended. This function will free all memory attached
 *  to 'game' that was created by initialize_game() or update_game().
 *  Players and sprites are not free'd, but the linked list is reset.
 *  Output devices, with their associtaed bitmaps, are also destroyed
 *  (except if bitmap == screen).
 */
void shut_down_game(GAME *game) {

	if (!game)
		return;
        
	destroy_output_devices(game->output);
	level_uninitiate_tilemap(game->map);
	destroy_all_sprites(game->sprite);
	destroy_players(game->player);

	free(game);

	return;
}

/* check_for_win(GAME *game) 
 * checks if a player won, and sets his won flag */
void check_for_win(GAME *game) {
	PLAYER *player;
	int num_players = 0;
	int num_lost = 0;

	if (game_is_draw || (player_won && player_won->hp > 0))
		return;

	player = game->player;
	while (player) {
		if (!player->sprite && player->time_of_death)
			num_lost++;
		num_players++;

		player = player->next;
	}

	/* Tie */
	if (num_players == num_lost) {
		game_time_left = 600;
		game_is_draw = 1;
		game_draw_time = game_time;
		player_won = NULL;
		LOG("Draw game @ %i, time left: %i\n", game_time, game_time_left);
	}
	else if (num_players - num_lost == 1) {
		player = game->player;
		while (player) {
			if (player->sprite && !player->time_of_death && player->hp > 0) {
				player->won = game_time;
				player_won = player;
				game_time_left = 800;
				LOG("Player %s won the game!\n", player_name[player->id]);
				break;
			}
			player = player->next;
		}
	}
	return;
}


int score_screen_update(void *data) {

	PLAYER *pl = data;
	int flag = 1;

	update_joystick();

	while (pl) {
		CONTROL *ctrl = pl->control;

		if (ctrl) {
			do {
				if (control_key(ctrl, CONTROL_KEY_FIRE) || control_key(ctrl, CONTROL_KEY_POWERUP)
					|| control_key(ctrl, CONTROL_KEY_LEFT) || control_key(ctrl, CONTROL_KEY_FORWARD)
					|| control_key(ctrl, CONTROL_KEY_RIGHT) || control_key(ctrl, CONTROL_KEY_BACKWARD)
					|| (keypressed() && (readkey() >> 8) == KEY_ESC))
					return 1;
			} while (control_ack(ctrl));
			flag = 0;
		}

		pl = pl->next;
	}

	if (!flag) {
		if (game_time > 300 || (keypressed() && (readkey() >> 8) == KEY_ESC))
			return 1;
	}

	return 0;
}

void score_screen_draw(void *foo) {

	int w = 0;
	int i;
	BITMAP *bmp;
	FONT *f;

	(void)foo;

#ifndef ZIG_GL
	bmp = get_screen_buffer();
	clear(bmp);

	if (bmp->w < 400)
		f = font;
	else
		f = LargeFont;

	w = MAX(text_length(f, player_name[0]), w);
	w = MAX(text_length(f, player_name[1]), w);
	w = MAX(text_length(f, player_name[2]), w);
	w = MAX(text_length(f, player_name[3]), w);
	w += text_length(f, "0000  0000");

	textout(bmp, f, " Wins  Losses", (bmp->w - w) / 2, bmp->h / 3, makecol(255, 255, 255));

	if (game_time < 15)
		return;

	hline(bmp, bmp->w / 2 - w, bmp->h / 3 + text_height(f), (bmp->w + w) / 2, makecol(192, 192, 192));
	vline(bmp, (bmp->w - w) / 2, bmp->h / 3, bmp->h / 3 + text_height(f) * (N_PLAYERS + 1), makecol(192, 192, 192));

	for (i = 0; i < N_PLAYERS; i++) {

		if (game_time < (i + 1) * 15)
			break;

		textout_right(bmp, f, player_name[i], (bmp->w - w) / 2 - 10,
			bmp->h / 3 + text_height(f) * (i + 1), makecol(255, 255, 255));

		textprintf(bmp, f, (bmp->w - w) / 2, bmp->h / 3 + text_height(f) * (i + 1),
			makecol(255, 255, 255),	"%4i  %4i", player_wins[i], player_losses[i]);
	}
	free_screen_buffer();
#else
#endif

	return;
}



void show_scores(PLAYER *pl_list) {

	timeloop(BPS_TO_TIMER(100), 30, pl_list,
          &score_screen_update,
          &score_screen_draw);
}



/* int update_game(GAME *game)
 *  Update the game logic. This will be called at 100 Hz on average. It should update
 *  all the physics (positions, collision detection), and the status of players.
 *  Returns 1 on game end.
 */
int update_game(void *data) {
 GAME *game = data;

 update_joystick();
 
 update_ai(game, game->sprite);

 update_tilemap(game->map);

 interact_sprites(game->map, game->sprite);
 update_sprites(game->map, &game->sprite); /* Players are handled here. */

 /* Check who won */
 check_for_win(game);

 if (game_time > 400)
	 game_has_started = TRUE;

/*
 update_output_devices(game->output);
*/

 while (keypressed()) {
  int k = readkey();

  if (k >> 8 == KEY_ESC) return 1;
 }

 if (game_time_left != -1)
	game_time_left--;
 if (!game_time_left)
	 return 1;

 return 0;
}



void player_camera_matrix(SPRITE *camspr, MATRIX_f *mtx) {

/* Unoptimised version:

  float xr =  cos(camspr->th);
  float yr =  sin(camspr->th);
  float zr =  0;

  float xf =  SIN45 * sin(camspr->th);
  float yf = -SIN45 * cos(camspr->th);
  float zf = -SIN45;

  float xu =  VIEW_ASPECT * xf;
  float yu =  VIEW_ASPECT * yf;
  float zu =  VIEW_ASPECT * SIN45;

  mtx->v[0][0] = xr;
  mtx->v[0][1] = yr;
  mtx->v[0][2] = zr;

  mtx->v[1][0] = xu;
  mtx->v[1][1] = yu;
  mtx->v[1][2] = zu;

  mtx->v[2][0] = xf;
  mtx->v[2][1] = yf;
  mtx->v[2][2] = zf;

  mtx->t[0] = - (xr*camspr->x + yr*camspr->y + zr*camspr->z);
  mtx->t[1] = - (xu*camspr->x + yu*camspr->y + zu*camspr->z);
  mtx->t[2] = - (xf*camspr->x + yf*camspr->y + zf*camspr->z);
*/

#define DEG_2_RAD(x) ((x) * M_PI / 180)

  float xr =  cos(camspr->th);
  float yr =  sin(camspr->th);
  float zr =  0;

  float xf =  sin(DEG_2_RAD(30)) * sin(camspr->th);
  float yf = -sin(DEG_2_RAD(30)) * cos(camspr->th);
  float zf = -sin(DEG_2_RAD(60));

  float xu =  -VIEW_ASPECT * xf * sin(DEG_2_RAD(60)) / sin(DEG_2_RAD(30));
  float yu =  -VIEW_ASPECT * yf * sin(DEG_2_RAD(60)) / sin(DEG_2_RAD(30));
  float zu =  -VIEW_ASPECT * sin(DEG_2_RAD(30));

  mtx->v[0][0] = xr;
  mtx->v[0][1] = yr;
  mtx->v[0][2] = zr;

  mtx->v[1][0] = xu;
  mtx->v[1][1] = yu;
  mtx->v[1][2] = zu;

  mtx->v[2][0] = xf;
  mtx->v[2][1] = yf;
  mtx->v[2][2] = zf;

  mtx->t[0] = - (xr*camspr->x + yr*camspr->y + zr*camspr->z);
  mtx->t[1] = - (xu*camspr->x + yu*camspr->y + zu*camspr->z);
  mtx->t[2] = - (xf*camspr->x + yf*camspr->y + zf*camspr->z);
}



/* void draw_game(GAME *game)
 *  This function will draw the game in the specified bitmap.
 *  The rendering will be done for every output device in the
 *  list. The polygons are transformed and clipped according to
 *  the output dev's camera, then rasterized on the output bitmap.
 *  This function will also draw the overlay (player stats), and
 *  game state texts (such as "Game Over").
 */
void draw_game(void *data) {
 GAME *game = data;

 /* NOTE: OUTPUTDEV needs changing. The bitmap cannot be specified in the way
    it is. Better would be dimensions into which to draw. This is because the
    'scrbuf' bitmap is only available while drawing.
 */

 MATRIX_f cam;
 OUTPUTDEV *output = game->output;
 BITMAP *sub;
 BITMAP *bmp = get_screen_buffer();

 while (output) {

  int x = INT(output->x / 65536.0 * bmp->w);
  int y = INT(output->y / 65536.0 * bmp->h);

  sub = create_sub_bitmap(bmp, x, y,
                             INT((output->x + output->w) / 65536.0 * bmp->w),
							 INT((output->y + output->h) / 65536.0 * bmp->h));

  player_camera_matrix(output->player->camspr, &cam);

  draw_level(sub, game->map, game->sprite, &cam,
                                      (int)floor(output->player->camspr->x),
                                      (int)floor(output->player->camspr->y),
									  output->player->camspr->z,
                                      output->player->camspr->th);

  draw_overlay(sub, output->player, game->player);

  destroy_bitmap(sub);

  output = output->next;
 }

 free_screen_buffer();
}


/* OUTPUTDEV *create_output_device(BITMAP *bitmap, PLAYER *player);
 *  Creates a device to output to. It also links in the player for
 *  displaying his stats. x, y, w and h define the area of the bitmap on to
 *  which to draw, and are resolution-independent; the screen is considered
 *  to be sized 65536 x 65536 when setting up these coordinates. Returns a
 *  pointer to an output device, or NULL on error.
 */
OUTPUTDEV *create_output_device(int x, int y, int w, int h, PLAYER *player) {

	OUTPUTDEV *dev = malloc(sizeof(OUTPUTDEV));

	if (!dev) exit(37);

	memset(dev, 0, sizeof(OUTPUTDEV));

	dev->x = x;  dev->y = y;
	dev->w = w;  dev->h = h;
	dev->player = player;

	return dev;
}


void destroy_output_device(OUTPUTDEV *dev) {

	if (dev)
		free(dev);
}

void destroy_output_devices(OUTPUTDEV *dev) {

	OUTPUTDEV *next;

	while (dev) {
		next = dev->next;
		destroy_output_device(dev);
		dev = next;
	}

	return;
}

/* int add_output_device(GAME *game, OUTPUTDEV *device)
 *  Adds an output device to the game. Returns 0 on success.
 */
int add_output_device(GAME *game, OUTPUTDEV *device) {

	if (!game || !device)
		return -1;

	device->next = game->output;
	game->output = device;

	return 0;
}



int chosen_level = 0;



/* mkc_play_game() is used in m_game.h. mkc_test_zig() is used in m_option.h.
   While mkc_play_game() should use the player allocation set up by the game
   menu (m_game.h), mkc_test_zig() should set up a game between four Zig AI
   players.
*/
int mkc_play_game(void) {

 int p;
 GAME *game;
 OUTPUTDEV *output;
 PLAYER *player;
 LEVEL *level;
 BITMAP *bmp;
 CONTROL *pl1_ctrl = NULL;

 PLAYER *human_player[N_PLAYERS] = {NULL, NULL, NULL, NULL};
 PLAYER *ai_player[N_PLAYERS] = {NULL, NULL, NULL, NULL};
 int num_humans = 0, num_ai = 0;

 /* Check if there are any players */
 for (p = 0; p < N_PLAYERS; p++) {
	 if (ControlDeviceType[player_control_device[p]] != CONTROL_TYPE_OFF)
		 break;
 }
 if (p >= N_PLAYERS) {

	BITMAP *temp = get_screen_buffer();
	BITMAP *temp2 = create_bitmap(320, 200);
    if (!temp2) exit(37);
	blit(temp, temp2, 160, 140, 0, 0, 320, 200);
	rectfill(temp, 160, 140, 480, 340, makecol(0, 0, 0));
	rect(temp, 160, 140, 480, 340, makecol(255, 255, 0));
	textout_centre(temp, LargeFont, "You must have at least", 320, 180, makecol(255, 255, 255));
	textout_centre(temp, LargeFont, "one player to play.", 320, 200, makecol(255, 255, 255));
	textout_centre(temp, LargeFont, "Press any key to continue.", 320, 240, makecol(255, 255, 255));
	free_screen_buffer();

	while (keypressed()) readkey();
	readkey();

    temp = get_screen_buffer();
	blit(temp2, temp, 0, 0, 160, 120, 320, 200);
	free_screen_buffer();

	 return 0;
 }


 stop_timeloop();
 shut_down_menu_system();

 level = the_levels[chosen_level];
 ASSERT(level);

 /* Switch to new screen mode */
 /* TODO: check for error here */
 if (resolution_w[resolution_id] != SCREEN_W || resolution_h[resolution_id] != SCREEN_H)
	 create_screen(resolution_w[resolution_id], resolution_h[resolution_id]);
 set_palette(GamePal);

 initialise_draw();

 LOG(" Game is being init...");
 game = initialise_game(level, 0, 0);
 if (!game) exit(37);
 LOG("[ok]\n");

 LOG(" Setting up players...");
	for (p = 0; p < N_PLAYERS; p++) {

		int t = ControlDeviceType[player_control_device[p]];

		if (t == CONTROL_TYPE_OFF || t == CONTROL_TYPE_UNKNOWN)
			continue;

		player = create_player(p);
        if (!player) exit(37);
		set_config_file("bomb.ini");
		player->control = load_control_config(player_orig_name[p], t);

		add_player_to_game(level, game, player);

		/* Is he human? */
		if (t != CONTROL_TYPE_ZIG_AI) {
			human_player[num_humans] = player;
			num_humans++;
			if (!pl1_ctrl)
				pl1_ctrl = player->control;
		}
		else {
			ai_player[num_ai] = player;
			num_ai++;
		}
	}
	LOG("[ok]\n");

	/* Create devices for 1, 2, 3 or 4 players */

	if (num_humans == 0) {
		num_humans = num_ai;
		for (p = 0; p < num_ai; p++)
			human_player[p] = ai_player[p];
	}

	if (num_humans == 1) {
		output = create_output_device(0, 0, 65536, 65536, human_player[0]);
		add_output_device(game, output);
	}
	else if (num_humans == 2) {
		output = create_output_device(0, 0, 65536, 32768, human_player[0]);
		add_output_device(game, output);
		output = create_output_device(0, 32768, 65536, 32768, human_player[1]);
		add_output_device(game, output);
	}
	else if (num_humans == 3) {
		output = create_output_device(0, 0, 32768, 32768, human_player[0]);
		add_output_device(game, output);
		output = create_output_device(32768, 0, 32768, 32768, human_player[1]);
		add_output_device(game, output);
		output = create_output_device(0, 32768, 32768, 32768, human_player[2]);
		add_output_device(game, output);
	}
	else if (num_humans == 4) {
		output = create_output_device(0, 0, 32768, 32768, human_player[0]);
		add_output_device(game, output);
		output = create_output_device(32768, 0, 32768, 32768, human_player[1]);
		add_output_device(game, output);
		output = create_output_device(0, 32768, 32768, 32768, human_player[2]);
		add_output_device(game, output);
		output = create_output_device(32768, 32768, 32768, 32768, human_player[3]);
		add_output_device(game, output);
	}

	/* Add cameras */
	output = game->output;
	while (output) {
		game->sprite = add_camera_sprite(game->map, game->sprite, output);
		output = output->next;
	}

 {
  SPRITE *sprite = game->sprite;
  while (sprite) {

   if (sprite->type == SPRITE_TYPE_PLAYER) {

    SPRITE *camspr = ((PLAYER *)sprite->data)->camspr;
    CAMERA *camera = camspr ? camspr->data : NULL;

	if (camera) {
		camera_set_target(game->map, camera, sprite);

		camspr->x = camera->xt;
		camspr->y = camera->yt;
		camspr->z = camera->zt;
		camspr->th = camera->tht;
	}
   }

   sprite = sprite->next;
  }
 }

 sound_select_game(game);


 LOG(" Activating control...");
 activate_control();
 LOG("[ok]\n");

 /* Draw logo background */
 LOG(" Drawing background.\n");
 bmp = get_screen_buffer();
 stretch_blit(Logo, bmp, 0, 0, Logo->w, Logo->h, 0, 0, SCREEN_W/2, SCREEN_H/2);
 stretch_blit(Logo, bmp, 0, 0, Logo->w, Logo->h, SCREEN_W/2, 0, SCREEN_W/2, SCREEN_H/2);
 stretch_blit(Logo, bmp, 0, 0, Logo->w, Logo->h, 0, SCREEN_H/2, SCREEN_W/2, SCREEN_H/2);
 stretch_blit(Logo, bmp, 0, 0, Logo->w, Logo->h, SCREEN_W/2, SCREEN_H/2, SCREEN_W/2, SCREEN_H/2);
 /* Don't copy back the logos untill the game starts */
 //free_screen_buffer(bmp);

 LOG(" Starting main loop.\n");

 timeloop(BPS_TO_TIMER(100), 30, game, &update_game, &draw_game);

 LOG(" Main loop is over, deactivating.\n");

	/* Adjust scores */
	if (player_won) {
		player = game->player;
		while (player) {
			if (player->won)
				player_wins[player->id]++;
			else
				player_losses[player->id]++;

			player = player->next;
		}
	}

 show_scores(game->player);

 sound_select_game(NULL);

 shut_down_game(game);

 shut_down_draw();

 clear_keybuf();

 deactivate_control();

 /* Revert to 640x480 */
 if (resolution_w[resolution_id] != 640 || resolution_h[resolution_id] != 480)
	 create_screen(640, 480);
 set_palette(GamePal);

 clear(screen);

 initialise_menu_system();
 restart_timeloop();

 return 0;
}

