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

package KoLBot::Limiter;
 
# Copyright (c) 2004 Alan W. Mahoney. All rights reserved. 
# This program is free software.  You may modify and/or 
# distribute it under the same terms as Perl itself.  
# This copyright notice must remain attached to the file.  
#

use warnings;
use strict;

use KoLBot;

BEGIN 
{
  use Exporter ();
  our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $ID);

  $VERSION     = 0.5;
  $ID          = '$Id: Limiter.pm 115 2005-12-01 01:23:27Z alan $';

  @ISA         = qw(Exporter);
  @EXPORT      = qw();
  %EXPORT_TAGS = ( );

  @EXPORT_OK   = qw();
}

our @EXPORT_OK;

use Carp;

use KoLBot::StateManager;

##########################################
## element access
##########################################

for my $field (qw( 
		   queues
		   state_manager
		   )) {
  no strict "refs";
  *$field = sub {
    my $self = shift;
    $self->{$field} = shift if @_;
    return $self->{$field};
  }
}

##########################################
## Constructor:
##########################################

sub new {
  my $invocant = shift;
  my $class = ref($invocant) || $invocant;
  my $limiter = { queues => {},
		  @_ };
  bless $limiter, $class;
}

##########################################
## The rest
##########################################

# queue, rate, burst
sub add {
  my $limiter=shift;
  my $params = { sm => $limiter->{state_manager},
		 type => 'bitbucket',
		 @_ };
  
  $limiter->queues->{$params->{queue}}->{rate} = $params->{rate};
  $limiter->queues->{$params->{queue}}->{burst} = $params->{burst};
  $limiter->queues->{$params->{queue}}->{type} = $params->{type};
  $limiter->queues->{$params->{queue}}->{sm} = $params->{sm};
  $limiter->queues->{$params->{queue}}->{cache} = {};
}

sub max {
  my @ar = @_;
  my $m = $ar[0];
  foreach my $x (@ar) {
    ($x > $m) && ($m = $x);
  }
  return $m;
}

sub min {
  my @ar = @_;
  my $m = $ar[0];
  foreach my $x (@ar) {
    ($x < $m) && ($m = $x);
  }
  return $m;
}

# returns how many uses are currently available
sub available {
  my $limiter = shift;

  my $params = { number => 1,
		 @_ };

  my $user = $params->{user};
  my $queue = $params->{queue};
  my $time = $params->{time};

#  my $sm = $limiter->state_manager;
  my $sm = $limiter->queues->{$queue}->{sm};

  my $state = $sm->get($user,'limiter'.$queue);
#   my $state;
#   if(exists($limiter->queues->{$queue}->{cache}->{$user})) {
#     $state = $limiter->queues->{$queue}->{cache}->{$user};
#   } else {
#     $state = $sm->get($user,'limiter'.$queue);
#     $limiter->queues->{$queue}->{cache}->{$user} = $state;
#   }

  $state->{x} ||= 0.;
  $state->{t} ||= 0;

  if($limiter->queues->{$queue}->{type} eq 'bitbucket') {
    $state->{x} = 
      max(0,
	  $state->{x}
	  - ($time -
	     $state->{t}
	     ) / $limiter->queues->{$queue}->{rate}),
  } elsif ($limiter->queues->{$queue}->{type} eq 'rollover') {
    # do the two time span a day?
    my ($second, $minute, $hour,$day,$month) = koltime($state->{t});
    my ($second1, $minute1, $hour1,$day1,$month1) = koltime($time);
    if ($day + 31*$month != $day1 + 31*$month1) {
      $state->{x} = 0;
    }
  } else {
    die "Bad queue type ",$limiter->queues->{$queue}->{type};
  }
  $state->{t} = $time;

  my $available = int($limiter->queues->{$queue}->{burst}
		      - $state->{x} + 1);

  return $available;
}

# return how many they may/have used, up to number
# queue, user, time
sub check {
  my $limiter = shift;
  my $params = { number => 1,
		 @_ };

  my $sm = $limiter->state_manager;
  my $user = $params->{user};
  my $queue = $params->{queue};
  my $number = $params->{number};
  my $time = $params->{time};

  if($number < 0) {
    croak "Someone is pulling a fast one, \$number = $number\n";
    return 0;
  }

  my $available = $limiter->available(%$params);
  if($available < 1) {
    return 0;
  }

  my $state = $sm->get($user,'limiter'.$queue);
#   my $state;
#   if(exists($limiter->queues->{$queue}->{cache}->{$user})) {
#     $state = $limiter->queues->{$queue}->{cache}->{$user};
#   } else {
#     $state = $sm->get($user,'limiter'.$queue);
#     $limiter->queues->{$queue}->{cache}->{$user} = $state;
#   }

  $state->{x} ||= 0.;
  $state->{t} ||= 0;

  if($limiter->queues->{$queue}->{type} eq 'bitbucket') {
    $state->{x} = 
      max(0,
	  $state->{x}
	  - ($time -
	     $state->{t}
	     ) / $limiter->queues->{$queue}->{rate}),
  } elsif ($limiter->queues->{$queue}->{type} eq 'rollover') {
    # do the two time span a day?
    my ($second, $minute, $hour,$day,$month) = koltime($state->{t});
    my ($second1, $minute1, $hour1,$day1,$month1) = koltime($time);
    if ($day + 31*$month != $day1 + 31*$month1) {
      $state->{x} = 0;
    }
  } else {
    die "Bad queue type ",$limiter->queues->{$queue}->{type};
  }

  $state->{t} = $time;
  # end get rid of

  my $used = min($number,$available);
  $state->{x} += $used;
  $sm->set($user,'limiter'.$queue,$state);
  return $used;
}

END {} ;

1;

