#include "move.h"

#include <unistd.h>
#include <stdlib.h>
#include <assert.h>

#include "dungeon.h"
#include "heap.h"
#include "move.h"
#include "npc.h"
#include "pc.h"
#include "character.h"
#include "utils.h"
#include "path.h"
#include "event.h"
#include "io.h"
#include "npc.h"
#include "object.h"

void do_combat(dungeon *d, character *atk, character *def)
{
  uint32_t damage, i;
  const char *organs[] = {
    "liver",
    "pancreas",
    "heart",
    "brain",
    "eye",
    "arm",
    "leg",
    "intestines",
    "gall bladder",
    "lungs",
    "hand",
    "foot",
    "spinal cord",
    "pituitary gland",
    "thyroid",
    "tongue",
    "bladder",
    "diaphram",
    "frontal lobe",
    "hippocampus",
    "stomach",
    "pharynx",
    "esophagus",
    "trachea",
    "urethra",
    "spleen",
    "cerebellum",
    "ganglia",
    "ear",
    "subcutaneous tissue",
    "prefrontal cortex"
  };
  const char *attacks[] = {
    "punches",
    "kicks",
    "stabs",
    "impales",
    "slashes",
    "massages",
    "soothes",
    "bites",
    "jabs",
    "coerces",
    "threatens",
    "manipulates",
    "arm locks",
    "conquers",
    "buries the hatchet in",
    "indicates displeasure with",
    "quarrels with",
    "scrimmages with",
    "tickles",
    "engages in fisticuffs with",
    "strikes",
    "belts",
    "wallops",
    "gives the old one-two to",
    "bumps into",
    "behaves inappropriately with",
    "smacks",
    "body slams",
    "fondues",
    "flambes",
    "pokes",
    "anoints",
  };
  if (character_is_alive(def)) {
    if (atk != d->PC) {
      damage = atk->damage->roll();
      io_queue_message("%s%s %s your %s for %d.", is_unique(atk) ? "" : "The ",
                       atk->name, attacks[rand() % (sizeof (attacks) /
                                                    sizeof (attacks[0]))],
                       organs[rand() % (sizeof (organs) /
                                        sizeof (organs[0]))], damage);
    } else {
      for (i = damage = 0; i < num_eq_slots; i++) {
        if (i == eq_slot_weapon && !d->PC->eq[i]) {
          damage += atk->damage->roll();
        } else if (d->PC->eq[i]) {
          damage += d->PC->eq[i]->roll_dice();
        }
      }
      io_queue_message("You hit %s%s for %d.", is_unique(def) ? "" : "the ",
                       def->name, damage);
    }

    if (damage >= def->hp) {
      if (atk != d->PC) {
        io_queue_message("You die.");
        io_queue_message("As %s%s eats your %s,", is_unique(atk) ? "" : "the ",
                         atk->name, organs[rand() % (sizeof (organs) /
                                                     sizeof (organs[0]))]);
        io_queue_message("   ...you wonder if there is an afterlife.");
        /* Queue an empty message, otherwise the game will not pause for *
         * player to see above.                                          */
        io_queue_message("");
      } else {
        io_queue_message("%s%s dies.", is_unique(def) ? "" : "The ", def->name);
      }
      def->hp = 0;
      def->alive = 0;
      character_increment_dkills(atk);
      character_increment_ikills(atk, (character_get_dkills(def) +
                                       character_get_ikills(def)));
      if (def != d->PC) {
        d->num_monsters--;
      }
      charpair(def->position) = NULL;
    } else {
      def->hp -= damage;
    }
  }
}

void move_character(dungeon *d, character *c, pair_t next)
{
  int can_see_atk, can_see_def;
  pair_t displacement;
  uint32_t found_cell;
  pair_t order[9] = {
    { -1, -1 },
    { -1,  0 },
    { -1,  1 },
    {  0, -1 },
    {  0,  0 },
    {  0,  1 },
    {  1, -1 },
    {  1,  0 },
    {  1,  1 },
  };
  uint32_t s, i;

  if (charpair(next) &&
      ((next[dim_y] != c->position[dim_y]) ||
       (next[dim_x] != c->position[dim_x]))) {
    if ((charpair(next) == d->PC) ||
        c == d->PC) {
      do_combat(d, c, charpair(next));
    } else {
      /* Easiest way for a monster to displace another monster is *
       * to swap them.  This could lead to some strangeness where *
       * two monsters of the exact same speed continually         *
       * displace each other and never make progress, but I don't *
       * have any real problem with that.  When we have better    *
       * game balance, weaker monsters should not be able to      *
       * displace stronger monsters.                              */
      /* Turns out I don't like swapping them after all.  We'll   *
       * instead select a random square from the 8 surrounding    *
       * the target cell.  Keep doing it until either we swap or  *
       * find an empty one for the displacement.                  */
      for (s = rand() % 9, found_cell = i = 0;
           i < 9 && !found_cell; i++) {
        displacement[dim_y] = next[dim_y] + order[s % 9][dim_y];
        displacement[dim_x] = next[dim_x] + order[s % 9][dim_x];
        if (((npc *) charpair(next))->characteristics & NPC_PASS_WALL) {
          if (!charpair(displacement) ||
              (charpair(displacement) == c)) {
            found_cell = 1;
          }
        } else {
          if ((!charpair(displacement) &&
               (mappair(displacement) >= ter_floor)) ||
              (charpair(displacement) == c)) {
            found_cell = 1;
          }
        }
      }

      if (!found_cell) {
        return;
      }

      assert(charpair(next));

      can_see_atk = can_see(d, character_get_pos(d->PC),
                            character_get_pos(c), 1, 0);
      can_see_def = can_see(d, character_get_pos(d->PC),
                            character_get_pos(charpair(next)), 1, 0);

      if (can_see_atk && can_see_def) {
        io_queue_message("%s%s pushes %s%s out of the way.  How rude.",
                         is_unique(c) ? "" : "The ", c->name,
                         is_unique(charpair(next)) ? "" : "the ",
                         charpair(next)->name);
      } else if (can_see_atk) {
        io_queue_message("%s%s angrily shoves something out of the way.",
                         is_unique(c) ? "" : "The ", c->name);
      } else if (can_see_def) {
        io_queue_message("Something slams %s%s out of the way.",
                          is_unique(charpair(next)) ? "" : "the ",
                         charpair(next)->name);
      }

      charpair(c->position) = NULL;
      charpair(displacement) = charpair(next);
      charpair(next) = c;
      charpair(displacement)->position[dim_y] = displacement[dim_y];
      charpair(displacement)->position[dim_x] = displacement[dim_x];
      c->position[dim_y] = next[dim_y];
      c->position[dim_x] = next[dim_x];
    }
  } else {
    /* No character in new position. */

    d->character_map[c->position[dim_y]][c->position[dim_x]] = NULL;
    c->position[dim_y] = next[dim_y];
    c->position[dim_x] = next[dim_x];
    d->character_map[c->position[dim_y]][c->position[dim_x]] = c;
  }

  if (c == d->PC) {
    pc_reset_visibility((pc *) c);
    pc_observe_terrain((pc *) c, d);
  }
}

void do_moves(dungeon *d)
{
  pair_t next;
  character *c;
  event *e;

  /* Remove the PC when it is PC turn.  Replace on next call.  This allows *
   * use to completely uninit the heap when generating a new level without *
   * worrying about deleting the PC.                                       */

  if (pc_is_alive(d)) {
    /* The PC always goes first one a tie, so we don't use new_event().  *
     * We generate one manually so that we can set the PC sequence       *
     * number to zero.                                                   */
    e = (event *) malloc(sizeof (*e));
    e->type = event_character_turn;
    /* Hack: New dungeons are marked.  Unmark and ensure PC goes at d->time, *
     * otherwise, monsters get a turn before the PC.                         */
    if (d->is_new) {
      d->is_new = 0;
      e->time = d->time;
    } else {
      e->time = d->time + (1000 / d->PC->speed);
    }
    e->sequence = 0;
    e->c = d->PC;
    heap_insert(&d->events, e);
  }

  while (pc_is_alive(d) &&
         (e = (event *) heap_remove_min(&d->events)) &&
         ((e->type != event_character_turn) || (e->c != d->PC))) {
    d->time = e->time;
    if (e->type == event_character_turn) {
      c = e->c;
    }
    if (!c->alive) {
      if (d->character_map[c->position[dim_y]][c->position[dim_x]] == c) {
        d->character_map[c->position[dim_y]][c->position[dim_x]] = NULL;
      }
      if (c != d->PC) {
        event_delete(e);
      }
      continue;
    }

    npc_next_pos(d, (npc *) c, next);
    move_character(d, (npc *) c, next);

    heap_insert(&d->events, update_event(d, e, 1000 / c->speed));
  }

  io_display(d);
  if (pc_is_alive(d) && e->c == d->PC) {
    c = e->c;
    d->time = e->time;
    /* Kind of kludgey, but because the PC is never in the queue when   *
     * we are outside of this function, the PC event has to get deleted *
     * and recreated every time we leave and re-enter this function.    */
    e->c = NULL;
    event_delete(e);
    io_handle_input(d);
  }
}

void dir_nearest_wall(dungeon *d, character *c, pair_t dir)
{
  dir[dim_x] = dir[dim_y] = 0;

  if (c->position[dim_x] != 1 && c->position[dim_x] != DUNGEON_X - 2) {
    dir[dim_x] = (c->position[dim_x] > DUNGEON_X - c->position[dim_x] ? 1 : -1);
  }
  if (c->position[dim_y] != 1 && c->position[dim_y] != DUNGEON_Y - 2) {
    dir[dim_y] = (c->position[dim_y] > DUNGEON_Y - c->position[dim_y] ? 1 : -1);
  }
}

uint32_t against_wall(dungeon *d, character *c)
{
  return ((mapxy(c->position[dim_x] - 1,
                 c->position[dim_y]    ) == ter_wall_immutable) ||
          (mapxy(c->position[dim_x] + 1,
                 c->position[dim_y]    ) == ter_wall_immutable) ||
          (mapxy(c->position[dim_x]    ,
                 c->position[dim_y] - 1) == ter_wall_immutable) ||
          (mapxy(c->position[dim_x]    ,
                 c->position[dim_y] + 1) == ter_wall_immutable));
}

uint32_t in_corner(dungeon *d, character *c)
{
  uint32_t num_immutable;

  num_immutable = 0;

  num_immutable += (mapxy(c->position[dim_x] - 1,
                          c->position[dim_y]    ) == ter_wall_immutable);
  num_immutable += (mapxy(c->position[dim_x] + 1,
                          c->position[dim_y]    ) == ter_wall_immutable);
  num_immutable += (mapxy(c->position[dim_x]    ,
                          c->position[dim_y] - 1) == ter_wall_immutable);
  num_immutable += (mapxy(c->position[dim_x]    ,
                          c->position[dim_y] + 1) == ter_wall_immutable);

  return num_immutable > 1;
}

static void new_dungeon_level(dungeon *d, uint32_t dir)
{
  /* Eventually up and down will be independantly meaningful. *
   * For now, simply generate a new dungeon.                  */

  switch (dir) {
  case '<':
    io_queue_message("You go up the stairs.");
    io_queue_message(""); /* To force "more" */
    io_display(d); /* To force queue flush */
    new_dungeon(d);
    break;
  case '>':
    io_queue_message("You go down the stairs.");
    io_queue_message(""); /* To force "more" */
    io_display(d); /* To force queue flush */
    new_dungeon(d);
    break;
  default:
    break;
  }
}


uint32_t move_pc(dungeon *d, uint32_t dir)
{
  pair_t next;
  uint32_t was_stairs = 0;
  const char *wallmsg[] = {
    "There's a wall in the way.",
    "BUMP!",
    "Ouch!",
    "You stub your toe.",
    "You can't go that way.",
    "You admire the engravings.",
    "Are you drunk?"
  };

  next[dim_y] = d->PC->position[dim_y];
  next[dim_x] = d->PC->position[dim_x];


  switch (dir) {
  case 1:
  case 2:
  case 3:
    next[dim_y]++;
    break;
  case 4:
  case 5:
  case 6:
    break;
  case 7:
  case 8:
  case 9:
    next[dim_y]--;
    break;
  }
  switch (dir) {
  case 1:
  case 4:
  case 7:
    next[dim_x]--;
    break;
  case 2:
  case 5:
  case 8:
    break;
  case 3:
  case 6:
  case 9:
    next[dim_x]++;
    break;
  case '<':
    if (mappair(d->PC->position) == ter_stairs_up) {
      was_stairs = 1;
      new_dungeon_level(d, '<');
    }
    break;
  case '>':
    if (mappair(d->PC->position) == ter_stairs_down) {
      was_stairs = 1;
      new_dungeon_level(d, '>');
    }
    break;
  }

  if (was_stairs) {
    return 0;
  }

  if ((dir != '>') && (dir != '<') && (mappair(next) >= ter_floor)) {
    move_character(d, d->PC, next);
    dijkstra(d);
    dijkstra_tunnel(d);
    d->PC->pick_up(d);

    return 0;
  } else if (mappair(next) < ter_floor) {
    io_queue_message(wallmsg[rand() % (sizeof (wallmsg) /
                                       sizeof (wallmsg[0]))]);
    io_display(d);
  }

  return 1;
}