#!/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::Entity::Meeting;
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://myserver.com/my-site[:port] '*groupId[=role]' ...

  Authentication

    -user someAdminUser          # server login user
    -pass somePass               # server login 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

  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

=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 %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,
    )
	or die pod2usage(1);

    pod2usage(0) if $help;

    do {
	print "Elive v${Elive::VERSION} (c) 2009-2010\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;

    if ($upload) {
	open(my $upload_fh, '<', $upload)
	    or die "unable to open $upload: $!\n";

	$upload_data = do { local $/ = undef;  <$upload_fh> };
	close ($upload_fh);
    }

    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 $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 %meeting_data = (
	    name => $meeting_name,
	    facilitatorId => $facilitator,
	    start => $start . '000',
	    end => $end . '000',
	);

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

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

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

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

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

    my @meetings = Elive::Entity::Meeting->insert(\%meeting_data);

    foreach my $meeting (@meetings) {
	print "created meeting: ".$meeting->name." with id ".$meeting->meetingId."\n";
	$meeting->add_preload($existing_preload)
	    if $existing_preload;

	do {
	
	    my $meeting_parameters
		= $meeting->parameters
		or die "Unable to retrieve meeting parameters for this meeting\n";

	    $meeting_parameters->maxTalkers($max_talkers)
		if defined $max_talkers;

	    $meeting_parameters->raiseHandOnEnter($raise_hands)
		if defined $raise_hands;

	    $meeting_parameters->inSessionInvitation($invites)
		if defined $invites;

	    $meeting_parameters->recordingStatus($recording);

	    $meeting_parameters->update;
	};

	do {
	    my $server_parameters = $meeting->server_parameters;

	    $server_parameters->fullPermissions($permissions)
		if defined $permissions;

	    $server_parameters->supervised($supervised)
		if defined $supervised;

	    $server_parameters->boundaryMinutes($boundary)
		if defined $boundary;

	    $server_parameters->update;
	};

	if (@participants || @groups) {

	    setup_participants($meeting, @participants, @groups);

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

	#
	# to do - allow multiple uploads
	#
	if ($upload) {
	    
	    my $upload_basename = File::Basename::basename($upload);

	    my $preload = Elive::Entity::Preload->upload({
		name => $upload_basename,
		data => $upload_data,
		ownerId => $facilitator->stringify,
							 });

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

	    $meeting->add_preload($preload);
	}

	#
	# to do - allow multiple imports
	#
	if ($import) {

	    print "importing (from server): $import\n";

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

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

	    $meeting->add_preload($preload);
	}

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

    Elive->disconnect;

    return @meetings;
}

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

sub setup_participants {
    my ($meeting,@participants) = @_;
    #
    # Create participant list
    #
    my $participant_list = $meeting->participant_list;

    $participant_list
	->update({meetingId => $meeting->meetingId,
		  participants => \@participants,
		 });

    my $participant_objs = $participant_list->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);
}
