#include <cstdint>
#include <iostream>
#include <cstdlib>

#include "character.h"
#include "heap.h"
#include "util.h"
#include "distance_map.h"

static int32_t character_turn_cmp(const void *key, const void *with) {
  if (((character *) key)->next_turn == ((character *) with)->next_turn) {
    return ((character *) key)->seq_num - ((character *) with)->seq_num;
  } else {
    return ((character *) key)->next_turn - ((character *) with)->next_turn;
  }
}

char characteristics_symbols[] = "0123456789abcdef";

character::character(uint8_t x, uint8_t y) {
  pos.x = x;
  pos.y = y;
  
  speed = 0;
  seq_num = 0;
  next_turn = 0;
  alive = 1;
  symbol = ' ';
}

character::~character() { }

bool character::is_player() {
  return false;
}

int character::has_characteristic(int bit) {
  return 0;
}

monster::monster(uint8_t x, uint8_t y) : character(x, y) {
  static int next_seq = 1;
  uint32_t characteristics = rand() & 0x0000000f;
  
  this->characteristics = characteristics;
  
  // Initialize the starting "last seen" position to the position of the monster
  // This will keep smart monsters from moving until they actually see the player
  last_seen.x = x;
  last_seen.y = y;
  
  speed = randrange(5, 20);
  seq_num = next_seq++;
  symbol = characteristics_symbols[characteristics];
}

bool monster::is_player() {
  return false;
}

int monster::has_characteristic(int bit) {
  return characteristics & bit;
}

// Erratic tunneling monsters don't care about anything but the border
position monster::next_monster_move_tunnel_erratic(dungeon &d) {
	position next_move;
	next_move = pos;
	
	while(next_move.x == pos.x && next_move.y == pos.y) {
		next_move.x = randrange(max(1, pos.x - 1), min(MAP_WIDTH-2, pos.x + 1));
		next_move.y = randrange(max(1, pos.y - 1), min(MAP_HEIGHT-2, pos.y + 1));
	}
	
	return next_move;
}

// Erratic non-tunneling monsters have to pick a random open space
position monster::next_monster_move_erratic(dungeon &d) {	
	position next_move;
	next_move = pos;
	
	while(next_move.x == pos.x && next_move.y == pos.y && !d.hardness[next_move.y][next_move.x]) {
		next_move.x = randrange(max(1, pos.x - 1), min(MAP_WIDTH-2, pos.x + 1));
		next_move.y = randrange(max(1, pos.y - 1), min(MAP_HEIGHT-2, pos.y + 1));
	}
	
	return next_move;
}

// Smart tunneling monsters move toward the last known position of the player following
// the tunneling distance map. Smart non-tunneling monsters do the same but use the non-tunneling
// distance map
position monster::next_monster_move_smart(dungeon &d) {
	int i, j;
	
	uint32_t dist_to_target[MAP_HEIGHT][MAP_WIDTH];
	dijkstra_from_pos(d, dist_to_target, (has_characteristic(TUNNEL)) ? 255 : 1, last_seen.x, last_seen.y);
		
	position best = {pos.x, pos.y};
	uint32_t best_dist = dist_to_target[pos.y][pos.x];
	for (i = max(0, pos.y-1); i < min(MAP_WIDTH, pos.y+2); i++) {
		for (j = max(0, pos.x-1); j < min(MAP_WIDTH, pos.x+2); j++) {
			if (dist_to_target[i][j] < best_dist || (dist_to_target[i][j] == best_dist && i == pos.y)) {
				best_dist = dist_to_target[i][j];
				best.x = j;
				best.y = i;
			}
		}
	}
	
	return best;
}

// Dumb tunneling monsters move directly toward the player position if they know it, and
// move erratically otherwise
// Dumb non-tunneling monsters do the same, but can't tunnel
position monster::next_monster_move_dumb(dungeon &d) {
	if (last_seen.x == pos.x && last_seen.y == pos.y) {
		if (has_characteristic(TUNNEL)) {
			return next_monster_move_tunnel_erratic(d);
		} else {
			return next_monster_move_erratic(d);
		}
	} else {
		position next_move;
		next_move.x = pos.x + sign(last_seen.x - pos.x);
		next_move.y = pos.y + sign(last_seen.y - pos.y);
		return next_move;
	}
}

position monster::monster_move(dungeon &d) {
	if (is_player()) {
		return pos;
	}
	
	// Handle telepathic monsters
	
	// If the monster is telepathic, update the position of the PC
	// Otherwise if it can currently see the PC then update the position
	// Otherwise if it is not smart then forget the pc position
	if (has_characteristic(TELE)) {
		last_seen = d.player_pos;
	} else if (d.pc_sight[pos.y][pos.x]) {
		last_seen = d.player_pos;
	} else if (!(has_characteristic(SMART))) {
		last_seen = pos;
	}
	
	// Handle erratic monsters
			
	// If the monster is erratic, then 50% chance to move randomly
	// Movement based on whether it can tunnel or not
	if (has_characteristic(ERRATIC) && randchance(0.5)) {
		if (has_characteristic(TUNNEL)) {
			return next_monster_move_tunnel_erratic(d);
		} else {
			return next_monster_move_erratic(d);
		}
	}
	
	// Handle smart/dumb monsters
	if (has_characteristic(SMART)) {
		return next_monster_move_smart(d);
	} else {
		return next_monster_move_dumb(d);
	}
		
	return pos;
}

player_character::player_character(uint8_t x, uint8_t y) : character(x, y){
  speed = 10;
  seq_num = 0;
  symbol = '@';
}

bool player_character::is_player() {
  return true;
}

int player_character::has_characteristic(int bit) {
  return 0;
}

void init_character_turn_heap(heap_t *h) {
  heap_init(h, character_turn_cmp, NULL);
}