# +=======================================================================+
# || XML-RPC client to register, deregister and list a CAT module.       ||
# ||                                                                     ||
# || Copyright (C) 2008 - 2010 by Christian Kuelker                      ||
# ||                                                                     ||
# || License: GNU General Public License - GNU GPL - version 2           ||
# ||          or (at your opinion) any later version.                    ||
# +=======================================================================+
# $Id$
# $Revision$
# $HeadURL$
# $Date$
# $Source$

package CipUX::CAT::Web::Setup::Client;

use 5.008001;
use strict;
use warnings;
use Carp;
use Class::Std;
use CipUX::CAT::Web::Plugin;
use CipUX::RPC::Client;
use Data::Dumper;
use Date::Manip;
use English qw( -no_match_vars );
use Getopt::Long;
use Log::Log4perl qw(get_logger :levels);
use Pod::Usage;
use Readonly;
use base qw(CipUX);

{

    use version; our $VERSION = qv('3.4.0.3');
    use re 'taint';    # Keep data captured by parens tainted
    delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)};    # Make %ENV safer

    # CONST
    Readonly::Scalar my $EMPTY_STRING => q{};
    Readonly::Scalar my $SCRIPT       => 'cipux_cat_web_module';
    Readonly::Scalar my $L            => "=" x 78 . "\n";
    Readonly::Scalar my $CACHE_DIR    => '/var/cache/cipux-cat-web';

    # GLOBAL
    my $cfg_hr  = {};                                 # configuration
    my $rpc     = undef;                              # rpc obj
    my %opt     = ();
    my $verbose = 0;

    # COMMAND LINE API
    my %option = (
        'cipux_cat_web_module' => {
            'must' => [],
            'may'  => [
                qw(a add-member all c cfg d D debug deregister e enable h
                    ? help host list-deregistered list-installed
                    list-registered l login m maxtest no-ssl o object
                    password port ssl V verbose version w)
            ],
            'not' => [],
        },
    );

    # MAIN
    sub run {

        my ( $self, $arg_r ) = @_;
        my $cd
            = ( exists $arg_r->{cache_dir} )
            ? $self->l( $arg_r->{cache_dir} )
            : $CACHE_DIR;

        $cfg_hr = $self->cfg(
            { 'pkg' => 'cipux', 'sub' => 'cat-web', cache_dir => $cd } );

        # ENVIRONMENT
        Getopt::Long::Configure("bundling");

        my $msg = $L . 'Problems parsing command line!' . "\n$L";

        GetOptions(
            \%opt,            'add-member|m=s',
            'all|a',          'debug|D',
            'deregister|d',   'enable|e',
            'help|h',         'list-deregistered',
            'list-installed', 'list-registered',
            'login|l=s',      'object|o=s',
            'password|w=s',   'register|r',
            'verbose',        'version|V',
        ) or pod2usage( -exitstatus => 2, -msg => $msg );

        my $l4pcfg
            = ( exists $cfg_hr->{base}->{catweb_l4pconf} )
            ? $self->l( $cfg_hr->{base}->{catweb_l4pconf} )
            : '/usr/share/cipux/etc/cipux-cat-web.log4perl';

        if ( exists $opt{debug} and defined $opt{debug} ) {
            Log::Log4perl::init_once($l4pcfg);
        }

        my $l = get_logger(__PACKAGE__);

        my $date = UnixDate( 'today', '%O' );
        $l->debug("    CipUX : $VERSION   ");
        $l->debug("    date  : $date");

        # display help page
        if ( exists $opt{help} and defined $opt{help} and $opt{help} ) {
            pod2usage( -exitstatus => 0, -verbose => 1 );
        }

        # display version and exit
        if (    exists $opt{version}
            and defined $opt{version}
            and $opt{version} )
        {
            print "$SCRIPT $VERSION\n";
            exit(0);
        }

        print "$SCRIPT $VERSION\n";

        # url  = 'https://localhost:8000/RPC2';
        # url  = 'http://localhost:8001/RPC2';
        my $proto = 'http';
        my $host  = 'localhost';
        my $port  = 8001;
        my $url
            = ( exists $cfg_hr->{base}->{catweb_rpc_server}
                and defined $cfg_hr->{base}->{catweb_rpc_server} )
            ? $cfg_hr->{base}->{catweb_rpc_server}
            : $proto . q{://} . $host . q{:} . $port . q{/RPC2};

        if ( exists $cfg_hr->{base}->{catweb_rpc_server}
            and defined $cfg_hr->{base}->{catweb_rpc_server} )
        {
            $l->debug(
                "catweb_rpc_server: [$cfg_hr->{base}->{catweb_rpc_server}]");
        }
        $l->debug("host: [$host]");
        $l->debug("port: [$port]");
        $l->debug("url: [$url]");

        $rpc = CipUX::RPC::Client->new(
            { url => $url, client => $SCRIPT, version => "$VERSION", } );

        # laundering 2/5
        my $object
            = (     exists $opt{object}
                and defined $opt{object}
                and $opt{object} )
            ? $self->l( $opt{object} )
            : $EMPTY_STRING;

        # option register
        my $register
            = (     exists $opt{register}
                and defined $opt{register}
                and $opt{register} )
            ? 1
            : 0;

        # option deregister
        my $deregister
            = (     exists $opt{deregister}
                and defined $opt{deregister}
                and $opt{deregister} )
            ? 1
            : 0;

        # option register
        my $list_registered
            = (     exists $opt{'list-registered'}
                and defined $opt{'list-registered'}
                and $opt{'list-registered'} )
            ? 1
            : 0;

        # option register
        my $list_deregistered
            = (     exists $opt{'list-deregistered'}
                and defined $opt{'list-deregistered'}
                and $opt{'list-deregistered'} )
            ? 1
            : 0;

        # option list-installed
        my $list_installed
            = (     exists $opt{'list-installed'}
                and defined $opt{'list-installed'}
                and $opt{'list-installed'} )
            ? 1
            : 0;

        # option verbose
        $verbose
            = (     exists $opt{verbose}
                and defined $opt{verbose}
                and $opt{verbose} )
            ? 1
            : 0;

        $l->debug( 'register         : ', $register );
        $l->debug( 'deregister       : ', $deregister );
        $l->debug( 'list_deregistered: ', $list_deregistered );
        $l->debug( 'list_installed   : ', $list_installed );
        $l->debug( 'list_registered  : ', $list_registered );
        $l->debug( 'verbose          : ', $verbose );

        my $mandatory
            = $register 
            + $deregister 
            + $list_registered 
            + $list_deregistered
            + $list_installed;
        my $reg = $register + $deregister;

        $l->debug( 'mandatory        : ', $mandatory );
        $l->debug( 'reg              : ', $reg );

        if ( $mandatory > 1 ) {

            #my $msg = $L . $self->loc('Please');
            my $msg .= ' PLEASE provide only one --register, --deregister, ';
            $msg    .= '--list-registered or --list-deregistered ';
            $msg    .= '--list-installed command line option!' . "\n" . $L;

            pod2usage(
                -exitstatus => 4,
                -verbose    => 0,
                -message    => $msg
            );

        }
        elsif ( $mandatory < 1 ) {

            my $msg = $L . 'Please';
            $msg .= ' provide only one --register, --deregister, ';
            $msg .= '--list-registered or --list-deregistered ';
            $msg .= '--list-installed command line option!' . "\n" . $L;

            pod2usage(
                -exitstatus => 3,
                -verbose    => 0,
                -message    => $msg
            );

        }
        elsif ( $object eq $EMPTY_STRING
            and $reg > 0
            and not exists $opt{all}
            and not defined $opt{all} )
        {

            my $msg = $L . 'Please provide --object <NAME> ';
            $msg .= 'command line option!' . "\n" . $L;

            pod2usage(
                -exitstatus => 4,
                -verbose    => 0,
                -message    => $msg
            );

        }

        $l->debug( 'script: ', $SCRIPT );
        $l->debug( 'object: ', $object ) if defined $object;
        if ($list_installed) {
            $self->list_installed();
            exit 1;
        }

        # ping
        $l->debug('going to ping the XML-RPC server');
        if ( $rpc->rpc_ping() ) {
            $l->debug('rpc_ping SUCCESS');    # SUCCESS
            print "Sever [$url] is reachable.\n" if $verbose;
        }
        else {
            $l->debug('rpc_ping FAILURE');    # FAILURE
            croak "XML RPC server $url not reachable!\n";
        }

        # login
        $l->debug('going to login into the XML-RPC server');

        # laundering 4/5
        my $login
            = ( defined( $opt{login} ) and $opt{login} )
            ? $self->l( $opt{login} )
            : $self->l( $self->login_prompt( { prompt => 'CAT Login: ' } ) );
        $l->debug("CAT Login: $login");

        # laundering 5/5
        my $password
            = ( defined( $opt{password} ) and $opt{password} )
            ? $self->l( $opt{password} )
            : $self->lp(
            $self->password_prompt( { prompt => 'Password: ' } ) );

        $l->debug('rpc_login');
        my $login_ok
            = $rpc->rpc_login( { login => $login, password => $password } );

        if ($login_ok) {    # SUCCESS
            $l->debug('rpc_login SUCCESS');
        }
        else {              # FAILURE
            $l->debug('rpc_login FAILURE');
        }

        # registered
        $l->debug('retrieve registerd CAT modules from the XML-RPC server');

        my $cmd = 'cipux_task_list_cat_modules';
        my $a_hr = $rpc->xmlrpc( { cmd => $cmd } );

        $l->debug('got answer of registered modules from XML-RPC server');
        my %registered = ();

        if ( defined( $a_hr->{status} ) and $a_hr->{status} eq 'TRUE' ) {
            foreach my $module ( sort keys %{ $a_hr->{cmdres_r} } ) {
                next if not $module =~ m{\.cgi$}mx;    # we handle only CGIs
                if ($list_registered) {
                    my $msg = $module . "\n";
                    print $msg;
                }
                $registered{$module} = 1;
            }
        }
        else {
            print "EXCEPTION: Can not get list about registered modules!\n";
            print $a_hr->{msg} . "\n";
            exit 1;
        }

        $l->debug('end if listing of registered modules');

        # deregistered
        $l->debug('begin printing deregistered modules (if any)');

        my %deregistered = ();

        my $plugin = CipUX::CAT::Web::Plugin->new();

        $l->debug('plugin init');
        $plugin->init();

        $l->debug('plugin get_module_name_register');
        my $p_hr = $plugin->get_module_name_register();

        foreach my $m ( sort keys %{$p_hr} ) {

            if ( not defined( $registered{$m} ) ) {
                if ($list_deregistered) {
                    print $m . "\n";
                }
                $deregistered{$m} = $m;
                $l->debug( 'dergisterd module is: ', $m );
            }

        }

        $l->debug('end printing deregistered modules (if any)');

        # register
        $l->debug('begin register modules ...');

        # if we can register it
        if ( defined($register) and $register ) {

            $l->debug( 'register (BOOLEAN): ', $register );

            my @object = ();

            if ( defined $opt{all} ) {
                @object = sort keys %deregistered;
            }
            else {
                push @object, $object;
            }

            foreach my $object (@object) {

                # ok, option --register
                $l->debug('option --register');

                if ( defined( $deregistered{$object} )
                    and $deregistered{$object} )
                {

                    # ok, it is deregistered we may proceed
                    $l->debug('proceed register');

                    if ( defined( $registered{$object} )
                        and $registered{$object} )
                    {

                        # we can not register, it is already registered
                        $l->debug('EXCEPTION: already registerd');
                        my $msg = 'EXCEPTION: module already registered!';
                        die $msg;

                    }
                    else {

                        # ok, it is deregistered and NOT registered, we may
                        # register it! We want to have fun tonight ...
                        $l->debug('OK deregistered and NOT registered');

                        my $rp_hr = {};    # paremeters to be registerd

                        # we can register:
                        my $o_hr = $plugin->get_module_cfg_register(
                            { name => $object } );
                        $l->debug( 'o_hr ',
                            { filter => \&Dumper, value => $o_hr } );

                        $rp_hr->{object} = $object;

                        $l->debug("about to register: $object\n");

                        # process only cipuxTask an not NULL task
                        if ( ref $o_hr->{cipuxTask} eq 'ARRAY' ) {
                            $l->debug("cipuxTask is ARRAY\n");
                            foreach my $v ( @{ $o_hr->{cipuxTask} } ) {
                                $l->debug("register task [$v]?\n");
                                next if $v eq 'NULL';
                                $l->debug("yes register task [$v]\n");

                                # if we have not 'NULL' task assigned
                                # to that module
                                $l->debug("add $object to task [$v]\n");
                                $self->add_module_to_task(
                                    {
                                        module => $object,
                                        task   => $v
                                    }
                                );
                            }
                        }
                        $rp_hr = $o_hr;

                        # if -e | --enable, we enable the module
                        if ( defined $opt{enable} ) {
                            $rp_hr->{cipuxIsEnabled} = 'TRUE';
                            if ($verbose) {
                                print "Object [$object] will be enabled\n";
                            }
                        }

                        my $member
                            = ( exists $opt{'add-member'}
                                and defined $opt{'add-member'} )
                            ? $opt{'add-member'}
                            : undef;

                        # TODO cipuxMemberRid get that from cipux-rbac-conf.
                        # FIXME: Why should that come from that file(?) ?
                        if ( defined $opt{enable} and defined $member ) {
                            $rp_hr->{cipuxMemberRid} = $member;
                            $l->debug("add $member to ACL of $object");
                        }
                        $l->debug( 'rp_hr ',
                            { filter => \&Dumper, value => $rp_hr } );

                        $self->register_module(
                            { module => $object, param_hr => $rp_hr } );

                    }
                }
                else {

                    # we cannot register
                    $l->debug('EXCEPTION: unable to register');
                    my $msg = 'EXCEPTION: Can not register module!';
                    $msg .= ' Reason: It is not deregistered.';
                    $msg .= ' So it is probably already registered.';
                    $msg .= " The module in question is: [$object]'\n";
                    croak $msg;
                }

            }

            my $cmd = 'rpc_intern';
            $l->debug("cmd [$cmd]");
            my $p_hr = { subcmd => 'flush', };
            my $a_hr = $rpc->xmlrpc( { cmd => $cmd, param_hr => $p_hr } );

            if ( $rpc->rpc_logout ) {
                print "Logout OK\n" if $verbose;
            }
            else {
                print "Logout not OK\n" if $verbose;
                exit 1;
            }

        }

        # deregister
        # if we can deregister it
        if ( defined($deregister) and $deregister ) {

            my @object = ();

            if ( exists $opt{all} and defined $opt{all} ) {
                @object = sort keys %registered;
            }
            else {
                push @object, $object;
            }

            foreach my $object (@object) {

                # ok CLI option --deregister
                $l->debug('option --deregister');

                if ( defined( $deregistered{$object} )
                    and $deregistered{$object} )
                {

                    # problem: we cannot deregister a deregistered module
                    $l->debug('EXCEPTION: already deregisterd');
                    my $msg = $L;
                    $msg .= 'EXCEPTION: module already deregistered!';
                    $msg .= "\n" . $L;
                    croak $msg;

                }
                else {

                    # ok, proceed
                    $l->debug('proceed deregister');

                    if ( defined( $registered{$object} )
                        and $registered{$object} )
                    {

                        # ok, if registered, we could deregister the module
                        $l->debug('try to recieve tasks from module');
                        my $task_ar = $self->retrieve_tasks_from_module(
                            { module => $object } );

                        $l->debug('try to remove module from tasks');
                        foreach my $t ( @{$task_ar} ) {
                            next if $t eq 'NULL';
                            $l->debug("processiong task [$t]");
                            $self->remove_module_from_task(
                                { task => $t, module => $object } );
                        }

                        $l->debug("try to deregister module [$object]");
                        $self->deregister_module( { module => $object } );

                    }
                    else {

                        # we cannot deregister, because it is not registered
                        $l->debug('EXCEPTION: no such module');
                        my $msg = $L . 'EXCEPTION: deregistration';
                        $msg .= "impossible: wrong name.\n";
                        $msg .= 'Make sure the name of the module';
                        $msg .= "is correct.\nIf uncertain then";
                        $msg .= " use --list-registered\noption";
                        $msg .= " to get a list of valid names.\n" . $L;
                        croak $msg;

                    }
                }
            }
        }

        return;
    }

    sub list_installed {

        my ( $self, $arg_r ) = @_;
        my $plugin = CipUX::CAT::Web::Module->new();
        $plugin->init();
        my $p_hr = $plugin->get_module_name_register();
        foreach my $m ( sort keys %{$p_hr} ) {
            print "plugin [$m]\n";
        }
        return;
    }

    sub retrieve_tasks_from_module {

        my ( $self, $arg_r ) = @_;

        my $module
            = ( exists $arg_r->{module} )
            ? $self->l( $arg_r->{module} )
            : $self->perr('module');

        my $l = get_logger(__PACKAGE__);

        my $cmd = 'cipux_task_optain_cat_module_task';

        my $a_hr = $rpc->xmlrpc(
            {
                cmd      => $cmd,
                param_hr => { object => $module, },
            }
        );

        my $target
            = ( exists $a_hr->{ltarget} )
            ? $a_hr->{ltarget}
            : 'cipuxTask';

        if ( defined $module
            and exists $a_hr->{cmdres_r}->{$module}->{$target} )
        {
            my $res_ar = $a_hr->{cmdres_r}->{$module}->{$target};

            return $res_ar;
        }

        return [];
    }

    sub register_module {

        my ( $self, $arg_r ) = @_;

        my $module
            = ( exists $arg_r->{module} )
            ? $self->l( $arg_r->{module} )
            : $self->perr('module');

        my $param_hr
            = ( exists $arg_r->{param_hr} )
            ? $self->h( $arg_r->{param_hr} )
            : $self->perr('param_hr');

        my $l = get_logger(__PACKAGE__);

        my $cmd = 'cipux_task_register_cat_module';
        $l->debug( 'command: ', $cmd );
        $param_hr->{object} = $module;

        # add the module
        my $a_hr = $rpc->xmlrpc(
            {
                cmd      => $cmd,
                param_hr => $param_hr,
            }
        );
        if ( defined( $a_hr->{status} )
            and $a_hr->{status} eq 'TRUE' )
        {
            if ($verbose) {
                print "Successfully registered [$module].\n";
            }
        }
        else {
            print "EXCEPTION: Can not register [$module]!\n";
            if ( defined $a_hr->{status} ) {
                print 'Message from the server: ' . $a_hr->{msg} . "\n";
            }

            exit 1;
        }

        if ($verbose) {
            print $L;
        }

        return;
    }

    sub deregister_module {

        my ( $self, $arg_r ) = @_;

        my $module
            = ( exists $arg_r->{module} )
            ? $self->l( $arg_r->{module} )
            : $self->perr('module');

        my $l = get_logger(__PACKAGE__);

        my $param_hr = {};
        $param_hr->{object} = $module;
        my $cmd = 'cipux_task_deregister_cat_module';
        $l->debug( 'command: ', $cmd );

        my $a_hr = $rpc->xmlrpc(
            {
                cmd      => $cmd,
                param_hr => $param_hr,
            }
        );

        if ( defined( $a_hr->{status} )
            and $a_hr->{status} eq 'TRUE' )
        {
            if ($verbose) {
                print "Successfully deregistered [$module].\n";
            }
        }
        else {
            print "EXCEPTION: Can not deregister!\n";
            print "The module that was tried to be deregisered: [$module].\n";
            if ( defined $a_hr->{status} ) {
                print 'Message from the server: ' . $a_hr->{msg} . "\n";
            }

            exit 1;
        }

    }

    sub add_module_to_task {

        my ( $self, $arg_r ) = @_;

        my $task
            = ( exists $arg_r->{task} )
            ? $self->l( $arg_r->{task} )
            : $self->perr('task');
        my $module
            = ( exists $arg_r->{module} )
            ? $self->l( $arg_r->{module} )
            : $self->perr('module');

        my $l = get_logger(__PACKAGE__);

        my $a_hr = $rpc->xmlrpc(
            {
                cmd      => 'cipux_task_add_member_to_task',
                param_hr => {
                    object => $task,
                    value  => $module
                },
            }
        );
        if ( defined( $a_hr->{status} )
            and $a_hr->{status} eq 'TRUE' )
        {
            if ($verbose) {
                print "Successfully added to task [$task]\n";
            }
        }
        else {
            print "EXCEPTION: Could not add module to task!\n";
            print "The module that was tried to be added: [$module].\n";
            print "The task that was targeted: [$task].\n";
            if ( defined $a_hr->{status} ) {
                print 'Message from the server: ' . $a_hr->{msg} . "\n";

            }
            exit 1;
        }

        return;

    }

    sub remove_module_from_task {

        my ( $self, $arg_r ) = @_;

        my $task
            = ( exists $arg_r->{task} )
            ? $self->l( $arg_r->{task} )
            : $self->perr('task');
        my $module
            = ( exists $arg_r->{module} )
            ? $self->l( $arg_r->{module} )
            : $self->perr('module');

        my $l = get_logger(__PACKAGE__);

        my $a_hr = {};
        eval {
            $a_hr = $rpc->xmlrpc(
                {
                    cmd      => 'cipux_task_remove_member_from_task',
                    param_hr => {
                        object => $task,
                        value  => $module
                    },
                }
            );

            if ( defined( $a_hr->{status} )
                and $a_hr->{status} eq 'TRUE' )
            {
                if ($verbose) {
                    print "Sucessfully removed from task [$task]\n";
                }
            }
            else {
                print "EXCEPTION: Could not remove module from task!\n";
                print "The module that was tried to be added: [$module].\n";
                print "The task that was targeted: [$task].\n";
                if ( defined $a_hr->{status} ) {
                    print 'Message from the server: ' . $a_hr->{msg} . "\n";

                }
                exit 1;
            }
        };
        if ($EVAL_ERROR) {
            warn
                "WARNING: module could not be removed from task [$task]! Already removed?\n";
        }
        return;

    }

    sub exc_no_config {

        my ( $self, $arg_r ) = @_;

        my $data
            = ( exists $arg_r->{data} )
            ? $self->l( $arg_r->{data} )
            : $self->perr('data');

        return <<"END_EXC";
$L
EXCEPTION: registration is impossible: there is no data for that object.

$data

The reason for this is, that there is no configuration data for that object.
This could either mean the module is already registered or that the module you
requested to register is not installed. Make sure the name of the module is
correct. If uncertain then use --list-deregistered option to get a list of
valid names.

$L
END_EXC

    }

}

1;

__END__

