package Qooxdoo::Services::Tr;
use strict;
use POSIX qw(setsid);
use Time::HiRes qw(usleep);
use Socket;
sub GetAccessibility {
        return "public";
}

sub launch {
     my $error = shift;
     my $rounds = shift;
     my $delay = shift;
     my $host = shift;
     defined(my $pid = fork) or do { $error->set_error(101,"Can't fork: $!");return $error};
     if ($pid){
        open my $x, ">/tmp/tr_session.$pid" or do {
            $error->set_error(199,"Opening /tmp/tr_session.$$: $!");
            return $error;
        };
        close ($x);
        return $pid;
     }
     chdir '/'               or die "Can't chdir to /: $!";

#     $|++; # unbuffer
     open STDOUT, ">>/tmp/tr_session.$$"
                             or die "Can't write to /tmp/tr_session.$$: $!";
     open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
     setsid                  or die "Can't start a new session: $!";
     open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
     for (my $i = 0; $i<$rounds;$i++){
         my $start = time;
         system "traceroute","-I","-q","1",$host;        
         my $wait = $delay - (time - $start);
         if ($wait > 0 and $i+1< $rounds){
             print "SLEEP $wait\n";
             sleep $wait;
         }
     }
     exit 0;
}

sub get_number {
    my $error = shift;
    my $data = shift;
    $data = 'Undefined' unless defined $data;
    if ($data =~ /^(\d+)$/){
        return $1;
    }
    else {
        $error->set_error(104,"Expected a number but got: $data");
        return $error;
    }
}

sub method_stop_tr {
    my $error = shift;
    my $arg = shift; 
    my $handle = get_number($error,$arg);
    return $handle if ref $handle;
    my $data = "/tmp/tr_session.".$handle;
    if (-r $data){
        warn "Sending kill $handle";
        if (kill('KILL',$handle)){
            waitpid($handle,0);
        }
    }            
    return 'ok';
}

sub method_run_tr
{
    my $error = shift;
    my $arg = shift;
    my $handle = get_number($error,$arg->{handle});
    my $point = get_number($error,$arg->{point});
    my @array;
    if ($arg->{host}){
        my $host = $arg->{host};
        if ( my @addresses = gethostbyname($host) ){
             @addresses = map { inet_ntoa($_) } @addresses[4 .. $#addresses];        
             if ($#addresses > 1){                
                 $host = $addresses[rand($#addresses)];
                 push @array, ['INFO',"Found $#addresses addresses for $arg->{host}. Using $host."];
             }
        }
        my $delay = get_number($error,$arg->{delay});
        return $delay if ref $delay;
        my $rounds = get_number($error,$arg->{rounds});
        return $rounds if ref $rounds;
        $handle = launch ($error,$rounds,$delay,$host);
        $point = 0;
    }
    return $point if ref $point;
    return $handle if ref $handle;
    my $data = "/tmp/tr_session.".$handle;
    if (open my $fh,$data){
        my $again;
        my $size;
        my $rounds = 0;
        do {
            $size = -s $fh;
            # make sure we reap any zombi instances of tr
            # this is especially important when running with speedy of fastcgi
            # 1 = WNOHANG
            waitpid($handle,1);
            $again = kill(0, $handle);
            usleep(1000*300) if $rounds;
#           print STDERR "$again, $handle, $size, $point\n";
            $rounds ++;
        } while ($again and $point >= $size);
	# print STDERR "$point > $size\n";
        if (seek $fh, $point,0){
            while (<$fh>){
                #print STDERR ">$_<";
		next if /^\s*$/ or /traceroute to/;
                if (/^\s*(\d+)\s+(\S+)\s+\((\S+?)\)\s+(\S+)\s+ms/){
                    my ($hop,$host,$ip,$value) = ($1,$2,$3,$4);
                    $value = undef unless $value =~ /^\d+(\.\d+)?$/;
                    push @array, [$hop,$host,$ip,$value];
                }
                elsif (/^\s*(\d+)\s+\*/){
                    push @array, [$1,undef,undef,undef];
                }
                elsif (/^SLEEP\s+(\d+)/){
                    push @array, ['SLEEP',$1];
                }
                elsif (s/traceroute:\s*//g or /\n$/){
                    push @array, ['INFO',$_];
                }
		else {
	            last;
                }
                $point = tell($fh);
            };
            close $fh;
            unlink $data unless $again;
            return {
                handle=>$handle,
                point=>$point,
                output=>\@array,
                again=> $again,
            }
        }
        else {
            $error->set_error(102,"Seeking in traceroute output to $point: $!");
            return $error;
        }            
    }
    else {
        $error->set_error(103,"Opening $data: $!");
        return $error;
    }
}

1;

