#!/usr/bin/perl
package Elive::script::elive_raise_meeting;
use warnings; use strict;
use version;

use Getopt::Long;
use Date::Parse qw{};
use Pod::Usage;
use File::Basename qw{};

use Carp;

use Elive;
use Elive::Util;
use Elive::Entity;
use Elive::View::Session;
use Elive::Entity::Preload;
use Elive::Entity::User;

use URI;
use URI::Escape;

=head1 NAME

elive_raise_meeting

=head1 SYNOPSIS

  elive_raise_meeting http://myserver.com/my-site[:port] userid[=role] [userid[=role]]...
  elive_raise_meeting http://user:pass@myserver.com/my-site[:port] ...

  Authentication

    -user someAdminUser          # SDK/SOAP username
    -pass somePass               # SDK/SOAP password

  Basic

    -facilitator  userId
    -name         'meeting name'
    -meeting_pass otherpass      # meeting password
    -start  '[YYYY-MM-DD ]HH:MM' # start time
    -end    '[YYYY-MM-DD ]HH:MM' # end time
    -occurs {days|weeks}=n       # repeat the meeting over n days/weeks

  Meeting Settings

    -boundary     0|15|30|..     # minutes participants can arrive before or
                                 # leave after the scheduled meeting times.
    -max_talkers  n              # max no. of simultaneous talkers
    -recording    off|on|manual  # set recording status
                                 #  - off:    recording disabled
                                 #  - on:     starts automatically
                                 #  - manual: recording started by moderator
    -seats        count          # number of seats to reserve on server
    -[no]invites                 # allow in-session invitations
    -[no]private                 # hide from public schedule
    -[no]restricted              # only allow entry to registered users
    -[no]permissions             # let participants perform activities
    -[no]raise_hands             # automatically raise hands on entry
    -[no]supervised              # moderator can see private messages

    -user_notes      'blurb1'
    -moderator_notes 'blurb2'
    -cost_center     'code'

  Preloads

    -upload              local_file     # upload a file from the client
    -import_from_server  remote_file    # import a server side file
    -use_preload         preload_id     # reuse a preload from the database

  Information

    -? --help                   # print this help
    -v --version                # print version and exit
    --debug=n                   # set debug level

=head1 DESCRIPTION

Creates a meeting on an Elluminate I<Live!> server

=head1 SEE ALSO

    perldoc Elive
    http://search.cpan.org/dist/Elive

=cut

my $username;
my $password;
my $debug;
my $facilitator_id;
my $start_str;
my $end_str;
my $meeting_name = 'elive test meeting';
my $meeting_password;
my $max_talkers = 1;
my $recording = 'remote';
my $raise_hands;
my $import;
my $private;
my $restricted;
my $upload;
my $preload_opt;
my $url;
my $help;
my $boundary = 15;
my $seats;
my $permissions;
my $supervised;
my $invites;
my $moderator_notes;
my $user_notes;
my $cost_center;
my %occurs;
my $version;

main(@ARGV) unless caller;

sub main {

    local(@ARGV) = @_;

    GetOptions(
	'name|meeting_name=s' => \$meeting_name,
	'username|user=s' => \$username,
	'password|pass=s' => \$password,
	'facilitator|moderator=s' => \$facilitator_id,
	'start=s' => \$start_str,
	'end=s' => \$end_str,
	'meeting_password|meeting_pass=s' => \$meeting_password,
	'max_talkers=i' => \$max_talkers,
	'raise_hands!' => \$raise_hands,
	'private!' => \$private,
	'restricted!' => \$restricted,
	'supervised!' => \$supervised,
	'permissions!' => \$permissions,
	'debug=i' => \$debug,
	'help|?' => \$help,
	'upload=s' => \$upload,
	'import_from_server=s' => \$import,
	'use_preload=i' => \$preload_opt,
	'recording=s' => \$recording,
	'boundary=i' => \$boundary,
	'seats=i' => \$seats,
	'invites|invitations!' => \$invites,
        'occurs=i%' => \%occurs,
	'v|version' => \$version,
	'user_notes=s' => \$user_notes,
	'moderator_notes=s' => \$moderator_notes,
	'cost_center|cost_centre=s' => \$cost_center,
    )
	or die pod2usage(1);

    pod2usage(0) if $help;

    do {
	print "Elive v${Elive::VERSION} (c) 2009-2011\n";
	exit(0);
    } if $version;

    ($url = shift @ARGV)
	or pod2usage("missing url argument");

    $recording = lc($recording);
    #
    # accept some of the other symonymns for the various modes as
    # seen in documentation and elluminate's web interface.
    #
    $recording =~ s{^none|disabled$}{off}x;
    $recording =~ s{^manual$}{remote}x;
    $recording =~ s{^auto(matic)?$}{on}x;

    die pod2usage("recording mode must be on/auto, off/none/disabled or manual/remote\n")
	unless $recording =~ m{^on|off|remote$}x;

    my ($recurrence_count, $recurrence_days) = _get_occurances(\%occurs);

    Elive->debug($debug);

    if ($debug) {
	$SIG{__WARN__} = \&Carp::cluck if $debug > 1;
	$SIG{__DIE__} = \&Carp::confess;
    }

    $url ||= Elive::Util::prompt("Url ('http://...'): ");

    unless ($username && $password) {
	#
	# look for credentials encoded in the uri
	#
	my $uri_obj = URI->new($url, 'http');
	my $userinfo = $uri_obj->userinfo; # credentials supplied in URI

	if ($userinfo) {
	    my ($uri_user, $uri_pass) = split(':', $userinfo, 2);
	    $username ||= URI::Escape::uri_unescape($uri_user);
	    $password ||= URI::Escape::uri_unescape($uri_pass)
		if $uri_pass;
	}
    }

    $username ||= Elive::Util::prompt('Username: ');
    $password ||= Elive::Util::prompt('Password: ', password => 1);

    our $connection = Elive->connect($url, $username, $password);

    my $server_version = $connection->server_details->version;
    my $server_version_num = version->new($server_version)->numify;

    my $start = $start_str
	? Date::Parse::str2time($start_str)
	: time() + 15 * 60;

    my $end = $end_str
	? Date::Parse::str2time($end_str)
	: $start + 30 * 60;

    die "end time must be later than start time\n"
	unless ($end > $start);

    my $upload_data;

    my $facilitator = $facilitator_id
	? _get_user($facilitator_id)
	: Elive->login;

    my @participants;
    my @groups;

    foreach my $spec (map {split(';',$_)} @ARGV) {

	#
	# parse participant specs: user[=roleId]
	#
	my $user_spec;
	my $role;

	my $is_group = ($spec =~ s{^ \s* \*}{}gx);

	if (($user_spec, $role) = ($spec =~ m{^\s*([^=]*)=([^=]+)$}x)) {
	    die "non-numeric role: $spec\n"
		unless $role =~ m{^\d+$}x;
	}
	else {
	    $user_spec = $spec;
	}

	$role = 3 unless defined $role;

	die "role ($role) not in range 0-3: $spec\n"
	    unless ($role >= 0 && $role <= 3);

	die "null username: $spec\n"
	    if ($user_spec eq '');

	#
	# Ignore the facilitator. we're going to add them later
	#
	next if ($user_spec eq $facilitator->userId
		 || $user_spec eq $facilitator->loginName);

	if ($is_group) {
	    push (@groups, 
		  {
		      group => $user_spec,
		      role => $role,
		      type => 1,
		  },
		);
	}
	else {
	    push (@participants, 
		  {
		      user => $user_spec,
		      role => $role,
		      type => 0,
		  },
		);
	}
    }

    #
    # Include the facilitator as a moderator/participant.
    #
    push (@participants, {user => $facilitator->userId, role => 2});

    #
    # Vet participants
    #
    @participants = _get_users(@participants) if @participants;
    @groups = _get_groups(@groups) if @groups;

    my %session_data = (
	name => $meeting_name,
	facilitatorId => $facilitator,
	start => $start . '000',
	end => $end . '000',
	recordingStatus => $recording,
	);

    $session_data{password} = $meeting_password
	if defined $meeting_password;

    $session_data{seats} = $seats
	if defined $seats;

    $session_data{privateMeeting} = $private
	if defined $private;

    $session_data{maxTalkers}  = $max_talkers
	if defined $max_talkers;

    $session_data{raiseHandOnEnter} = $raise_hands
	if defined $raise_hands;

    $session_data{inSessionInvitation}  = $invites
		if defined $invites;

    $session_data{fullPermissions} = $permissions
	if defined $permissions;

    $session_data{supervised} = $supervised
	if defined $supervised;

    $session_data{boundaryMinutes} = $boundary
	if defined $boundary;

    $session_data{user_notes} = $user_notes
	if defined $user_notes;

    $session_data{moderator_notes} = $moderator_notes
	if defined $moderator_notes;

    $session_data{cost_center} = $cost_center
	if defined $cost_center;

    if (defined $restricted) {
	if ($server_version_num < '9999.007001') {
	    warn "[-restricted option ignored: Not supported by Elluminate Live!]\n";
	}
	else {
	    $session_data{restrictedMeeting} = $restricted;
	}
    }

    if ($recurrence_count > 1) {
	$session_data{recurrenceCount} = $recurrence_count;
	$session_data{recurrenceDays} = $recurrence_days || 1;
    }

    $session_data{participants} = [@participants, @groups];

    my $uploaded_preload;
    $uploaded_preload = Elive::Entity::Preload->upload($upload)
	if $upload;

    my $existing_preload;

    if ($preload_opt) {

	pod2usage("non numeric preload id")
	    unless $preload_opt =~ m{^\d+$}x;

	$existing_preload = Elive::Entity::Preload->retrieve([$preload_opt]);
	
	die "no existing preload: $preload_opt\n"
	    unless $existing_preload;
    }

    my $imported_preload;
    if ($import) {

	print "importing server-side preload: $import\n";

	$imported_preload = Elive::Entity::Preload->import_from_server({
	    fileName => $import,
	    ownerId => $facilitator,
								      });

	printf("imported '%s' preload: %s (%s)\n",
	       $imported_preload->type, $imported_preload->name, $imported_preload->mimeType);
    }

    my @preloads = grep {$_} ($existing_preload, $uploaded_preload, $imported_preload);

    $session_data{add_preload} = \@preloads if @preloads;

    my @sessions = Elive::View::Session->insert(\%session_data);

    foreach my $session (@sessions) {
	print "created meeting: ".$session->name." with id ".$session->sessionId."\n";

	if (@participants || @groups) {

	    show_participants($session);

	}
	else {
	    print "no participants\n";
	}

	print "session address: ".$session->web_url."\n";
    }

    Elive->disconnect;

    return @sessions;
}

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

sub show_participants {
    my ($session) = @_;

    my $participant_objs = $session->participants;

    my @participants_display = map  {
	if ($_->type) {
	    my $group_obj = $_->group;
	    my $id = $group_obj->groupid;
	    my $name = $group_obj->name;

	    my $participant_str = '*'.$id;
	    $participant_str .= ' <group:'.$name.'>'
		if $name;
	}
	else {
	    my $user_obj = $_->user;
	    my $loginName = $user_obj->loginName;
	    my $email = $user_obj->email;

	    my $participant_str = ($loginName || $user_obj->userId);
	    $participant_str .= ' <'.$email.'>'
		if $email;

	    $participant_str;
	}
    } @$participant_objs;

    print "participants: ".join(', ', @participants_display)."\n";
    return;
}

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

sub _get_users {
    my @participants_in = @_;

    my %user_roles;

    foreach (@participants_in) {

	my $user = $_->{user};
	my $role = $_->{role};
	
	die "bad username: $user\n"
	    if $user =~ m{'}x;

	$user_roles{$user} = $role || 3;
    }

    my $filter = join(' OR ',
		      map {sprintf("loginName='%s' OR userId='%s'",
				   $_, $_)}
		      (keys %user_roles)
	);

    my $users = Elive::Entity::User->list(filter => $filter);

    my @participants_out;

    foreach my $user (@$users) {

	my $role = delete($user_roles{$user->loginName})
	    || delete($user_roles{$user->userId});

	if ($role) {
	    push (@participants_out, {user => $user->userId,
				      role => $role,
				      type => 0,
		  });
	}
    }

    my @users_not_found = keys %user_roles;

    die "unknown user(s): @users_not_found\n"
	if @users_not_found;

    return @participants_out;
}

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

sub _get_groups {
    my @groups_in = @_;

    my %group_roles;

    foreach (@groups_in) {

	my $group = $_->{group};
	my $role = $_->{role};
	
	die "bad groupname: $group\n"
	    if $group =~ m{'}x;

	$group_roles{$group} = $role || 3;
    }

    my $filter = join(' OR ',
		      map {sprintf("groupId='%s' OR groupName='%s'",
				   $_, $_)}
		      (keys %group_roles)
	);

    my $groups = Elive::Entity::Group->list(filter => $filter);

    my @groups_out;

    foreach my $group (@$groups) {

	my $role = delete($group_roles{$group->groupId})
	    || delete($group_roles{$group->groupName});

	if ($role) {
	    push (@groups_out, {group => $group->groupId,
			       role => $role,
			       type => 1,
		  });
	}
    }

    my @groups_not_found = keys %group_roles;

    die "unknown group(s): @groups_not_found\n"
	if @groups_not_found;

    return @groups_out;
}

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

sub _get_user {
    my $user_spec = shift;

    die 'usage: _get_user($user_name_or_id)'
	unless (defined $user_spec && $user_spec ne '');

    my $user = (Elive::Entity::User->retrieve([$user_spec])
		|| Elive::Entity::User->get_by_loginName($user_spec))
	or die "unkown user: $user_spec\n";

    return $user;
}

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

sub _get_occurances {
    my $occurs = shift;

    my $recurrence_count = 1;
    my $recurrence_days = 1;

    foreach (keys %$occurs) {

	$recurrence_count = $occurs{$_};

	$recurrence_days =
	    m{^.*day(s?).*$}i       ? 1
	  : m{^.*week(s?).*$}i      ? 7
          : m{^.*fortnight(s?).*$}i ? 14
          : die "occurs usage: --occurs days=n  or --occurs weeks=n\n";

    }

    return ($recurrence_count, $recurrence_days);
}
