/* destroy.c */
#include "os.h"
#include "copyright.h"
#include "config.h"

#ifdef DESTROY

#include "db.h"
#include "externs.h"
#include "globals.h"
#include "interface.h"
#include "match.h"

dbref first_free = NOTHING;

static void dbmark (dbref loc);
static void dbmark1 (void);
static void dbunmark (void);
static void dbunmark1 (void);
static void do_empty (dbref thing);

#ifdef DESTROY
void do_destroy (dbref player, char *name, int confirm)
{
  dbref thing;
  dbref loc;
  int a;

  init_match (player, name, NOTYPE);
  match_everything ();
  thing = last_match_result ();
  if ((thing != NOTHING) &&
    !controls (player, thing) &&
    !((Typeof (thing) == TYPE_EXIT) &&
      (controls (player, db[thing].location) ||
        controls (player, db[thing].exits))) &&
    !((db[thing].flags & THING_DEST_OK) && (Typeof (thing) == TYPE_THING))) {
    notify (player, "Permission denied.");
    return;
  }
  if (thing == PLAYER_START || thing == MASTER_ROOM) {
    notify (player, "Permission denied.");
    return;
  }

  if (thing == NOTHING)
    thing = match_controlled (player, name);
  if (thing <= 0)               /* I hope no wizard is this stupid but just
                                 * in case */
    return;
  /* what kind of thing we are destroying? */
  switch (Typeof (thing)) {
  case TYPE_PLAYER:
    if (Typeof (player) != TYPE_PLAYER) {
      notify (player, "Programs don't kill people; people kill people!");
      return;
    }
    if (!Wizard (player) && (db[thing].owner == thing)) {
      notify (player, "Sorry, no suicide allowed.");
      return;
    }
    if (Wizard (thing) && !God (player)) {
      notify (player, "Even you can't do that!");
      return;
    }
    if (God (player) && player == thing) {
      notify (player, "Sorry, God cannot destroy himself.");
      return;
    }
    if (!confirm) {
      notify (player, "You must use @nuke to destroy a player.");
      return;
    }
    /* bye bye */
    do_halt (thing, "");
    fprintf (stderr, "*** PLAYER NUKED*** %s(#%d) destroyed %s(#%d)\n",
      db[player].name, player, db[thing].name, thing);
    notify (player, tprintf ("You get your 0 %s deposit back.", MONEY));
    boot_off (thing);
    do_chownall (player, name, "");
#ifdef USE_MAILER
    do_mail (thing, "clear", "");
#endif
    delete_player (thing);
    loc = db[thing].location;
    if (loc != NOTHING) {
      db[loc].contents = remove_first (db[loc].contents, thing);
      free_object (thing);
    }
    break;
  case TYPE_THING:
    /* check to make sure there's no accidental destruction */
    if (!confirm && Wizard (player) && !(db[thing].flags & THING_DEST_OK)
      && (db[thing].owner != player)) {
      notify (player,
        "That object does not belong to you. Use @nuke to destroy it.");
      return;
    }
    if (!confirm && (db[thing].flags & THING_SAFE)) {
      notify (player, "That object is marked SAFE. Use @nuke to destroy it.");
      return;
    }
    if (!confirm && (db[thing].flags & WIZARD)) {
      notify (player,
        "That object is set WIZARD. You must use @nuke to destroy it.");
      return;
    }
    do_halt (thing, "");
    /* give player money back */
    giveto (db[thing].owner, (a = OBJECT_DEPOSIT (Pennies (thing))));
#ifdef QUOTA
    add_quota (db[thing].owner, 1);
#endif /* QUOTA */
    if (db[thing].flags & THING_PUPPET)
      db[thing].flags &= ~THING_PUPPET;
    if (!Quiet (thing) && !Quiet (db[thing].owner))
      notify (db[thing].owner,
        tprintf ("You get your %d %s deposit back for %s.",
          a, ((a == 1) ? MONEY : MONIES), db[thing].name));
    notify (player, "Destroyed.");
    loc = db[thing].location;
    if (loc != NOTHING) {
      db[loc].contents = remove_first (db[loc].contents, thing);
      free_object (thing);
    }
    break;
  case TYPE_ROOM:
    if (db[thing].flags & GOING) {
      notify (player, "No use beating a dead room.");
      return;
    }
    /* don't let room be deleted if >2 exits */
    if (((loc = db[thing].exits) != NOTHING) &&
      ((loc = db[loc].next) != NOTHING) &&
      ((loc = db[loc].next) != NOTHING)) {
      notify (player,
        "The room must have less than 3 exits before it can be destroyed.");
      return;
    }
    do_halt (thing, "");
    notify_except (db[thing].contents, 0,
      "The room shakes and begins to crumble.");
    if (player == db[thing].owner)
      notify (db[thing].owner,
        tprintf ("You will be rewarded shortly for %s(#%d).",
          db[thing].name, thing));
    else
      notify (player, tprintf ("The wrecking ball is on its way for %s(#%d).",
          db[thing].name, thing));
    db[thing].flags |= GOING;   /* mark for deletion but don't empty yet */
    return;
  case TYPE_EXIT:
    /* Patched 12/1/90 by Michael Stanley */
    if (db[thing].exits == NOTHING)
      loc = find_entrance (thing);
    else
      loc = db[thing].exits;
    db[loc].exits = remove_first (db[loc].exits, thing);
    giveto (db[thing].owner, EXIT_COST);
#ifdef QUOTA
    add_quota (db[thing].owner, 1);
#endif /* QUOTA */
    do_halt (thing, "");
    notify (db[thing].owner,
      tprintf ("You get your %d %s deposit back for %s.", EXIT_COST,
        ((EXIT_COST == 1) ? MONEY : MONIES), db[thing].name));
    notify (player, "Destroyed.");
    break;
  }
  do_empty (thing);
}
#endif /* DESTROY */

/* object must be empty and reference free before being freed */
void free_object (dbref obj)
{
  db[obj].next = first_free;
  first_free = obj;
}


/*
 * unlink all dead rooms+build new free list.  Must be called whenever
 * database is read from disk.  May also be called at other times to
 * straighten out the free list.
 */
#define CHECK_REF(a) if ((((a)>-1) && (db[a].flags & GOING)) || (a>=db_top)\
  || (a<-3))
/* check for free list corruption */
#define NOT_OK(thing)\
 ((db[thing].location!=NOTHING) || (db[thing].owner!=GOD) ||\
 ((db[thing].flags & ~ACCESSED)!=(TYPE_THING | GOING)))

/* return a cleaned up object off the free list or NOTHING */
dbref free_get (void)
{
  dbref newobj;
  if (first_free == NOTHING)
    return (NOTHING);
  newobj = first_free;
  first_free = db[first_free].next;
  /* Make sure this object really should be in free list */
  /* NOTE: A little problem here, a wiz can @teleport an object */
  /* out of the free list, when that object comes up for recycling */
  /* it will be caught here and the free list will be rebuilt. */
  if (NOT_OK (newobj)) {
    static int nrecur = 0;
    if (nrecur++ == 20) {
      first_free = NOTHING;
      report ();
      fprintf (stderr, "ERROR: Removed free list and continued\n");
      return (NOTHING);
    }
    report ();
    fprintf (stderr, "ERROR: Object #%d should not free\n", newobj);
    fprintf (stderr, "ERORR: Corrupt free list, fixing\n");
    FIX;
    nrecur--;
    return (free_get ());
  }
  /* free object name */
  SET (db[newobj].name, NULL);
  return (newobj);
}


void fix_free_list (void)
{
  dbref thing;
  first_free = NOTHING;
  /* destroy all rooms+make sure everything else is really dead */
  for (thing = 0; thing < db_top; thing++)
    if (db[thing].flags & GOING) {
      if (Typeof (thing) == TYPE_ROOM)
        do_empty (thing);
      else
        /*
         * if something other than room, make sure it is located in
         * NOTHING.  Otherwise undelete it.  Needed in case @tel is
         * used on an object.
         */
      if (NOT_OK (thing))
        db[thing].flags &= ~GOING;
    }
  first_free = NOTHING;
  /* check for references to destroyed objects */
  for (thing = 0; thing < db_top; thing++)
    /* if object is alive make sure it doesn't refer to any dead objects */
    if (!(db[thing].flags & GOING)) {
      /* test object home */
      CHECK_REF (db[thing].exits)
        switch (Typeof (thing)) {
      case TYPE_PLAYER:
      case TYPE_THING:
        db[thing].exits = PLAYER_START; /* set home to limbo */
        break;
      case TYPE_ROOM:          /* yuck probably corrupted set to nothing */
        {
          fprintf (stderr, "ERROR: Dead exit in exit list for room #%d\n",
            thing);
          report ();
          db[thing].exits = NOTHING;
        }
      }
      CHECK_REF (db[thing].location)
        switch (Typeof (thing)) {
      case TYPE_PLAYER:        /*
                                 * this case shouldn't happen but just
                                 * in case...
                                 */
      case TYPE_THING:
        moveit (thing, PLAYER_START);
        break;
      case TYPE_EXIT:          /* Make exits destination limbo */
        db[thing].location = 0;
        SET (db[thing].name, "limbo");
        db[thing].flags |= GOING;
        break;
      case TYPE_ROOM:          /* Remove drop to if it goes to dead object */
        db[thing].location = NOTHING;
      }
      if (((db[thing].next < 0) || (db[thing].next >= db_top)) &&
        (db[thing].next != NOTHING)) {
        fprintf (stderr, "ERROR: Invalid next pointer from object %s(%d)\n",
          db[thing].name, thing);
        report ();
        db[thing].next = NOTHING;
      }
      if ((db[thing].owner < 0) || (db[thing].owner >= db_top)) {
        fprintf (stderr, "ERROR: Invalid object owner %s(%d)\n",
          db[thing].name, thing);
        report ();
        db[thing].owner = GOD;
      }
    } else
      /* if object is dead stick in free list */
      free_object (thing);
  /* mark all rooms that can be reached from limbo */
  dbmark (0);
  /* look through list and inform any player with an unconnected room */
  dbunmark ();
}

/* Check data base for disconnected rooms */
static void dbmark (dbref loc)
{
  dbref thing;
  if ((loc < 0) || (loc >= db_top) ||
    (db[loc].flags & MARKED) || (Typeof (loc) != TYPE_ROOM))
    return;
  db[loc].flags |= MARKED;
  /* destroy any exits needing destruction */
  do {
    for (thing = db[loc].exits;
      (thing != NOTHING) && !(db[thing].flags & GOING);
      thing = db[thing].next);
    if (thing != NOTHING) {
      db[loc].exits = remove_first (db[loc].exits, thing);
      do_empty (thing);
    }
  }
  while (thing != NOTHING);
  /* recursively trace */
  for (thing = db[loc].exits; thing != NOTHING; thing = db[thing].next)
    dbmark (db[thing].location);
}

static void dbunmark (void)
{
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (db[loc].flags & MARKED)
      db[loc].flags &= ~MARKED;
    else if (Typeof (loc) == TYPE_ROOM) {
      dest_info (NOTHING, loc);
    }
}

/* Check data base for disconnected objects */
static void dbmark1 (void)
{
  dbref thing;
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (Typeof (loc) != TYPE_EXIT) {
      for (thing = db[loc].contents; thing != NOTHING; thing = db[thing].next) {
        if ((db[thing].location != loc) || (Typeof (thing) == TYPE_EXIT)) {
          fprintf (stderr,
            "ERROR: Contents of object %d corrupt at object %d cleared\n",
            loc, thing);
          db[loc].contents = NOTHING;
          break;
        }
        db[thing].flags |= MARKED;
      }
      if (Typeof (db[loc].owner) != TYPE_PLAYER) {
        fprintf (stderr, "ERROR: Bad owner on object %d changed to %d\n", loc,
          GOD);
        db[loc].owner = GOD;
      }
    } else {                    /* lets convert old style exits to new ones. */
      /* but only if it needs it */
      if (db[loc].exits == NOTHING)
        db[loc].exits = find_entrance (loc);
    }
}

static void dbunmark1 (void)
{
  dbref loc;
  for (loc = 0; loc < db_top; loc++)
    if (db[loc].flags & MARKED)
      db[loc].flags &= ~MARKED;
    else if (((Typeof (loc) == TYPE_PLAYER) || (Typeof (loc) == TYPE_THING))
      && !(db[loc].flags & GOING)) {
      fprintf (stderr, "ERROR DBCK: Moved object %d\n", loc);
      moveto (loc, 0);
    }
}

void do_dbck (dbref player)
{
  if (!Wizard (player)) {
    notify (player, "Silly mortal chicks are for kids!");
    return;
  }
  FIX;
  dbmark1 ();
  dbunmark1 ();
}

/* send contents of destroyed object home+destroy exits */
/* all objects must be moved to nothing or otherwise unlinked first */
static void do_empty (dbref thing)
{
  static int nrecur = 0;
  if (nrecur++ > 20) {          /* if run away recursion return */
    report ();
    fprintf (stderr, "ERROR: Runaway recursion in do_empty\n");
    nrecur--;
    return;
  }
  switch (Typeof (thing)) {
  case TYPE_ROOM:              /* if room destroy all exits out of it */
    {
      dbref first;
      dbref rest;
      /* before we kill it tell people what is happening */
      dest_info (thing, NOTHING);
      /* return owners deposit */
      if (db[thing].owner > 0) {
        giveto (db[thing].owner, ROOM_COST);
#ifdef QUOTA
        add_quota (db[thing].owner, 1);
#endif /* QUOTA */
      }
      first = db[thing].exits;
      db[thing].exits = NOTHING;
      /* set destination of all exits to nothing */
      DOLIST (rest, first) {
        db[rest].location = NOTHING;
      }
      /* Clear all exits out of exit list */
      while (first != NOTHING) {
        rest = db[first].next;
        if (Typeof (first) == TYPE_EXIT) {
          /* compensate owner for loss then destroy */
          if (db[first].owner > 0) {
            giveto (first, EXIT_COST);
#ifdef QUOTA
            add_quota (db[first].owner, 1);
#endif /* QUOTA */
          }
          do_empty (first);
        }
        first = rest;
      }
    }
  case TYPE_THING:
  case TYPE_PLAYER:            /* if room or player send contents home */
    {
      dbref first;
      dbref rest;
      first = db[thing].contents;
      db[thing].contents = NOTHING;
      /* send all objects to nowhere */
      DOLIST (rest, first) {
        db[rest].location = NOTHING;
      }
      /* now send them home */
      while (first != NOTHING) {
        rest = db[first].next;
        /* if home is in thing set it to limbo */
        if (db[first].exits == thing)
          db[first].exits = 0;
        switch (Typeof (first)) {
        case TYPE_EXIT:        /* if player holding exits, destroy it */
          do_empty (first);
          break;
        case TYPE_THING:       /* move to home */
        case TYPE_PLAYER:
          if (db[first].exits != NOTHING) {
            PUSH (first, db[db[first].exits].contents);
            db[first].location = db[first].exits;
            /* notify players they have been moved */
            if (Typeof (first) == TYPE_PLAYER)
              dest_info (first, NOTHING);
          }
          break;
        }
        first = rest;
      }
    }
    break;
  }
  /* chomp chomp */
  atr_free (thing);
  db[thing].list = NULL;
  /* don't eat name otherwise examine will crash */
  free_boolexp (db[thing].key);
  db[thing].key = TRUE_BOOLEXP;
  s_Pennies (thing, 0);
  db[thing].owner = GOD;
  db[thing].flags = GOING | TYPE_THING; /* toad it */
  db[thing].location = NOTHING;
  SET (db[thing].name, "Garbage");
  db[thing].exits = 0;
  free_object (thing);
  nrecur--;
}
#endif /* DESTROY */