#!/usr/bin/perl

=head1 NAME

rt-notify-group-admin - Command line tool for administrating NotifyGroup actions

=head1 SYNOPSIS

	rt-notify-group-admin --list
	rt-notify-group-admin --create 'Notify foo team' --group Foo
	rt-notify-group-admin --create 'Notify foo team as comment' --comment --group Foo
	rt-notify-group-admin --create 'Notify group Foo and Bar' --group Foo --group Bar
	rt-notify-group-admin --create 'Notify user foo@bar.com' --user foo@bar.com
	rt-notify-group-admin --create 'Notify VIPs' --user vip1@bar.com
	rt-notify-group-admin --add 'Notify VIPs' --user vip2@bar.com --group vip1 --user vip3@foo.com
	rt-notify-group-admin --rename 'Notify VIPs' --newname 'Inform VIPs'
	rt-notify-group-admin --switch 'Notify VIPs'
	rt-notify-group-admin --delete 'Notify user foo@bar.com'

=head1 DESCRIPTION

rt-notify-group-admin 

=cut

use warnings;
use strict;
### replace: use lib qw(@RT_LIB_PATH@);
use lib qw(/opt/rt3/local/lib /opt/rt3/lib);

use RT;
RT::LoadConfig;
RT::Init;

require RT::Principal;
require RT::User;
require RT::Group;
require RT::ScripActions;

require Storable;
use Getopt::Long qw(GetOptions);

our $cmd = 'usage';
our $opts = {};

sub parse_args
{
	my $tmp;
	Getopt::Long::Configure( "pass_through" );
	if( GetOptions( 'list' => \$tmp ) && $tmp ) {
		$cmd = 'list';
	} elsif( GetOptions( 'create=s' => \$tmp ) && $tmp ) {
		$cmd = 'create';
		$opts->{'name'} = $tmp;
		$opts->{'groups'} = [];
		$opts->{'users'} = [];
		GetOptions( 'comment' => \$opts->{'comment'} );
		GetOptions( 'group:s@' => $opts->{'groups'} );
		GetOptions( 'user:s@' => $opts->{'users'} );
		unless( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
			usage();
			exit(-1);
		}
	} elsif( GetOptions( 'add=s' => \$tmp ) && $tmp ) {
		$cmd = 'add';
		$opts->{'name'} = $tmp;
		$opts->{'groups'} = [];
		$opts->{'users'} = [];
		GetOptions( 'group:s@' => $opts->{'groups'} );
		GetOptions( 'user:s@' => $opts->{'users'} );
		unless( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
			usage();
			exit(-1);
		}
	} elsif( GetOptions( 'switch=s' => \$tmp ) && $tmp ) {
		$cmd = 'switch';
		$opts->{'name'} = $tmp;
	} elsif( GetOptions( 'rename=s' => \$tmp ) && $tmp ) {
		$cmd = 'rename';
		$opts->{'name'} = $tmp;
		GetOptions( 'newname=s' => \$opts->{'newname'} );
		unless( $opts->{'newname'} ) {
			usage();
			exit(-1);
		}
	} elsif( GetOptions( 'delete=s' => \$tmp ) && $tmp) {
		$cmd = 'delete';
		$opts->{'name'} = $tmp;
	} else {
		$cmd = 'usage';
	}
	
	return;
}

sub usage
{
	eval "require Pod::PlainText;";
	if( $@ ) {
		print "see `perldoc $0`\n";
	} else {
		my $parser = Pod::PlainText->new (sentence => 0, width => 78);
		$parser->parse_from_file( $0 );
	}
}

parse_args();

{
	eval "main::$cmd()";
	if( $@ ) {
		print STDERR $@ ."\n";
	}
}

exit(0);

=head1 USAGE

rt-notify-group-admin --COMMAND ARGS

=head1 COMMANDS

=head2 list

Lists actions and its descriptions.

=cut

sub list
{
	my $actions = _get_our_actions();
	while( my $a = $actions->Next ) {
		_list( $a );
	}

	return;
}

sub _list
{
	my $action = shift;

	print "Name: ". $action->Name() ."\n";
	print "Module: ". $action->ExecModule() ."\n";

	my $arg = $action->Argument;

	# old variant via Storable
	my $old = eval { Storable::thaw( $arg ) };
	unless( $@ ) {
		$arg = __convert_old($old);
	}

	my @princ = _split_arg( $arg );

	print "Members: \n";
	foreach( @princ ) {
		my $obj = RT::Principal->new( $RT::SystemUser );
		$obj->Load( $_ );
		next unless( $obj->id );

		print "\t". $obj->PrincipalType;
		print "\t=> ". $obj->Object->Name;
		print "(Disabled!!!)" if $obj->Disabled;
		print "\n";
	}
	print "\n";
	return;
}

=head2 create NAME [--comment] [--group GNAME] [--user UNAME]

Creates new action with NAME and adds users and/or groups to its
recipient list. Would be notify as comment if --comment specified.

=cut

sub create
{
	my $actions = RT::ScripActions->new( $RT::SystemUser );
	$actions->Limit( FIELD => 'Name',
			 VALUE => $opts->{'name'} );

	if( $actions->Count ) {
		print STDERR "ScripAction '". $opts->{'name'} ."' allready exists\n";
		exit(-1);
	}
	my @groups = _check_groups( @{ $opts->{'groups'} } );
	my @users  = _check_users( @{ $opts->{'users'} } );
	
	unless( @users + @groups ) {
		print STDERR "List of groups and users is empty\n";
		exit(-1);
	}

	my $action = __create_empty( $opts->{'name'}, $opts->{'comment'} );

	__add( $action, $_ ) foreach( @users );
	__add( $action, $_ ) foreach( @groups );

	return;
}

sub __create_empty
{
	my $name = shift;
	my $as_comment = shift || 0;
	require RT::ScripAction;
	my $action = RT::ScripAction->new( $RT::SystemUser );
	$action->Create(
			Name => $name,
			Description => "Created with rt-notify-group-admin script",
			ExecModule => $as_comment? 'NotifyGroupAsComment': 'NotifyGroup',
			Argument => '',
			);

	return $action;
}

sub _check_groups
{
	return grep { $_ ? 1: do { print STDERR "Group '$_' skipped, doesn't exist\n"; 0; } }
		map { __check_group($_) } @_;
}

sub __check_group
{
	my $instance = shift;
	require RT::Group;
	my $obj = RT::Group->new( $RT::SystemUser );
	$obj->LoadUserDefinedGroup( $instance );
	return $obj->id ? $obj : undef;
}

sub _check_users
{
	return grep { $_ ? 1: do { print STDERR "User '$_' skipped, doesn't exist\n"; 0; } }
		map { __check_user($_) } @_;
}

sub __check_user
{
	my $instance = shift;
	require RT::User;
	my $obj = RT::User->new( $RT::SystemUser );
	$obj->Load( $instance );
	return $obj->id ? $obj : undef;
}

=head2 add NAME [--group GNAME] [--user UNAME]

Adds groups and/or users to recipients of the action NAME.

=cut

sub add
{
	my $action = _get_action_by_name( $opts->{'name'} );

	unless( $action ) {
		print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
		exit(-1);
	}

	my @groups = _check_groups( @{ $opts->{'groups'} } );
	my @users = _check_users( @{ $opts->{'users'} } );
	
	unless( @users + @groups ) {
		print STDERR "List of groups and users is empty\n";
		exit(-1);
	}

	__add( $action, $_ ) foreach( @users );
	__add( $action, $_ ) foreach( @groups );

	return;
}

sub __add
{
	my $action = shift;
	my $obj = shift;

	my $arg = $action->Argument;

# Support old variant with storable
	my $old = eval { Storable::thaw( $arg ) };
	unless( $@ ) {
		$arg = __convert_old($old);
	}

	my @cur = _split_arg( $arg );
	my $id = $obj->id;
	foreach( @cur ) {
		return if( $_ == $id );
	}

	push( @cur, $id );
	$action->__Set( Field => 'Argument', Value => join(';', @cur) );

	return;
}

=head2 delete NAME

Deletes action NAME if scrips doesn't use it.

=cut

sub delete
{
	my $action = _get_action_by_name( $opts->{'name'} );

	unless( $action ) {
		print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
		exit(-1);
	}

	require RT::Scrips;
	my $scrips = RT::Scrips->new( $RT::SystemUser );
	$scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id );
	if( $scrips->Count ) {
		my @sid;
		while( my $s = $scrips->Next ) {
			push @sid, $s->id;
		}
		print STDERR "ScripAction '"
			. $opts->{'name'}
			. "' is in use by Scrip(s) #"
			. join( ";", @sid )
			. "\n";
		exit(-1);
	}

	return __delete( $action );
}

sub __delete
{
	require DBIx::SearchBuilder::Record;
	DBIx::SearchBuilder::Record::Delete( shift );
}

sub _get_action_by_name
{
	my $name = shift;
	my $actions = _get_our_actions();
	$actions->Limit( FIELD => 'Name',
			 VALUE => $name );

	if( $actions->Count > 1 ) {
		print STDERR "More then one ScripAction with name '$name'\n";
	}

	return $actions->First;
}

=head2 switch NAME

Switch action NAME from notify as correspondence to comment and back.

=cut

sub switch
{
	my $action = _get_action_by_name( $opts->{'name'} );

	unless( $action ) {
		print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
		exit(-1);
	}

	my %h = ('NotifyGroup' => 'NotifyGroupAsComment',
		 'NotifyGroupAsComment' => 'NotifyGroup');

	$action->__Set( Field => 'ExecModule',
		       Value => $h{ $action->ExecModule } );
}

=head2 rename NAME --newname NEWNAME

Renames action NAME to NEWNAME.

=cut

sub rename
{
	my $action = _get_action_by_name( $opts->{'name'} );

	unless( $action ) {
		print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
		exit(-1);
	}

	my $actions = RT::ScripActions->new( $RT::SystemUser );
	$actions->Limit( FIELD => 'Name',
			 VALUE => $opts->{'newname'} );
	if( $actions->Count ) {
		print STDERR "ScripAction '". $opts->{'newname'} ."' allready exists\n";
		exit(-1);
	}

	$action->__Set( Field => 'Name',
		       Value => $opts->{'newname'} );
}

=head2 NOTES

If command has option --group or --user then you can use it more then once,
if other is not specified.

=cut

###############
#### Utils ####
###############

sub _split_arg
{
	return split /[^0-9]+/, $_[0];
}

sub __convert_old
{
	my $arg = shift;
	my @res;
	foreach my $r ( @{ $arg } ) {
		my $obj;
		next unless $r->{'Type'};
		if( lc $r->{'Type'} eq 'user' ) {
			$obj = RT::User->new( $RT::SystemUser );
		} elsif ( lc $r->{'Type'} eq 'user' ) {
			$obj = RT::Group->new( $RT::SystemUser );
		} else {
			next;
		}
		$obj->Load( $r->{'Instance'} );
		my $id = $obj->id;
		next unless( $id );

		push @res, $id;
	}

	return join ';', @res;
}

sub _get_our_actions
{
	my $actions = RT::ScripActions->new( $RT::SystemUser );
	$actions->Limit( FIELD => 'ExecModule',
			 VALUE => 'NotifyGroup',
			 ENTRYAGGREGATOR => 'OR');
	$actions->Limit( FIELD => 'ExecModule',
			 VALUE => 'NotifyGroupAsComment',
			 ENTRYAGGREGATOR => 'OR');

	return $actions;
}

=head1 AUTHOR

	Ruslan U. Zakirov
	cubic@wildgate.miee.ru

=head1 COPYRIGHT

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the
LICENSE file included with perl distribution.

=head1 SEE ALSO

RT::Action::NotifyGroup, RT::Action::NotifyGroupAsComment

=cut
