package Reader::Dependency;

#
# Compute dependencies
#
# accepts arguments:
#
#    file="dependency file"
#
# if a file is given dependencies are read from this file,
# otherwise this module expects them on the command line.

use strict;

@Reader::Dependency::ISA = qw( Reader::Reader );
use StatusLog;
use FileHandle;

my %operator_priority = (
    "&" => 2,
    "|" => 3,
    ">" => 5,
    "<" => 5 
);


sub new {
    my( $this ) = @_;
    my $class = ref($this) || $this;
    my $self = {};
    bless( $self, $class );
    return $self;
}



sub read {
    my( $self ) = @_;
    my $log = new StatusLog();
    my $fh = new FileHandle();
    my %status = ();

    my $start = $self->get_argument( "start" );
    my $end = $self->get_argument( "end" );
    my $file = $self->get_argument( "file" );

    my $current = $self->{"current"};
    unless( $current ) {
	$current = Reader::Reader::create( "StatusDB", {
	    "start" => $start,
	    "end" => $end
	} ) -> read();
	$current->sort();
	$self->{"current"} = $current;
    }

    my @expressions = ();
    if( $file ) {
	open( $fh, "<$file" ) || die "cannot open dependency file $file: $!";
	while( <$fh> ) {
	    chomp;
	    s/\r//;
	    next if( /^#/ || /^$/ );
	    unless( /^(.*?)[\s\t]+=(.*)$/ ) {
		print STDERR "$file: line $. looks invalid\n";
		next;
	    }
	    my( $item, $expression ) = ($1,$2);
	    while( $expression =~ /\\$/ ) {
		$expression = $`;
		chomp( $_ = <$fh> );
		s/\r//;
		$expression .= $_;
	    }
	    push( @expressions, "$item=$expression" );
	}
        close $fh;
    }
    else {
	@expressions = @{$self->get_argument( "args" )};
    }

    foreach my $expression ( @expressions ) {
	$expression =~ s/[\s\t]//g;
	unless( $expression =~ /^(.*?)=(.*)$/ ) {
	    die "expression $expression is invalid\n";
	}
	my( $item, $expr ) = ($1,$2);
	$item = "comp:".$item unless( $item =~ /^comp:/ );
	if( $expr eq "-" ) {
	    $log->put( $item, [] );
	    next;
	}
	my $list = $self->compute( $current, $expr, $item );
	$log->put( $item, $list );
	$current->put( $item, $list ); 
    }
    return $log;
}



sub compute {
    my( $self, $log, $expr, $target ) = @_;

    my @items = split( /[\&\^\|\(\)\>\<]+/, $expr );
    my @times = ($self->get_argument("start"), $self->get_argument("end"));
    foreach my $item (@items) {
	my $list = $log->get( $item );
	#die "dependancy $item unknown in $target" unless( $list );
	next unless( $list );
	foreach my $part (@$list) {
	    push( @times, $part->{"start"}, $part->{"end"} );
	}
    }
    @times = sort @times;
    my $former_time = -1;
    my @entries = ();
    foreach my $time (@times) {
	next if( $time == $former_time );
	$former_time = $time;
	my $status_time = ($time == $self->get_argument("end"))?($time-0.1):$time;
	my( $status ) = $self->status_at( $log, $expr, $target, $status_time );
	$status->{"start"} = $time;
	$status->{"item"} = $target;
	if( @entries ) {
	    next if( $entries[$#entries]->{"status"} eq $status->{"status"} );
	    $entries[$#entries]->{"end"} = $time;
	}
	push( @entries, $status );
    }
    $entries[$#entries]->{"end"} = $former_time;
    return( \@entries );
}


sub status_at {
    my( $self, $log, $expr, $target, $time, $priority, $depth ) = @_;

    print "\ncomputing $target = $expr (".(scalar localtime($time)).")\n" if( ($BigSister::common::dl > 3) && ! $depth );
    $priority = $priority?$priority:10;
    $depth = $depth?$depth:1;
    if( $depth > 20 ) {
	die "invalid expression (depth) in $target";
    }

    my $status;
    if( $expr =~ /^\(/ ) {
	($status, $expr) = $self->status_at( $log, $', $target, $time, 10, $depth+1 );
    }
    elsif( $expr =~ /^(.*?)([\&\^\|\(\)\>\<]+.*)/ ) {
	$expr = $2;
	$status = $self->atomic_status_at( $log, $1, $time );
    }
    else {
	$status = $self->atomic_status_at( $log, $expr, $time );
	$expr = "";
    }
    while( $expr ) {
	my $operator = substr( $expr, 0, 1 );
	$expr = substr( $expr, 1 );
	if( $operator eq ")" ) {
	    die "too many ')' in $target" if( $depth < 2 );
	    return( $status, $expr );
	}

	my $new_priority = $operator_priority{$operator};
	if( $new_priority > $priority ) {
	    return( $status, $operator.$expr );
	}

	my $second;
	($second, $expr) = $self->status_at( $log, $expr, $target, $time, $new_priority, $depth );
	my $color1 = $status->{"status"};
	my $color2 = $second->{"status"};
	my $code1 = $BigSister::common::status_codes{$color1};
	my $code2 = $BigSister::common::status_codes{$color2};
	if( $operator eq "<" ) {
	    $status=$second if( $status->{"status"} eq "purple" );
	}
	elsif( $operator eq ">" ) {
	    $status=$second if( $second->{"status"} ne "purple" );
	}
	elsif( $operator eq "|" ) {
	    $status = $second if( $code1 < $code2 );
	}
	elsif( $operator eq "&" ) {
	    $status = $second if( $code2 < $code1 );
	}
	print "  computing: $color1 $operator $color2 => " if( $BigSister::common::dl > 4 );
	print ($status->{"status"}."\n") if( $BigSister::common::dl>4 );
    }
    return( $status, $expr );
}


sub atomic_status_at {
    my( $self, $log, $item, $time ) = @_;

    print "    computing $item => " if( $BigSister::common::dl > 5 );
    my $list = $log->get( $item );
    unless( $list ) {
	$list = [];
    }
    foreach my $status (@$list) {
	if( ($status->{"start"} <= $time)
	    && ($status->{"end"} > $ time) ) {
	    print ($status->{"status"}." (".(scalar localtime($time)).")\n") if( $BigSister::common::dl > 5 );
	    # make sure we return a copy!
	    my %status = %$status;
	    return \%status;
	}
    }
    print "purple (".(scalar localtime($time)).")\n" if( $BigSister::common::dl > 5 );
    return {
	"status" => "purple",
	"comment" => "undefined"
    };
}


1;
