#include <kernel/kernel.h>

#include <phantasmal/lpc_names.h>

private mapping segments;
private string* owners;
private int     segments_full;
private int     highest_segment;

/* Prototypes */
void upgraded(varargs int clone);


static void create(varargs int clone) {
  if(clone) {
    error("Can't clone objnumd!");
  }
  segments = ([ ]);
  segments_full = -1;
  highest_segment = -1;

  upgraded();
}

void upgraded(varargs int clone) {
  if(SYSTEM() || COMMON())
    owners = ({ MAPD, EXITD, MOBILED });
}

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

  }
}

private int owner_for_program(string program_name) {
  int owner;

  for(owner = 0; owner < sizeof(owners); owner++) {
    if(program_name == owners[owner])
      return owner;
  }

  return -1;
}

string get_segment_owner(int segment) {
  if(!SYSTEM() && !COMMON())
    return nil;

  if(segments[segment]) {
    return owners[segments[segment][0]];
  } else
    return nil;
}

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

  return highest_segment;
}

private void set_segment_owner(int segment, int owner) {
  if(segment < 0)
    error("Can't allocate negative segment in set_segment_owner!");

  if(segments[segment]) {
    segments[segment][0] = owner;
    return;
  }

  /* This location defines segment structure... */
  segments[segment] = ({ owner, ({ }) });

  if(segments_full == segment - 1) {
    while(segments[segments_full + 1])
      segments_full++;
  }
  if(segment > highest_segment)
    highest_segment = segment;
}

int allocate_new_segment(void) {
  int owner;
  int seg;

  owner = owner_for_program(previous_program());
  if(owner == -1)
    error("Unknown owner " + previous_program()
	  + " calling allocate_new_segment!");

  seg = segments_full + 1;
  if(get_segment_owner(seg)) {
    error("Internal error -- attempting to reassign segment!");
  }

  set_segment_owner(seg, owner);

  return seg;
}

private void unallocate_segment(int segment) {
  error("Should not use?");

  if(segments_full >= segment) {
    segments_full = segment - 1;
  }

  if(segment == highest_segment) {
    error("Haven't implemented recalculating highest segment!");
  }

  segments[segment] = nil;
}

/* Note:  doing an allocate_in_segment with a valid tracking number
   and nil as the object is a quite acceptable way to allocate or
   pre-grow a segment */
void allocate_in_segment(int segment, int tr_num, object obj) {
  int    offs;
  mixed* seg;
  int    owner;

  if(tr_num < 0)
    error("Negative tracking number in allocate_in_segment!");
  if(tr_num / 100 != segment)
    error("Tracking number not in segment in allocate_in_segment!");

  owner = owner_for_program(previous_program());
  if(owner == -1)
    error("Unknown owner " + previous_program()
	  + " calling allocate_in_segment!");

  offs = tr_num % 100;
  seg = segments[segment];
  if(!seg) {
    /* Allocate a new segment for caller */
    set_segment_owner(segment, owner);
    seg = segments[segment];
    if(!seg)
      error("Cannot allocate segment -- why?");
  }
  if(seg[0] != owner)
    error(owners[owner] + " can't allocate object " + tr_num + " in segment "
	  + segment + "!\n"
	  + "That segment is owned by " + owners[seg[0]] + "!");

  if(sizeof(segments[segment][1]) <= offs) {
    seg[1] += allocate(offs - sizeof(seg[1]) + 1);
  }

  if(seg[1][offs])
    error("Reassigning used number #" + tr_num + " in allocate_in_segment!");

  seg[1][offs] = obj;
}

/* Note: because a destructed object's references will all become
   nil automatically, doing a remove_from_segment is unnecessary
   if the object has already been destructed */
void remove_from_segment(int segment, int tr_num) {
  mixed* seg;
  int    offs;

  seg = segments[segment];
  if(!seg)
    error("Can't remove from unallocated segment!");

  if(tr_num / 100 != segment)
    error("Can't remove tr_num from different segment!");

  offs = tr_num % 100;

  if(sizeof(seg[1]) <= offs || !seg[1][offs])
    error("Object not in segment in remove_from_segment!");

  seg[1][offs] = nil;
}

/* A segment owner calls this function to retrieve an object from its
   own segment. */
object get_object(int tr_num) {
  int    segment, offs;
  int    owner;
  object ret;

  if(tr_num < 0)
    error("Tracking numbers must be >= 0!");
  segment = tr_num / 100;
  offs = tr_num % 100;
  owner = segments[segment] ? segments[segment][0] : -1;
  if(owner == -1
     || owners[owner] != previous_program()) {
    return nil;
  }

  if(sizeof(segments[segment][1]) <= offs) {
    return nil;
  }

  ret = segments[segment][1][offs];

  return ret;
}

/* Attempts to allocate a new tracking number in the given
   segment.  If the segment is full, it returns -1. */
int new_in_segment(int segment, object obj) {
  mixed* seg;
  int    ctr;

  seg = segments[segment];
  if(!seg) {
    set_segment_owner(segment, owner_for_program(previous_program()));
    seg = segments[segment];
  }
  if(owners[seg[0]] != previous_program())
    error("Can't allocate in segment you don't own!");

  for(ctr = 0; ctr < sizeof(seg[1]); ctr++) {
    if(!seg[1][ctr]) {
      seg[1][ctr] = obj;
      return segment * 100 + ctr;
    }
  }

  if(sizeof(seg[1]) < 100) {
    int offs;

    offs = sizeof(seg[1]);

    seg[1] += allocate(1);
    seg[1][offs] = obj;

    return segment * 100 + offs;
  }

  return -1;
}

/* Returns a list of object numbers in an owned segment.  Caller
   must be the segment owner and the segment must be allocated.
*/
int* objects_in_segment(int segment) {
  int*   objs;
  int    ctr, tr_num;
  mixed* seg;

  seg = segments[segment];
  if(!seg || previous_program() != owners[seg[0]])
    error("Can't get listing of segment " + segment
	  + " that you don't own!");

  objs = ({ });
  for(ctr = 0, tr_num = segment*100; ctr < sizeof(seg[1]); ctr++, tr_num++) {
    if(seg[1][ctr]) {
      objs += ({ tr_num });
    }
  }

  return objs;
}
