#!/usr/local/bin/perl -w
#
# Written by Martin Bartosch
# extended by Oliver Welter
# for the OpenXPKI project 2009 - 2012
# Copyright (c) 2012 by The OpenXPKI Project
#

use JSON;
use strict;
use warnings;
use English;
use POSIX qw( strftime );
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;

use OpenXPKI::Client;
#use OpenXPKI::Serialization::Simple;

# we want utf8
binmode(STDOUT, ":utf8");

my %params = (
    authstack => '_System',
    authuser => undef,
    authpass => undef,
    wfid => undef,
    wfaction => undef,
    param => [],
    nosplit => undef,
    dosplit => undef,
    context => undef,
    attribute => undef,
    json => undef,
    timeout => 30,
    );

sub debug {
    my $arg = shift;
    if ($params{debug}) {
    print $arg . "\n";
    }
}

sub dump_workflow {

    my $reply = shift;
    my $json = shift || {};

    if ($params{context} && $reply->{PARAMS}->{workflow}->{context}) {

        if (defined $params{json}) {
            $json->{context} = $reply->{PARAMS}->{workflow}->{context};
        } else {
            print "\n===Context===\n";
            my $context = $reply->{PARAMS}->{workflow}->{context};
            foreach my $key (sort keys %{$context}) {
                my $val = $context->{$key};
                if (!defined $val) {
                    $val = 'undef';
                }
                printf "=%-20s: %s\n", $key, $val;
            }
        }
    }

    if ($params{attribute} && $reply->{PARAMS}->{workflow}->{attribute}) {
        if (defined $params{json}) {
            $json->{attribute} = $reply->{PARAMS}->{workflow}->{attribute};
        } else {
            print "\n===Attributes===\n";
            my $attr = $reply->{PARAMS}->{workflow}->{attribute};
            foreach my $key (keys %{$attr}) {
                printf "=%-20s: %s\n", $key, $attr->{$key};
            }
        }
    }

    if (defined $params{json}) {
        $json->{workflow} = { id => $reply->{PARAMS}->{workflow}->{id},
            state => $reply->{PARAMS}->{workflow}->{state} };

        print JSON->new->pretty(defined $params{'json-pretty'})->encode($json);
        print "\n";
    }
}

# GetOpts would work without this and leave the stuff in ARGV but we
# want to enforce a proper syntax to avoid any issues when upgrading
# or changing something
my @extra;
my ($dashidx) = grep { $ARGV[$_] eq '--' } (0 .. @ARGV-1);
if ($dashidx) {
    @extra = splice @ARGV, $dashidx;
    shift @extra;
}

GetOptions(\%params,
       qw(
            help|?
            man
            socketfile=s
            instance|i=s
            realm=s
            authstack=s
            authuser=s
            authpass=s
            wfid=i
            wfaction=s
            param|set=s@
            nosplit
            dosplit
            debug
            context
            attribute
            json
            json-pretty
            timeout|to=i
         )) or pod2usage(-verbose => 0);

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


# json pretty implies json
if ($params{'json-pretty'}) {
    $params{'json'} = 1;
}

if (scalar @ARGV != 1 && !$params{wfid}) {
    print STDERR "Usage: openxpkicmd [OPTIONS] WF_TYPE or openxpkicmd --wfid 1234\n";
    exit 0;
}

my $wf_type = shift;

# expect foo=bar or foo:bar in @param, split at assignment and
# build hash
my @wf_parameters = @{$params{param}};
if ( $params{nosplit} ) {
    print STDERR "The --nosplit option was removed as it is the default now";
} elsif ($params{dosplit}) {
    @wf_parameters = split(/,/, join(',', @{$params{param}}));
}

my %wf_parameters;
push @wf_parameters, @extra;
foreach my $entry (@wf_parameters) {
    my ($key, $value) = ($entry =~ m{ \A (.*?) [=:] (.*) }xms);
    if (! defined $key) {
        $key = $entry;
    }
    # parameter given more than once = make array
    if (defined $wf_parameters{$key}) {
        if (!ref $wf_parameters{$key}) {
            $wf_parameters{$key} = [ $wf_parameters{$key} ];
        }
        push @{$wf_parameters{$key}}, $value;
    } else {
        $wf_parameters{$key} = $value;
    }
}

if($params{instance}){
    $params{socketfile} = sprintf '/var/openxpki/%s/openxpki.socket', $params{instance};
} elsif(!$params{socketfile}){
    $params{socketfile} = '/var/openxpki/openxpki.socket';
}

debug(sprintf("Socketfile: %s", $params{socketfile}));

my $reply;
my $client = OpenXPKI::Client->new({
    SOCKETFILE => $params{socketfile},
    TIMEOUT  => $params{timeout},
    API_VERSION => 2,
});


if (! defined $client) {
    die "Could not instantiate OpenXPKI client. Stopped";
}

if (! $client->init_session()) {
    die "Could not initiate OpenXPKI server session. Stopped";
}

my $session_id = $client->get_session_id();
debug("Session id: $session_id");

$reply = $client->send_receive_service_msg('PING');

SERVICE_MESSAGE:
while (1) {
    debug(Dumper $reply);
    my $status = $reply->{SERVICE_MSG};

    if ($status eq 'GET_PKI_REALM') {
    if (! $params{realm}) {
        print "Available PKI realms:\n";
        foreach my $entry (keys %{$reply->{PARAMS}->{PKI_REALMS}}) {
        print "  $entry\n";
        }
        die "No --realm specified. Stopped";
    }
    $reply = $client->send_receive_service_msg('GET_PKI_REALM',
                           {
                               PKI_REALM => $params{realm},
                           });
    next SERVICE_MESSAGE;
    }

    if ($reply->{SERVICE_MSG} eq 'GET_AUTHENTICATION_STACK') {
    if (! $params{authstack}) {
        print "Available authentication stacks:\n";
        foreach my $entry (keys %{$reply->{PARAMS}->{AUTHENTICATION_STACKS}}) {
        print "  $entry\n";
        }
        die "No --authstack specified. Stopped";
    }
    $reply = $client->send_receive_service_msg('GET_AUTHENTICATION_STACK',
                           {
                               AUTHENTICATION_STACK => $params{authstack},
                           });
    next SERVICE_MESSAGE;
    }

    if ($reply->{SERVICE_MSG} eq 'GET_PASSWD_LOGIN') {
    if (! $params{authuser}) {
        die "No --authuser specified. Stopped";
    }
    $reply = $client->send_receive_service_msg('GET_PASSWD_LOGIN',
                           {
                               LOGIN => $params{authuser},
                               PASSWD => $params{authpass},
                           });
    next SERVICE_MESSAGE;
    }

    if ($reply->{SERVICE_MSG} eq 'SERVICE_READY') {
    last SERVICE_MESSAGE;
    }

    print Dumper $reply;
    die "Unhandled service message. Stopped";
}

# logged in, now run required commands

if ($params{wfid}) {

    if ($params{wfaction}) {

        $reply = $client->send_receive_command_msg(
            'execute_workflow_activity',
            {
                id => $params{wfid},
                activity => $params{wfaction},
                params => \%wf_parameters,
            }
        );

        if ($reply->{SERVICE_MSG} eq 'COMMAND') {
            printf "New Workflow State: %s\n", $reply->{PARAMS}->{workflow}->{state} unless(defined $params{json});
            dump_workflow( $reply );
        }
    } else {
        $reply = $client->send_receive_command_msg( 'get_workflow_activities',
            {
                id => $params{wfid},
            }
        );

        if ($reply->{SERVICE_MSG} eq 'COMMAND') {

            my $json = {};
            if (!defined $params{json}) {
                print "Possible Actions:\n";
                print join "\n", @{$reply->{PARAMS}};
                print "\n";
            } else {
                $json = { actions => $reply->{PARAMS} };
            }

            if ($params{context} || $params{attribute} || defined $params{json}) {
                $reply = $client->send_receive_command_msg( 'get_workflow_info',
                    {
                        id => $params{wfid},
                        with_attributes => ($params{attribute} ? 1 : 0),
                    }
                );
                dump_workflow( $reply, $json );
            }
        }

    }
} else {

    $reply = $client->send_receive_service_msg('COMMAND',{
        COMMAND => 'create_workflow_instance',
        PARAMS => {
            workflow => $wf_type,
            ($params{wfaction} ? (activity => $params{wfaction}) :()),
            params => \%wf_parameters,
        },
        API => 2
    });

    if ($reply->{SERVICE_MSG} eq 'COMMAND') {
        printf "Workflow created (ID: %d), State: %s\n", $reply->{PARAMS}->{workflow}->{id}, $reply->{PARAMS}->{workflow}->{state}
            unless(defined $params{json});
        dump_workflow( $reply );
    }

}

if ($reply->{SERVICE_MSG} ne 'COMMAND') {
    print "Error:\n";
    print Dumper $reply;
    exit 1;
}


__END__

=head1 NAME

openxpkicmd - command line tool for starting OpenXPKI workflows

=head1 USAGE

openxpkicmd [options] WORKFLOW_TYPE

  Options:
    --help                brief help message
    --man                 full documentation
    --socketfile FILE     OpenXPKI daemon socket file
    --realm REALM         OpenXPKI realm
    --authstack STACK     authentication stack to use (optional)
    --authuser USER       authentication user (optional)
    --authpass PASS       password for authentication user (optional)
    --param KEY=VALUE     pass VALUE to WF parameter KEY (optional)
                          Giving the same KEY more than once creates an array
    --dosplit             Re-enable legacy behaviour to map mulitple params
                          using --param KEY1=VAL1,KEY2=VAL2.
                          Deprecared, please pass multiple --params instead.
    --wfid                Pickup WF with this id at its current state
    --wfaction            Execute action on exisiting workflow
    --debug               enable debug mode
    --context             show context data
    --attribute           show attribute data
    --json                dump data using json
    --json-pretty         human readble json
    --timeout             socket timeout in seconds (default 30s)

=head1 INVOCATION

This command may be used to create OpenXPKI workflows to automate internal
actions, e. g. issuance of CRLs..

You can use wfid/wfaction to continue stopped or failed workflows. If you pass
only wfid and no action, a list of possible actions is shown.

=head1 EXAMPLES

openxpkicmd --socketfile /var/openxpki/openxpki.socket --realm "Server CA" I18N_OPENXPKI_WF_TYPE_CRL_ISSUANCE

