BEGIN{
  my @modules=('Sys::Hostname',' File::Basename','Cwd','File::Find','File::Copy','File::Path','Getopt::Long','File::stat','Pod::Usage');
  foreach my $moduleName (@modules) {
    my $moduleDefined=eval "use $moduleName; 1" ?1:0;
    if($moduleDefined==0){
       print("\n$moduleName  module not present/module is not accessible\n");
       exit(2);
    }
  }
  
  use POSIX qw(uname);
  use Cwd  qw(abs_path);
  use File::Basename qw(dirname basename);
  my @uname = uname();
  my $PLATFORM="$uname[0]";
  if($PLATFORM eq 'AIX'){
    push @INC, dirname(abs_path($0));
  }
}

#Stick to perl version v5.x.x
use v5.8.8;
use Sys::Hostname;
use lib dirname(abs_path($0));

use module::CommandOptions qw(addOpt removeOpt getArguments getOptValue optExists  isDescSession  isApplySession isRollbackSession isQuerySession processArgs getPatchDir);
use module::DBUtilServices qw(getJavaHomePath getJreMemoryOptions readPropertiesFile removeFile getLogDir getConfigDir getFileOwner);
use module::OPatchAutoBinaryCommandOptions qw();
use module::ClassPathLib qw(setBaseDir setCP getOPatchAutoBinaryCP getBootStrapCP setSrvmLibPath);

#Dumping additional args which does not have mapping.
foreach my $key (@ARGV) {
  processArgs($key);
}

sub configFile{
    my $fp = shift;
    my @fd;
    print("\nReading the host list from the config file $fp\n");
    #open(DATA, "<$fp") or die "Couldn't open file $fp, $!";
    open(DATA, "<$fp") or die "Couldn't open file $fp";

    while(<DATA>) {
       #print " $_";
       chomp( $_ );
	   $_ =~ s/\s+$//;
	   if (length $_ > 0) {
	     push @fd, "$_";
	   }
       #push @fd, $line;
    }

    return @fd;
}

sub isSharedHome{
  my $localhost = shift;
  my $remote_node = shift;
  my $home = shift;
  my $dstLocn = $home . "/.opatchauto_storage";
  my $timeStamp = gentimeStamp();
  my $file = "$dstLocn/$localhost$timeStamp.tmp";  
  
  my $ssh_mkdir = "ssh -o FallBackToRsh=no -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=0 $remote_node mkdir -p $dstLocn ";
  (my $output, my $exit_code) = execute_cmd($ssh_mkdir);
  if($exit_code!=0)
  {
    print("\n$output\n");
	print "Command $ssh_mkdir failed with an exit code of $exit_code. Please ensure that the destination directory is accessible.\n";
	exit($exit_code);
  }
  
  my $ssh_touch = "ssh -o FallBackToRsh=no -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=0 $remote_node touch $file ";
  ($output, $exit_code) = execute_cmd($ssh_touch);
  if($exit_code!=0)
  {
    #print("\n$output\n");
	print "\nUnable to detect if this is a shared-home.\n";
	return 0;
  }
  
  if (-e $file){
    unlink $file;
    return 1;
  }else{
    return 0;
  }
}


sub buildArgs {  
  my $local_locn  = shift;
  my $remote_locn = shift;
  my $argList; 
  
  my @myArgs=split(' ',getArguments());
  foreach (@myArgs){    
    if (length($local_locn) > 0 && $_ eq $local_locn){
	  my $patchDirName = basename($local_locn);
	  $argList = $argList.' '.$remote_locn.'/'.$patchDirName;
	}else {
	  $argList = $argList.' '.$_;
	}
  }
  return $argList;
}

# Execute a given command as the specified user and return the output and result
sub execute_cmd
{
    my $command = $_[0];
	#print "Running $command";
    
    my $output = `$command 2>&1`;
    my $result = $? >> 8;
    return ($output , $result );
}

sub gentimeStamp
{
  my ($sec, $min, $hour, $day, $month, $year) =
        (localtime) [0, 1, 2, 3, 4, 5];
  $month = $month + 1;
  $year = $year + 1900;

  my $ts = sprintf("%04d-%02d-%02d_%02d-%02d-%02d",$year, $month, $day, $hour, $min, $sec);
  return $ts;
}

my $scriptDir=dirname(abs_path($0));
my $opatchAutoDBDir=dirname($scriptDir);
my $opatchAutoDir=dirname($opatchAutoDBDir);
my $BASE=dirname($opatchAutoDir);
my $DETECT_OH=dirname($BASE);
setBaseDir($BASE);

my $LOG_DIR=getLogDir($DETECT_OH);
removeOpt('customLogDir');
addOpt('customLogDir',$LOG_DIR);

if (optExists('customConfigDir') == 1){
  print("Invalid argument passed : '-customConfigDir' \n");
  exit(2);
}

my $orginalArgs=getArguments();

my $OH="";
if(optExists('oh') == 1){
 $OH=getOptValue('oh');
 $ENV{'LD_LIBRARY_PATH'}="$OH/lib:$OH/srvm/lib";
}elsif(defined $ENV{'ORACLE_HOME'}){
 $OH=$ENV{'ORACLE_HOME'};
}else{
 $OH = $DETECT_OH;
}

my $localhost = hostname();
$localhost = (split('\.', $localhost))[0];

if(optExists('version') == 0 && optExists('help') == 0 && (isApplySession()==0 && isRollbackSession()==0 && isQuerySession()==0)){
 print("\nERROR : Select either apply or rollback session.\n");
 exit(2);
}

if(optExists('cluster_hosts') == 1 && optExists('hostConfig') == 1){
 print("\nERROR : Provide either -host or -hostConfig, not both\n");
 exit(4);
}

my $CP=$DETECT_OH."/oui/jlib";
setCP($CP);

my $jreOpt="";
if(optExists('jre')==1){
 $jreOpt=getOptValue('jre');
}


my $JAVA_HOME=getJavaHomePath($DETECT_OH,$jreOpt);
my $JAVA=$JAVA_HOME."/bin/java";
removeOpt('jre');

my $ouiLoc=getOptValue('oui');
if(optExists('oui')==0 || ! -d getOptValue('oui')){
  $ouiLoc=$DETECT_OH."/oui";
  if(! -d $ouiLoc){
   $ouiLoc="";
  }
}

my $JRE_MEMORY_OPTIONS="";
if(defined $ENV{'JRE_MEMORY_OPTIONS'}){
 $JRE_MEMORY_OPTIONS=$ENV{'JRE_MEMORY_OPTIONS'};
}
$JRE_MEMORY_OPTIONS=getJreMemoryOptions($ouiLoc."/oraparam.ini",$JRE_MEMORY_OPTIONS,$JAVA);

my $JAVA_VM_OPTION="";
if(defined $ENV{'JAVA_VM_OPTION'}){
  $JAVA_VM_OPTION=$ENV{'JAVA_VM_OPTION'};
}

my $match=0;
my @hosts;
my $line;

#Identify and store remote hosts

if(optExists('cluster_hosts')==1){
	@hosts= split(",", getOptValue("cluster_hosts"));	
	
}elsif(optExists('hostConfig')==1){
	#print(getOptValue("hostConfig"));
	@hosts=configFile(getOptValue("hostConfig"));
}
print("\nSelected hosts for binary patching are @hosts\n");

if (optExists('cluster_hosts')==1){
  removeOpt('cluster_hosts');
}
if (optExists('hostConfig')==1){
  removeOpt('hostConfig');
}

my $args=getArguments();

if(isQuerySession()==1){  
  my $systemPropsutil="-DOPatchauto.ORACLE_HOME=".$OH." -DOPatch.ORACLE_HOME=".$OH;
  my $opatchAutoBinaryUtilCP=getOPatchAutoBinaryCP();
  my $opatchAutoCoreUtil=$JAVA." ".$JAVA_VM_OPTION." ".$JRE_MEMORY_OPTIONS." -cp ".$opatchAutoBinaryUtilCP." ".$systemPropsutil." oracle/opatchauto/core/OpatchAutoCoreUtility ".$args;

  my $result=system($opatchAutoCoreUtil);
  $result = $result >> 8;
  exit($result);
}

my @remote_hosts;
foreach (@hosts){
	$tmp = (split('\.', $_))[0];
	$tmp =~ s/\s+$//;
	$tmp =~ s/^\s+//; 
	if ($localhost eq $tmp){
		$match = 1;		
	}else{
	  if (length $tmp > 0){
	    push(@remote_hosts, $tmp);
	  }
	}
}
if (scalar @remote_hosts > 0)
{
  print "Remote hosts selected for binary patching are : @remote_hosts\n\n";
}else {
  print "Remote hosts have not been provided for patching. \n\n";
}

if ($match==1){
	#print("localhost is present\n");
	foreach(@remote_hosts){		
		my $ssh_cmd = "ssh -o FallBackToRsh=no -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=0 $_ ls $OH ";
        #print("ssh_cmd=$ssh_cmd \n");
		(my $output, my $exit_code) = execute_cmd($ssh_cmd);
		if($exit_code!=0)
		{
		  print "\n$output\n";
		  print "Command $ssh_cmd failed with an exit code of $exit_code. Please ensure that it is a valid host with password-less SSH access.\n";
		  exit($exit_code);
		}
		else
		{
		  #print "\n $output \n";
		  print "Validations successfully run on host $_ \n";
		}		
	}
}
else{
	print("ERROR: localhost is not included in the specified host list.\n");
	exit(6);
}

my $patchDir = getPatchDir();
my $homeOwner=getFileOwner($OH."/oraInst.loc");
my $CONFIG_DIR=getConfigDir($OH);
my $dstDirectory = $CONFIG_DIR."/db/dbtmp";

if ( (scalar @remote_hosts > 0) && isSharedHome($localhost, $remote_hosts[0], $OH) == 1){
  print "\nERROR: This is a shared-home configuration, do not use the options \'-cluster_hosts\' or \'-hostConfig\' \n";
  exit (13);
}

if (scalar @remote_hosts > 0){
   print "Validations have successfully completed on all remote hosts.\n\n";
 }

if (length $patchDir > 0 ) {
 foreach(@remote_hosts){
  
  print "Transferring patches to the remote host $_\n";   
  
  my $ssh_mkdir = "ssh -o FallBackToRsh=no -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=0 $_ mkdir -p $dstDirectory ";
  (my $output, my $exit_code) = execute_cmd($ssh_mkdir);
  if($exit_code!=0)
  {
    print("\n$output\n");
	print "Command $ssh_mkdir failed with an exit code of $exit_code. Please ensure that the destination directory is accessible.\n";
	exit($exit_code);
  }
    
  my $copy_locn = $patchDir;
  $copy_locn =~ s/\/$//;
  
  my $rsync_cmd = "rsync -a $copy_locn $homeOwner\@$_:$dstDirectory/";
  #print("Executing cmd $rsync_cmd\n");
  ( $output, $exit_code) = execute_cmd($rsync_cmd);
  if($exit_code!=0)
  {
    print("\n$output\n");
    print "Command $rsync_cmd failed with an exit code of $exit_code. Please ensure that the destination directory is accessible.\n";
	exit($exit_code);
  }
  else{
    #print "\n $output \n";
    print "Patches successfully copied to $_\n";
  }
 }
 if (scalar @remote_hosts > 0){
   print "Patches have been successfully copied to all remote hosts.\n\n";
 }
}

#Patch the localhost
print "Performing binary actions on the local host...\n\n";
my $args = getArguments();
my $local_binary = "$OH/perl/bin/perl $OH/OPatch/auto/database/bin/OPatchAutoBinary.pl $args";
my $exit_code=system($local_binary);
if($exit_code!=0)
  {
    print "Command $local_binary failed with an exit code of $exit_code.\n";
	exit($exit_code);
  }
  else{    
	if (optExists('analyze')==0){
	  if(isApplySession()==1){
        print "Patches have been successfully applied on the local host.\n";
      }elsif(isRollbackSession()==1){
        print "Patches have been successfully rolled back on the local host.\n";
      }	  
	}else {
	  print "Patches have been successfully analyzed on the local host.\n";
	}
  }

#Patch remote hosts
my $remote_args = buildArgs($patchDir, $dstDirectory);
my $remote_cmd = "$OH/perl/bin/perl $OH/OPatch/auto/database/bin/OPatchAutoBinary.pl $remote_args";

foreach(@remote_hosts){
  my $binary_cmd = "ssh -o FallBackToRsh=no -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=0 $_ $remote_cmd ";
  print("\nPerforming binary actions on the remote host $_ ...\n");
  (my $output, my $exit_code) =execute_cmd($binary_cmd);
  if($exit_code!=0)
  {
    print "\n $output";
    print "Command $binary_cmd failed with an exit code of $exit_code.\n";
	exit($exit_code);
  }
  else{
    print "\n $output";
	if (optExists('analyze')==0){
	  if(isApplySession()==1){
        print "Patches have been successfully applied on the remote host $_\n";
      }elsif(isRollbackSession()==1){
        print "Patches have been successfully rolled back on the remote host $_\n";
      }
	}else {
	  print "Remote host $_ has been successfully analyzed for patching.\n";
	}
    
	#print "Exit code $exit_code \n";
  }
}

# Delete patches copied to remote nodes after apply/rollback. Not to be deleted after analyze.
if (optExists('analyze')==0){
  if (length $patchDir > 0 ) {
    my $patchDirName = basename($patchDir);
	my $dstDir = "$dstDirectory/$patchDirName";
    foreach(@remote_hosts){
	  my $ssh_rm = "ssh -o FallBackToRsh=no -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o NumberOfPasswordPrompts=0 $_ rm -rf $dstDir ";
      (my $output, my $exit_code) = execute_cmd($ssh_rm);
      if($exit_code!=0)
      {
	    print "Could not delete the patch from the temporary location $dstDir on the host $_.\n";
      }
	}
  }
}

print "\nopatchauto succeeded on all the nodes.\n";


exit(0);