#include <kernel/kernel.h>

#include <phantasmal/log.h>
#include <phantasmal/exit.h>
#include <phantasmal/map.h>
#include <phantasmal/lpc_names.h>

#include <type.h>

inherit PHRASE_REPOSITORY;

private int*    exit_segments;
private mapping name_for_dir;
private mapping shortname_for_dir;
private mapping builder_directions;

/* When loading files, this lets us resolve room numbers *after* all rooms
   load... */
private mixed*  deferred_add_newexit;

/* Prototypes */
private void add_complex_exit_by_unq(int roomnum1, mixed value);
void upgraded(varargs int clone);

#define PHR(x) PHRASED->new_simple_english_phrase(x)
#define FILE(x) PHRASED->file_phrase(EXITD_PHRASES,(x))

#define EXITD_PHRASES       "/usr/common/sys/exitd.phr"

static void create(varargs int clone) {
  ::create(clone);

  if(!find_object(SIMPLE_EXIT))
    compile_object(SIMPLE_EXIT);

  exit_segments = ({ });

  deferred_add_newexit = ({ });

  upgraded();

  if(!load_filemanaged_file(EXITD_PHRASES)) {
    error("Can't load ExitD phrase file!");
  }
}

void upgraded(varargs int clone) {
  if(!SYSTEM() && !COMMON())
    return;

  ::upgraded();

  name_for_dir = ([ DIR_NORTH : FILE("north"),
		    DIR_SOUTH : FILE("south"),
		    DIR_EAST : FILE("east"),
		    DIR_WEST : FILE("west"),
		    DIR_NORTHWEST : FILE("northwest"),
		    DIR_NORTHEAST : FILE("northeast"),
		    DIR_SOUTHWEST : FILE("southwest"),
		    DIR_SOUTHEAST : FILE("southeast"),
		    DIR_IN : FILE("in"),
		    DIR_OUT : FILE("out"),
		    DIR_UP : FILE("up"),
		    DIR_DOWN : FILE("down"),
		    ]);

  shortname_for_dir = ([ DIR_NORTH : FILE("n"),
			 DIR_SOUTH : FILE("s"),
			 DIR_EAST : FILE("e"),
			 DIR_WEST : FILE("w"),
			 DIR_NORTHWEST : FILE("nw"),
			 DIR_NORTHEAST : FILE("ne"),
			 DIR_SOUTHWEST : FILE("sw"),
			 DIR_SOUTHEAST : FILE("se"),
			 DIR_IN : FILE("i"),
			 DIR_OUT : FILE("o"),
			 DIR_UP : FILE("u"),
			 DIR_DOWN : FILE("d"),
			 ]);

  /* This is the set of direction string acceptable for
     builder commands (currently) and file formats (probably forever) */
  builder_directions = ([ "north"      : DIR_NORTH,
			  "south"      : DIR_SOUTH,
			  "east"       : DIR_EAST,
			  "west"       : DIR_WEST,
			  "northeast"  : DIR_NORTHEAST,
			  "northwest"  : DIR_NORTHWEST,
			  "southeast"  : DIR_SOUTHEAST,
			  "southwest"  : DIR_SOUTHWEST,
			  "up"         : DIR_UP,
			  "down"       : DIR_DOWN,
			  "in"         : DIR_IN,
			  "out"        : DIR_OUT,

			  "n"  : DIR_NORTH,
			  "s"  : DIR_SOUTH,
			  "e"  : DIR_EAST,
			  "w"  : DIR_WEST,
			  "ne" : DIR_NORTHEAST,
			  "nw" : DIR_NORTHWEST,
			  "se" : DIR_SOUTHEAST,
			  "sw" : DIR_SOUTHWEST,
			  "u"  : DIR_UP,
			  "d"  : DIR_DOWN,
			  ]);
}

void destructed(int clone) {
  if(SYSTEM()) {

  }
}


/* Return phrase for direction name */
object get_name_for_dir(int direction) {
  if (direction <= 0) {
    error("Can't get the name of a special direction!");
  }
  return name_for_dir[direction];
}

/* Return phrase for direction short name */
object get_short_for_dir(int direction) {
  object phr;
  if (direction <= 0) {
    error("Can't get the short name of a special direction!");
  }
  phr = shortname_for_dir[direction];
  if(!phr) error("Can't get short name for direction " + direction);
  return phr;
}

int direction_by_string(string direc) {
  if(builder_directions[direc])
    return builder_directions[direc];

  return DIR_ERR;
}

int opposite_direction(int direction) {
  if (direction <= 0) {
    error("Can't get the opposite direction of a special direction!");
  }

  if(direction % 2) {
    return direction + 1;
  }

  return direction - 1;
}

private void push_or_add_newexit(int roomnum1, mixed *value) {
  int ctr, roomnum2;
  object exit1, exit2;
  object room1, room2;

  for(ctr=0;ctr<sizeof(value);ctr++) {
    if (value[ctr][0]=="destination") {
      roomnum2 = value[ctr][1];
      room1 = MAPD->get_room_by_num(roomnum1);
      if (roomnum2 > 0) {
        room2 = MAPD->get_room_by_num(roomnum2);
        if(room1 && room2) {
          add_complex_exit_by_unq(roomnum1, value);
        } else {
          deferred_add_newexit += ({ ({ roomnum1, value }) });
        }
      } else if (room1) {
          add_complex_exit_by_unq(roomnum1, value);
      } else {
          deferred_add_newexit += ({ ({ roomnum1, value }) });
      }
    }
  }
}

void room_request_complex_exit(int roomnum1, mixed value) {
  if(previous_program() != ROOM) {
    error("Only ROOM can request deferred exit creation!");
  }
  push_or_add_newexit(roomnum1, value);
}

void add_deferred_exits(void) {
  int    ctr;
  mixed* exits, *ex;

  if(!SYSTEM() && !COMMON() && !GAME())
    return;

  exits = deferred_add_newexit;
  deferred_add_newexit = ({ });

  if (sizeof(exits)) {
    for(ctr = 0; ctr < sizeof(exits); ctr++) {
      ex = exits[ctr];
      push_or_add_newexit(ex[0], ex[1]);
    }
  }

}

int num_deferred_exits(void) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return -1;

  if(!deferred_add_newexit) return -1;

  return sizeof(deferred_add_newexit);
}

private int allocate_exit_obj(int num, object obj) {
  int segment;

  if(num >= 0 && OBJNUMD->get_object(num))
    error("Object already exists with number " + num);

  if(num != -1) {
    OBJNUMD->allocate_in_segment(num / 100, num, obj);

    /* If that succeeded, add it to exit_segments */
    if(!(sizeof( ({ num / 100 }) & exit_segments ))) {
      string tmp;

      exit_segments |= ({ num / 100 });
    }

    return num;
  }

  for(segment = 0; segment < sizeof(exit_segments); segment++) {
    num = OBJNUMD->new_in_segment(exit_segments[segment], obj);
    if(num != -1) {
      return num;
    }
  }

  segment = OBJNUMD->allocate_new_segment();

  exit_segments += ({ segment });
  num = OBJNUMD->new_in_segment(segment, obj);

  return num;
}

private void add_complex_exit_by_unq(int roomnum1, mixed value) {
  int ctr, ctr2, num1, num2, roomnum2;
  object exit1, exit2;
  object room1, room2;
  mixed nouns, adjectives;

  exit1 = clone_object(SIMPLE_EXIT);
  room1 = MAPD->get_room_by_num(roomnum1);
  exit1->set_from_location(room1);

  for(ctr=0;ctr<sizeof(value);ctr++) {
    if (value[ctr][0]=="rnumber") {
      exit1->set_number(value[ctr][1]);
    } else if (value[ctr][0]=="direction") {
      exit1->set_direction(value[ctr][1]);
    } else if (value[ctr][0]=="destination") {
        room2 = MAPD->get_room_by_num(value[ctr][1]);
        exit1->set_destination(room2);
	exit1->set_from_location(room1);
    } else if (value[ctr][0]=="return") {
      exit1->set_link(value[ctr][1]);
    } else if (value[ctr][0]=="type") {
      exit1->set_exit_type(value[ctr][1]);
    } else if (value[ctr][0]=="rdetail") {
      /* what to do? */
    } else if (value[ctr][0]=="rbdesc") {
      exit1->set_brief(value[ctr][1]);
    } else if (value[ctr][0]=="rgdesc") {
      /* exit1->set_glance(value[ctr][1]) */ ;
    } else if (value[ctr][0]=="rldesc") {
      exit1->set_look(value[ctr][1]);
    } else if (value[ctr][0]=="redesc") {
      exit1->set_examine(value[ctr][1]);
    } else if (value[ctr][0]=="rflags") {
      exit1->set_all_flags(value[ctr][1]);
    } else if(value[ctr][0]=="rnouns") {
      for(ctr2 = 0; ctr2 < sizeof(value[ctr][1]); ctr2++) {
        exit1->add_noun(value[ctr][1][ctr2]);
      }
    } else if(value[ctr][0]=="radjectives") {
      for(ctr2 = 0; ctr2 < sizeof(value[ctr][1]); ctr2++) {
        exit1->add_adjective(value[ctr][1][ctr2]);
      }
    }
  }

  room1->add_exit(exit1->get_direction(), exit1);
  num1 = allocate_exit_obj(exit1->get_number(), exit1);
}

/* note:  caller must make sure not to override existing exits!!! */
void add_twoway_exit_between(object room1, object room2, int direction,
			     int num1, int num2) {
  object exit1, exit2;
  object dir, opp_dir;

  if(!SYSTEM() && !COMMON() && !GAME())
    return;

  if (direction <= 0) {
    error("Can't add two-way exit in a nonstandard direction!");
  }

  dir = get_name_for_dir(direction);
  opp_dir = get_name_for_dir(opposite_direction(direction));

  exit1 = clone_object(SIMPLE_EXIT);
  exit2 = clone_object(SIMPLE_EXIT);

  exit1->set_destination(room2);
  exit1->set_from_location(room1);
  exit1->set_direction(direction);
  exit1->set_exit_type(ET_TWOWAY);
  exit1->set_open(TRUE);
  exit1->set_container(TRUE);

  exit2->set_destination(room1);
  exit2->set_from_location(room2);
  exit2->set_direction(opposite_direction(direction));
  exit2->set_exit_type(ET_TWOWAY);

  exit2->set_open(TRUE);
  exit2->set_container(TRUE);

  room1->add_exit(direction, exit1);
  room2->add_exit(opposite_direction(direction), exit2);

  num1 = allocate_exit_obj(num1, exit1);
  num2 = allocate_exit_obj(num2, exit2);

  if(num1 < 0 || num2 < 0) {
    error("Exit numbers not assigned successfully!");
  }

  exit1->set_number(num1);
  exit1->set_link(num2);
  exit2->set_number(num2);
  exit2->set_link(num1);

  if(exit1->get_number() < 0
     || exit2->get_number() < 0) {
    destruct_object(exit1);
    destruct_object(exit2);
    error("Exit numbers not assigned successfully!");
  }

  exit1->set_brief(PHRASED->new_simple_english_phrase("exit"));
  exit2->set_brief(PHRASED->new_simple_english_phrase("exit"));
}

/* note:  caller must make sure not to override existing exits!!! */
void add_oneway_exit_between(object room1, object room2, int direction,
			     int num1) {
  object exit1, dir;

  if(!SYSTEM() && !COMMON() && !GAME())
    return;

  if (direction <= 0) {
    error("Can't add an exit in a special direction!");
  }

  exit1 = clone_object(SIMPLE_EXIT);
  dir = get_name_for_dir(direction);

  exit1->set_destination(room2);
  exit1->set_from_location(room1);
  exit1->set_direction(direction);
  exit1->set_exit_type(ET_ONEWAY);
  exit1->set_open(TRUE);
  exit1->set_container(TRUE);

  room1->add_exit(direction, exit1);

  num1 = allocate_exit_obj(num1, exit1);

  if(num1 < 0 ) {
    error("Exit number not assigned successfully!");
  }

  exit1->set_number(num1);
  exit1->set_link(-1);

  if(exit1->get_number() < 0) {
    error("Exit number not assigned successfully!");
    destruct_object(exit1);
  }
}

void remove_exit(object room, object exit) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return;

  room->remove_exit(exit);
}

void clear_exit(object exit) {
  object exit2;

  if(!SYSTEM() && !COMMON() && !GAME())
    return;

  if (exit->get_exit_type() == ET_TWOWAY) {
    exit2 = EXITD->get_exit_by_num(exit->get_link());
    destruct_object(exit2);
  }
  destruct_object(exit);
}

void clear_all_exits(object room) {
  object exit, exit2;

  if(!SYSTEM() && !COMMON() && !GAME())
    return;

  if(!room)
    error("Passed nil to clear_all_exits!");

  while(exit = room->get_exit_num(0)) {
    if (exit->get_exit_type() == ET_TWOWAY) {
      exit2 = EXITD->get_exit_by_num(exit->get_link());
      destruct_object(exit2);
    }
    destruct_object(exit);
  }

}

object get_exit_by_num(int num) {
  int seg;

  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  if(num < 0) return nil;
  seg = num / 100;
  if(sizeof( ({ seg }) & exit_segments )) {
    return OBJNUMD->get_object(num);
  }

  return nil;
}

int* get_exit_segments(void) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  return exit_segments[..];
}

int* get_all_exits(void) {
  int* exits, *tmp;
  int  iter;

  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  exits = ({ });
  for(iter = 0; iter < sizeof(exit_segments); iter++) {
    tmp = OBJNUMD->objects_in_segment(exit_segments[iter]);
    if(tmp)
      exits += tmp;
  }

  if(!sizeof(exits))
    return nil;

  return exits;
}

int* exits_in_segment(int seg) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  if(sizeof( ({ seg}) & exit_segments )) {
    return OBJNUMD->objects_in_segment(seg);
  }

  return nil;
}
