﻿package Rose::DB::Object::Cached::CHI;

use base 'Rose::DB::Object';

use CHI;

use Rose::DB::Object::Constants qw(STATE_IN_DB); use constant LEVEL_SEP => "\1\1"; 



### Gets the object that will handle all the cache calls 
### in this case it will look to see if a cache handle 
### has been set.  If not it will create a new CHI object 
### and store that as the cache handle.
### This is here because I originally had a Cached::FastMmap
### class and was planning on having things like Cached::OtherCache
### and was looking for a single place to save the the object that
### handles the cache while keeping other functions constant.

sub __xrdbopriv_get_cache_handle {
    my $self = shift;

    if (defined $self->meta->cache_handle) {
        return $self->meta->cache_handle;
    } else {
        my %defaults = (
            driver=>'Memory',
            namespace=>$self->meta->class,
            expires_in => $self->meta->cached_objects_expire_in,
        );
        my $current_settings = $self->meta->cache_settings;

        my %chi_settings = (%defaults, %{$current_settings});

        my $cache = new CHI(%chi_settings);
        $self->meta->cache_handle($cache);
        return $cache;
    }
}


sub delete {
  my($self) = shift;

  my $ret = $self->SUPER::delete(@_);
  $self->forget  if($ret);
  return $ret;
}


sub remember_all {
  my($class) = shift;

  require Rose::DB::Object::Manager;

  my(undef, %args) = Rose::DB::Object::Manager->normalize_get_objects_args(@_);

  my $objects =
    Rose::DB::Object::Manager->get_objects(
      object_class => $class,
      share_db     => 0,
      %args);

  foreach my $object (@$objects)
  {
    $object->remember;
  }

  return @$objects  if(defined wantarray); }



sub forget {
  my($self) = shift;

  my $cache = $self->__xrdbopriv_get_cache_handle;

  my $class = ref $self;
  my $pk = join(PK_SEP, grep { defined } map { $self->$_() } $self->meta->primary_key_column_names);

  no strict 'refs';
  $cache->remove("${class}::Objects_By_Id::" . LEVEL_SEP . $pk);

  foreach my $cols ($self->meta->unique_keys_column_names)
  {
    no warnings;
    my $key_name  = join(UK_SEP, @$cols);
    my $key_value = ${"${class}::Objects_Keys"}{$pk}{$key_name};
    $cache->remove("${class}::Objects_Keys" . LEVEL_SEP . $pk . LEVEL_SEP . $key_name);
  }

  return 1;
}


sub save {
  my($self) = shift;

  my $ret = $self->SUPER::save(@_);
  return $ret  unless($ret);

  my $class = ref $self;

  my $pk = join(PK_SEP, grep { defined } map { $self->$_() } $self->meta->primary_key_column_names);

  __xrdbopriv_save_object($self);

  return $ret;
}


sub load {
  # XXX: Must maintain alias to actual "self" object arg

  my %args = (self => @_); # faster than @_[1 .. $#_];

  unless(delete $args{'refresh'})
  {
    my $pk = join(PK_SEP, grep { defined } map { $_[0]->$_() } $_[0]->meta->primary_key_column_names);

    my $object = $_[0]->__xrdbopriv_get_object($_[0], $pk);

    if(defined $object)
    {
      $_[0] = $object;
      $_[0]->{STATE_IN_DB()} = 1;
      return $_[0] || 1;
    }
    else
    {
      foreach my $cols ($_[0]->meta->unique_keys_column_names)
      {
        no warnings;
        my $key_name  = join(UK_SEP, @$cols);
        my $key_value = join(UK_SEP, grep { defined($_) ? $_ : UNDEF } 
                             map { $_[0]->$_() } @$cols);

        if(my $object = $_[0]->__xrdbopriv_get_object($_[0], $key_name, $key_value))
        {
          $_[0] = $object;
          $_[0]->{STATE_IN_DB()} = 1;
          return $_[0] || 1;
        }
      }
    }
  }

  my $ret = $_[0]->SUPER::load(%args);
  __xrdbopriv_save_object($_[0])  if($ret);


  return $ret;
}


sub remember {
  my($self) = shift;

  my $cache = $self->__xrdbopriv_get_cache_handle;

  my $class = ref $self;
  my $pk = join(PK_SEP, grep { defined } map { $self->$_() } $self->meta->primary_key_column_names);

  $cache->set("${class}::Objects_By_Id" . $pk, $self); }




sub __xrdbopriv_save_object {
  my($self) = shift;
  my $cache = $self->__xrdbopriv_get_cache_handle;

  my $class = ref $self;
  my $pk = join(PK_SEP, grep { defined } map { $self->$_() } $self->meta->primary_key_column_names);

   
  $cache->set("${class}::Objects_By_Id::" . LEVEL_SEP . $pk, $self->strip); 

  foreach my $cols ($self->meta->unique_keys_column_names) {
    my $key_name  = join(UK_SEP, @$cols);
    my $key_value = join(UK_SEP, grep { defined($_) ? $_ : UNDEF } 
                         map { $self->$_() } @$cols);

    $cache->set("${class}::Objects_By_Key::" . LEVEL_SEP . $key_name . LEVEL_SEP . $key_value, $self->strip);
    $cache->set("${class}::Objects_Keys::" . LEVEL_SEP . $pk . LEVEL_SEP . $key_name, $key_value);
  }

};




sub __xrdbopriv_get_object {
  my $self = shift;
  my($class) = ref $_[0] || $_[0];

  my $cache = $self->__xrdbopriv_get_cache_handle;


  if(@_ == 2)
  {
    my($pk) = $_[1];

    my $obj = $cache->get("${class}::Objects_By_Id::" . LEVEL_SEP . $pk);


    return $obj;
  }
  else
  {
    my($key_name, $key_value) = ($_[1], $_[2]);

    no strict 'refs';
    no warnings;

    my $obj = $cache->get("${class}::Objects_By_Key::" . LEVEL_SEP . $key_name . LEVEL_SEP . $key_value);
    if(defined $obj)
    {
      __xrdbopriv_save_object($obj);
      return $obj;
    }

    return undef;
  }
};






1;
