#
# Copyright (c) 2003 Lev A. Serebryakov <lev@serebryakov.spb.ru>
#
#    This module is free software; you can redistribute it and/or modify it
#    under the same terms as Perl itself.
#
# This package impelemnts cache for deltas and check-out results
#
# $Id: DeltaCache.pm 783 2003-12-05 17:08:35Z lev $
#
package Cvs::Repository::DeltaCache;

use strict;

use vars qw($VERSION);
$VERSION  = join('.',0,76,('$LastChangedRevision: 783 $' =~ /^\$\s*LastChangedRevision:\s+(\d+)\s*\$$/),'cvs2svn');

use Cvs::Repository::Exception qw(:INTERNAL);

use constant SET_MMEM  => 0;
use constant SET_MSIZE => 1;
use constant SET_UMEM  => 2;

use constant ELEM_SIZE => 0;
use constant ELEM_USED => 1;
use constant ELEM_CONT => 2;

use constant STAT_HIT => 0;
use constant STAT_ACC => 1;
use constant STAT_ADD => 2;
use constant STAT_DEL => 3;

sub New
{
  my $proto = shift;
  my $class = ref($proto) || $proto;
  my $self = bless({}, $class);
  my ($diffmem,$maxdiffsize,$resmem,$maxressize) = @_;
  
  $diffmem     = 0         unless defined $diffmem;
  $maxdiffsize = $diffmem  unless defined $maxdiffsize && $maxdiffsize < $diffmem;
  $resmem      = 0         unless defined $resmem;
  $maxressize  = $resmem   unless defined $maxressize  && $maxressize  < $resmem;;
  
  $self->{'diffs'}    = {};
  $self->{'diffset'}  = [$diffmem, $maxdiffsize, 0];
  $self->{'diffstat'} = [0,0,0,0];
  
  $self->{'results'}  = {};
  $self->{'resset'}   = [$resmem,  $maxressize,  0];
  $self->{'resstat'}  = [0,0,0,0];
  
  return $self;
}

sub getDiff
{
  ++$_[0]->{'diffstat'}->[STAT_ACC];
  return undef unless exists $_[0]->{'diffs'}->{$_[1]};
  ++$_[0]->{'diffs'}->{$_[1]}->[ELEM_USED];
  ++$_[0]->{'diffstat'}->[STAT_HIT];
  return $_[0]->{'diffs'}->{$_[1]}->[ELEM_CONT];
}

sub addDiff
{
  my ($self,$key,$diff,$size) = @_;
  return if !$self->{'diffset'}->[SET_MSIZE];
  return unless $size < $self->{'diffset'}->[SET_MSIZE];
  return &__addElement($self->{'diffs'},$self->{'diffset'},$key,$diff,$size,$self->{'diffstat'});
}

sub diffsEnabled
{
  return $_[0]->{'diffset'}->[SET_MSIZE] && $_[0]->{'diffset'}->[SET_MMEM];
}

sub getResult
{
  ++$_[0]->{'resstat'}->[STAT_ACC];
  return undef unless exists $_[0]->{'results'}->{$_[1]};
  ++$_[0]->{'results'}->{$_[1]}->[ELEM_USED];
  ++$_[0]->{'resstat'}->[STAT_HIT];
  return $_[0]->{'results'}->{$_[1]}->[ELEM_CONT];
}

sub addResult
{
  my ($self,$key,$result) = @_;
  my $size = 0;
  return if !$self->{'resset'}->[SET_MSIZE];
  map { $size += length($_) } @{$result};
  return unless $size < $self->{'resset'}->[SET_MSIZE];
  return &__addElement($self->{'results'},$self->{'resset'},$key,$result,$size,$self->{'resstat'});
}

sub resultsEnabled()
{
  return $_[0]->{'resset'}->[SET_MSIZE] && $_[0]->{'resset'}->[SET_MMEM];
}

sub getStat
{
  my $d = $_[0]->{'diffstat'};
  my $r = $_[0]->{'resstat'};
  return (
    $d->[STAT_HIT],$d->[STAT_ACC],$d->[STAT_ADD],$d->[STAT_DEL],scalar(keys %{$_[0]->{'diffs'}}),
    $r->[STAT_HIT],$r->[STAT_ACC],$r->[STAT_ADD],$r->[STAT_DEL],scalar(keys %{$_[0]->{'results'}}),
  );       
}

sub __addElement
{
  my ($cache, $settings, $key, $value, $size, $stat) = @_;
  my $used = 0;
  
  if(exists $cache->{$key}) {
    # Delete if presents
    $settings->[SET_UMEM] -= $cache->{$key}->[ELEM_SIZE];
    # Save used
    $used = $cache->{$key}->[ELEM_USED];
    # Undef content
    undef $cache->{$key}->[ELEM_CONT];
    delete $cache->{$key};
  }
  
  # Free memory, if needed
  &__freeMem($cache,$settings,$size,$stat) if $size > $settings->[SET_MMEM] - $settings->[SET_UMEM];
  # Memory could not be found :(
  return unless $size < $settings->[SET_MMEM] - $settings->[SET_UMEM];
  
  # Add element
  ++$used;
  $cache->{$key} = [$size, $used, $value];
  $settings->[SET_UMEM] += $size;
  ++$stat->[STAT_ADD];
}

sub __freeMem
{
  my ($cache, $settings, $size, $stat) = @_;
  foreach my $k (sort {$cache->{$a}->[ELEM_USED] <=> $cache->{$b}->[ELEM_USED] || $cache->{$b}->[ELEM_SIZE] <=> $cache->{$b}->[ELEM_SIZE]} keys %{$cache}) {
    $settings->[SET_UMEM] -= $cache->{$k}->[ELEM_SIZE];
    delete $cache->{$k};
    ++$stat->[STAT_DEL];
    last unless $size > $settings->[SET_MMEM] - $settings->[SET_UMEM];
  }
}

1;
