#! /usr/bin/env perl
##**************************************************************
##
## Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
## University of Wisconsin-Madison, WI.
## 
## Licensed 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.
##
##**************************************************************

use strict;
use warnings;

# Update the module include path
BEGIN
{
	my $Dir = $0;
	if ( $Dir =~ /(.*)\/.*/ )
	{
		push @INC, "$1";
	}
}
use CondorConfig;
$| = 1;

# Prototypes
sub main( );
sub CommandLine( @ );
sub FindConfigFile( );
sub FindConfigToUpdate( );
sub ReadConfigFiles( );
sub InstallFiles( );
sub ProcessConfigLine( $ );
sub WriteConfig( );
sub Expand( $ );
sub ExpandString( $ );
sub Usage ( $ );
sub Help ( );


# ******************************************************
# Command line options
# ******************************************************
my $Distribution = "condor";
my %Options =
    (
     "[--modules=dir]"	=> "Specify modules directory",
     "[--source=dir]"	=> "Specify installation source",
     "[--config=file]"	=> "Specify $Distribution config file",
     "[--no-update|-n]"	=> "Don't update the config file",
     "[--update=file]"	=> "Specify config file to update",
     "[--update|-u]"	=> "Enable config update <default>",
     "[--help|-h]"	=> "Dump help",
    );

# Hash of directories & files
my %Config = (
	      RootDir => "",
	      SourceDir => ".",
	      ConfigFile => {
			     Base => "",
			     Updated => "",
			    },
	      ModulesDir => "",
	      UpdateConfig => 0,
	      UseDefaults => 1,
	      Startd => -1,
	      CronName => "",
	      SbinDir => "",
	      );

# Modules to install
my %InstallFiles = (
		    "HawkeyeLib.pm" => "ModulesDir",
		    "HawkeyePublish.pm" => "ModulesDir",
		    "condor_install_module" => "SbinDir",
		    "CondorConfig.pm" => "SbinDir",
		   );


# Basic setups
my $Program = "condor_setup_hawkeye";
my $ConfigEnv = "CONDOR_CONFIG";
my $ConfigMacros;
my $ConfigFiles;

main( );

# Main
sub main( )
{
    # "Main" Logic
    if ( ! exists $ENV{PWD} )
    {
	$ENV{PWD} = `/bin/pwd`; chomp $ENV{PWD};
    }

    # Process command line, etc..
    CommandLine( @ARGV );
    $#ARGV = -1;
    $| = 1;

    # Make sure that we can run 'condor_config_val'
    if ( ! open( CCV, "condor_config_val daemon_list|" ) )
    {
	print STDERR "Can't run condor_config_val\n";
	print STDERR "make sure the condor bin directory is in your path";
	print STDERR "and try again\n";
	exit 1;
    }
    # And, drain the output of condor_config_val ...
    while( <CCV> ) { }
    close( CCV );

    # Verify the source directory exists
    if ( ! -d $Config{SourceDir} )
    {
	print STDERR
	    "Source directory \"$Config{SourceDir}\" does not exist!";
	print STDERR
	    "Please specify a valid source directory via \"--source=<dir>\"\n";
	exit 1;
    }

    # Find the release tarball
    if ( ! -f "$Config{SourceDir}/release.tar" )
    {
	print STDERR
	    "\nUnable to access 'release.tar' in $Config{SourceDir}\n";
	print STDERR
	    "You may need to specify --source=<dir>\n";
	die "No release tarball found\n";
    }

    # If it's relative, make it absolute
    if ( ! ( $Config{SourceDir} =~ /^\// ) )
    {
	my $OldCwd = $ENV{PWD};
	chdir( $Config{SourceDir} )
	    or die "Can't chdir to $Config{SourceDir}: $!";
	my $Cwd = `pwd`; chomp $Cwd;
	$Config{SourceDir} = $Cwd;
	chdir( $OldCwd );
    }

    # Create the config macro & file handlers
    $ConfigMacros = CondorConfigMacros->new( $Distribution );

    # Construct the config file object
    $ConfigFiles = CondorConfigFiles->new( $ConfigMacros );

    # Find the config file to use
    FindConfigFile( );

    # Read the config file
    ReadConfigFiles( );

    # Copy in the perl modules
    InstallFiles( );

    # And, write the new config
    WriteConfig( );

} # end of "main"
# ******************************************************

# ******************************************************
# Parse the command line, etc.
# ******************************************************
sub CommandLine( @ )
{
    foreach my $Arg ( @_ )
    {
	# Speicfy modules directory
	if ( $Arg =~ /^--modules=(.+)/ )
	{
	    $Config{ModulesDir} = $1;
	}

	# Speicify config file
	elsif ( $Arg =~ /--config=(.+)/ )
	{
	    my $File = $1;
	    if ( ! $File =~ /^\// )
	    {
		$File = $ENV{PWD} . "/$File";
	    }
	    $Config{ConfigFile}{Base} = $File;
	}

	# Config file to update
	elsif ( $Arg =~ /^--update=(.+)/ )
	{
	    $Config{ConfigFile}{Updated} = $1;
	    $Config{UpdateConfig} = 1;
	}

	# Config update
	elsif ( ( $Arg eq "-u") || ( $Arg eq "--update" ) )
	{
	    $Config{UpdateConfig} = 1;
	}

	# Don't update config
	elsif ( ( $Arg eq "-n") || ( $Arg eq "--no-update" ) )
	{
	    $Config{UpdateConfig} = 0;
	}

	# Installation directory
	elsif ( $Arg =~ /^--source=(.+)/ )
	{
	    $Config{SourceDir} = $1;
	}

	# Specify the modules directory...
	elsif ( $Arg =~ "--modules=(.+)" )
	{
	    $Config{ModulesDir} = $1;
	}

	# Help
	elsif ( ( $Arg =~ /^-h/ ) || ( $Arg eq "--help" ) )
	{
	    Help( );
	    exit 0;
	}

	# Unknown option
	else
	{
	    Usage( $Arg );
	    exit( 1 );
	}
    }


} # CommandLine( )
# ******************************************************

# ******************************************************
# Find the configuration file to use
# ******************************************************
sub FindConfigFile( )
{
    my $Base = $ConfigFiles->FindConfig( $ConfigEnv,
					 $Config{ConfigFile}{Base},
					 $Distribution,
				       );

    # Finally, let's go check the config
    if ( $Base eq "" )
    {
	print STDERR "No config found\n";
	Usage( "" );
	print STDERR "\tOr, set the $ConfigEnv env variable\n";
	exit 1;
    }

    return 1;

} # FindConfigFile()
# ******************************************************

# ******************************************************
# Read the config file
# ******************************************************
sub ReadConfigFiles( )
{

    # Read them all in
    $ConfigFiles->ReadAll( );

    # Try to find the file that last definied the job list
    FindConfigToUpdate( );

    # Add basic things to the config if they're not defined...
    my $Tmp = $ConfigMacros->Expand( "STARTD_CRON_NAME" );
    if ( defined $Tmp )
    {
	$Config{CronName} = $Tmp;
    }
    else
    {
	$Config{CronName} =
	    ( $Distribution eq "hawkeye" ) ? "Hawkeye" : "Cron";
	$ConfigFiles->AddText( "STARTD_CRON_NAME = $Config{CronName}" );
	$ConfigMacros->Set( "STARTD_CRON_NAME", $Config{CronName} );
    }

    # Figure out where the modules go
    {
	my $Modules = $ConfigMacros->Get( "MODULES" );
	if ( ! defined $Modules )
	{
	    my $Hawkeye = $ConfigMacros->Expand( "HAWKEYE" );
	    my $HawkeyeDir = $ConfigMacros->Expand( "HAWKEYE_DIR" );
	    my $ReleaseDir = $ConfigMacros->Expand( "RELEASE_DIR" );

	    # 1. Look for direct config via cmd line
	    if ( $Config{ModulesDir} ne "" )
	    {
		$Modules = $Config{ModulesDir};
	    }
	    # 2. Look for HAWKEYE_DIR in config
	    elsif ( ( defined $HawkeyeDir ) && ( -d $HawkeyeDir ) )
	    {
		$Modules = "\$(HAWKEYE_DIR)/modules";
	    }
	    # 3. Look for HAWKEYE in config
	    elsif ( ( defined $Hawkeye ) && ( -d $Hawkeye ) )
	    {
		$Modules = "\$(HAWKEYE)/modules";
	    }
	    # 4. Look for RELEASE_DIR in config
	    elsif ( defined $ReleaseDir )
	    {
		$Modules = "\$(RELEASE_DIR)/modules";
	    }
	}

	# Confirm from user
	my $Expanded = "";
	while( 1 )
	{
	    print "What directory would you like modules installed in?\n";
	    print "  You may specify something like \$(RELEASE_DIR)/modules\n";
	    print "  Default = '$Modules'\n" if ( $Modules ne "" );
	    print "  Modules: ";
	    my $Input = <>; chomp $Input;
	    $Input = $Modules if ( $Input eq "" );

	    $Expanded = $ConfigMacros->ExpandString( $Input );
	    print "  $Input expands to '$Expanded': Is this correct [Y/n]? ";
	    $_ = <>;
	    if ( /^n/i )
	    {
		next;
	    }

	    # Ok, let's run with it...
	    $Modules = $Input;
	    last;
	}

	# Create the modules directory if it doesn't exit
	if ( ! -d $Expanded )
	{
	    print "  Creating modules directory '$Expanded'...\n";
	    mkdir( $Expanded, 0755 ) || die "Can't mkdir '$Expanded'";
	}

	# Store it away...
	$Config{ModulesDir} = $Expanded;

	# And, let's set the config
	my $Tmp = $ConfigMacros->Get( "MODULES" );
	if ( ( ! $Tmp ) || ( $Tmp ne $Modules ) )
	{
	    print "Defining MODULES\n";
	    $ConfigFiles->AddText( "MODULES = $Modules" );
	    $ConfigMacros->Set( "MODULES", $Modules );
	}

	print "\n";
    }

    # Jobs defined?
    my $CronJobsVar = uc( $Config{CronName} . "_JOBS" );
    my $Jobs = $ConfigMacros->Get( $CronJobsVar );
    if ( ! defined $Jobs )
    {
	$ConfigFiles->AddText( "$CronJobsVar = " );
	$ConfigMacros->Set( $CronJobsVar, "" );
    }

    # Find the "sbin" & "bin" directories
    my $Sbin = $ConfigMacros->Expand( "SBIN" );
    if ( ( ! defined $Sbin ) || ( $Sbin eq "" ) )
    {
	print STDERR "SBIN not defined in config.  Giving up.\n";
	exit 1;
    }
    $Config{SbinDir} = $Sbin;

    my $Bin = $ConfigMacros->Expand( "BIN" );
    if ( ( ! defined $Bin ) || ( $Bin eq "" ) )
    {
	print STDERR "BIN not defined in config.  Giving up.\n";
	exit 1;
    }
    $Config{BinDir} = $Bin;


} # ReadConfigFiles
#  ******************************************************

# ******************************************************
# Figure out which config file to update
# ******************************************************
sub FindConfigToUpdate( )
{

    # User specified?
    if ( $Config{ConfigFile}{Updated} ne "" )
    {
	$ConfigFiles->SetUpdateFile( $Config{ConfigFile}{Updated} );
	return 1;
    }

    # Raw file
    my $Raw = undef;

    # Is "INSTALL_MODULE_CONFIG_FILE" set?
    if ( ! $Raw )
    {
	my $File = $ConfigMacros->Expand( "INSTALL_MODULE_CONFIG_FILE" );
	$Raw = $File if ( ! defined $File );
    }

    # Try to find the file that last definied the job list
    if ( ! $Raw )
    {
	my $Info = $ConfigMacros->GetInfo( $Config{CronName} );
	if ( ( $Info ) && ( $Info->{File} ) )
	{
	    $Raw = $Info->{File};
	    print "Found '$Config{CronName}' in "
		. $Info->{File} . " line " . $Info->{Line} . "\n";
	}
    }

    # Try to find the file that last defined STARTD_CRON_NAME
    if ( ! $Raw )
    {
	my $Info = $ConfigMacros->GetInfo( "STARTD_CRON_NAME" );
	if ( ( $Info ) && ( $Info->{File} ) )
	{
	    $Raw = $Info->{File};
	    print "Found STARTD_CRON_NAME in "
		. $Info->{File} . " line " . $Info->{Line} . "\n";
	}
    }

    # If there's a local config that's named ".jobs" or similar, use it as
    # the config to modify
    if ( ! $Raw )
    {
	my @Match = $ConfigFiles->GrepName( "(jobs|joblist|hawkeye)\$" );
	if ( scalar @Match )
	{
	    $Raw = shift( @Match );
	}
    }

    # Finally, we'll grab a reasonable fallback
    if ( ! $Raw )
    {
	$Raw = "\$(RELEASE_DIR)/condor_config.hawkeye";
    }

    # Confirm from user
    while( 1 )
    {
	my $File = $ConfigMacros->ExpandString( $Raw );
	print "What config file would you like to store the module definitions in?\n";
	print "  You may specify something like \$(RELEASE_DIR)/etc/config.jobs\n";
	print "  Default = '$Raw'\n";
	print "  Local config: ";
	my $Input = <>; chomp $Input;
	$Input = $Raw if ( $Input eq "" );

	$File = $ConfigMacros->ExpandString( $Input );
	print "$Input expands to '$File': Is this correct [Y/n]? ";
	$_ = <>;
	redo if ( /^n/i );

	# Ok, let's run with it...
	$Raw = $Input;

	my @List = $ConfigFiles->GrepName( "^$File\$" );
	if ( $#Found < 0 )
	{
	    print "\n";
	    print "WARNING; $Raw does not appear to be in the\n";
	    print "  LOCAL_CONFIG_FILE list.  You'll need to verify and correct\n";
	    print "  this for your Hawkeye to work\n";
	    print "  Press <enter> to continue\n";
	    $_ = <>;
	}
	last;
    }

    # Store it
    $ConfigFiles->SetUpdateFile( $Raw );

} # FindConfigToUpdate( $ )
# ******************************************************

# ******************************************************
# Install the perl modules & executables
# ******************************************************
sub InstallFiles( )
{
    # Extract the tarball
    my $CurDir = $ENV{PWD};
    my $Dir = "/tmp/condor_setup_hawkeye.$$";
    mkdir( $Dir ) or die "Can't create '$Dir'\n";
    chdir( $Dir ) or die "Can't chdir to '$Dir'\n";
    print "Using temp dir '$Dir'\n";

    print "Extracting release tarball... ";
    my $TarBall = "$Config{SourceDir}/release.tar";
    system( "tar xf $TarBall" );
    print "Done\n";

    # Walk through the expected files...
    print "\nInstalling files:\n";
    while( my ( $File, $Dest) = each ( %InstallFiles ) )
    {
	my $Full = "./" . $File;
	if ( ! -f $Full )
	{
	    print STDERR "\nUnable to access $File; Bad 'release.dir'?\n";
	    exit 1;
	}
	print "$File -> $Config{$Dest} ...";
	my $Cmd = "/bin/cp -f $Full $Config{$Dest}";
	system( $Cmd ) == 0 or die "Failed to run '$Cmd': $?";
	print "ok\n";
	unlink( $Full );
    }

    # Create "hawkeye_config_val"
    my $File = $Config{BinDir} . "/hawkeye_config_val";
    if ( ! ( -f $File ) )
    {
	print "hawkeye_config_val -> $Config{BinDir} ... ";
	my $CronPrefix = uc $Config{CronName} . "_";
	open( HCV, ">$File" ) or die "Can't create $File";
	print HCV <<HCV_1 ;
#! /usr/bin/env perl
use strict;
use warnings;

# Parse the command line
foreach my \$ArgNo ( 0 .. \$#ARGV )
{
    # Get the "real" variable name
    \$ARGV\[\$ArgNo\] =~ s/^hawkeye_/$CronPrefix/i;
}

# Now, run the real condor_config_val
# print join( " ", "condor_config_val", \@ARGV ) . "\\n";
exec( "condor_config_val", \@ARGV );
HCV_1
	close( HCV );
	chmod( 0755, $File );
	print "Ok\n";
    }

    chdir( $CurDir );
    rmdir( $Dir );

    # Done
    return 1;

} # InstallFiles
# ******************************************************

# ******************************************************
# Write the new config file
# ******************************************************
sub WriteConfig( )
{
    # Any work to do?
    my $Num = $ConfigFiles->NewTextLines( );
    if ( $Num <= 0 )
    {
	print "\nNo config updates required\n";
	return 1;
    }

    # Write the updates to the temp file
    my ( $File, $TmpFile ) = $ConfigFiles->WriteUpdates( );
    if ( ! $File or ! $TmpFile )
    {
	die "Error writing update";
    }

    # Ask the user unless they explicietly asked us to overwrite it
    my $OverWrite = $Config{UpdateConfig};
    if ( ! $OverWrite )
    {
	if ( -f $File )
	{
	    print "\nAbout to overwrite $File; ok? ";
	    $OverWrite = 1 if ( <> =~ /^y/ );
	}
	else
	{
	    $OverWrite = 1;
	}
    }

    # OverWrite it?
    if ( $OverWrite )
    {
	my $Backup = "$File.bak";
	unlink( $Backup );
	rename( $File, $Backup );
	rename( $TmpFile, $File );
	print "\n$File updated\n";
    }
    else
    {
	print "New config in $TmpFile\n";
    }

    return 1;

} # WriteConfig()
# ******************************************************

# ******************************************************
# Dump out usage
# ******************************************************
sub Usage ( $ )
{
    my $Unknown = shift;

    print "$Program: unknown option '$Unknown'\n" if ( $Unknown ne "" );
    printf "usage: $Program [options] [files]\n";
    print "use '-h' for more help\n";
    exit 1;

} # usage ()
# ******************************************************

# ******************************************************
# Dump out help
# ******************************************************
sub Help ( )
{
    my ($opt, $text);

    printf "usage: $Program [options] [files]\n";
    foreach $opt (sort {lc($a) cmp lc($b) } keys %Options)
    {
	printf ("  %15s : %-40s\n", $opt, $Options{$opt} );
    }
    exit 0;

} # help ()
# ******************************************************
