#include <kernel/kernel.h>

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

#include <gameconfig.h>
#include <type.h>

/* The Mapd keeps track of room objects, their groupings and their
   relationship to each other.  It also loads in rooms in
   fundamentally data-based formats rather than code-based formats and
   turns them into proper room objects. */

/* room_objects keeps track of rooms by object name and certain aliases */
private mapping room_objects;

/* which unq tags are mapped to which lpc code */
private mapping tag_code;
private object  default_binding_handler;

private object  room_dtd, bind_dtd;
private int     initialized;

/* Rooms that haven't yet fully resolved... */
private object* unresolved_rooms;

#define PHR(x) PHRASED->new_simple_english_phrase(x)

#define ROOM_BIND_FILE "/usr/common/sys/room_binder.unq"

/* Prototypes */
object get_room_by_num(int num);
int* rooms_in_zone(int zone);
private int assign_room_to_zone(int num, object room, int zone);
void upgraded(varargs int clone);


static void create(varargs int clone) {
  room_objects = ([ ]);
  tag_code = ([ ]);
  default_binding_handler = nil;

  upgraded(clone);

  initialized = 0;
}

void upgraded(varargs int clone) {
  if(!SYSTEM() && previous_program() != MAPD)
    return;

  if(!find_object(UNQ_PARSER))
    compile_object(UNQ_PARSER);
  if(!find_object(UNQ_DTD))
    compile_object(UNQ_DTD);

  if(!unresolved_rooms)
    unresolved_rooms = ({ });
}

void init(string room_dtd_str, string bind_dtd_str) {
  int    ctr;
  mixed *unq_data;
  string bind_file, tag, file;

  if(!SYSTEM())
    return;

  if(!initialized) {
    room_dtd = clone_object(UNQ_DTD);
    room_dtd->load(room_dtd_str);

    /* read the binder file */
    bind_dtd = clone_object(UNQ_DTD);
    bind_dtd->load(bind_dtd_str);

    bind_file = read_file(ROOM_BIND_FILE);
    if (!bind_file)
      error("Cannot read binder file " + ROOM_BIND_FILE + "!");

    unq_data = UNQ_PARSER->unq_parse_with_dtd(bind_file, bind_dtd);
    if(!unq_data)
      error("Cannot parse binder text in MAPD::init()!");

    if (sizeof(unq_data) % 2)
      error("Odd sized unq chunk in MAPD::init()!");

    for (ctr = 0; ctr < sizeof(unq_data); ctr += 2) {
      if (STRINGD->stricmp(unq_data[ctr],"bind"))
	error("Not a code/tag binding in MAPD::init()!");

      if (typeof(unq_data[ctr+1]) != T_ARRAY || sizeof(unq_data[ctr+1]) != 2) {
	/* Should never get here for proper DTD */
	error("Internal error in MAPD->init()");
      }


      if (!STRINGD->stricmp(unq_data[ctr+1][0][0],"tag")) {
	tag = unq_data[ctr+1][0][1];
	file = unq_data[ctr+1][1][1];
      } else {
	tag = unq_data[ctr+1][1][1];
	file = unq_data[ctr+1][0][1];
      }

      if (tag_code[tag] != nil) {
	error("Tag " + tag + " is already bound in MAPD::init()!");
      }

      /* Assign file to tag, and make sure it exists and is clonable */
      tag_code[tag] = file;
      if(!find_object(file))
	compile_object(file);
    }

    initialized = 1;
  } else error("MAPD already initialized!");
}

void destructed(int clone) {
  mixed* rooms;
  int    numzones, riter, ziter;

  if(!SYSTEM())
    return;

  if(room_dtd)
    destruct_object(room_dtd);
  if(bind_dtd)
    destruct_object(bind_dtd);

  /* Now go through and destruct all rooms */
  numzones = ZONED->num_zones();
  for(ziter = 0; ziter < numzones; ziter++) {
    rooms = rooms_in_zone(ziter);
    for(riter = 0; riter < sizeof(rooms); riter++) {
      destruct_object(get_room_by_num(rooms[riter]));
    }
  }
}


void add_unq_binding(string tag_name, string tag_path) {
  if(!GAME() && !COMMON() && !SYSTEM())
    error("Only privileged code may add a room binding!");

  if(!tag_name)
    error("(Nil) isn't a valid tag in add_unq_binding!");

  if (tag_code[tag_name] != nil) {
    error("Tag name '" + tag_name
	  + "' is already bound in MAPD::add_unq_binding()!");
  }

  /* Assign file to tag, and make sure it exists and is clonable */
  if(!find_object(tag_path))
    compile_object(tag_path);

  /* If we make it through all that without error, do the assignment. */
  tag_code[tag_name] = tag_path;
}

void set_binding_handler(object bhandler) {
  if(previous_program() != GAME_INITD)
    error("Only GAME_INITD may set the room-binding handler!");

  if(!bhandler)
    error("(Nil) isn't a valid handler in set_binding_handler!");

  /* do the assignment */
  default_binding_handler = bhandler;
}

void add_room_object(object room) {
  string name;

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

  name = object_name(room);
  if(room_objects[name])
    error("Room already registered in add_room_object!");

  room_objects[name] = room;
}

void add_room_to_zone(object room, int num, int req_zone) {
  int seg, allocated, zone;

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

  allocated = 0;

  if(!room_objects[object_name(room)])
    error("Adding num for unregistered object " + object_name(room) + "!");

  num = assign_room_to_zone(num, room, req_zone);
  if(num < 0) {
    error("Error assigning room number!");
  }

  seg = num / 100;
  zone = ZONED->get_segment_zone(seg);
  if(zone != req_zone && req_zone != -1)
    error("Room assigned to unreasonable segment!  Wrong zone!");

  if(zone == -1)
    zone = 0;  /* Fix offset */

  room->set_number(num);
}

void remove_room_object(object room) {
  string name;

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

  name = object_name(room);
  if(!room_objects[name]) {
    error("Removing room not in room_objects mapping!");
  }
  room_objects[name] = nil;

  room->set_number(-1);
}

object get_room_by_num(int num) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  if(num < 0) return nil;

  return OBJNUMD->get_object(num);
}


/* Find appropriate room number in the requested zone (if any) */
private int assign_room_to_zone(int num, object room, int req_zone) {
  int    segnum, ctr;
  string segown;

  if(num >= 0) {
    int zone;

    segnum = num / 100;

    segown = OBJNUMD->get_segment_owner(segnum);
    if(segown && strlen(segown) && segown != MAPD) {
      LOGD->write_syslog("Can't allocate room number " + num
			 + " in non-MAPD segment!", LOG_WARN);
      return -1;
    }
    zone = ZONED->get_segment_zone(segnum);
    if(zone != req_zone && req_zone >= 0)
      error("Room number (#" + num
	    + ", zone #" + zone + ") not in requested zone (#" + req_zone
	    + ") in assign_room_to_zone!");

    if(zone < 0) {
      error("Illegal zone in assign_room_to_zone(" + num
	    + ", <room>, " + req_zone + ")!");
    }

    OBJNUMD->allocate_in_segment(segnum, num, room);

    return num;
  } else {
    /* This section happens if there is no specific requested room
       number */
    int *zoneseg;

    /* If no specific room number or zone is requested, give us something
       unzoned */
    if(req_zone < 0)
      req_zone = 0;

    zoneseg = ZONED->get_segments_in_zone(req_zone);

    for(ctr = 0; ctr < sizeof(zoneseg); ctr++) {
      num = OBJNUMD->new_in_segment(zoneseg[ctr], room);
      if(num != -1)
	break;
    }
    if(num == -1) {
      segnum = OBJNUMD->allocate_new_segment();
      num = OBJNUMD->new_in_segment(segnum, room);

      if(req_zone)
	ZONED->set_segment_zone(segnum, req_zone);
    }

    return num;
  }
}


/* Take an UNQ description parsed with a DTD and add the appropriate room
   to mapd. */
private object add_struct_for_room(mixed* unq) {
  object room;
  int    num;
  string tag_name, room_program, err;

  /* no unq passed in, so no object passed out */
  if (sizeof(unq) == 0) {
    return nil;
  }

  if(STRINGD->stricmp(unq[0], "object")) {
    error("Label '" + unq[0] + "' doesn't look like the start of an object!");
  }

  if(!STRINGD->stricmp(unq[1][0][0], "obj_type")) {
    if(typeof(unq[1][0][1]) != T_STRING)
      error("UNQ obj_type data is not a string!");

    tag_name = STRINGD->trim_whitespace(unq[1][0][1]);
  } else {
    /* Default object type */
    tag_name = "object";
  }

  if (tag_code[tag_name] == nil
      && default_binding_handler) {
    err = catch(room_program
		= default_binding_handler->type_for_tag(tag_name));
    if(err) {
      error("Error calling type_for_tag on binding handler, type " + tag_name
	    + ": " + err);
    }
  } else {
    room_program = tag_code[tag_name];
  }

  if(room_program == nil) {
    error("Tag " + tag_name
	  + " is not bound to any room type!");
  }

  if (!find_object(room_program)) {
    catch {
      compile_object(room_program);
    } : {
      error("Could not compile object '" + room_program
	    + "' for tag '" + tag_name + "'!");
    }
  }

  err = catch(room = clone_object(room_program));
  if(err) {
    error("Error cloning program " + room_program + " of type '" + tag_name
	  + "' when making new room: " + err);
  }
  room->from_dtd_unq(unq);

  /* Get the requested number from the room, or -1 for default.
     Attempt to assign this number. */
  num = room->get_number();
  num = assign_room_to_zone(num, room, -1); /* assign to any zone */
  if(num < 0) {
    error("Can't assign room number!");
  }
  room->set_number(num);

  if(!room_objects[object_name(room)])
    error("You forgot to register with MAPD in a room object def maybe?");

  return room;
}


private int resolve_parent(object room) {
  int*    pending_parents;
  int     ctr;
  object* parents, *pending_phr;
  object  parent;

  pending_parents = room->get_pending_parents();
  parents = ({ });
  if(pending_parents && sizeof(pending_parents)) {
    for(ctr = 0; ctr < sizeof(pending_parents); ctr++) {
      parent = MAPD->get_room_by_num(pending_parents[ctr]);
      if(!parent) {
	return 0;
      }
      parents += ({ parent });
    }

    room->set_archetypes(parents);

    /* Remove nouns in pending_removed_nouns */
    pending_phr = room->get_pending_removed_nouns();
    for(ctr = 0; ctr < sizeof(pending_phr); ctr++) {
      room->remove_noun(pending_phr[ctr]);
    }

    /* Removed adjectives in pending_removed_adjectives */
    pending_phr = room->get_pending_removed_adjectives();
    for(ctr = 0; ctr < sizeof(pending_phr); ctr++) {
      room->remove_adjective(pending_phr[ctr]);
    }
    return 1;
  }

  /* Don't need to set removed_nouns and removed_adjectives if there
     are no parents... */
  return 1;
}


private int resolve_removed_details(object room) {
  int    *rem_det;
  int     ctr;
  object  tmp;
  object *new_rem_det;

  rem_det = room->get_pending_removed_details();
  new_rem_det = ({ });
  for(ctr = 0; ctr < sizeof(rem_det); ctr++) {
    tmp = MAPD->get_room_by_num(rem_det[ctr]);
    if(!tmp)
      return 0;

    new_rem_det += ({ tmp });
  }

  room->set_removed_details(new_rem_det);
  return 1;
}

private int resolve_location(object room) {
  int    pending;
  object container;

  /* Resolve details (rather than regular containment) */
  pending = room->get_pending_detail_of();
  if(pending != -1) {
    container = get_room_by_num(pending);
    if(!container) {
      return 0;
    }

    container->add_detail(room);
    return 1;
  }

  /* Resolve regular containment */
  pending = room->get_pending_location();
  if(pending != -1) {
    container = get_room_by_num(pending);
    if(!container) {
      return 0;
    }

    container->add_to_container(room);
    return 1;
  } else {
    container = get_room_by_num(0);  /* Else, add to The Void */
    if(!container)
      error("Can't find room #0!  Panic!");

    container->add_to_container(room);
    return 1;
  }
}


object *get_deferred_rooms(void) {
  if(SYSTEM())
    return unresolved_rooms[..];

  return nil;
}


void do_room_resolution(int fully) {
  int     iter, res_tmp, finished;
  mapping done_resolve;
  object* new_unres;

  if(!SYSTEM() && !COMMON() && !GAME())
    error("Only privileged code can request room resolution!");

  done_resolve = ([ ]);
  finished = 0;

  while(finished < sizeof(unresolved_rooms)) {
    res_tmp = 0;

    /* Do one pass through the list, trying once each to
       resolve each unresolved room. */
    for(iter = 0; iter < sizeof(unresolved_rooms); iter++) {

      /* Skip things we've already resolved. */
      if(done_resolve[unresolved_rooms[iter]])
	continue;

      if(resolve_location(unresolved_rooms[iter])) {
	res_tmp = 1;
	done_resolve[unresolved_rooms[iter]] = 1;
	finished++;
      }
    }

    if(!res_tmp) {
      /* We're not resolving any more rooms... */
      break;
    }
  }

  /* Now resolve parents and removed details. */
  new_unres = ({ });
  for(iter = 0; iter < sizeof(unresolved_rooms); iter++) {
    if(!done_resolve[unresolved_rooms[iter]]
       || !resolve_parent(unresolved_rooms[iter])
       || !resolve_removed_details(unresolved_rooms[iter])) {
      new_unres += ({ unresolved_rooms[iter] });
      continue;
    }

    /* Clear all pending data */
    unresolved_rooms[iter]->clear_pending();
  }

  unresolved_rooms = new_unres;

  /* See if we were asked to resolve 100% of remaining rooms */
  if(fully && sizeof(unresolved_rooms)) {
    string tmp;
    int    ctr;

    tmp = "Can't resolve the following rooms: ";
    for(ctr = 0; ctr < sizeof(unresolved_rooms) - 1; ctr++) {
      tmp += unresolved_rooms[ctr]->get_number() + ", ";
    }

    /* This makes sure there's no final comma */
    tmp += unresolved_rooms[sizeof(unresolved_rooms) - 1]->get_number();

    LOGD->write_syslog("Can't resolve rooms: " + tmp, LOG_FATAL);
    error("Can't resolve all rooms!  Edit room files to fix this!");
  }
}


void add_dtd_unq_rooms(mixed* unq, string filename) {
  int    iter;
  mixed* resolve_rooms;
  object room;

  if(!initialized)
    error("Can't add rooms to uninitialized mapd!");

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

  /* TODO: we'll need the filename for objectd notify dependencies
     -- we'll need to keep track of the fact that if that file
     changes, these room objects change. */

  iter = 0;
  resolve_rooms = ({ });
  while(iter < sizeof(unq)) {
    room = add_struct_for_room( ({ unq[iter], unq[iter + 1] }) );
    resolve_rooms += ({ room });
    iter += 2;
  }

  unresolved_rooms += resolve_rooms;
  do_room_resolution(0);
}

/* Take a chunk of text to parse as UNQ and add rooms appropriately... */
void add_unq_text_rooms(string text, string filename) {
  mixed* unq_data;

  if(!initialized)
    error("Can't add rooms to uninitialized mapd!");

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

  unq_data = UNQ_PARSER->unq_parse_with_dtd(text, room_dtd, filename);
  if(!unq_data)
    error("Cannot parse text in add_unq_text_rooms!");

  add_dtd_unq_rooms(unq_data, filename);
}

int* segments_in_zone(int zone) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  return ZONED->get_segments_in_zone(zone);
}

int* rooms_in_segment(int segment) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  if(OBJNUMD->get_segment_owner(segment) != MAPD)
    return nil;

  return OBJNUMD->objects_in_segment(segment);
}

int* rooms_in_zone(int zone) {
  int *segs, *rooms, *tmp;
  int  iter;

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

  segs = segments_in_zone(zone);
  if(!segs) return nil;
  rooms = ({ });

  for(iter = 0; iter < sizeof(segs); iter++) {
    tmp = rooms_in_segment(segs[iter]);
    if(tmp)
      rooms += tmp;
  }

  return rooms;
}
