package Requester::Requester;

use strict;
use TestWorker;

my $pid = $$;

my %domain_handlers = ();
my %cache = ();

my %domain_warned = ();
my %loaded = ();

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

    bless($self, $class);
    return $self;
}


sub register {
    my( $self, @domains ) = @_;
    
    foreach my $domain (@domains) {
        $domain_handlers{$domain} = $self;
    }
}


sub preexit {
    %domain_handlers = ();
}


sub get {
    my( $env, $variables ) = @_;

    my %result = ();
    my %perdomain = ();
    my $frequency = $env->{"sched_frequency"};
    my $time = $env->{"sched_minute"};
    my $host = $env->{"nodeinstance"}->{"host"};
    foreach my $variable (@$variables) {
        if( defined $cache{"$host $variable $frequency $time"} ) {
	    my $cached = $cache{"$host $variable $frequency $time"};
	    return( $cached ) if( UNIVERSAL::isa( $cached, "CWorker" ) );
	    $result{$variable} = $cached;
	}
	else {
	    $variable =~ /^(\@|)([^\.]+)\./;
	    my( $qualifier, $domain, $object ) = ($1, $2, $');
	    if( ! defined $perdomain{$domain} ) {
	        $perdomain{$domain} = [];
	    }
	    push( @{$perdomain{$domain}}, $qualifier.$object );
	}
    }
    my $returned = \%result;
    while( my( $domain, $vars ) = each %perdomain ) {
        my $handler = get_handler( $domain );
	unless( $handler ) {
	    unless( $domain_warned{$domain} ) {
	        BigSister::common::log( "err", "Requester: no handler for domain $domain" );
	    }
	    return( undef );
	}
	my $results = $handler->request( $env, $domain, $vars );
	return(undef) unless( $results );
	if( UNIVERSAL::isa( $results, "CWorker" ) ) {;
 	    foreach my $name (@$vars) {
		set_cache( $env, $domain, $name, $results );
	    }
	    $returned = $results;
	}
	else {
	    while( my( $name, $value ) = each %{$results} ) {
		if( $name =~ /^@/ ) {
		    $name = $&.$domain.".".$';
		}
		else {
		    $name = $domain.".".$name;
		}
		$result{$name} = $value;
		$cache{"$host $name $frequency $time"} = $value;
	    }
	}
    }
    return $returned;
}


sub set_cache {
    my( $env, $domain, $name, $value ) = @_;

    if( $name =~ /^@/ ) {
	$name = $&.$domain.".".$';
    }
    else {
	$name = $domain.".".$name;
    }
    my $host = $env->{"nodeinstance"}->{"host"};
    my $frequency = $env->{"sched_frequency"};
    my $time = $env->{"sched_minute"};
    $cache{"$host $name $frequency $time"} = $value;
}



sub request {
    my( $self, $env, $domain, $variables ) = @_;

    $self->{"env"} = $env;
    $self->{"domain"} = $domain;
    my @gets;
    my @walks;
    my @indexes;
    foreach my $variable (@$variables) {
        if( $variable =~ /^@/ ) {
	    push( @walks, $' );
	}
	else {
	    push( @gets, $variable );
	    if( $variable =~ /\.(\d+)$/ ) {
	        my $idx = $1;
	        push( @indexes, $idx ) unless( grep( $_ eq $idx, @indexes ) );
	    }
	}
    }

    my $time = time;
    my $info;
    if( @walks ) {
        $info = $self->getinfo( );
    }
    else {
        $info = $self->getinfo( \@gets, \@indexes );
    }
    $time = ($time + time)/2;

    my %result = ();
    my $code = sub {
	foreach my $get (@gets) {
	    my $val;
	    if( ($get =~ /\.(\d+)$/) && defined $info->{$1}->{$`} ) {
		my $idx = $1;
		my $var = $`;
		$result{$get} = $val = { 0 => {
		    "time" => time,
		    "value" => $info->{$idx}->{$var}
		} };
	    }
	    else {
		$result{$get} = $val = { 0 => {
		    "time" => time,
		    "value" => 0
		} };
	    }
	    set_cache( $env, $domain, $get, $val );
	}

	foreach my $walk (@walks) {
	    my $this = {};
	    foreach my $idx (keys %$info) {
		$this->{$idx} = {
		    "time" => $time,
		    "value" => $info->{$idx}->{$walk}
		}
	    }
	    $result{"\@$walk"} = $this;
	    set_cache( $env, $domain, "\@$walk", $this );
	}
	return 1;
    };
    if( UNIVERSAL::isa( $info, "CWorker" ) ) {
	# if getinfo() deferred we just create another CWorker
	# waiting for getinfo() to finish and then storing the
	# results in the cache
        my $worker = new TestWorker();
	$worker->start_after( $info );
	$worker->do_next( sub {
	    $info = $info->{"Test.info"};
	    return 1;
	} );
	$worker->do_next( $code );
	return $info;
    }
    else {
        $code->();
	return \%result;
    }

}


#
# return no info by default
sub getinfo {
    return {};
}



sub clear_cache {
    %cache = ();
}


sub get_handler {
    my( $domain ) = @_;

    unless( defined $domain_handlers{$domain} ) {
        load_module( $domain );
    }
    return $domain_handlers{$domain};
}


sub load_module {
    my( $module ) = @_;
    my( $stat, $dir, $file );
    my( $ret );

    return if( $loaded{$module} );
    $ret = 0;
    eval {
	require "Requester/$module.pm";
	$loaded{$module} = 1;
	$ret = 1;
    };
    return $ret;
}


#
# return true if Requester is applicable to target
sub available {
    my( $self, $env ) = @_;

    return( 1 );
}


#
# tests if @cmd is runnable - usually called by classes inherited from Requester::Requester
sub command_available {
    my( $self, @cmd ) = @_;

	return( 0 ) unless( $Platform::has_fork );
	my $pid = open( _SUB, "-|" );
	if( $pid ) {
	    while( <_SUB> ) {;}
		close _SUB;
		return( ! $? );
    }
	else {
		close STDERR;
		open( STDERR, ">&STDOUT" );
	    exit( (system( @cmd ) & 0xFF == 0xFF) ? 1 : 0 );
    }
}


END {
    if( $pid == $$ ) {
	    preexit();
    }
}

1;
