:
eval 'exec perl -S $0 ${1+"$@"}'
    if 0;
#**************************************************************
#  
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#  
#    http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.
#  
#**************************************************************


#
# build_client - client for the build tool in server mode
#

use strict;
use Socket;
use Sys::Hostname;
use File::Temp qw(tmpnam);
use POSIX;
use Cwd qw (cwd);

$SIG{KILL} = \&handle_temp_files;
$SIG{INT} = \&handle_temp_files;

### main ###
my $enable_multiprocessing = 1;
my $server_list_file;
my $server_list_time_stamp = 0;
my %ENV_BACKUP;
$ENV_BACKUP{$_} = $ENV{$_} foreach (keys %ENV);

if ($^O eq 'MSWin32') {
    eval { require Win32::Process; import Win32::Process; };
    $enable_multiprocessing = 0 if ($@);
} else {
    use Cwd 'chdir';
};
my $processes_to_run = 1;

my %hosts_ports = ();
my $default_port = 7890;
my @ARGV_COPY = @ARGV; # @ARGV BACKUP
#$ARGV_COPY{$_}++ foreach (@ARGV);
print "arguments: @ARGV\n";
get_options();

my $proto = getprotobyname('tcp');
my $paddr;
my $host = hostname();
my $current_server = '';
my $got_job = 0;
my %job_temp_files = ();
my %environments = (); # hash containing all environments
my $env_alias;
my %platform_rejects = (); # hash containing paddr of server, that replied "Wrong platform"

my $child = 0;
if ($processes_to_run > 1) {
    my $started_processes = 1;
    if ($^O eq 'MSWin32') {
        my $process_obj = undef;
        my $child_args = "perl $0";
        foreach (@ARGV_COPY) {
            /^-P(\d+)$/        and next;
            /^-P$/     and shift @ARGV_COPY  and next;
            $child_args .= " $_";
        };
        do {
            my $rc = Win32::Process::Create($process_obj, $^X,
                                            $child_args, 
	 	   	                                0, 0, #NORMAL_PRIORITY_CLASS,
                                            ".");
            print_error("Cannot start child process") if (!$rc);
            $started_processes++;
        } while ($started_processes < $processes_to_run);
    } else {
        my $pid;
        do {
            if ($pid = fork) { # parent
                $started_processes++;
                print $started_processes . "\n";
            } elsif (defined $pid) { # child
                $child++;
            };
        } while (($started_processes < $processes_to_run) && !$child);
    };
};

run_client();
### end of main procedure ###

#########################
#                       #
#      Procedures       #
#                       #
#########################
sub handle_temp_files {
    print STDERR "Got signal - clearing up...\n";
    foreach (keys %job_temp_files) {
        if ($job_temp_files{$_}) {
            rename($_, $job_temp_files{$_}) or system("mv", $_, $job_temp_files{$_});
            print STDERR "Could not rename $_ to $job_temp_files{$_}\n" if (-e $_); 
        } else {
            unlink $_ or system("rm -rf $_");
            print STDERR "Could not remove $_\n" if (-e $_); 
        };
    };
    exit($?);
};

sub run_client {
# initialize host and port
    if (!scalar keys %hosts_ports) {
        $hosts_ports{localhost} = $default_port;
    }
    
    print "Started client with PID $$, hostname $host\n";
    
    my $message = '';
    my $current_port = '';
    my %active_servers = ();
    
    do {
        $got_job = 0;
        foreach $current_server (keys %hosts_ports) {
            foreach $current_port (keys %{$hosts_ports{$current_server}}) {

                #before each "inactive" server/port connect - connect to each "active" server/port 
                next if (defined ${$active_servers{$current_server}}{$current_port});
                # "active" cycle
                foreach my $active_server (keys %active_servers) {
                    foreach my $active_port (keys %{$active_servers{$active_server}}) {
#                        print "Active: $active_server:$active_port\n";
                        my $iaddr = inet_aton($active_server);
                        $paddr = sockaddr_in($active_port, $iaddr);
                        do {
                            my $server_is_active = 0; 
                            $message = request_job($message, $active_server, $active_port);
                            $server_is_active++ if ($message);
                            if (!$server_is_active) {
                                delete ${$active_servers{$active_server}}{$active_port};
                                # throw away obsolete environments
                                foreach (keys %environments) {
                                    /^\d+@/;
                                    if ($' eq "$active_server:$active_port") {
                                        delete $environments{$_};
                                    };
                                };
                            };
                            $message = '' if ($message eq 'No job');
                        } while ($message);
                    };
                };
                
                # "inactive" cycle
#                print "Inactive: $current_server:$current_port\n";
                my $iaddr = inet_aton($current_server);
                $paddr = sockaddr_in($current_port, $iaddr);
                do {
                    $message = request_job($message, $current_server, $current_port);
                    if ($message) {
                        if (!defined $active_servers{$current_server}) {
                            my %ports;
                            $active_servers{$current_server} = \%ports;
                        };
                        ${$active_servers{$current_server}}{$current_port}++;
                    };
                    $message = '' if ($message eq 'No job');
                } while ($message);
            };
        };
        sleep 5 if (!$got_job);
        read_server_list();
    } while(1);
};

sub usage {
    my $error = shift;
    print STDERR "\nbuild_client\n";
    print STDERR "Syntax:    build_client [-PN] host1[:port1:...:portN] [host2[:port1:...:portN] ... hostN[:port1:...:portN]]|\@server_list_file\n";
    print STDERR "        -P           - start multiprocessing build, with number of processes passed\n";
    print STDERR "Example1:   build_client myserver1 myserver2:7891:7892\n";
    print STDERR "            the client will be asking for jobs on myserver1's default ports (7890-7894)\n";
    print STDERR "            and on myserver2's ports 7891 and 7892\n";
    print STDERR "Example2:   build_client -P2 myserver1:7990 myserver2\n";
    print STDERR "            start 2 clients which will be asking for jobs myserver1's port 7990\n";
    print STDERR "            and myserver2's default ports (7890-7894)\n";
    exit ($error);
};

sub get_options {
    my $arg;
    usage(1) if (!scalar @ARGV);
    while ($arg = shift @ARGV) {
        usage(0) if /^--help$/;
        usage(0) if /^-h$/;
        $arg =~ /^-P(\d+)$/        and $processes_to_run = $1 and next;
        $arg =~ /^-P$/            and $processes_to_run = shift @ARGV     and next;
        $arg =~ /^@(\S+)$/            and $server_list_file = $1    and next;
        store_server($arg);
    };
    if (($processes_to_run > 1) && (!$enable_multiprocessing)) {
        print_error("Cannot load Win32::Process module for multiple client start");
    };
    if ($server_list_file) {
        print_error("$server_list_file is not a regular file!!") if (!-f $server_list_file);
        read_server_list();
    }
    print_error("No server info") if (!scalar %hosts_ports);
};

sub store_server {
    my $server_string = shift;
    my @server_params = ();
    @server_params = split (/:/, $server_string);
    my $host = shift @server_params;
    my @names = gethostbyname($host);
    my $host_full_name = $names[0];
    my %ports = ();
    if (defined $hosts_ports{$host_full_name}) {
        %ports = %{$hosts_ports{$host_full_name}};
    };
    # To do: implement keys in form server:port -> priority
    if (defined $hosts_ports{$host_full_name}) {
        if (!$server_list_time_stamp) {
            print "The $host with ip address " . inet_ntoa(inet_aton($host)) . " is at least two times in the server list\n";
        };
    } else {
        print "Added server $host as $host_full_name\n";
    };
    if (scalar @server_params) {
         $ports{$_}++ foreach (@server_params);
    } else {
         $ports{$_}++ foreach ($default_port .. $default_port + 4);
    };
    $hosts_ports{$host_full_name} = \%ports;
};

sub read_server_list {
    open(SERVER_LIST, "<$server_list_file") or return;
    my $current_time_stamp = (stat($server_list_file))[9];
    return if ($server_list_time_stamp >= $current_time_stamp);
    my @server_array = ();
    foreach my $file_string(<SERVER_LIST>) {
        while ($file_string =~ /(\S+)/) {
            $file_string = $';
            store_server($1);
        };
    };
    close SERVER_LIST;
    $server_list_time_stamp = $current_time_stamp;
};

sub request_job {
    my ($message, $current_server, $current_port) = @_;
    $message = "platform=$ENV_BACKUP{OUTPATH} pid=$$ osname=$^O" if (!$message);
    # create the socket, connect to the port
    socket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die "socket: $!";
    connect(SOCKET, $paddr) or return '';#die "connect: $!";
    my $error_code = 1;
    $message .= "\n";
    syswrite SOCKET, $message, length $message;
    while (my $line = <SOCKET>) {
        chomp $line;
        if ($line eq 'No job') {
            close SOCKET or die "close: $!";
            return $line;
        };
        if ($line eq "Wrong platform") {
            if (!defined $platform_rejects{$paddr}) {
                $platform_rejects{$paddr}++;
                print STDERR $line . "\n";
            }
            close SOCKET or die "close: $!";
            delete $hosts_ports{$current_server};
            return '';
        } elsif (defined $platform_rejects{$paddr}) {
            delete $platform_rejects{$paddr};
        };
        $got_job++;
        $error_code = do_job($line . " server=$current_server port=$current_port");
    }
    close SOCKET or die "close: $!";
    return("result=$error_code pid=$$");
}

sub do_job {
    my @job_parameters = split(/ /, shift);
    my %job_hash = ();
    my $last_param;
    my $error_code;
    print "Client $$@" . "$host\n";
    foreach (@job_parameters) {
        if (/(=)/) {
            $job_hash{$`} = $';
            $last_param = $`;
        } else {
           $job_hash{$last_param} .= " $_"; 
        };
    };
    $env_alias = $job_hash{server_pid} . '@' . $job_hash{server} . ':' . $job_hash{port};
    my $result = "1"; # default value
    my $cmd_file = File::Temp::tmpnam($ENV_BACKUP{TMP});
    my $tmp_log_file = File::Temp::tmpnam($ENV_BACKUP{TMP});
    $job_temp_files{$tmp_log_file} = $job_hash{log};
    my $setenv_string = '';
    if (defined $job_hash{setenv_string}) {
        # use configuration string from server
        $setenv_string .= $job_hash{setenv_string};
        print "Environment: $setenv_string\n";
    
        my $directory = $job_hash{job_dir};
        open (COMMAND_FILE, ">$cmd_file");
        print COMMAND_FILE "$setenv_string\n";
        if (!defined $job_hash{job_dir}) {
            close COMMAND_FILE;
            print "No job_dir, cmd file: $cmd_file\n";
            foreach (keys %job_hash) {
                print "key: $_ $job_hash{$_}\n";
            };
            exit (1);
        };
    
        print COMMAND_FILE "pushd $job_hash{job_dir} && ";
        print COMMAND_FILE $job_hash{job} ." >& $tmp_log_file\n";
        print COMMAND_FILE "exit \$?\n";
        close COMMAND_FILE;
        $job_temp_files{$cmd_file} = 0;
        $job_temp_files{$tmp_log_file} = $job_hash{log};
        $error_code = system($ENV{SHELL}, $cmd_file);
        unlink $cmd_file or system("rm -rf $cmd_file");
        delete $job_temp_files{$cmd_file};
    } else {
        # generate setsolar string
        if (!defined $environments{$env_alias}) {
            $error_code = get_setsolar_environment(\%job_hash);
            return($error_code) if ($error_code);
        };
        my $solar_vars = $environments{$env_alias};
 
        delete $ENV{$_} foreach (keys %ENV);
        $ENV{$_} = $$solar_vars{$_} foreach (keys %$solar_vars);
        print 'Workspace: ';
        if (defined $ENV{CWS_WORK_STAMP}) {
            print $ENV{CWS_WORK_STAMP}; 
        } else { 
            print $ENV{SOLARSRC};
        };
 
        print "\nplatform: $ENV{INPATH} $^O";
        print "\ndir: $job_hash{job_dir}\n";
        print "job: $job_hash{job}\n";
        chdir $job_hash{job_dir};
        getcwd();
        my $job_string = $job_hash{job} . ' > ' . $tmp_log_file . ' 2>&1';
        $error_code = system($job_string);
#        rename($tmp_log_file, $job_hash{log}) or system("mv", $tmp_log_file, $job_hash{log});
#        delete $job_temp_files{$tmp_log_file};# = $job_hash{log};
    };
    rename($tmp_log_file, $job_hash{log}) or system("mv", $tmp_log_file, $job_hash{log});
    delete $job_temp_files{$tmp_log_file};

    if ($error_code) {
        print "Error code = $error_code\n\n";
    } else {
        print "Success!!\n\n";
    };
    return $error_code;
};

sub get_setsolar_environment {
    my $job_hash = shift;
    my $server_pid = $$job_hash{server_pid};
    my $setsolar_string = $$job_hash{setsolar_cmd};
    # Prepare the string for the client
    $setsolar_string =~ s/\s-file\s\S+//g;
    my $error_code = 0;
    my $cmd_file = File::Temp::tmpnam($ENV_BACKUP{TMP});
    my $tmp_log_file = File::Temp::tmpnam($ENV_BACKUP{TMP});
    if (defined $$job_hash{updater}) {
        $ENV{UPDATER} = $$job_hash{updater};
    } else {
        undef $ENV{UPDATER} if (defined $ENV{UPDATER});
    };
    if (defined $$job_hash{source_root}) {
        $ENV{SOURCE_ROOT} = $$job_hash{source_root};
    } else {
        undef $ENV{SOURCE_ROOT} if (defined $ENV{SOURCE_ROOT});
    };
    $error_code = system("$setsolar_string -file $cmd_file");
    store_env_hash($cmd_file);
    return $error_code;
};

sub print_error {
    my $message = shift;
    print STDERR "\nERROR: $message\n";
    exit(1);
};
sub store_env_hash {
    my $ss_setenv_file = shift;#($$job_hash{server_pid}.$$job_hash{setsolar_cmd}, $cmd_file);
    my %solar_vars = ();
    my $cmd_file = File::Temp::tmpnam($ENV_BACKUP{TMP});
    my $env_vars_file = File::Temp::tmpnam($ENV_BACKUP{TMP});
    print "$cmd_file $env_vars_file\n";
    #get all env variables in $env_vars_file
    open (COMMAND_FILE, ">$cmd_file");
    print COMMAND_FILE "source $ss_setenv_file\n";
    print COMMAND_FILE "env > $env_vars_file\n";
    close COMMAND_FILE;
    system($ENV{SHELL}, $cmd_file);
    print_error($?) if ($?);
    unlink $cmd_file or system("rm -rf $cmd_file");
    unlink $ss_setenv_file or system("rm -rf $ss_setenv_file");

    open SOLARTABLE, "<$env_vars_file" or die "canīt open solarfile $env_vars_file";
    while(<SOLARTABLE>) {
        chomp;
        s/\r\n//o;
        /(=)/;
        $solar_vars{$`} = $';
    };
    close SOLARTABLE;
    unlink $env_vars_file or system("rm -rf $env_vars_file");
    $environments{$env_alias} = \%solar_vars;
};
