#!/usr/local/bin/perl
#
#Modifies PDB entry for an user
#

use strict;
use warnings;
use lib '/cell_root/software/HPCC/lib/site_perl';
#use lib '/afs/.glue.umd.edu/software/HPCC/1.1/lib/site_perl';

use Glue::HPCC::Cluster;
use Glue::HPCC::Project::Person;
use Glue::HPCC::Project::Project;
use Glue::HPCC::Project::PersonProject;
use Glue::HPCC::Project::Log;

use Getopt::Long;
use Time::ParseDate;
use POSIX qw(strftime);

my $FORCE;

sub usage()
{	print STDERR <<EOF;
$0: Modifies entries for user in projects Database (PDB)

Usage:
$0 -user USERNAME -email NEWEMAIL -reason REASON
$0 -user USERNAME -fullname NEWFULLNAME -reason REASON
$0 -user USERNAME -[no]active  -reason REASON
$0 -user USERNAME -min_hrs_btwn_alerts MINHOURS -reason REASON
$0 -user USERNAME -project PROJECT -reason REASON [ -[no]member ] \
	[ -[no]poc ] [ -[no]coord ] 
$0 -user USERNAME [ -project PROJECT ] --DELETE -reason REASON

This script modifies the users entry in the PDB.  It affects either the
main person table, or the person project table; the latter requires a PROJECT.

All modifications will be logged and require a REASON.

-email option will change the email address associated with the user.
-fullname will change fullname (human readable name) of the user in the table.
--[no]active will set/unset the active flag for the user.
--min_hrs_btwn_alerts sets the min. hours between email alerts to user.  Set
	negative to NULLify.

The --[no]member, --[no]poc, --[no]coord all require a project be specified,
and will add (or remove, in the "no" variants) the user from that role for
for the specified project.  

The -cluster CLUSTER argument can be given to specify which cluster's
PDB should be acted on; default is based on host running the command.

***NOTE: ALL CHANGES OCCUR IN THE DB ONLY***
Use adduser/deluser to add or remove membership in a project.   This script
is really only good for adding/deleting pocs or coordinators --- adding/deleting
members is more complicated and requires actions outside of the projects DB
to be complete.  Similar for activating/deactivating an account.

The --DELETE option will delete the record in the person project table if
PROJECT is given, or the person table if no PROJECT is given.  In latter case,
there must not be any rows for that person in person project table.
THIS DELETES THE ROW OF THE TABLE.  
This should not normally be done; removing person from project properly
requires extra actions.  It is incompatible with any other options.

EOF
}

sub warn_about_member_changes($$$)
#Warns user if aobut to change membership status with this code
{	my $uname = shift;
	my $project = shift;
	my $mode = shift;

	return if $FORCE;

	my $action = "remove $uname from project $project";
	$action = "add $uname to project $project" if $mode;
	my $altprog = 'delusers';
	$altprog = 'addusers' if $mode;

	print STDERR <<EOF;
********************************************************
		WARNING
********************************************************

You requested to $action.
To do this fully and properly required more than just editting the
data in the projects database, but that is ALL that this script does.

Normally, you should be using the script $altprog.
This script only allows modifying membership status to facilitate repairing
stuff.  If you are not sure what you are doing, ctrl-c out of here NOW!

If you are sure, type yes to continue:
Continue (y/N)?
EOF

	my $ans = <STDIN>;
	chomp $ans;
	if ( $ans && $ans =~ /^\s*y(es?)?\s*$/i )
	{	return;
	}

	die "Aborting at user request.\n";
}

sub warn_about_deactivation($$)
#Warns user if aobut to change active status with this code
{	my $uname = shift;
	my $mode = shift;

	return if $FORCE;

	my $action = "deactivate user account $uname";
	$action = "activate user account $uname i" if $mode;
	my $altprog = 'delusers';
	$altprog = 'addusers' if $mode;

	print STDERR <<EOF;
********************************************************
		WARNING
********************************************************

You requested to $action.
To do this fully and properly required more than just editting the
data in the projects database, but that is ALL that this script does.

Normally, you should be using the script $altprog.
This script only allows modifying membership status to facilitate repairing
stuff.  If you are not sure what you are doing, ctrl-c out of here NOW!

If you are sure, type yes to continue:
Continue (y/N)?
EOF

	my $ans = <STDIN>;
	chomp $ans;
	if ( $ans && $ans =~ /^\s*y(es?)?\s*$/i )
	{	return;
	}

	die "Aborting at user request.\n";
}

sub warn_about_deletes($$)
#Warns user if aobut to delete a row with this code
{	my $uname = shift;
	my $project = shift;

	return if $FORCE;

	my $action = "delete row from person table with user=$uname";
	if ( $project )
	{	$action = "delete row from person_project table user=$uname, proj=$project";
	}

	print STDERR <<EOF;
********************************************************
		WARNING
********************************************************

You requested to $action.

This should NOT be done to remove someone from a project/etc.
That should normally be done with 'delusers' script.

This script only allows deleting records to facilitate repairing
stuff.  If you are not sure what you are doing, ctrl-c out of here NOW!

If you are sure, type yes to continue:
Continue (y/N)?
EOF

	my $ans = <STDIN>;
	chomp $ans;
	if ( $ans && $ans =~ /^\s*y(es?)?\s*$/i )
	{	return;
	}

	die "Aborting at user request.\n";
}

	
#------------------------------------------------------
#		Main
#------------------------------------------------------

my ($help, $dryrun, $verbose);
my ($uname, $project, $newemail, $newfull, $reason );
my ($member, $poc, $coord);
my ($DELETE);
my $activate;
my $minhrs;
my $cluster;

my $res = GetOptions(
	'help!' => \$help,
	'dryrun!' => \$dryrun,
	'verbose!' => \$verbose,
	'force!' => \$FORCE,
	'delete!' => \$DELETE,

	'cluster=s' => \$cluster,
	'user=s' => \$uname,
	'email=s' => \$newemail,
	'fullname=s' => \$newfull,
	'project=s' => \$project,
	'reason=s' => \$reason,
	'member!' => \$member,
	'poc|point-of-contact|point_of_contact|pointofcontact!' => \$poc,
	'coordinator!' => \$coord,
	'active|activate!' => \$activate,
	'min_hours_between_alerts|min-hours-between-alerts|min_hrs_btwn_alerts|min-hrs-btwn-alerts=f' 
		=> \$minhrs,
);

unless ( $res )
{	usage;
	die "\nError processing arguments\n";
}

if ( $help )
{	usage;
	exit 0;
}

my $tmp;
if (scalar(@ARGV) )
{	usage;
	$tmp = join ', ', @ARGV;
	die "\nUnrecognized arguments: [ $tmp ], aborting\n";
}

#Default cluster if needed
my $hpc = Glue::HPCC::Cluster->new_from_clustername($cluster);
die "\nInvalid cluster $cluster at " if $cluster && ! $hpc && ! ref($hpc);
die "\nError instantiating cluster $hpc " unless $hpc && ref($hpc);
$cluster = $hpc->db_clustername;

$hpc->verbose($verbose);
$hpc->dryrun($dryrun);

unless ($uname)
{	usage;
	die "\nMissing required parameter 'user'\n";
}

unless ($reason)
{	usage;
	die "\nMissing required parameter 'reason'\n";
}

if ( defined $poc || defined $coord || defined $member )
{	unless ( $project )
	{	usage;
		die "\nProject is required with any of --[no]member, " .
			"--[no]poc, --[no]coordinator.\n";
	}
}

#Canonicalize booleans
if ( defined $poc ) { $poc = $poc?1:0; }
if ( defined $coord ) { $coord = $coord?1:0; }
if ( defined $member ) { $member = $member?1:0; }
if ( defined $activate ) { $activate = $activate?1:0; }

my $did_something = 0;
my $error;
my ($tstamp, $date);

my $user = Glue::HPCC::Project::Person->person_by_username($hpc, $uname);
die "Illegal value $uname for user, not found in PDB.\n" unless ( $user );
die "Error looking up $uname in PDB: $user.\n" unless ref($user);


my $proj;
if ($project )
{	unless ( defined $member || defined $poc || defined $coord || $DELETE )
	{	warn "Useless use of --project: only meaningful if " .
			"changing member/poc/coord status.\n";
	}
	$proj = Glue::HPCC::Project::Project->project_by_name($hpc, $project);
	die "Illegal value $project for project, not found in PDB.\n" 
		unless $proj;
	die "Error looking up project $project in PDB: $proj.\n" 
		unless ref($proj);
}

if ( $DELETE )
{	if ( defined $member || defined $poc || defined $coord )
	{	die "--DELETE flag incompatible with --member/--poc/--coord flags.\n";
	}
	if ( $newemail || $newfull )
	{	die "--DELETE flag incompatible with --email or --fullname flags.\n";
	}
	if ( defined $activate ) 
	{	die "--DELETE flag incompatible with --[no]active flags.\n";
	}
	if ( defined $minhrs ) 
	{	die "--DELETE flag incompatible with --minhrs flags.\n";
	}
	warn_about_deletes($uname,$project);

	if ( $project )
	{	#Delete from person-project table
		my $prj_pkey = $proj->prj_pkey;
		my $ppl_pkey = $user->ppl_pkey;
		Glue::HPCC::Project::PersonProject->pdb_delete($hpc,
			prj_pkey=>$prj_pkey, ppl_pkey => $ppl_pkey);
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			project => $project,
			tablename => 'person_project_m2m',
			reason => $reason,
			description => "Delete row for person $uname, project $project",
			action_code => 'delete-row',
		);
		print STDERR "Deleted user $uname from project $project.\n";
		$hpc->db_commit;
		exit 0;
	} else
	{	#Delete from person table

		#First check no rows in person-project table
		my $temp = Glue::HPCC::Project::PersonProject->get_project_persons_by_username(
			$hpc, $uname);
		if ( $temp )
		{	die "Error looking up records in PersonProject table: $temp at "
				unless ref($temp);
			if ( scalar(@$temp) )
			{ 	print STDERR "User $uname associated with projects in PersonProject table.\n";
				foreach my $rec (@$temp)
				{	my $tmppkey = $rec->prj_pkey;
					my $tmpproj = Glue::HPCC::Project::Project->project_by_pkey(
						$hpc, $tmppkey);
					print STDERR "\t", $tmpproj->name, "\n";
				}
				die "Cannot delete row for $uname until delete rows for user/project\n"; 
			}
		}

		Glue::HPCC::Project::Person->pdb_delete($hpc, ppl_pkey => $user->ppl_pkey );
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			tablename => 'person',
			reason => $reason,
			description => "Delete row for person $uname",
			action_code => 'delete-row',
		);
		print STDERR "Deleted user $uname from person table.\n";
		$hpc->db_commit;
		exit 0;
	}
}

			
my $oldval;

if ( $newemail )
{	$oldval = $user->email;
	if ( $newemail eq $oldval )
	{	warn "User $uname: Email is already set to $newemail, nothing to do.\n";
	} else
	{	$error = $user->pdb_update_me($hpc, email => $newemail );
		die "Error setting email to $newemail for $uname: $error\n"
			if $error;
		print "Set email=$newemail for user $uname\n";
		$did_something=1;
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			tablename => 'person',
			fieldname => 'email',
			oldval => $oldval,
			newval => $newemail,
			reason => $reason,
			description => "Change email address for $uname",
			action_code => 'modify-user',
		);
	}
}

if ( $newfull )
{	$oldval = $user->fullname;
	if ( $newfull eq $oldval )
	{	warn "User $uname: Fullname is already set to $newfull, nothing to do.\n";
	} else
	{	$error = $user->pdb_update_me($hpc, fullname => $newfull );
		die "Error setting fullname to $newfull for $uname: $error\n"
			if $error;
		print "Set fullname=$newfull for user $uname\n";
		$did_something=1;
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			tablename => 'person',
			fieldname => 'fullname',
			oldval => $oldval,
			newval => $newfull,
			reason => $reason,
			description => "Change fullname for $uname",
			action_code => 'modify-user',
		);
	}
}

if ( defined $minhrs )
{	$oldval = $user->min_hrs_btwn_alerts;
	$minhrs = undef if $minhrs < 0;
	$tmp = 1;
	$tmp = 0 if ! defined $oldval && ! defined $minhrs;
	$tmp = 0 if defined $oldval && defined $minhrs && $oldval == $minhrs;
	if ( $tmp )
	{	$error = $user->pdb_update_me($hpc, min_hrs_btwn_alerts => $minhrs );
		die "Error setting min_hrs_btwn_alerts to $minhrs for $uname: $error\n"
			if $error;
		print "Set min_hrs_btwn_alerts to $minhrs for user $uname\n";
		$did_something=1;
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			tablename => 'person',
			fieldname => 'min_hrs_btwn_alerts',
			oldval => $oldval,
			newval => $minhrs,
			reason => $reason,
			description => "Change min_hrs_btwn_alerts for $uname",
			action_code => 'modify-user',
		);
	}
	else
	{	warn "User $uname: Min_hrs_btwn_alerts is already set to $minhrs, nothing to do.\n";
	} 
}

my $tmpneg;

if ( defined $activate )
{	#Activate/deactivate user
	$oldval = $user->active;
	#$oldval = 1 unless defined $oldval;
	$oldval = $oldval?1:0;
	if ( $oldval == $activate )
	{	$tmp = 'enabled';
		$tmp = 'disabled' unless $activate;
		warn "User $uname already $tmp, nothing to do.\n";
	} else 
	{ 	$tmpneg = 'enabling';
		$tmpneg = 'disabling' unless $activate;

		unless ( $activate )
		{	#Should not disable user if member of any projects
			$tmp = Glue::HPCC::Project::PersonProject->get_project_persons_by_username(
				$hpc, $uname);
			die "Error looking up projects for user $uname: $tmp at "
				if $tmp && ! ref($tmp);
			if ( ref($tmp) )
			{	my @tmp = grep { $_->is_member || $_->is_poc ||
					$_->is_coordinator } @$tmp;
				if ( scalar(@tmp) )
				{	@tmp = map { Glue::HPCC::Project::Project->project_by_pkey(
						$hpc, $_->prj_pkey) } @tmp;
					@tmp = map { $_->name } @tmp;
					$tmp = join ", ", @tmp;
					die <<EOF;
Unable to deactivate user who is still part of projects.
User $uname is part of projects [ $tmp ]
Please remove user from these projects before deactivating.

EOF
				}
			}
		}
			
		warn_about_deactivation($uname, $activate);
		$error = $user->pdb_update_me($hpc, active => $activate );
		die "Error $tmpneg user $uname/proj $project: "
			. "$error\n" if $error;
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			tablename => 'person',
			fieldname => 'active',
			oldval => $oldval,
			newval => $activate,
			reason => $reason,
			description => "$tmpneg user $uname",
			action_code => 'modify-user',
		);
		$did_something=1;
	}
}
	
#Handle role modification
my $perproj;
if ( $project )
{   $perproj = Glue::HPCC::Project::PersonProject->get_project_person_by_names(
		$hpc, $project, $uname);
    unless ( $perproj )
    { 	    if ( $member || $poc || $coord )
    	    {	#Wants to set at least one, so create it
		$perproj = Glue::HPCC::Project::PersonProject->pdb_insert($hpc,
			prj_pkey => $proj->prj_pkey,
			ppl_pkey => $user->ppl_pkey,
		);
		die "Error inserting PersonProject record for project $project"
			 .  " and user $uname: $perproj.\n" if $perproj;
		$perproj = Glue::HPCC::Project::PersonProject->get_project_person_by_names(
			$hpc, $project, $uname);
		unless ( $perproj )
		{	die "Error: cannot find newly created PersonProject " .
				"for project $project, user $uname.\n";
		}
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			project => $project,
			tablename => 'personproject',
			reason => $reason,
			description => "Insert blank person project record",
			action_code => 'insert-person-project',
		);
     	    } else
            {	#Not setting anything, no reason to create a record
		warn "User $uname already is not a member of project $project "
		 	.  "(no PersonProject record); nothing to do.\n" 
			if defined $member;
		warn "User $uname already is not a poc of project $project " .
			"(no PersonProject record); nothing to do.\n" 
			if defined $poc;
		warn "User $uname already is not a coordinator of project " .
			"$project (no PersonProject record); nothing to do.\n" 
			if defined $coord;
    	    }
    }
}

if ( $perproj )
{   if ( defined $poc )
    {	$oldval = ($perproj->is_poc)?1:0;
	$tmpneg = '';
	$tmpneg = 'not ' unless $poc;
	if ( $poc == $oldval )
	{	warn "User $uname is already ${tmpneg}a poc of project " .
			"$project; nothing to do.\n";
	} else
	{ 	$tmpneg = 'setting';
		$tmpneg = 'unsetting' unless $poc;
		$error = $perproj->pdb_update_me($hpc, is_poc => $poc );
		die "Error $tmpneg poc role for user $uname/proj $project: "
			. "$error\n" if $error;
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			project => $project,
			tablename => 'personproject',
			fieldname => 'is_poc',
			oldval => $oldval,
			newval => $poc,
			reason => $reason,
			description => "$tmpneg user $uname as poc of $project",
			action_code => 'modify-person-project',
		);
		$did_something=1;
	}
    }

   if ( defined $coord )
    {	$oldval = ($perproj->is_coordinator)?1:0;
	$tmpneg = '';
	$tmpneg = 'not ' unless $coord;
	if ( $coord == $oldval )
	{	warn "User $uname is already ${tmpneg}a coordinator of project"
			 .  " $project; nothing to do.\n";
	} else
	{ 	$tmpneg = 'setting';
		$tmpneg = 'unsetting' unless $coord;
		$error = $perproj->pdb_update_me($hpc, is_coordinator => $coord );
		die "Error $tmpneg coord role for user $uname/proj $project: "
			. "$error\n" if $error;
		print STDERR "HERE: oldval=$oldval\n";
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			project => $project,
			tablename => 'personproject',
			fieldname => 'is_coordinator',
			oldval => $oldval,
			newval => $coord,
			reason => $reason,
			description => "$tmpneg user $uname as coord of $project",
			action_code => 'modify-person-project',
		);
		$did_something=1;
	}
    }
		

   if ( defined $member )
    {	$oldval = ($perproj->is_member)?1:0;
	$tmpneg = '';
	$tmpneg = 'not ' unless $member;
	if ( $member == $oldval )
	{	warn "User $uname is already ${tmpneg}a member of project"
			 .  " $project; nothing to do.\n";
	} else
	{ 	$tmpneg = 'setting';
		$tmpneg = 'unsetting' unless $coord;
		warn_about_member_changes($uname, $project, $member);
		$error = $perproj->pdb_update_me($hpc, is_member => $member );
		die "Error $tmpneg member role for user $uname/proj $project: "
			. "$error\n" if $error;
		Glue::HPCC::Project::Log->add_log_entry($hpc,
			person => $uname,
			project => $project,
			tablename => 'personproject',
			fieldname => 'is_member',
			oldval => $oldval,
			newval => $member,
			reason => $reason,
			description => "$tmpneg user $uname as member of $project",
			action_code => 'modify-person-project',
		);
		$did_something=1;
	}
    }
}

if ( $did_something )
{	print "Changes committed in PDB.\n";
	$hpc->db_commit;
} else
{	warn "Nothing to do.\n";
}

