#include "copyright.h"

/* Wizard-only commands */

#include <ctype.h>
#include "db.h"
#include "config.h"
#include "interface.h"
#include "match.h"
#include "externs.h"

void do_teleport(dbref player, const char *arg1, const char *arg2)
{
    dbref victim;
    dbref destination;
    const char *to;

#ifdef RESTRICTED_TELEPORT
    if(!Wizard(player)) {
#ifndef TINKER
	notify(player, "Only a Wizard may teleport at will.");
#else TINKER
	notify(player, "Only a Tinker may teleport at will.");
#endif TINKER
	return;
    }
#endif /* RESTRICTED_TELEPORT */

    /* get victim, destination */
    if(*arg2 == '\0') {
	victim = player;
	to = arg1;
    } else {
	init_match(player, arg1, NOTYPE);
	match_neighbor();
	match_possession();
	match_me();
	match_absolute();
	match_player();

	if((victim = noisy_match_result()) == NOTHING) {
	    return;
	}
	to = arg2;
    }

    /* get destination */
    init_match(player, to, TYPE_PLAYER);
    match_here();
    match_absolute();
    if(Wizard(player)) {
	match_neighbor();
	match_me();
	match_player();
    }

    switch(destination = match_result()) {
      case NOTHING:
	notify(player, "Send it where?");
	break;
      case AMBIGUOUS:
	notify(player, "I don't know which destination you mean!");
	break;
      default:
	/* check victim, destination types, teleport if ok */
	if(Typeof(destination) == TYPE_EXIT
	   || Typeof(destination) == TYPE_THING
	   || Typeof(victim) == TYPE_EXIT
	   || Typeof(victim) == TYPE_ROOM
	   || (Typeof(victim) == TYPE_PLAYER
	       && Typeof(destination) != TYPE_ROOM)) {
	    notify(player, "Bad destination.");
#ifndef RESTRICTED_TELEPORT
	} else if(!Wizard(player)
		  && !(Typeof(victim) == TYPE_THING
		       && Typeof(destination) == TYPE_ROOM
		       && (controls(player, victim)
			   || (Typeof(db[victim].location) == TYPE_ROOM
			       && controls(player, db[victim].location)))
		       && (can_link_to(player, TYPE_PLAYER, destination)))) {
	    notify(player, "Permission denied.");
#endif /* RESTRICTED_TELEPORT */		  
	} else if(Typeof(victim) == TYPE_PLAYER) {
	    notify(victim, "You feel a wrenching sensation...");
	    enter_room(victim, destination);
	    notify(player, "Teleported.");
	} else {
	    /* check for non-sticky dropto */
	    if(Typeof(destination) == TYPE_ROOM
	       && db[destination].location != NOTHING
	       && !(db[destination].flags & STICKY)) {
		/* destination has immediate dropto */
		destination = db[destination].location;
	    }

	    /* do the move */
	    moveto(victim, destination);
	    notify(player, "Teleported.");
	}
    }
}

void do_mass_teleport(dbref player, const char *arg1)
{
    dbref victim;
    dbref destination;
    char buf[BUFFER_LEN];
    int moved = 0;

    if(!God(player)) {
#ifndef TINKER
	notify(player, "Only God can do a mass teleport.");
#else TINKER
	notify(player, "Only the Master Tinker can do a mass teleport.");
#endif TINKER
	return;
    }

    /* get destination */
    init_match(player, arg1, TYPE_ROOM);
    match_here();
    match_absolute();

    if ((destination = match_result()) == NOTHING) {
	notify(player, "Please specify a destination to send everyone to.");
	return;
    }
    
    switch (Typeof(destination)) {
      case NOTHING:
	notify(player, "Send them where?");
	break;

      case TYPE_ROOM:
        for (victim = 0; victim < db_top; victim++) {
	    if (victim != player && Typeof(victim) == TYPE_PLAYER) {
	        notify(victim,
		 "You and everyone around you feels a wrenching sensation...");
		moveto(victim, destination);
		look_room(victim, destination);
		moved++;
	    }
	}
	sprintf (buf, "Teleported %d players to %s.",
		 moved, unparse_object (player, destination));
	notify(player, "Teleported.");
        break;
	
      default:
	notify(player, "Mass teleports are legal to rooms only.");
    }
}

void do_force(dbref player, const char *what, char *command)
{
    dbref victim;

    if(!Wizard(player)) {
	writelog ("FORCE: failed, priv, player %s(%d), who '%s', what '%s'\n",
		  db[player].name, player, what, command);
#ifndef TINKER
	notify(player, "Only Wizards may use this command.");
#else TINKER
	notify(player, "Only Tinkers may use this command.");
#endif TINKER
	return;
    }

    /* get victim */
    if((victim = lookup_player(what)) == NOTHING) {
	writelog ("FORCE: failed, victim, player %s(%d), who '%s', what '%s'\n",
		  db[player].name, player, what, command);
	notify(player, "That player does not exist.");
	return;
    }

#ifdef GOD_PRIV
    if(God(victim)) {
#ifndef TINKER
	writelog ("FORCE: failed, wizard, player %s(%d), who '%s', what '%s'\n",
#else TINKER
	writelog ("FORCE: failed, tinker, player %s(%d), who '%s', what '%s'\n",
#endif TINKER
		  db[player].name, player, what, command);
#ifndef TINKER
	notify(player, "You can't force God.");
#else TINKER
	notify(player, "You can't force the Master Tinker.");
#endif TINKER
	return;
    }
#endif GOD_PRIV

    /* force victim to do command */
    writelog ("FORCE: success, player %s(%d), who '%s', what '%s'\n",
	      db[player].name, player, what, command);

    process_command(victim, command);
}

void do_stats(dbref player, const char *name)
{
    dbref rooms;
    dbref exits;
    dbref things;
    dbref players;
    dbref total;
    dbref i;
    dbref owner;
    char buf[BUFFER_LEN];

    if(!Wizard(player)) {
	sprintf(buf, "The universe contains %d objects.", db_top);
	notify(player, buf);
    } else {
	owner = lookup_player(name);
	total = rooms = exits = things = players = 0;
	for(i = 0; i < db_top; i++) {
	    if(owner == NOTHING || owner == db[i].owner) {
		total++;
		switch(Typeof(i)) {
		  case TYPE_ROOM:
		    rooms++;
		    break;
		  case TYPE_EXIT:
		    exits++;
		    break;
		  case TYPE_THING:
		    things++;
		    break;
		  case TYPE_PLAYER:
		    players++;
		    break;
		  default:
		    abort();
		    break;
		}
	    }
	}
	sprintf(buf,
		"%d objects = %d rooms, %d exits, %d things, %d players.",
		total, rooms, exits, things, players);
	notify(player, buf);
#ifdef TEST_MALLOC
	sprintf(buf, "Malloc count = %d.", malloc_count);
	notify(player, buf);
#endif /* TEST_MALLOC */
    }
}
		
void do_bobble(dbref player, const char *name, const char *rname)
{
    dbref victim, recip, i;
    char buf[BUFFER_LEN];
    int newflags = 0;

    if(!Wizard(player)) {
#ifndef TINKER
        writelog("TOAD: failed, priv %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "Only a Wizard can turn a person into a toad.");
#else TINKER
        writelog("BOBBLE: failed, priv %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "Only a Tinker can bobble a person.");
#endif TINKER
	return;
    }

    if (!name || !*name) {
#ifndef TINKER
        writelog("TOAD: failed, syntax %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("BOBBLE: failed, syntax %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER
	notify(player, "You must specify a victim.");
	return;
    }
    
    init_match(player, name, TYPE_PLAYER);
    match_neighbor();
    match_absolute();
    match_player();
    if((victim = noisy_match_result()) == NOTHING) {
#ifndef TINKER
        writelog("TOAD: failed, victim %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("BOBBLE: failed, victim %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER
	notify(player, "Please specify another victim.");
	return;
    }

#ifdef RECYCLE
    /* Default the recipient to RECYCLER */
    if (!rname || !*rname || string_compare (rname, RECYCLER) == 0) {
	recip = lookup_player(RECYCLER);
	newflags = UNWANTED;		/* Can be gotten by other players */
    } else {
	recip = lookup_player(rname);
    }
#else
    recip = lookup_player(rname);
#endif

    if(recip == NOTHING || recip == victim) {
#ifndef TINKER
        writelog("TOAD: failed, recip %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("BOBBLE: failed, recip %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER
	notify(player, "Please specify another player to own the victim's effects.");
	return;
    }

    if(Typeof(victim) != TYPE_PLAYER) {
#ifndef TINKER
        writelog("TOAD: failed, type %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "You can only turn players into toads!");
#else TINKER
        writelog("BOBBLE: failed, type %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "You can only bobble players!");
#endif TINKER
    } else if(Wizard(victim)) {
#ifndef TINKER
        writelog("TOAD: failed, wizard %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "You can't turn a Wizard into a toad.");
#else TINKER
        writelog("BOBBLE: failed, tinker %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "You can't bobble a Tinker.");
#endif TINKER
    } else if(db[victim].contents != NOTHING) {
#ifndef TINKER
        writelog("TOAD: failed, contents %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("BOBBLE: failed, contents %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER

	notify(player, "What about what they are carrying?");
    } else {
#ifndef TINKER
        writelog("TOAD: success %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("BOBBLE: success %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER

	/* we are ok */
	/* do it */
	if(db[victim].password) {
	    free((void *) db[victim].password);
	    db[victim].password = 0;
	}
	db[victim].flags = TYPE_THING;

	/* Give the sphere and all old belongings to recipient */
	db[victim].owner = recip;
	for (i=0; i<db_top; i++) {
	    if (db[i].owner == victim) {
		db[i].owner = recip;
		db[i].flags |= newflags;
	    }
	}

	db[victim].pennies = 1;	/* dont let him keep his immense wealth */

	/* notify people */
#ifndef TINKER
	sprintf(buf, "You have been turned into a toad by %s.",
		db[player].name);
#else TINKER
	sprintf(buf, "You have been encased in a stasis sphere by %s.",
		db[player].name);
#endif TINKER
	notify(victim, buf);
#ifndef TINKER
	sprintf(buf, "You turned %s into a toad!", db[victim].name);
#else TINKER
	sprintf(buf, "You bobbled %s!", db[victim].name);
#endif TINKER
	notify(player, buf);

	/* reset name */
#ifdef PLAYER_LIST
	delete_player(victim);
#endif PLAYER_LIST	
#ifndef TINKER
	sprintf(buf, "a slimy toad named %s", db[victim].name);
#else TINKER
	sprintf(buf, "a silvery sphere containing %s", db[victim].name);
#endif TINKER
	free((void *) db[victim].name);
	db[victim].name = alloc_string(buf);
    }
}

void do_unbobble(dbref player, const char *name, const char *newname)
{
    dbref victim;
    char buf[BUFFER_LEN];

    if(!Wizard(player)) {
#ifndef TINKER
        writelog("UNTOAD: failed, priv %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "Only a Wizard can turn a toad into a person.");
#else TINKER
        writelog("UNBOBBLE: failed, priv %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "Only a Tinker can unbobble a person.");
#endif TINKER
	return;
    }

    if (!name || !*name) {
#ifndef TINKER
        writelog("UNTOAD: failed, syntax %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("UNBOBBLE: failed, syntax %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER
	notify(player, "You must specify a thing.");
	return;
    }
    
    if (!newname || !*newname) {
#ifndef TINKER
        writelog("UNTOAD: failed, syntax %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player,"You must specify a new name: @untoad <thing> = <name>");
#else TINKER
        writelog("UNBOBBLE: failed, syntax %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player,
	       "You must specify a new name: @unbobble <thing> = <name>");
#endif TINKER
	return;
    }
    
    init_match(player, name, TYPE_THING);
    match_neighbor();
    match_absolute();
    match_player();
    if((victim = noisy_match_result()) == NOTHING) {
#ifndef TINKER
        writelog("UNTOAD: failed, victim %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("UNBOBBLE: failed, victim %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER
	notify(player, "Please specify another thing.");
	return;
    }

    if(Typeof(victim) != TYPE_THING) {
#ifndef TINKER
        writelog("UNTOAD: failed, type %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "You can only turn players into toads!");
#else TINKER
        writelog("UNBOBBLE: failed, type %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "You can only bobble players!");
#endif TINKER
    } else if (!ok_player_name(newname)) {
#ifndef TINKER
        writelog("UNTOAD: failed, name %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("UNBOBBLE: failed, name %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER
	notify(player, "You can't give a player that name!");
    } else {
#ifndef TINKER
        writelog("UNTOAD: success %s(%d) who '%s'\n",
		 db[player].name, player, name);
#else TINKER
        writelog("UNBOBBLE: success %s(%d) who '%s'\n",
		 db[player].name, player, name);
#endif TINKER

	/* we are ok */
	/* do it */
	db[victim].flags = TYPE_PLAYER;
	db[victim].owner = victim;
	db[victim].pennies = KILL_BONUS;

	/* reset name */
	free((void *) db[victim].name);
	db[victim].name = alloc_string(newname);

#ifndef TINKER
	sprintf(buf, "You turned the toad back into %s!", db[victim].name);
#else TINKER
	sprintf(buf, "You unbobbled %s!", db[victim].name);
#endif TINKER
	notify(player, buf);

	sprintf(buf, "Use @newpassword to give %s a password",
		db[victim].name);
	notify(player, buf);

#ifdef PLAYER_LIST
	add_player(victim);
#endif PLAYER_LIST	
    }
}

void do_newpassword(dbref player, const char *name, const char *password)
{
    dbref victim;
    char buf[BUFFER_LEN];

    if(!Wizard(player)) {
        writelog("PASSWORD: failed, priv %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "Your delusions of grandeur have been duly noted.");
	return;
    } else if((victim = lookup_player(name)) == NOTHING) {
        writelog("PASSWORD: failed, victim %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "No such player.");
    } else if(*password != '\0' && !ok_password(password)) {
	/* Wiz can set null passwords, but not bad passwords */
        writelog("PASSWORD: failed, password %s(%d) who '%s'\n",
		 db[player].name, player, name);
	notify(player, "Bad password");
#ifdef GOD_PRIV	
    } else if (God(victim) && !God(player)) {
#ifndef TINKER
        writelog("PASSWORD: failed, wizard %s(%d) who '%s'\n",
#else TINKER
        writelog("PASSWORD: failed, tinker %s(%d) who '%s'\n",
#endif TINKER
		 db[player].name, player, name);
	notify(player, "You cannot change that player's password.");
#endif GOD_PRIV	
    } else {
        writelog("PASSWORD: success %s(%d) who '%s'\n",
		 db[player].name, player, name);
	/* it's ok, do it */
	if(db[victim].password) free((void *) db[victim].password);
	db[victim].password = alloc_string(password);
	notify(player, "Password changed.");
	sprintf(buf, "Your password has been changed by %s.", db[player].name);
	notify(victim, buf);
    }
}

void do_boot(dbref player, const char *name)
{
    dbref victim;
    char buf[BUFFER_LEN];

    if(!Wizard(player)) {
	writelog ("BOOT: failed, priv player %s(%d), who '%s'\n",
		  db[player].name, player, name);

#ifndef TINKER
	notify(player, "Only a Wizard can boot another player off!");
#else TINKER
	notify(player, "Only a Tinker can boot another player off!");
#endif TINKER
	return;
    }

    init_match(player, name, TYPE_PLAYER);
    match_neighbor();
    match_absolute();
    match_player();
    if((victim = noisy_match_result()) == NOTHING) return;

#ifdef GOD_PRIV
    if(God(victim)) {
#ifndef TINKER
	writelog ("BOOT: failed, wizard, player %s(%d), who '%s'\n",
#else TINKER
	writelog ("BOOT: failed, tinker, player %s(%d), who '%s'\n",
#endif TINKER
		  db[player].name, player, name);

	notify(player, "You can't boot that player!");
	return;
    }
#endif GOD_PRIV

    if(Typeof(victim) != TYPE_PLAYER) {
	writelog ("BOOT: failed, victim, player %s(%d), who '%s'\n",
		  db[player].name, player, name);

	notify(player, "You can only boot off other players!");
    } else {
	writelog ("BOOT: success, player %s(%d), who '%s'\n",
		  db[player].name, player, name);

	/* we are ok */
	/* do it */
	/* notify people */
	sprintf(buf, "You have been booted off the game by %s.",
		db[player].name);
	notify(victim, buf);
	sprintf(buf, "You booted %s off!", db[victim].name);
	notify(player, buf);
	boot_off(victim);
	/* reset name */
    }
}

# define MINUTES (60)
# define HOURS   (3600)
# define DAYS    (24 * HOURS)
# define WEEKS   (7 * DAYS)
# define MONTHS  (30 * DAYS)
# define YEARS   (365 * DAYS)

long atosec (const char *str)
{
    long num;
    const char *s;
    
    if (!str || !*str) return (0L);
    
    num = atol (str);
    
    for (s=str; *s && isspace (*s) || isdigit (*s); s++) ;
    
    switch (*s) {
      case '\0':
      case 's':
      case 'S':		return (num); break;

      case 'm':
      case 'M':		if (s[1] == 'i' || s[1] == 'I') {
			    return (num * MINUTES); break;
      			} else {
			    return (num * MONTHS); break;
			}

      case 'h':
      case 'H':		return (num * HOURS); break;

      case 'd':
      case 'D':		return (num * DAYS); break;

      case 'w':
      case 'W':		return (num * WEEKS); break;

      case 'y':
      case 'Y':		return (num * YEARS); break;

      default:		return (num);
    }
}

void do_date(dbref player, const char *start, const char *thstr)
{
    dbref rooms=0, arooms=0;
    dbref exits=0, aexits=0;
    dbref things=0, athings=0;
    dbref players=0, aplayers=0;
    dbref total=0, atotal=0;
    dbref i;
    dbref owner;
    long now, start_tm, thresh;
    char buf[BUFFER_LEN];

    now = time (0);

    if(!Wizard(player)) {
	sprintf(buf, "%24.24s", ctime (&now));
	notify(player, buf);
	return;
    }

    start_tm = (start && *start) ?  now - atosec (start) : now - MONTHS;
    thresh = (thstr && *thstr) ? atoi (thstr) : 0;
    
    for (i=0; i<db_top; i++) {
	if (db[i].lastused >= start_tm && db[i].usecnt > thresh) {
	    switch (Typeof(i)) {
	      case TYPE_ROOM:	rooms++; arooms++; break;
	      case TYPE_THING:	things++; athings++; break;
	      case TYPE_EXIT:	exits++; aexits++; break;
	      case TYPE_PLAYER:	players++; aplayers++; break;
	    }
	    total++; atotal++;
	} else {
	    switch (Typeof(i)) {
	      case TYPE_ROOM:	rooms++; break;
	      case TYPE_THING:	things++; break;
	      case TYPE_EXIT:	exits++; break;
	      case TYPE_PLAYER:	players++; break;
	    }
	    total++;
	}
    }
    
    if (thresh == 0) {
	sprintf (buf, "Objects used since %20.20s:", ctime (&start_tm)+4);
    } else {
	sprintf (buf, "Objects used more than %d time%s since %20.20s:",
		 thresh, (thresh == 1) ? "" : "s", ctime (&start_tm)+4);
    }
    notify (player, buf);
    
    sprintf (buf, "Totals:  %8d of %8d", atotal, total);
    notify (player, buf);
    
    sprintf (buf, "Rooms:   %8d of %8d", arooms, rooms);
    notify (player, buf);
    
    sprintf (buf, "Exits:   %8d of %8d", aexits, exits);
    notify (player, buf);
    
    sprintf (buf, "Things:  %8d of %8d", athings, things);
    notify (player, buf);

    sprintf (buf, "Players: %8d of %8d", aplayers, players);
    notify (player, buf);
    
    writelog ("DATE %s(%d) %s\n", db[player].name, player, start);
}

void do_top(dbref player, const char *numstr, const char *typestr)
{
    dbref *top = NULL;
    long num=10, type=NOTHING;
    dbref i, j, k;
    char buf[BUFFER_LEN];
  
    if(!Wizard(player)) {
	notify(player, "@Top is a restricted command.");
	return;
    }

    if (numstr && *numstr) num = atol (numstr);
    
    if (num < 1 || num > db_top) {
    	sprintf (buf, "You must specify an integer from %d to %d for @top",
		 1, db_top);
	notify (player, buf);
	return;
    }
    
    if (typestr && *typestr) {
	switch (*typestr) {
	  case 'p':
	  case 'P':	type = TYPE_PLAYER; break;

	  case 'e':
	  case 'E':	type = TYPE_EXIT; break;

	  case 'r':
	  case 'R':	type = TYPE_ROOM; break;

	  case 't':
	  case 'T':	type = TYPE_THING; break;
	}
    }
    
    top = (dbref *) malloc (num * sizeof (dbref));
    for (j=0; j<num; j++) top[j] = NOTHING;
    
    /* For each item in the database */
    for (i=0; i<db_top; i++) {
	if (db[i].usecnt == 0) continue;
	
	if (type != NOTHING && Typeof(i) != type) continue;
	
	/* Loop through the top table, sorted by usecnt */
	for (j = num; j > 0; j--) {
	    if (top[j-1] == NOTHING) continue;
	    if (db[i].usecnt <= db[top[j-1]].usecnt) break;
	}
	
	/* Roll down the less used items, and insert i */
	if (j < num) {
	    for (k = num-1; k > j; k--) top[k] = top[k-1];
	    top[j] = i;
	}
    }

    /* Now print the items */
    switch (type) {
      case NOTHING:	sprintf (buf, "The top %d items:", num);
      			break;
      case TYPE_PLAYER:	sprintf (buf, "The top %d most active players:", num);
      			break;
      case TYPE_ROOM:	sprintf (buf, "The top %d most active rooms:", num);
      			break;
      case TYPE_EXIT:	sprintf (buf, "The top %d most used exits:", num);
      			break;
      case TYPE_THING:	sprintf (buf, "The top %d most used things:", num);
      			break;
    }
    notify (player, buf);
    
    for (j=0; j<num && top[j] != NOTHING; j++) {
	sprintf (buf, "%10d  %s", db[top[j]].usecnt,
		 unparse_object (player, top[j]));
	notify (player, buf);
    }

    writelog ("TOP  %s(%d) %s %s\n", db[player].name, player,
    	      numstr ? numstr : "", typestr ? typestr : "");
}