package CWorker;


use strict;

my $max_parallel = 8;

# ------------------ static section ------------------
#
# this holds a list of all known active CWorkers
#
my @active = ();


sub do_them {
    while( active_number() ) {
        one_pass();
    }
}


sub one_pass {
    _one_pass();
    while( active_number() > $max_parallel ) {
        _one_pass();
    }
}

    
sub _one_pass {
    my $timeout = 0;

    return unless( @active );
    my $criticalness = $active[0]->{"critical"};
    my $rmask = '';
    my $wmask = '';
    my $i = 0;
    foreach my $active (@active) {
	next if( $i && (3 - $criticalness - $active->{"consumer"} < 0) );
	$i++;
        if( defined $active->{"filenumber"} ) {
	    if( $active->{"timeout"} > $timeout ) {
	        $timeout = $active->{"timeout"};
	    }
	    ($active->{"read"}) && (vec( $rmask, $active->{"filenumber"}, 1 ) = 1);
	    ($active->{"write"}) && (vec( $wmask, $active->{"filenumber"}, 1 ) = 1);
	}
	else {
	    $active->do();
	}
    }
    if( $rmask || $wmask ) {
	my $emask = $rmask | $wmask;
	if( $timeout ) {
	    $timeout -= time;
	    $timeout = 0 if( $timeout < 0 );
	}
	else {
	    undef $timeout;
	}
	my $nr = select( $rmask, $wmask, $emask, $timeout );
	foreach my $active (@active) {
	    my $mask = $rmask|$wmask|$emask;
	    my $time = time;
	    if( defined $active->{"filenumber"} ) {
		if( vec( $mask, $active->{"filenumber"}, 1 ) ) {
		    $active->do();
		}
		elsif( $active->{"timeout"} 
		       && ($active->{"timeout"} < $time ) ) {
		    $active->timed_out();
		}
	    }
	}
    }
}


sub active_number {
    return( $#active + 1 );
}

	

# ------------------ instance section ------------------
#
# create a new CWorker
sub new {
    my ($this, @files) = @_;
    my  $class = ref($this) || $this;
    my  $self = {};
    bless($self, $class);
    $self->initialize();

    return $self;
}


#
# initialize some instance variables
sub initialize {
    my( $self ) = @_;

    $self->{"finished"} = 0;
    $self->{"filenumber"} = undef;
    $self->{"preworkers"} = [];
    $self->{"postworkers"} = [];
    $self->{"timeout"} = 0;
    $self->{"critical"} = 0 unless( $self->{"critical"} );
    $self->{"consumer"} = 3 unless( defined $self->{"consumer"} );
    $self->activate();
}


#
# add a CWorker that must be finished before our CWorker may
# become active
sub start_after {
    my( $self, $cworker ) = @_;
    
    return if( $cworker->{"finished"} );
    push( @{$self->{"preworkers"}}, $cworker );
    $self->inactivate();
    $cworker->notify_started_after( $self );
}


#
# this is called by any CWorker depending on us to notify
# us that it *is* depending on us
sub notify_started_after {
    my( $self, $cworker ) = @_;

    push( @{$self->{"postworkers"}}, $cworker );
    $self->finish() if( $self->{"finished"} );
}


#
# we are done :-)
sub finish {
    my( $self ) = @_;

    $self->{"finished"} = 1;
    $self->{"filenumber"} = undef;
    foreach my $post (@{$self->{"postworkers"}}) {
        $post->notify_finished( $self );
    }
    $self->{"postworkers"} = [];
    $self->inactivate();
}


#
# finished pre-workers will notify us that their job has been
# done by invoking notify_finished()
sub notify_finished {
    my( $self, $preworker ) = @_;

    my $pre = $self->{"preworkers"};
    my $active = 1;
    for( my $i = $#$pre; $i >= 0; $i-- ) {
        if( $pre->[$i]->is_finished() ) {
	    splice( @$pre, $i, 1 );
	}
	else {
	    $active = 0;
	}
    }
    if( $active && ! $self->{"active"} ) {
        $self->activate();
    }
}


#
# we are allowed to become active!
sub activate {
    my( $self ) = @_;
    return if( $self->{"active"} );
    $self->{"active"} = 1;
    push( @active, $self );
    @active = sort { $b->{"critical"} <=> $a->{"critical"} } @active;
}



#
# we are not active any more
sub inactivate {
    my( $self ) = @_;
    return unless( $self->{"active"} );
    $self->{"active"} = 0;
    for( my $i = $#active; $i>=0; $i-- ) {
        unless( $active[$i]->{"active"} ) {
	    splice( @active, $i, 1 );
	}
    }
}


#
# are we finished?
sub is_finished {
    my( $self ) = @_;

    return( $self->{"finished"} );
}


#
# Set the consumer level (higher level means: worker might consume
# more time during one do() run. Lowest: 0 (does not significantly
# consume time) to 3 (potentially consuming >3secs per do())
sub consumes {
    my( $self, $level ) = @_;

    $self->{"consumer"} = $level;
}


#
# Set the "time criticalness level". The higher this level the more
# the worker will be struck by pauses between do() calls.
# Lowest: 0 (not time critical at all) to 3 (does not like being
# interrupted at all)
sub criticalness {
    my( $self, $level ) = @_;
  
    $self->{"critical"} = $level;
}

# ----------------- these methods do the work ------------------
# (they must be overriden)

#
# do our work
sub do {
    my( $self ) = @_;
    $self->finish();
}

#
# we timed out
sub timed_out {
    my( $self ) = @_;
}



1;
