#!/usr/bin/perl
#
# Written by Martin Bartosch for the OpenXPKI project 2006
# Copyright (c) 2006 by The OpenXPKI Project
# $Revision: 459 $
#

use strict;
use warnings;
use English;
use Getopt::Long qw( :config permute pass_through );
use Pod::Usage;
use Term::ReadLine;
use File::Spec;
use File::Path;
use Config::Std;

use Smart::Comments;
use Data::Dumper;
use OpenXPKI::Debug;

$SIG{PIPE} = 'IGNORE';

# global CLI client handle
my $cli;

# global client configuration
my $config;


# side effect: sets $cli
sub reopen_connection {
    my %args;
    if (exists $config->{client}->{session}) {
	$args{SESSION_ID} = $config->{client}->{session},
    }

    $cli = undef;
    $cli = OpenXPKI::Client::CLI->new(
	{
	    SOCKETFILE => $config->{server}->{socketfile},
	});
    my $response;
    eval {
	$response = $cli->init_session(\%args);
    };
    if (my $exc = OpenXPKI::Exception->caught()) {
	if ($exc->message() eq 'I18N_OPENXPKI_CLIENT_INIT_SESSION_FAILED') {
	    # create new session
	    print STDERR "Session expired, starting a new one.\n";
	    $cli = undef;
	    $cli = OpenXPKI::Client::CLI->new(
		{
		    SOCKETFILE => $config->{server}->{socketfile},
		});
	    $response = $cli->init_session();
	} else {
	    print STDERR "ERROR: " . $exc->full_message() . "\n";
	}
    } elsif ($EVAL_ERROR) {
	print STDERR "ERROR: $EVAL_ERROR\n";
    }

    $config->{client}->{session} = $cli->get_session_id();
    return $response;
}


my %params;
GetOptions(\%params,
	   qw(
	      help|?
	      man
	      socketfile=s
              session=s
              debug:s@
	      ));

pod2usage(-exitstatus => 0, -verbose => 2) if $params{man};
pod2usage(-verbose => 1) if ($params{help});

# read .openxpki/cli.conf file
my $configdir = File::Spec->catfile($ENV{HOME}, '.openxpki');
my $configfile = File::Spec->catfile($configdir, 'cli.conf');

# only read configuration file for interactive sessions
if (scalar @ARGV == 0 && -r $configfile) {
    read_config $configfile => $config;
}

###########################################################################
# override config file with command line arguments
if (defined $params{socketfile}) {
    $config->{server}->{socketfile} = $params{socketfile};
}

if (defined $params{session}) {
    $config->{client}->{session} = $params{session};
}

###########################################################################

my @debug_entries;
# pull debug configuration from configuration file (if configured at all)
if (defined $config->{client}->{debug}) {
    if (ref $config->{client}->{debug} eq 'ARRAY') {
	@debug_entries = @{$config->{client}->{debug}};
    } else {
	@debug_entries = ( $config->{client}->{debug} );
    }
}

# override debug settings from the command line
if (defined $params{debug}) {
    @debug_entries = split(m{,}, join(',', @{$params{debug}}));
}

foreach my $param (@debug_entries) {
    my ($module, $level) = ($param =~ m{ \A (.*?):?(\d*) \z }xms);
    if ($level eq '') {
	$level = 1;
    }
    if ($module eq '') {
	$module = '.*';
    }
    print STDERR "Debug level for module '$module': $level\n";
    $OpenXPKI::Debug::LEVEL{$module} = $level;
}


require OpenXPKI::Client::CLI;

# get a CLI instance
my $response = reopen_connection();

# render first server message (as returned by init_session())
### $response
$cli->render($response) if defined $response;

my $exitcode = 0;
my $OUT = \*STDOUT;

if (scalar @ARGV == 0) {
    print "Entering interactive OpenXPKI shell.\n";
    print "Type 'help' for help, 'quit' to exit.\n\n";

    my $term = new Term::ReadLine 'OpenXPKI';
    $OUT = $term->OUT;
    my $state = '';

  INTERACTION:
    while (1) {
	### $cli->get_communication_state()
	# this should not happen: after initialization the client
	# should be able to send commands, and if we are re-iterating
	# the process_command() method should have handled the server
	# response
	if ($cli->get_communication_state() eq 'can_receive') {
	    ### handle response from last command and render output...
	    eval {
		$response = $cli->collect();
	    };
	    $cli->render($response) if defined $response;
	}

	# get current session status
	eval {
	    $response = $cli->send_receive_service_msg('STATUS');
	};
	if (my $exc = OpenXPKI::Exception->caught()) {
	    # check for timed-out client session
	    if ($exc->message() eq 'I18N_OPENXPKI_TRANSPORT_SIMPLE_CLIENT_READ_CLOSED_CONNECTION') {

		$response = reopen_connection();
		redo INTERACTION;
	    }
	} elsif ($EVAL_ERROR) {
	    print $OUT "error: $EVAL_ERROR\n";
	    next INTERACTION;
	}

	### $response
	if (exists $response->{SESSION}->{ROLE}) {
	    my $user = $response->{SESSION}->{USER} || 'nobody';
	    my $role = $response->{SESSION}->{ROLE};
	    $state = "$user/$role";
	}

	my $prompt = 'OpenXPKI [' . $state . ']: ';
	my $line = $term->readline($prompt);
	last INTERACTION unless defined $line;  # accept EOF to leave shell

	eval {
	    $response = $cli->process_command($line, 
					     {
						 READLINE => $term,
					     }
		);
	};
	if (my $exc = OpenXPKI::Exception->caught()) {
	    if ($exc->message() eq 'I18N_OPENXPKI_TRANSPORT_SIMPLE_CLIENT_READ_CLOSED_CONNECTION') {
		print $OUT "Server terminated connection.\n";
		$response = reopen_connection();

		$cli->render($response) if defined $response;

		# resend user query
		$response = $cli->process_command($line, 
						  {
						      READLINE => $term,
						  }
		    );
		redo INTERACTION;
	    }
	} elsif ($EVAL_ERROR) {
	    print $OUT "error: $EVAL_ERROR\n";
	    next INTERACTION;
	}

	# command exit requested?
	last INTERACTION if (! defined $response);

	$cli->render($response) if defined $response;

	my @message;
	if (exists $response->{MESSAGE}) {
	    if (ref $response->{MESSAGE} eq '') {
		push @message, $response->{MESSAGE};
	    }
	    if (ref $response->{MESSAGE} eq 'ARRAY') {
		@message = @{$response->{MESSAGE}};
	    }
	}

	if (! defined $response->{ERROR}) {# ||
	    #($response->{ERROR} == 0)) {
	    print $OUT join("\n", @message);
	    print $OUT "\n";
	} else {
	    if ($response->{ERROR} ne '0') {
		print $OUT "ERROR $response->{ERROR}: ". join("\n", @message) . "\n";
	    }
	}

	$term->addhistory($line) if ($line ne "");
    }    

    # try to write configuration (only in interactive mode)
    mkpath $configdir;
    write_config %{$config}, $configfile;

    exit $exitcode;

} else {
    # non-interactive
    if ($cli->get_communication_state() eq 'can_receive') {
	### handle response from last command and render output...
	eval {
	    $response = $cli->collect();
	};
	# discard output
	#$cli->render($response) if defined $response;
    }

    eval {
	$response = $cli->process_command(join(' ', @ARGV));
    };
    if (my $exc = OpenXPKI::Exception->caught()) {
	if ($exc->message() eq 'I18N_OPENXPKI_TRANSPORT_SIMPLE_CLIENT_READ_CLOSED_CONNECTION') {
	    print STDERR "Server terminated connection.\n";
	    exit 0;
	}
    } elsif ($EVAL_ERROR) {
	print STDERR "error: $EVAL_ERROR\n";
	exit 1;
    }
    
    $cli->render($response) if defined $response;
    
    my @message;
    if (exists $response->{MESSAGE}) {
	if (ref $response->{MESSAGE} eq '') {
	    push @message, $response->{MESSAGE};
	}
	if (ref $response->{MESSAGE} eq 'ARRAY') {
	    @message = @{$response->{MESSAGE}};
	}
    }
    
    if (! defined $response->{ERROR}) {# ||
	#($response->{ERROR} == 0)) {
	print $OUT join("\n", @message);
	print $OUT "\n";
    } else {
	if ($response->{ERROR} ne '0') {
	    print STDERR "ERROR $response->{ERROR}: ". join("\n", @message) . "\n";
	}
	$exitcode = 1;
    }

    exit $exitcode;
}

__END__

=head1 NAME

