package Net::Paraget::Interval;
#
# $Id: Interval.pm,v 1.6 2001/04/28 07:50:42 ftobin Exp $
#

use strict;
use Carp;

use Class::MethodMaker
  get_set       => [ qw( start end prev next assignment ) ],
  boolean       => [ qw( completed ) ],
  copy          => 'copy',
  new_hash_init => [ qw( new hash_init ) ];


sub flexible
{
    my ( $self ) = @_;
    return not ( $self->assignment()
		 or ( $self->completed() and $self->size() != 0  ) 
	       );
}


sub info
{
    my ( $self ) = @_;
    my $start = $self->start();
    my $end   = $self->end();
    my $size = $end-$start;
    my $assigned = $self->assignment() ? 'yes' : 'no';
    my $flexible = $self->flexible() ? 'yes' : 'no';
    return "from $start to $end, size $size, assigned $assigned, flexible $flexible";
}


sub is_empty
{
    my ( $self ) = @_;
    return $self->end() <= $self->start();
}


sub size
{
    my ( $self ) = @_;
    my $size = $self->end() - $self->start();
    if ($size < 0) 
    {
	die $self->info(), " is wrong: an interval has a negative size";
    }
    return $size;
}


# Split $self into three intervals:
#   start - offset, offset - offset, offset - end
# The middle one is intended to be assigned
# $self must not be assigned
sub split
{
    my ( $self, $offset ) = @_;

    die "Splitting assigned interval" if $self->assignment();

    die "Splitting interval after end" if $self->end() < $offset;

    my $end = $self->end();

    my $i1 = Net::Paraget::Interval->new( start => $offset, end => $offset);
    my $i2 = Net::Paraget::Interval->new( start => $offset, end => $end);

    my $start = $self->start;
    $self->state( "splitting $start at $offset", 1 );

    $self->end($offset);

    # Insert into interval list
    my $orig_next = $self->next;
    $self->next($i1);
    $i1->next($i2);
    $i2->next($orig_next);
    
    $orig_next->prev($i2) if $orig_next;
    $i2->prev($i1);
    $i1->prev($self);

    die "self is negative" if $self->size() < 0;
    die "i1 is negative" if $i1->size() < 0;
    die "i2 is negative" if $i2->size() < 0;

    die "self end not at i1 start" if $self->end() != $i1->start();
    die "i1 end not at i2 start" if $i1->end() != $i2->start();
    die "i2 end not at orig_next start" 
	if $orig_next and $i2->end() != $orig_next->start();

    # Check if the first interval is empty, if so, remove it
    if ($self->start == $self->end)
    {
	$i1->remove();
	return $self;
    }
    else
    {
	return $i1;
    }
}

# Extends one interval `self' to a new ending point `new_end'.
# Any blocks after `self' that end before `new_end' gets removed.
# Flexible blocks after `self' that start before `new_end' will have
# their start moved to new_end.
sub extend
{
    my ( $self, $new_end ) = @_;

    die "Too many args to extend" if @_ > 2;
    
    die "Resizing unassigned interval" if not $self->assignment();

    die "Resizing negatively" if $new_end < $self->end();

    while ($self->next() and $self->next()->end() <= $new_end)
    {
	if ($self->start() > $self->next()->start()) {
	    my $self_start = $self->start();
	    my $next_start = $self->next()->start();
	    die "Misordered interval list: $self_start > $next_start";
	}

	$self->next()->remove();
    }
    
    $self->state( "resizing to $new_end -- speed = " . ( $self->assignment->client->speed || 0), 2);
    
    if ($self->next and not $self->next()->flexible())
    {
	$new_end = $self->next()->start();
    }
    $self->end( $new_end );
    $self->next()->start( $new_end ) if $self->next();

#      if ( defined $new_start and $new_start != $self->start()
#  	 and $self->flexible()
#         )
#      {
#  	$self->resize( undef, $new_start ) if $new_start > $self->end();
#  	$self->start( $new_start );
#  	$self->prev->resize( undef, $new_start ) if $self->prev();
#      }
    
#      if ( defined $new_end and $new_end != $self->end() )
#      {
#  	$self->resize( $new_end, undef ) if $new_end < $self->start();
#  	$self->end( $new_end );
#  	$self->next->resize( $new_end, undef ) if $self->next();
#      }
    
#    $self->remove() if $self->size() < 0;
}



sub remove
{
    my ( $self ) = @_;
    
    $self->prev->next( $self->next() ); # this must always happen, given the head
    $self->next->prev( $self->prev() ) if $self->next();
    
    if ( $self->assignment() and $self->assignment->client() )
    {
	$self->state( "removing self and stopping client", 1);
	$self->assignment->client->stop();
    }
    
    $self->DESTROY();
}


# Assumes that both intervals are flexible
sub merge_ahead
{
    my ( $self ) = @_;

    croak "Inflexible interval" if not ($self->flexible() and
					$self->next()->flexible);
    
    my $old_next = $self->next() or return; # don't worry if we're at the end of the list
    my $new_next = $old_next->next();
    
    $self->hash_init( end    => $old_next->end(),
		      next   => $new_next,
		    );
    
    $new_next->prev( $self ) if $new_next;
    $old_next->DESTROY();
}


sub similar_to
{
    my ( $self, $compare ) = @_;
    return not ( $self->assignment() xor $compare->assignment() );
}


sub resize_from_report
{
    my ( $self, $report ) = @_;
    
    my $start   = $self->start();
    my $amount  = $report->amount_completed();
    
    my $new_end = $start + $amount;
    
    $self->extend( $new_end );
}


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

    my $prev = $self->prev();
    
    return undef if not $prev->assignment();
    
    my $speed = $prev->assignment->server->a_speed();
    return undef if $speed == 0;
    
    return $self->size / $speed;
}


sub extendable_to
{
    my ( $self ) = @_;
    my $i = $self;
    my $point = $self->end();
    
    while ( $i = $i->next() )
    {
	last if not $i->flexible();
	$point = $i->end();
    }
    
    return $point;
}


sub DESTROY
{
    my ( $self ) = @_;
    $self->clear_next();
    $self->clear_prev();
}

sub die
{
    my ( $self, @errors ) = @_;

    CORE::die $self->info(), "\n", @errors;
}


sub state
{
    my ( $self, $string, $priority ) = @_;
    main::state( "interval " . $self->info() . ": " . $string, $priority );
}

1;


syntax highlighted by Code2HTML, v. 0.9.1