#!/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::Preload;
use Elive::Entity::User;
use Elive::Entity::Participant;

use URI;
use URI::Escape;

=head1 NAME

elive_raise_meeting

=head1 SYNOPSIS

  elive_raise_meeting [url] [options] [participants]

  Authentication Options

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

  Basic Options

    -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 (elm2)

  Moderators and Participants

     -moderators user|*group|guest-name(guest-id) ...
     -participants user|*group|guest-name(guest-id) ...

     Where each participant or moderator can be:

        'user'        - add this elluminate user. This can be either the
                        user's username or user-id.

        '*group'      - include all users from this group. This can be
                        either the group-name or group-id.

        'Display Name(loginName)
                      - grant one-time access to this guest.

  Meeting Setup Options

    -boundary     0|15|30|..     # minutes participants can arrive before or
                                 # leave after the scheduled meeting times.
    -max_talkers  n              # max no. of simultaneous talkers
    -max_cameras  n              # max no. of simultaneous cameras (elm3)
    -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 (elm3)
    -[no]permissions             # let participants perform activities
    -[no]raise_hands             # automatically raise hands on entry
    -[no]supervised              # moderator can see private messages

    -user_notes      'some text'
    -moderator_notes 'some text'
    -cost_center     'code'
    -exit_url=http://somesite.com  # URL to visit on meeting exit (elm3)

  Preload Options

    -upload              local_file     # upload a file from the client
    -import_from_server  remote_file    # import a server side file
    -add_preload         preload_id,... # add existing preloads

  Compatibility Options

     -use elm2                  # ELM 2.x compat (via Elive::View::Session)
     -use elm3                  # ELM 3.x compat (via Elive::Entity::Session)
     -use Some::Custom::Class   # create session via a custom session class

  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 $class = 'Elive::Entity::Session';

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;
my $max_cameras;
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 @moderators;
my @participants;
my $exit_url;

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,
	'moderators|moderator=s{,}' => \@moderators,
	'participants|participant|others|other=s{,}' => \@participants,
	'meeting_password|meeting_pass=s' => \$meeting_password,
	'max_talkers=i' => \$max_talkers,
	'max_cameras=i' => \$max_cameras,
	'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,
	'add_preload|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,
	'use=s' => \$class,
	'exit_url=s' => \$exit_url,
    )
	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);

    $class = {elm2 => 'Elive::View::Session',
	      elm3 => 'Elive::Entity::Session'}->{$class} || $class;

    my $can_insert = eval "use $class";
    die "unable to load class $class: $@" if $@;

    die "class $class: does not implement the 'insert' method"
	unless eval{ $class->can('insert') };
    #
    # 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;

    $facilitator_id ||= $facilitator->userId;
    #
    # Ensure that the facilitator is included as a moderator, but not as
    # a regular participant
    #
    push (@moderators, $facilitator_id)
	unless grep {$_ eq $facilitator_id} @moderators;

    @participants = grep {$_ ne $facilitator_id} @participants;

    # participants can also be passed in argv (old usage)
    my @args = grep {$_ ne $facilitator_id} @ARGV;

    my @participants = (
	parse_participants(\@moderators, role => 2),
	parse_participants(\@participants, role => 3),
	parse_participants(\@args),
	);
    #
    # collate by type
    #
    my @users;
    my @groups;
    my @guests;

    foreach my $participant (@participants) {

	my $type = $participant->type;

	if (! $type ) {
	    push (@users, $participant);
	}
	elsif ($type == 1) {
	    push (@groups, $participant);
	}
	elsif ($type == 2) {
	    push (@guests, $participant); 
	}
	else {
	    warn "don't know how to handle participant type: $type";
	}
    }
    #
    # Vet participants
    #
    @users = _get_users(@users) if @users;
    @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{videoWindow}  = $max_cameras
	if defined $max_cameras;

    $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;

    $session_data{restrictedMeeting} = $restricted
	if defined $restricted;

    $session_data{redirectURL} = $exit_url
	if $exit_url;

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

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

    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 $ptmp = Elive::Entity::Participants->new($session_data{participants});

    my @sessions = $class->insert(\%session_data);

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

	if (@users || @groups || @guests) {

	    show_participants($session);

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

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

    Elive->disconnect;

    return @sessions;
}

sub parse_participants {
    my $spec = shift;
    my %opt = @_;

    my @participants;

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

	my $participant = eval {
	    Elive::Entity::Participant->new($spec)
	};

	if ($@) {
	    warn "$@\n";
	    next;
	}
	#
	# Ignore the facilitator. we're going to add them later
	#
	$participant->role( $opt{role} ) if $opt{role};

	push (@participants, $participant);
    }

    return @participants;
}

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

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

    my $participant_objs = $session->participants;

    my @participants_display = map  {
	my $type = $_->type;
	my $str;

	if (! $type)  {
	    my $user_obj = $_->user;
	    my $loginName = $user_obj->loginName;
	    my $email = $user_obj->email;

	    $str = ($loginName || $user_obj->userId);
	    $str .= ' <'.$email.'>'
		if $email;
	}
	elsif ($type == 1) {
	    my $group_obj = $_->group;
	    my $id = $group_obj->groupId;
	    my $name = $group_obj->name;

	    $str = '*'.$id;
	    $str .= ' <group:'.$name.'>'
		if $name;
	}
	elsif ($type == 2) {
	    my $guest_obj = $_->guest;
	    my $loginName = $guest_obj->loginName;
	    my $displayName = $guest_obj->displayName;

	    $str = $displayName;
	    $str .= ' ('.$loginName.')'
		if $loginName;
	}
	else {
	    warn "unknown participant type $type: ignored";
	    $str = ''
	}
	$str;
    } @$participant_objs;

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

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

sub _get_users {
    my @users_in = @_;

    my %users;

    foreach (@users_in) {
	my $user = Elive::Entity::User->stringify($_->user);
	
	die "bad username: $user\n"
	    if $user =~ m{'}x;

	$users{$user} = $_;
    }

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

    my $db_users = Elive::Entity::User->list(filter => $filter);
    my @users_out;

    foreach my $user (@$db_users) {

	my $obj1 = delete $users{$user->loginName};
	my $obj2 =  delete $users{$user->userId};
	my $obj = $obj1 || $obj2;

	if ($obj) {
	    # replace with db user
	    $obj->user( $user );
	    push (@users_out, $obj);
	}
    }

    my @users_not_found = keys %users;

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

    return @users_out;
}

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

sub _get_groups {
    my @groups_in = @_;

    my %groups;

    foreach (@groups_in) {

	my $group_spec = Elive::Entity::Group->stringify($_->group);
	$group_spec =~ s{^\*}{};

	$groups{$group_spec} = $_;
    }

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

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

    my @groups_out;

    foreach my $group (@$db_groups) {

	my $obj1 = delete $groups{$group->groupId};
	my $obj2 = delete $groups{$group->groupName};
	my $obj = $obj1 || $obj2;

	if ($obj) {
	    $obj->group($group);
	    push (@groups_out, $obj)
	}
    }

    my @groups_not_found = keys %groups;

    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);
}
