#!/usr/bin/perl

use strict;
use warnings;

use NextCAM::Conf "GetCfgs";
use Digest::MD5 "md5_hex";
use IO::Socket::INET;
use Socket qw(IPPROTO_TCP $CRLF);
use Node::Conf;

# Args ==========================================================
my $Collect_Dir = $ARGV[0];
my $Timeout     = $ARGV[1] || 600;

# Constants =====================================================
my $LOGD_PORT    = 8511;
my $SOCK_TIMEOUT = 30;
my $BUF_SIZE     = 1024 ** 2; # 1M

# Vars ==========================================================
my %DS;	# Hash of Display Server configs
my %Wait_Kids; # Pids and exit codes of child processes
my $UNI = UNI;
my $Exit_Code = 0;
my $Debug = $ENV{DEBUG}; # Print debugging info

# Proc ==========================================================
sub prepare
{
	%DS = GetCfgs('DEVICETYPE' => 'MONITOR');
	unless (%DS) {
		print "No Display Servers registered in the system\n";
		exit 0;
	}
}

sub request
{	
	my $ds = shift;
	my $target = shift;
	
	my $dev = $ds->{DEVID};
	my $usrname = $ds->{USRNAME};
	my $passwd = $ds->{PASSWD};
	my $sock;
	my $digest = "";
	my $request = "";
	my $err = "";	# Error message
	my %headers;
	
	# Connect to DS LOG daemon and send
	print "Sending $target request to $ds->{TCP_IP}:$LOGD_PORT ($ds->{DEVID})\n";
	$sock = IO::Socket::INET->new(
		PeerAddr => $ds->{TCP_IP},
		PeerPort => $LOGD_PORT,
		Proto    => 'tcp',
		Timeout  => $SOCK_TIMEOUT,
		KeepALive => 1
	);
	die "$!" unless $sock;
	
	# Tune up socket
	local $/ = $CRLF;
	$sock->autoflush(1);
	setsockopt($sock, IPPROTO_TCP, SOL_SOCKET, SO_KEEPALIVE) || warn "setsockopt: $!";
	
	# Submit request and process response
	$digest = md5_hex($passwd);
	$request = "GET $target\r\nUNI: $UNI\nUSRNAME: $usrname\r\nMD5: $digest\r\n\r\n";
	
	$sock->print($request);
	
	# read header and data
	eval {
	    while(defined(my $str = $sock->getline)) {
		    chomp $str;
		    unless($str) { # Empty line is a boundary. Header ends here
			    my $buff = "";
			    my $file = $headers{FILE} || $target;
			    my $len = $headers{'CONTENT-LENGTH'};
			    my ($cb, $total, $rb) = (0, 0, 0);
			    
			    # Die on errors
			    die "Operation failed: $headers{MESSAGE}\n" if $headers{STATUS} !~ /^OK$/i;
			    die "Response contains no data" unless $len;
			    			
			    # Read message body and store it to the file
			    open(FH, ">$Collect_Dir/${dev}.${file}") or die "Cannot open file for writing: $!";
			    FH->autoflush(1);
			    print "==> $dev: Content length: $len\n" if $Debug;
			    print "==> $dev: start reading binary data\n" if $Debug;
			    while($total < $len) {
				    $rb = $len - $total < $BUF_SIZE ? $len - $total : $BUF_SIZE;
				    $cb = $sock->read($buff, $rb);
				    die "Error occured while transferring data: $!" unless defined $cb;
				    die "Connection closed while transferring data" unless $cb;
				    print FH $buff;
				    $total += $cb;
				    print "==> $dev: read total $total bytes\n" if $Debug;
			    }
			    print "==> $dev: finished reading binary data\n" if $Debug;
			    close FH;
			    last;
		    }
		    $headers{uc($1)} = $2 if $str =~ /^\s*(\S+): (.+)$/;
	    }
	};
	$err = $@;
	$sock->shutdown(2);
	die $err if $err;
}

sub process_one
{	
	my $dev = shift;
	
	my $ds = $DS{$dev};
	my $pid;
	my $ts_started;
	my $tmt_log;
	
	# Do fork
	$pid = fork;
	die "Cannot fork: $!" unless defined $pid;
	return $pid if $pid > 0;
	
	print "Child process $$ started\n";
	
	# Request DS statistics
	$ts_started = time;
	eval {
		local $SIG{ALRM} = sub { die "TIMEOUT" };
		alarm $Timeout;
		request $ds, "STAT";
	};
	warn "STAT request failed for monitor $dev : $@" if $@;
	alarm 0;
	
	# Request DS log files (gzipped)
	$tmt_log = $Timeout - time + $ts_started;
	$tmt_log = 60 if $tmt_log <= 0; # Give extra minute if time is up
	eval {
		local $SIG{ALRM} = sub { die "TIMEOUT" };
		alarm $tmt_log;
		request $ds, "LOG";
	};
	if($@) {
		alarm 0;
		die "LOG request failed for monitor $dev : $@";
	}
	exit 0;
}

sub collect_ds_logs
{	
	my $kidpid;
	my $still_wait;
	foreach my $dev (keys %DS) {
		my $kidpid = process_one($dev);
		next unless $kidpid;
		$Wait_Kids{$kidpid} = undef;
	}
	
	# Now wait for child processes to exit
	while($kidpid = wait) {
		last if $kidpid == -1;
		$still_wait = 0;
		$Wait_Kids{$kidpid} = $?>>8;
		foreach (values %Wait_Kids) {
			$still_wait = 1 unless defined $_;
		}
		last unless $still_wait;
	}
	
	# Summarize
	foreach $kidpid (keys %Wait_Kids) {
		print "Child process $kidpid exited with code $Wait_Kids{$kidpid}\n";
		$Exit_Code = 1 if $Wait_Kids{$kidpid};
	}
}

sub main
{
	prepare;
	collect_ds_logs;
	exit $Exit_Code;
}

# Main ======================================================
main;
