#!/usr/bin/perl

use strict;
use warnings;

use NextCAM::Conf "GetCfgs";
use SKM::DB;
use IO::Socket::INET;
use Socket qw(IPPROTO_TCP $CRLF);
use Node::Conf;
use Data::Dumper;

# 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 %ACSG;	# 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
my $dbm;

# Proc ==========================================================
sub prepare
{
	eval {
	    $dbm = DBMaster({RaiseError=>1});
	    $dbm->{FetchHashKeyName} = 'NAME_uc';
	    my $raObjs = $dbm->selectall_arrayref(
		"SELECT obj FROM _objs WHERE otype='X' AND subtype='G' AND deleted=0", 
		{ Slice=>[] }
	    );
	    foreach my $o (@$raObjs) {
		    my $raAttr = $dbm->selectall_arrayref(
			"SELECT attr,val FROM _obj_attr WHERE obj=?",
			{ Slice=>{} },
			$o->[0]
		    );
		    $ACSG{$o->[0]}{$_->{ATTR}} = $_->{VAL} foreach @$raAttr;
	    }
	};
	if ($@) {
	    warn "DB Error: $@\n";
	    exit 1;
	}
	else {
	    $ACSG{$_}{OBJID} = $_ foreach keys %ACSG;
	    print Dumper(\%ACSG);
	}
	unless (%ACSG) {
		print "No ACSG Gateways registered in the system\n";
		exit 0;
	}
	eval { $dbm->disconnect; undef $dbm } if $dbm;
}

sub request
{	
	my $acsg = shift;
	my $target = shift;
	
	my $objid = $acsg->{OBJID};
	my $usrname = $acsg->{USRNAME};
	my $passwd = $acsg->{PASSWD};
	my $sock;
	my $request = "";
	my $err = "";	# Error message
	my %headers;
	
	# Connect to ACSG LOG daemon and send
	print "Sending $target request to $acsg->{IP}:$LOGD_PORT ($objid)\n";
	$sock = IO::Socket::INET->new(
		PeerAddr => $acsg->{IP},
		PeerPort => $LOGD_PORT,
		Proto    => 'tcp',
		Timeout  => $SOCK_TIMEOUT,
		KeepAlive => 1
	);
	die "Cannot open socket: $!" 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
	$request = "GET $target\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/${objid}.${file}") or die "Cannot open file for writing: $!";
			    FH->autoflush(1);
			    print "==> $objid: Content length: $len\n" if $Debug;
			    print "==> $objid: 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 "==> $objid: read total $total bytes\n" if $Debug;
			    }
			    print "==> $objid: 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 $objid = shift;
	
	my $acsg = $ACSG{$objid};
	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 ACSG log files (gzipped)
	eval {
		local $SIG{ALRM} = sub { die "TIMEOUT" };
		alarm $Timeout;
		request $acsg, "LOG";
	};
	if($@) {
		die "LOG request failed for gateway $objid : $@";
	}
	alarm 0;
	exit 0;
}

sub collect_acsg_logs
{	
	my $kidpid;
	my $still_wait;
	foreach my $objid (keys %ACSG) {
		if ($ACSG{$objid}{ENABLED} ne 'Y') {
		    print "Gateway $objid is disabled. Skipping...\n";
		    next;
		}
		my $kidpid = process_one($objid);
		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_acsg_logs;
	exit $Exit_Code;
}

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

END {
    eval { $dbm->disconnect } if $dbm;
}