package Mud::Storage;
use strict;
use Mud::CoreTools;
use vars qw(%StorageObjects %StorageObjectsRev);

=head1 Description

Mud::Storage is the base class for storage objects. E<lt>explanation of what a 
storage object/class isE<gt>

Note that an object may be accessible by multiple access keys, such as in the 
NamedFiles storage in a case-insensitive file system, or the numeric-ID
storages with/without creation dates. When storing access keys, they should
always be requested from the object itself.

Mud::Storage provides:

=over 4

=item *

A registry for storage objects.

=item *

A framework for caching persistent objects in memory.

=back

=head1 Methods

=over 4

=cut

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

=item CM register_storage(TAG)

Registers the storage object. TAG is the value used for identifying this
particular storage object in proxies, etc.

=cut

sub register_storage {
  my ($obj, $tag) = @_;
  $StorageObjects{$tag} = $obj;
  $StorageObjectsRev{$obj} = $tag;
  return;
}

=item CM unregister_storage(TAG)

Unregisters a storage object.

=cut

sub unregister_storage {
  my ($class, $tag) = @_;
  delete $StorageObjectsRev{$StorageObjects{$tag}};
  delete $StorageObjects{$tag};
  return;
}

=item CM storage(TAG)

Return the storage object for the given TAG.

=cut

sub storage { return $StorageObjects{$_[1]} }

=back

=item DM storage_tag()

Return the tag for this storage object.

=cut

sub storage_tag { return $StorageObjectsRev{$_[0]} }

=item IM obj(KEY)

Returns a proxy for the object for the specified access key., or undef
if the object does not exist. This method has no 'magic' and may be 
overridden.

=cut

sub obj {
  my ($self, $key) = @_;
  require Mud::Proxy::Key;
  return Mud::Proxy::Key->new($self->storage_tag, $key)
    if $self->real_obj($key);
  return undef;
}

=item IM object_register(OBJ)

<FIXME: see V4 Design for explanation> Should only be called from
the object being registered.

=cut

sub object_register {confess "Should be implemented in subclass"}

=item IM object_unregister(OBJ)

<FIXME: see V4 Design for explanation> Should only be called from
the object being unregistered.

=cut

sub object_unregister {confess "Should be implemented in subclass"}

=back

=head2 Subclass Methods

These are methods that must be implemented by the subclass.

=over 4

=item ?M real_obj(KEY)

Returns the object for the specified access key.

=back

=head2 Caching Methods

The following methods implement a cache for the subclass to use however
it likes. Removing least-recently-used items is handled automatically.

If the object is a class, variables beginning with '%StorageCache'
will be used. If the object is an instance (blessed hash), those names
will be used as hash keys.

=over 4

=cut

sub _cache_ref_data {
  no strict 'refs';
  return ref $_[0] ? $_[0]{StorageCache} : \%{"$_[0]::StorageCache"};
}
sub _cache_ref_meta {
  no strict 'refs';
  return ref $_[0] ? $_[0]{StorageCacheMeta} : \%{"$_[0]::StorageCacheMeta"};
}
sub _cache_ref_dirty {
  no strict 'refs';
  return ref $_[0] ? $_[0]{StorageCacheDirty} : \%{"$_[0]::StorageCacheDirty"};
}


=item DM cache_add(KEY, VAL)

Adds an entry to the cache hash. Will overwrite an existing entry.

=cut

sub cache_add {
  my ($self, $key, $val) = @_;
  (&_cache_ref_data)->{$key} = $val;
  (&_cache_ref_meta)->{$key} = {used => Mud::Scheduler->now};
  return;
}

=item DM cache_remove(KEY)

Removes an entry from the cache hash. Will write it if it is dirty.

=cut

sub cache_remove {
  my ($self, $key) = @_;
  $self->cache_write($key) if (&_cache_ref_dirty)->{$key};
  delete +(&_cache_ref_data)->{$key};
  delete +(&_cache_ref_meta)->{$key};
  delete +(&_cache_ref_dirty)->{$key};
  return;
}

=item DM cache_get(KEY, TOUCH)

Gets an entry from the cache hash. If TOUCH is true, sets the 'last used' time for the entry.

=cut

sub cache_get {
  my ($self, $key) = @_;
  my $dh = (&_cache_ref_data);
  (&_cache_ref_meta)->{$key}{used} = Mud::Scheduler->now
    if exists $dh->{$key};
  return $dh->{$key};
}

sub cache_touch {
  my ($self, $key) = @_;
  (&_cache_ref_meta)->{$key}{used} = Mud::Scheduler->now;
  return;
}

=item DM cache_dirty(KEY)

Sets the dirty flag for the entry.

=cut

sub cache_dirty {
  my ($self, $key) = @_;
  (&_cache_ref_dirty)->{$key} = 1;
}

=item DM cache_write(KEY)

For subclass implementation. Writes a cache entry to external storage.
Call SUPER::cache_write to clear the dirty flag.

=cut

sub cache_write {
  my ($self, $key) = @_;
  delete +(&_cache_ref_dirty)->{$key};
}

=item DM cache_flush()

Writes all dirty cache entries.

=cut

sub cache_flush {
  my ($self) = @_;
  for (keys %{(&_cache_ref_dirty)}) {
    $self->cache_write($_);
  }
}

=item DM cache_clean()

(Not yet implemented) Removes old cache entries.

=cut

=back

=cut

1;
__END__
