package Net::Paraget::AssignmentManager;
#
# $Id: AssignmentManager.pm,v 1.7 2001/05/10 04:33:48 lrclause Exp $
#

use strict;
use Carp;

use Net::Paraget::Assignment;
use Net::Paraget::Interval;

use Class::MethodMaker
  get_set       => [ qw( context
			 file
			 min_splittable_eta
		       ) ],
  new_hash_init => [ qw( new hash_init ) ];


sub state
{
    my ( $self, $string, $priority ) = @_;
    main::state( "assignment manager: " . $string, $priority );
}


sub get_n_assignments
{
    my ( $self, $n ) = @_;

    my $context = $self->context();
    my $server_queue = $context->server_queue();

    my @assignments = ();

    while ( $n > 0 ) 
    {
	my ( $server, $interval ) = $self->find_best_match();
	
	last unless defined $server and defined $interval;
	
	my $split_at = $self->find_split_offset( $interval, $server );
	
	$self->state( "Best match " . $server->info()
		      . " with interval " . $interval->info()
		      . " starting at $split_at",
		      3
		    );
	
	last if $split_at >= $interval->end();

	my $to_assign = $interval->split( $split_at );
	
	my $mirror = $server->get_mirror();
	$server_queue->remove( $server );
	my $assignment = 
	  Net::Paraget::Assignment->new( mirror   => $mirror, 
					 interval => $to_assign,
					 file     => $self->file,
					 server   => $server,
					 );
	
	$self->state( "created assignment " . $assignment->info(), 3 );
	
	$to_assign->hash_init( assignment => $assignment,
			       completed  => 1,
			       );
	
	push @assignments, $assignment;
	
	$n--;
    }
    
    return @assignments;
}


sub find_split_offset 
{
    my ( $self, $interval, $server ) = @_;
    
    my $prev = $interval->prev();
    
    return 0 unless $prev->assignment();
    
    my $prev_speed = 0;
    my $prev_assignment = $prev->assignment();
    $prev_speed = $prev_assignment->server->a_speed() if $prev_assignment;
    
    my $time = $server->time_over_interval( $interval );
    
    my $offset = int ( $interval->start() + $time * $prev_speed );

    return $offset;
}


sub interval_can_be_split
{
    my ( $self, $interval ) = @_;
    
    my $estimation = $interval->eta();
    my $min_eta    = $self->min_splittable_eta();
    
    return 1 if not defined $estimation;
    
    my $x = $min_eta <= $estimation;
    
    if ( not $x )
    {
	$self->state( "interval cannot be split: eta of $estimation is less than minimal $min_eta", 3 );
    }
    
    return $min_eta <= $estimation;
}

sub pick_untried_server
{
    my ($self) = @_;

    foreach my $server ( $self->context()->server_queue()->servers() )
    {
	return $server if not $server->tries() and not $server->disabled();
    }
    croak "Internal error: No untried servers in pick_untried_server";
}

sub pick_server_to_refresh
{
    my ($self) = @_;

    my $oldest;

    foreach my $server ( $self->context->server_queue()->servers() )
    {
	$oldest = $server if (not defined $oldest or
			      not defined $server->last_try() or
			      (defined $oldest->last_try() and
			       $server->last_try() < $oldest->last_try()));
    }

    return $oldest;
}

sub pick_fastest_server
{
    my ($self) = @_;
    
    my $best_server;

    foreach my $server ( $self->context->server_queue->servers() ) 
    {
	$best_server = $server if (not defined $best_server
				   and defined $server->speed());
	if ($server->speed() and
	    $server->speed() > $best_server->speed()) {
	    $best_server = $server;
	}
    }
    return $best_server;
}

sub pick_slowest_interval
{
    my ($self) = @_;
    
    my $best_interval;

    foreach my $interval ( $self->context->interval_manager->uncompleted() )
    {
	next unless $self->interval_can_be_split($interval);

	$best_interval = $interval if not defined $best_interval;

	if (not $interval->prev()->assignment()) {
	    if ($best_interval->assignment()) {
		if ($best_interval->size() < $interval->size()) {
		    $best_interval = $interval;
		}
	    } else {
		$best_interval = $interval;
	    }
	} else {
	    next if not $best_interval->prev()->assignment();
	    next if not defined $interval->prev()->assignment()->client->estimated_time_to_completion();
	    next if not defined $best_interval->prev()->assignment()->client->estimated_time_to_completion();

	    if ($interval->prev()->assignment()->client()->estimated_time_to_completion() >
		$best_interval->prev()->assignment()->client()->estimated_time_to_completion()) {
		$best_interval = $interval;
	    }
	}
    }
    return $best_interval;
}

sub pick_server_select_method
{
    my ($self) = @_;

    my $num_untried = 0;
    foreach my $server ( $self->context->server_queue->servers() ) 
    {
	$num_untried++ if not $server->disabled() and not $server->tries();
    }
    if ($num_untried > 0) 
    {
	my $probability = $num_untried / $self->context->server_queue->servers();
	if ($probability < 0.25) { $probability = 0.25; }

	return "pick_untried_server" if $probability > rand();
    }
    return "pick_server_to_refresh" if rand() > 0.10;
    return "pick_fastest_server";
}

sub find_best_match
{
    my ( $self ) = @_;
    
    my $best_server;
    my $best_interval;

    $best_interval = $self->pick_slowest_interval();

    return undef, undef unless defined $best_interval;
    
    my $server_method = $self->pick_server_select_method();
    $best_server = $self->$server_method();

    return undef, undef if not defined $best_server;

  main::state("Found ".$best_server->as_string()." using $server_method", 1);
    
    return $best_server, $best_interval;
}

sub complex_find_best_match
{
    my ( $self ) = @_;
    
    my $optimal_time = $self->optimal_time();
    
    return unless defined $optimal_time;
    
    my $best_diff = undef;
    my $best_server;
    my $best_interval;

    foreach my $server ( $self->context->server_queue->servers() ) 
    {
	foreach my $interval ( $self->context->interval_manager->uncompleted() )
	{
	    $self->state ("Testing for " . $server->id() . " vs interval " . $interval->info(), 4 );
	    
	    next unless $self->interval_can_be_split($interval);
	    
	    my $my_time = $server->time_over_interval( $interval );
	    $self->state( "est time is $my_time", 4 );
	    
	    my $diff = abs( $optimal_time - $my_time );
	    if ( not defined $best_diff or $diff < $best_diff )
	    {
		$best_diff = $diff;
		$best_server = $server;
		$best_interval = $interval;
	    }
	}
    }
    
    return $best_server, $best_interval;
}


sub optimal_time
{
    my ( $self ) = @_;
    
    my $total_server_speed = $self->total_server_speed() or return undef;
    my $total_uncompleted_size =
      $self->context->interval_manager->total_uncompleted_size();
    
    return $total_uncompleted_size / $total_server_speed;
}


sub total_server_speed
{
    my ( $self ) = @_;
    
    my $server_list = $self->context->server_list();
    
    my $speed = 0;
    
    foreach my $server ( $self->context->server_queue->servers() )
    {
	$speed += $server->a_speed() unless $server->disabled();
    }
    
    return $speed;
}


1;


syntax highlighted by Code2HTML, v. 0.9.1