package module::DBUtilServices;
use warnings;
use strict;
no warnings 'numeric';

use Exporter qw(import);
use POSIX qw(uname);
use File::stat;
use File::Copy;
use File::Path qw(mkpath rmtree);
use File::Find;
use File::Basename qw(dirname basename);

use Cwd  qw(abs_path getcwd);
use lib dirname(abs_path($0));

our @EXPORT_OK = qw(getActiveUser getFileOwner getFileOwnerGroup changeFileOwner changeFilePermission detectOraBaseHome detectOraBaseConfig getLogDir getConfigDir getJavaHomePath getJreMemoryOptions removeTmpJava removeConfigDir changeFileFolderPermission copyRecursively createDirectory removeDirectory openFile closeFile writeFile readFile removeFile findFilePattern formatDuration readPropertiesFile copyFileEndsWith touchFile writePropertiesFile getJavaAgentOptions storeCustomPerms restoreCustomPerms backupLogFiles);
use module::CommandOptions qw(getOptValue optExists isApplySession isRollbackSession);

#Getting current executing user
sub getActiveUser{
 my @_userDetails=getpwuid($<);
 return $_userDetails[0];
}

#Getting file owner name
sub getFileOwner{
 my $file = $_[0];
 my $userName = getpwuid(stat($file)->uid);
 return $userName;
}

#Getting file owner name
sub getFileOwnerGroup{
 my $file = $_[0];
 my $grpName = getgrgid(stat($file)->gid);
 return $grpName;
}

sub isWindows{
  my $windows=0;
  my $osname = $^O;
  if( $osname eq 'MSWin32' ){
	$windows = 1;
  }
  
  return $windows;
}

sub detectOraBaseHome{
  my $oracleHome = $_[0];
  my $oraBaseScript = $oracleHome."/bin/orabasehome";
  my $oraBaseHome = $oracleHome;
  
  if (-f $oraBaseScript){
    my $output = `ORACLE_HOME=$oracleHome $oraBaseScript 2>&1`;
	my $status = $? >> 8;
	if ($status == 0){
	  my @lines = split(/\n/, $output);
	  $oraBaseHome = $lines[0];
	  chomp $oraBaseHome;
	  if (! -d $oraBaseHome){
        $oraBaseHome = $oracleHome;
      }
	}	
  }  
  
  return $oraBaseHome;
}

sub detectOraBaseConfig{
  my $oracleHome = $_[0];
  my $oraBaseScript = $oracleHome."/bin/orabaseconfig";
  my $oraBaseConfig = $oracleHome;
  
  if (-f $oraBaseScript){
    my $output = `ORACLE_HOME=$oracleHome $oraBaseScript 2>&1`;
	my $status = $? >> 8;
	if ($status == 0){
	  my @lines = split(/\n/, $output);
	  $oraBaseConfig = $lines[0];
	  chomp $oraBaseConfig;
	  if (! -d $oraBaseConfig){
        $oraBaseConfig = $oracleHome;
      }
	}	
  }  
  
  return $oraBaseConfig;
}

sub getLogDir{
  my $logDir;
  
  if(optExists('customLogDir')==1){
    $logDir=getOptValue('customLogDir');
#	if (! -d $logDir)
#	{
#      print("\n$logDir does not exist! Please specify a proper log location.\n");
#      print("opatchauto returns with error code = 2\n");
#      exit(2);
#	}	
  }
  elsif(defined $ENV{'CUSTOM_LOG_DIR'}){
    $logDir=$ENV{'CUSTOM_LOG_DIR'};
	if (! -d $logDir)
	{
	  print("\n$logDir does not exist! Please provide a proper value for env var CUSTOM_LOG_DIR.\n");
      print("opatchauto returns with error code = 2\n");
      exit(2);
	}
  }
  else{
    my $oraBaseHome = detectOraBaseHome($_[0]);
	$logDir=$oraBaseHome."/cfgtoollogs";
  }
  
  return abs_path($logDir);
}

sub getConfigDir{
  my $configDir;
  
  if(optExists('customConfigDir')==1){
    $configDir=getOptValue('customConfigDir');
	if (! -d $configDir)
	{
	  print("\n$configDir does not exist! Please specify a proper config location.\n");
      print("opatchauto returns with error code = 2\n");
      exit(2);
	}	
  }
  elsif(defined $ENV{'CUSTOM_CONFIG_DIR'}){
    $configDir=$ENV{'CUSTOM_CONFIG_DIR'};
	if (! -d $configDir)
	{
	  print("\n$configDir does not exist! Please provide a proper value for env var CUSTOM_CONFIG_DIR.\n");
      print("opatchauto returns with error code = 2\n");
      exit(2);
	}
  }
  else{
    my $oraBaseConfig = detectOraBaseConfig($_[0]);
	$configDir=$oraBaseConfig."/opatchautocfg";
  }
  
  return abs_path($configDir);
}

sub getJavaHomePath{
  my $currentHome=$_[0];
  my $OPT_JRE=$_[1]; 
  my $location=$_[2];  
  
  my $JAVA="";
  my $JAVA_HOME="";
  my $JAVA_REL_PATH="/bin/";
  
  if(isWindows){
   $JAVA_REL_PATH=$JAVA_REL_PATH."java.exe";
  }else{
	$JAVA_REL_PATH=$JAVA_REL_PATH."java";
  }
  
  if(length($OPT_JRE) > 0){
     $OPT_JRE=~ s!/*$!!g;
     if(! -e $OPT_JRE){
     	 print("\n$OPT_JRE does not exist!Please provide a proper -jre option.\n");
    	 print("opatchauto returns with error code = 2\n");
    	 exit(2);
     }
     
     if(! -d $OPT_JRE) {
      	 print("\n$OPT_JRE is not a directory!Please provide a proper -jre option.\n");
    	 print("opatchauto returns with error code = 2\n");
    	 exit(2);
     }
     
     $JAVA=$OPT_JRE.$JAVA_REL_PATH;
     my $retVal=validateJavaHome($JAVA);
     if($retVal==2) {
     	print("\n$OPT_JRE is invalid!Please provide a proper -jre option.\n");
     	print("opatchauto returns with error code = 2\n");
       	exit(2);
     }    
     return $OPT_JRE;
  }  
  
  $JAVA_HOME=$currentHome."/OPatch/jre";
  $JAVA=$JAVA_HOME.$JAVA_REL_PATH;
 
  if(! -e $JAVA){
   $JAVA_HOME=$currentHome."/jdk";
   $JAVA=$JAVA_HOME.$JAVA_REL_PATH;
   if(! -e $JAVA){
     print("\nJava could not be located. opatchauto cannot proceed!\n");
     print("opatchauto returns with error code = 2\n");
     exit(2);
   }else{
     if ( (defined $location) && ($location eq "remote")){
	   $JAVA=$JAVA_HOME.$JAVA_REL_PATH;
	 }else{
	   $JAVA_HOME = getJavaTmpLocation($currentHome, $JAVA_HOME);
	   $JAVA=$JAVA_HOME.$JAVA_REL_PATH;
	 }     
   }
  }
   my $retVal=validateJavaHome($JAVA);
     if($retVal==2) {
        print("\nNo valid java found for patching.opatchauto cannot proceed!\n");
     	print("opatchauto returns with error code = 2\n");
       	exit(2);
   }    
  return $JAVA_HOME;
}

sub validateJavaHome {
   my $JAVA=$_[0];
   my $retVal=1;
   if(! -x $JAVA){
    	print("\n$JAVA is not a valid executable for this platform.\n");
    	$retVal=2;
   }
   my $checkJavaVerResult=checkJavaVer($JAVA);
   if($checkJavaVerResult==1){
   		my $javaVerStr=`$JAVA -version 2>&1`;
    	$javaVerStr =~ m/java version \"(.{3})/s;
    	$javaVerStr=$1;
    	print("\nUnsupported Java version $javaVerStr.\nJava version should be 1.7 or higher.\n");
    	$retVal=2;
   }
   elsif($checkJavaVerResult==2){
    	print("\nCould not detect Java version from $JAVA. Please check if it is a valid executable for this platform.\n");
    	$retVal=2;
   }
   
   return $retVal;
}

#Get supplied jre version
sub getJreVersion{
  my $retVal=1;
  my $javaVerStr=`$_[0] -version 2>&1`;
  #my $javaVer="";
  my $javaVer=$javaVerStr;

 $javaVer =~ m/java version \"(.{3})/s;
 $javaVer=$1;
 $javaVer=sprintf("%.2f",$javaVer);
  return $javaVer;
}

#Checking java version is greater than 1.7
sub checkJavaVer{
  my $javaVersion=getJreVersion($_[0]);
  my $retVal=0;
  if ($javaVersion eq "") {
  	$retVal=2;
  }
  elsif ($javaVersion < 1.7 ) {
    $retVal=1;
  }
   return $retVal;
}

sub getJavaTmpLocation {
  my $currentHome=$_[0];
  my $currentJava=$_[1];
  
  my $CONFIG_DIR=$currentHome."/opatchautocfg";
  if (! -e $CONFIG_DIR){
    createDirectory($CONFIG_DIR);
  }
  my $DB_CONFIG_DIR = $CONFIG_DIR."/db";
  if (! -e $DB_CONFIG_DIR){
    createDirectory($DB_CONFIG_DIR);
  }
  
  my $tmpJava = $DB_CONFIG_DIR."/jdk";
  
  if( -e $tmpJava){
    return $tmpJava;
  }else{
    #copyRecursively($currentJava,$tmpJava);
	my $result = system ("cp -Rp $currentJava $tmpJava");
	$result = $result >> 8;
	if($result != 0){
	  print("opatchauto unable to copy $currentJava to the location $tmpJava\n");
	  exit($result);
	}
	#my $HOME_OWNER = getFileOwner($currentHome."/oraInst.loc");
    #my $HOME_GROUP = getFileOwnerGroup($currentHome."/oraInst.loc");
	
	#changeFileOwner($HOME_OWNER,$HOME_GROUP,$tmpJava);
	
  }  
  
  return $tmpJava;
}

sub removeTmpJava {
  my $currentHome=$_[0];  
  my $tmpJava = $currentHome."/opatchautocfg/db/jdk";
  
  if ( -e $tmpJava){
    removeDirectory($tmpJava);
  }
}

sub removeConfigDir {
my $currentHome=$_[0];  
  my $tmpDir = $currentHome;  
  if ( -e $tmpDir){
    removeDirectory($tmpDir);
  }
}

sub getJreMemoryOptions{
  my $oraParamIniFile=$_[0];
  my $jreMemoryOpts=$_[1];
  
  my $JAVA=$_[2];
my $jreVersion=getJreVersion($JAVA);
  
  if ((length $jreMemoryOpts <= 0 ) && ($jreVersion < 9.00)) {
      $jreMemoryOpts=extractJreMemoryOpts($oraParamIniFile);
	  $jreMemoryOpts=setDefaultHeap($jreMemoryOpts);
      $jreMemoryOpts=updateJreMemoryOptsForJRockit($jreMemoryOpts);
   }else{
    $jreMemoryOpts=setDefaultHeap($jreMemoryOpts);
	if(index($jreMemoryOpts, "-d64") == -1){
       my $tmpVal=extractJreMemoryOpts($oraParamIniFile);
        if( ($tmpVal =~ m/ \-d64/) &&  ($jreVersion <  9.00 ))  {
         $jreMemoryOpts=$jreMemoryOpts." -d64";
       }
    }
   } 	
   
   
   
   if($jreVersion >= 1.8){
    $jreMemoryOpts=~s/MaxPermSize/MaxMetaspaceSize/g;
   }
   return $jreMemoryOpts;
}


sub getJavaAgentOptions{
my $JAVAAGENTOPTION;
my $JAVA=$_[1];
my $jreVersion=getJreVersion($JAVA);
my $oracleHome = $_[0];
#my $javaVersion=getJreVersion($_[0]);
if($jreVersion >= 9)
{
$JAVAAGENTOPTION="-javaagent:".$oracleHome."/OPatch/jlib/oracle.opatch.classpath.jar";
}
else {
 $JAVAAGENTOPTION="";
     }
     return $JAVAAGENTOPTION;
}


  

sub setDefaultHeap{
   # Set default memory to 3GB only in case of 64 bit Linux architecture and when CAS is in use.
   # When default heap setting is overwritten by OPATCH_JRE_MEMORY_OPTIONS, then do not modify value of heap setting.
   my $JRE_MEMORY_OPTIONS=$_[0];
   my @uname = uname();
   my $PLATFORM="$uname[0]";
   my $unameSize=@uname;
   my $ARCH=$uname[$unameSize-1];
   my $DEFAULT_HEAP="-Xmx768m";
   if($PLATFORM eq "Linux" && $ARCH eq "x86_64"){
      $DEFAULT_HEAP="-Xmx3072m"
   } else{
      $DEFAULT_HEAP="-Xmx1536m" 
   }
   if(length $JRE_MEMORY_OPTIONS > 0){
     if($JRE_MEMORY_OPTIONS =~ m/ \-X*mx[0-9][0-9]*m*G*/){
       $JRE_MEMORY_OPTIONS =~ s/ \-X*mx[0-9][0-9]*m*G*/ $DEFAULT_HEAP/;
     }else{
	   $JRE_MEMORY_OPTIONS=" ".$JRE_MEMORY_OPTIONS." ".$DEFAULT_HEAP;
     }
   }else{
     $JRE_MEMORY_OPTIONS=" ".$DEFAULT_HEAP;
   }
   if($PLATFORM eq 'HP-UX'){
      $JRE_MEMORY_OPTIONS=addXMpasOptionForHPUX($JRE_MEMORY_OPTIONS);
   }
   return $JRE_MEMORY_OPTIONS;
}

sub addXMpasOptionForHPUX{
   my $JRE_MEMORY_OPTIONS=$_[0];
   my $XMAPS_OPTION="-Xmpas:on";
   if(index($JRE_MEMORY_OPTIONS, $XMAPS_OPTION) == -1){
      $JRE_MEMORY_OPTIONS=$JRE_MEMORY_OPTIONS." ".$XMAPS_OPTION;
   }
   return $JRE_MEMORY_OPTIONS;
}

sub extractJreMemoryOpts{
  my $oraParamIniFile=$_[0];
  my $jreMemoryOpts="";
 if (-e $oraParamIniFile){
   open my $fh, '<', $oraParamIniFile or die "Could not open '$oraParamIniFile' $!\n";
    while (my $line = <$fh>){
     if($line=~s/JRE_MEMORY_OPTIONS=//){
        $jreMemoryOpts=$line;
        $jreMemoryOpts=~s/"//g;
        last;
     }
    }
    close $fh;
 }
 chomp $jreMemoryOpts;
 return $jreMemoryOpts;
}


sub updateJreMemoryOptsForJRockit{
 my $jreMemoryOpts=$_[0];
 if(defined $ENV{'JDK'}){
     my $JDK=$ENV{'JDK'};
     if($JDK =~ m/jrockit/){
       $jreMemoryOpts=~s/XX:MaxPermSize=/ms/;
     }
  }
  return $jreMemoryOpts;
}

sub changeFileOwner{
  my $userName=$_[0];
  my $grpName=$_[1];
  my $path=$_[2];
  my $uid=getpwnam($userName);
  my $gid=getgrnam($grpName);
 if (-d $path) {
   my $pwd = getcwd();
   chdir($path);
   find(sub{chown($uid, $gid,$_)}, $path);
   chdir($pwd);
  }else{
    chown($uid, $gid, "$path");
  }
  return 1;
}

sub findFilePattern{
 my $sourcedir=$_[0];
 my $filePattern=$_[1];
 my @filelist;
 my $pwd = getcwd();
 if (-d $sourcedir){
   chdir($sourcedir);
 } 
 find(sub { push @filelist, $File::Find::name }, $sourcedir);
 foreach my $filepath (@filelist){
     if ( $filepath =~ /$filePattern/){
	   chdir($pwd);
       return 1;
     }
 }
 chdir($pwd);
 return 0;
}

sub changeFilePermission{
  my $path=$_[0];
  my $permission=$_[1];
  if (-d $path) {
   my $pwd = getcwd();
   chdir($path);
   find(sub{chmod(oct($permission),$_)}, $path);
   chdir($pwd);
  }else{
    chmod(oct($permission), "$path");
  }
  return 0;
}

sub changeFileFolderPermission {
    my $path       = $_[0];
    my $file_perm  = 0640; 
    my $dir_perm   = 0750;  

    if (-d $path) {
        my $pwd = getcwd();
        chdir($path);
        find(
            sub {
                if (-d $_) {
                    chmod $dir_perm, $_;
                } elsif (-f $_) {
                    chmod $file_perm, $_;
                }
            },
            '.'
        );
        chdir($pwd);
    } elsif (-f $path) {
        chmod $file_perm, $path;
    }

    return 0;
}

sub copyRecursively{
  my $sourcedir = $_[0];
  my $destdir   = $_[1];
   my $filepath;
   my $filename;
   my @filelist;
   my $destfilepath;
   my $dirpath;
   my $pwd = getcwd();
   if (-d $sourcedir){
     chdir($sourcedir);
   }
   find(sub { push @filelist, $File::Find::name }, $sourcedir);
   chdir($pwd);
   
   foreach $filepath (@filelist)
   {
     if ( -f $filepath)
     {
        $filename = basename($filepath);
        $dirpath = dirname($filepath);
        $dirpath =~ s/$sourcedir/$destdir/g;
        if( !-e $dirpath){
         createDirectory($dirpath);
        }
        $destfilepath = "$dirpath/$filename";
        copy($filepath, $destfilepath);
     }
   }
}
sub copyFileEndsWith{
  my $sourcedir = $_[0];
  my $destdir   = $_[1];
  my $pattern   = $_[2];
  my @files;
  my $file;
  opendir(DIR, $sourcedir);
  @files = grep(/$pattern$/,readdir(DIR));
  closedir(DIR);
  foreach $file (@files) {
    my $sourcefile = "$sourcedir/$file";
    my $destfile =  "$destdir/$file";
    copy($sourcefile, $destfile);
  }
}



sub createDirectory{
  my $dir=$_[0];
  if(!-e $dir){
    eval { mkpath($dir) };
    if ($@) {
      print "Couldn't create $dir: $@";
      return 1;
    }
  }
  return 0;
}

sub removeDirectory{
  my $dir=$_[0];
  if(-e $dir){
    eval { rmtree($dir) };
    if ($@) {
      print "Couldn't remove $dir: $@";
      return 0;
    }
  }
  return 1;
}

sub removeFile{
 my $file = $_[0];
 if(-e $file) {
  unlink $file;
 }
 
 return 1;
}

sub openFile{
 my $mode=$_[1];
 my $file=$_[0];
 my $FileHandler;
 if($mode == 1){
   # Use the open() function to create the file.
   if(!open $FileHandler, ">".$file){
    print("\n perl say $! \n");
   }
}else{
   # Use the open() function to create the file.
  open $FileHandler,$file;
}
  return $FileHandler;
}


sub closeFile{
 my $FileHandler=$_[0];
 # close the file.
 close $FileHandler;
}

sub writeFile{
 my $fh=$_[0];
 my $content= $_[1];
 # Write some text to the file.
 print $fh "$content\n";
}

sub readFile{
 my $fh=$_[0];
 # Read the file one line at a time.
 my $line = <$fh>;
 return $line; 
}

sub readPropertiesFile{
 my $propertiesfile=$_[0];
 my %properties=();
 if( -e $propertiesfile){
    my $fh=openFile($propertiesfile,0);
    while (<$fh>) {    
		chomp;    
		my ($key, $val) = split /=/;    
		if($val) {
		    $properties{$key} = $val; 
		}
	}
	closeFile($fh);
 }
 return %properties; 
}

sub writePropertiesFile{
 my %properties=%{$_[0]};
 my $propertiesfile=$_[1];
 my $fh=openFile($propertiesfile,1);
 for my $key ( sort keys %properties ) {
	print $fh "$key=$properties{$key}\n";
 }
 closeFile($fh);
 my $result = system ("chmod 640 $propertiesfile");
}

sub formatDuration{
 my $DUR = $_[0];
 my $MINVAL = int($DUR/60);
 my $SECVAL = $DUR%60;
 my $MINSTR = "minute";
 my $SECSTR = "second";
 if($MINVAL > 1) {
   $MINSTR = "minutes";
 }
 if($SECVAL > 1) {
   $SECSTR = "seconds";
 }
 return $MINVAL." ".$MINSTR.", ".$SECVAL." ".$SECSTR;
}

sub touchFile{ 
 my $file = $_[0];
 my $folder = $_[1];
 open ( my $TOUCH,">>",$file ) || die "\nThe current user does not have permission to write in the folder: '$folder'. Check owner and permission of this folder.";
 close $TOUCH; 
}

sub storeCustomPerms{
 my %dirsToPerms;
 my $isOper=isApplySession() || isRollbackSession();
 if( $isOper!=1){
   return \%dirsToPerms;
 }
# if(optExists('customConfigDir')){
#   my $customConfigDir = getOptValue('customConfigDir');
#   if(-d $customConfigDir){
#     my @configDirAttr;
#	 my ($name, $uid, $gid);
#	 $uid = stat($customConfigDir)->uid;
#     ($name, $uid, $gid) = (getpwuid $uid )[0, 2, 3];
#	 my $perms = stat($customConfigDir)->mode;
#	 push @configDirAttr, ($uid, $gid, $perms);
#	 $dirsToPerms{$customConfigDir} = \@configDirAttr;
#   }
# }
 
  if(optExists('customLogDir')){
   my $customLogDir = getOptValue('customLogDir');
   if(-d $customLogDir){
     my @logDirAttr;
	 my ($name, $uid, $gid);
	 $uid = stat($customLogDir)->uid;
     ($name, $uid, $gid) = (getpwuid $uid )[0, 2, 3];
	 my $perms = stat($customLogDir)->mode;
	 push @logDirAttr, ($uid, $gid, $perms);
	 $dirsToPerms{$customLogDir} = \@logDirAttr;
   }
 }
 
  if(optExists('log')){
   my $logDir = dirname(abs_path(getOptValue('log')));
   if(-d $logDir){
     my @logAttr;
	 my ($name, $uid, $gid);
	 $uid = stat($logDir)->uid;
     ($name, $uid, $gid) = (getpwuid $uid )[0, 2, 3];
	 my $perms = stat($logDir)->mode;
	 push @logAttr, ($uid, $gid, $perms);
	 $dirsToPerms{$logDir} = \@logAttr;
   }
 } 
 
 return \%dirsToPerms;
}

sub backupLogFiles{
	my $home = shift;
	my $cfgtoollogs = getLogDir($home);
	
	my $refFile = $home."/oraInst.loc";	
	my $opatchauto = $cfgtoollogs."/opatchauto";
	my $opatchautozip = $cfgtoollogs."/opatchauto.zip";
	if ( -e $opatchautozip ) {
		system ("rm -rf $opatchautozip");
	}
	my $result = system ("bash -c ' cd $cfgtoollogs; zip -rq opatchauto.zip opatchauto ' ");
	$result = $result >> 8;
	if($result != 0){
	  print("opatchauto unable to backup $opatchauto\n");
	}else{
	  #print("Collected logs from $opatchauto after the failed session.\n");	
	}
	my $owner = getFileOwner($refFile);
	my $group = getFileOwnerGroup($refFile);
	
	if ( -e $opatchautozip ) {		
		changeFileOwner($owner, $group, $opatchautozip);
		changeFilePermission($opatchautozip, "0640");
	}
	
	
	my $opatchautodb = $cfgtoollogs."/opatchautodb";
	my $opatchautodbzip = $cfgtoollogs."/opatchautodb.zip";
	if ( -e $opatchautodbzip ) {
		system ("rm -rf $opatchautodbzip");
	}
	$result = system ("bash -c ' cd $cfgtoollogs; zip -rq opatchautodb.zip opatchautodb ' ");
	$result = $result >> 8;
	if($result != 0){
	  print("opatchauto unable to backup $opatchautodb\n");
	}else{
	  #print("Collected logs from $opatchauto after the failed session.\n");	
	}
	if ( -e $opatchautodbzip ) {
		changeFileOwner($owner, $group, $opatchautodbzip);
		changeFilePermission($opatchautodbzip, "0640");
	}		
}

sub restoreCustomPerms{
 my $customDirHashRef = shift;
 
 foreach my $dir ( keys %$customDirHashRef) {
   my $valuesRef = $$customDirHashRef{$dir};
   my @values = @$valuesRef;
   chown($values[0], $values[1], $dir);
   chmod($values[2], $dir);
 }
}