#include <kernel/kernel.h>

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

#include <type.h>

/* MobileD-owned Segments */
private int*    mobile_segments;

/* Whether the MOBILED has been initialized */
private int     initialized;

/* The mapping of tag names to files */
private mapping tag_code;

private object  mobfile_dtd;
private object  binder_dtd;

/* Prototypes */
        void   upgraded(varargs int clone);
private int    allocate_mobile_obj(int num, object obj);
private object add_struct_for_mobile(mixed* unq);
        void   add_dtd_unq_mobiles(mixed *unq, string filename);

#define PHR(x) PHRASED->new_simple_english_phrase(x)
#define MOBILE_BIND_FILE "/usr/common/sys/mobile_binder.unq"

static void create(varargs int clone) {
  if(clone)
    error("Cloning mobiled is not allowed!");

  mobile_segments = ({ });

  upgraded();

}

private void load_tag_codes(void) {
  string bind_file, tag, file;
  int    ctr;
  mixed  unq_data;

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

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

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

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

    tag_code[tag] = file;
    if(!find_object(file))
      compile_object(file);
  }
}

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

  /* Reload the Binder */
  tag_code = ([ ]);

  if(mobfile_dtd && binder_dtd) {
    load_tag_codes();
  }
}

void destructed(int clone) {
  if(SYSTEM()) {
    if(mobfile_dtd)
      destruct_object(mobfile_dtd);
    if(binder_dtd)
      destruct_object(mobfile_dtd);
  }
}

void init(void) {
  string mobfile_dtd_string, binder_dtd_string;

  if(!SYSTEM())
    return;

  if(!initialized) {
    mobfile_dtd_string = read_file(MOB_FILE_DTD);
    if(!mobfile_dtd_string)
      error("Can't read file " + MOB_FILE_DTD + "!");

    binder_dtd_string = read_file(BIND_DTD);
    if(!binder_dtd_string)
      error("Can't read file " + BIND_DTD + "!");

    binder_dtd = clone_object(UNQ_DTD);
    binder_dtd->load(binder_dtd_string);

    mobfile_dtd = clone_object(UNQ_DTD);
    mobfile_dtd->load(mobfile_dtd_string);

    load_tag_codes();
  } else
    error("MOBILED is already initialized in MOBILED::init()!");

  initialized = 1;
}


int add_mobile_number(object mobile, int num) {
  int newnum;

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

  if(!mobile)
    error("No mobile supplied to MOBILED::add_mobile_number!");

  if(!initialized)
    error("Can't add mobiles to uninitialized MOBILED!");

  newnum = allocate_mobile_obj(num, mobile);
  if(newnum <= 0) {
    error("Can't allocate mobile number!");
  }

  LOGD->write_syslog("Allocating mobile number: " + newnum, LOG_VERBOSE);

  mobile->set_number(newnum);

  return newnum;
}

private int allocate_mobile_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(!(sizeof( ({ num / 100 }) & mobile_segments ))) {
      string tmp;

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

    return num;
  }

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

  segment = OBJNUMD->allocate_new_segment();
  LOGD->write_syslog("Allocating segment " + segment + " to MOBILED.",
		     LOG_VERBOSE);

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

  return num;
}

/* This function needs to remove the mobile from the list of mobiles
   in the mobile's containing room.  Since that list is not directly
   exported or modifiable, mobiles must currently be destroyed by
   removing their body from a room, destructing the mobile, then
   adding the object back. */
void remove_mobile(object mobile) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return;

  if(mobile) {
    object body, location;

    body = mobile->get_body();
    if(body) {
      location = body->get_location();
      if(location) {
	location->remove_from_container(body);
	destruct_object(mobile);
	location->add_to_container(body);
      }
    }

    if(mobile)
      destruct_object(mobile);
  } else {
    error("Can't usefully remove a (nil) mobile!");
  }
}

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

  if(num < 0) return nil;

  return OBJNUMD->get_object(num);
}

int* mobiles_in_segment(int seg) {
  int* tmp;

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

  tmp = OBJNUMD->objects_in_segment(seg);
  if(!tmp)
    tmp = ({ });

  return tmp;
}

int* all_mobiles(void) {
  int  iter;
  int* ret, *tmp;

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

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

  return ret;
}


string get_file_by_mobile_type(string mobtype) {
  if(!SYSTEM())
    error("Only SYSTEM code can query the MOBILED for mobile types!");

  return tag_code[mobtype];
}

object clone_mobile_by_type(string mobtype) {
  if(!SYSTEM() && !COMMON() && !GAME())
    return nil;

  if(!tag_code[mobtype]) return nil;

  return clone_object(tag_code[mobtype]);
}

void add_unq_text_mobiles(string unq_text, string filename) {
  mixed *unq_data;

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

  if(!initialized)
    error("Can't add mobiles to uninitialized MOBILED!");

  unq_data = UNQ_PARSER->unq_parse_with_dtd(unq_text, mobfile_dtd);
  if(!unq_data) {
    if(filename) { 
      error("Cannot parse file '" + filename
	    + "' in add_unq_text_mobiles!");
   } else {
      error("Cannot parse text in add_unq_text_mobiles!");
    }
  }

  add_dtd_unq_mobiles(unq_data, filename);
}

void add_dtd_unq_mobiles(mixed *unq, string filename) {
  int    iter;
  object mobile;

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

  if(!initialized)
    error("Can't add mobiles to uninitialized MOBILED!");

  iter = 0;
  while(iter < sizeof(unq)) {
    mobile = add_struct_for_mobile( ({ unq[iter], unq [iter + 1] }) );
    if(!mobile)
      error("Loaded mobile is (nil) in add_dtd_unq_mobiles!");
    iter += 2;
  }
}

private object add_struct_for_mobile(mixed* unq) {
  object mobile, body;
  int    num;
  mixed* ctr;
  string type;

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

  if(STRINGD->stricmp(unq[0], "mobile")) {
    error("UNQ doesn't look like a mobile in add_struct_for_mobile!");
  }

  ctr = unq[1];
  while(sizeof(ctr)) {
    if(!STRINGD->stricmp(ctr[0][0],"type")) {
      type = ctr[0][1];
      break;
    }
    ctr = ctr[1..];
  }
  if(!type) {
    /* There doesn't seem to be a "type" field in the UNQ passed in */
    error("Can't find 'type' field in mobfile!  "
	  + "Do you need to delete or update it?");
  }

  if(!tag_code[type]) {
    error("Can't find binding for mobile type '" + type + "'.");
  }

  mobile = clone_object(tag_code[type]);
  mobile->from_dtd_unq(unq[1]);

  num = mobile->get_number();
  num = add_mobile_number(mobile, num);
  if(num < 0) {
    error("Can't assign mobile number in add_struct_for_mobile!");
  }
  mobile->set_number(num);

  body = mobile->get_body();
  if(body) {
    body->set_mobile(mobile);
  } else {
    LOGD->write_syslog("Body is (nil), mob #" + num, LOG_WARN);
  }

  LOGD->write_syslog("Added struct for mobile!", LOG_VERBOSE);

  return mobile;
}
