package MUnique;
use strict;
use vars qw(
  %Uniques
);

use MCoreTools;
use MLoaders;

# A MUnique handles the 'special functions' of player characters - quitting, returning from
# the dead, finding the right object to connect to if you disconnect and reconnect, etc.
#
# There is one MUnique for every player character currently logged in, and possibly
# some for non-player unique characters (hence the name "MUnique").
#
# One aspect of uniques is that, whether PCs or NPCs, they all share a common namespace.
# Therefore, one can create a NPC unique and be assured that no PC will take this name.
#
# Since MUDs often have a violent world, it is recommended that you either 1. allow
# NPC uniques to return from the dead, or 2. provide some way for creation of new NPC
# uniques to replace them.

### Class methods

sub exists {
  my ($class, $uname) = @_;
  $uname = lc $uname;
  
  return 1 if $Uniques{$uname};
  return 1 if -e $class->filename($uname, 'obj');
  return 0;
}

sub new {
  my ($class, $name, %fields) = @_;
  my $uname = lc $name;
  
  mudlog "New unique '$name'";
  my $obj = MObject->new(
    unique => $uname,
    %fields,
  );
  if (!-e _files_dir()) {
    mudlog "BOOTSTRAP: Unique save dir not found, creating first unique as controller";
    $obj->set_val(immortal => 1);
    $obj->set_val(unrestricted_edit => 1);
    $obj->set_val(CONTROLLER => 1);
    File::Path::mkpath(_files_dir(), 0, DATA_PERMS|0110);
  }
  
  my $self = bless {
    name => $uname,
    object => $obj,
  }, $class;
  $Uniques{$uname} = $self;

  call_hooks('new_unique_setup', $obj);
  
  return $self;
}

sub get {
  my ($class, $uname) = @_;
  $uname = lc $uname;
  return $Uniques{$uname} if exists $Uniques{$uname};
  
  mudlog "Loading unique '$uname'";
  my $obj;
  {
    my $file = $class->filename($uname, 'obj');
    my $hand = IO::File->new("< $file") or die $!;
    local $/;
    <$hand> =~ /^(.*)$/s; # FIXME: untaint
    $obj = MObject->thaw($1);
    $obj->unique($uname);
  }
  
  my $self = bless {
    name => $uname,
    object => $obj,
  }, $class;
  $Uniques{$uname} = $self;
    
  #mudlog "DEBUG: ending MUnique::get, self = $self, obj = $obj, name = $uname";
    
  return $self;
}

sub get_in_world {
  my ($class, $uname) = @_;
  $uname = lc $uname;
  
  return $Uniques{$uname};
}

sub filename {
  my ($class, $name, $ext) = @_;
  
  # the 'lc' is in there for those broken file systems that care about filename case.     :)
  (my $dname) = lc($name) =~ /(^\w+$)/ or die "bad name: $name";
  (my $dext) = $ext =~ /(^\w+$)/ or die "bad ext: $ext";
  my $path = relfpath("data/world/_uniques/\L$dname.$dext");
  return ($path =~ /^(.*)$/)[0];
}
sub _files_dir {return reldpath('data/world/_uniques')}

################################################################

sub place_in_world {
  my ($self) = @_;
  
  return if $self->{placed};

  mudlog "Unique '$self->{name}': inserting in world";
  my $obj = $self->object;
  $obj->body_pos('standing') if MLoaders->mloaded('body_pos') and $obj->has_body_pos;
  my $loadroom = $::Rooms{$obj->loadroom};
  $loadroom->add_contents($obj) if $loadroom;
  $obj->saveable(1);
  call_hooks('place_unique', $obj);

  $self->{placed} = 1;
  return 1;
}

sub save {
  my ($self) = @_;
  return unless my $obj = $self->{object};
  return unless $obj->saveable;

  { $obj->loadroom(($obj->container or last)->roomname or last); }
  
  my ($uname) = $self->{name} =~ /(^\w+$)/; # untaint
  if ($uname) {
    mudlog "Unique '$uname': saving (object '" . $obj->name . "')";
    open PLRSAVE, "> " . $self->filename($uname, 'obj') or do {mudlog "ERROR/UNIQUE: '$uname' couldn't open obj file: $!"; return};
    print PLRSAVE $obj->freeze;
    close PLRSAVE;
  } else {
    mudlog "ERROR/UNIQUE: Unsaveable name '$self->{name}'";
  }
  1;
}

sub object {$_[0]{object}}

sub remove {
  # should only be called from MObject::dispose
  my ($self) = @_;
  
  mudlog "Removing unique '$self->{name}'";
  $self->save;
  delete $Uniques{$self->{name}};
}

1;
