#!/usr/bin/perl
package IkiWiki::Plugin::newevent;

use strict;
use warnings;
use IkiWiki 3.00;
use Time::Local;
use DateTime;
use CGI::FormBuilder;
use Data::Dumper;

sub import {
	#hook(type => "formbuilder",       id => "newevent", call => \&formbuilder);
	#hook(type => "formbuilder_setup", id => "newevent", call => \&formbuilder_setup);
	hook(type => "getsetup",          id => "newevent", call => \&getsetup);
	#hook(type => "preprocess",        id => "newevent", call => \&preprocess);
	#hook(type => "refresh",           id => "newevent", call => \&refresh);
	hook(type => "sessioncgi",        id => "newevent", call => \&sessioncgi);
 }

my @days    = ('01'..'31');
my @hours   = ('00'..'23');
my @minutes = ('00'..'59');

sub getsetup () {
	return
	 ( plugin =>
		 { safe    => 1
		 , rebuild => undef
		 , section => "misc"
		 }
	 , newevent_base =>
		 { type        => "string"
		 , example     => "Agenda"
		 , description => "prefix of the agenda hierarchy"
		 , safe        => 1
		 , rebuild     => 1
		 }
	 );
 }
sub date_of_form ($$;%) {
	my ($form, $prefix, %default) = @_;
	%default =
	 ( year   => 0
	 , month  => 1
	 , day    => 1
	 , hour   => 0
	 , minute => 0
	 , %default
	 );
	my $date;
	eval { $date = DateTime->new
	 ( year       => ($form->field($prefix.'_year')   ne '' ? $form->field($prefix.'_year')                : $default{year})
	 , month      => ($form->field($prefix.'_month')  ne '' ? substr($form->field($prefix.'_month'), 0, 2) : $default{month})
	 , day        => ($form->field($prefix.'_day')    ne '' ? $form->field($prefix.'_day')                 : $default{day})
	 , hour       => ($form->field($prefix.'_hour')   ne '' ? $form->field($prefix.'_hour')                : $default{hour})
	 , minute     => ($form->field($prefix.'_minute') ne '' ? $form->field($prefix.'_minute')              : $default{minute})
	 , second     => 0
	 , nanosecond => 0
	 , time_zone  => 'local'
	 , locale     => $config{locale}
	 )->set_time_zone('floating') };
	return $date;
 };
sub duration_of_form ($$) {
	my ($form, $prefix) = @_;
	my $dur;
	eval { $dur = DateTime::Duration->new
	 ( years        => $form->field($prefix.'_year')
	 , months       => $form->field($prefix.'_month')
	 , days         => $form->field($prefix.'_day')
	 , weeks        => $form->field($prefix.'_week')
	 , hours        => $form->field($prefix.'_hour')
	 , minutes      => $form->field($prefix.'_minute')
	 , seconds      => 0
	 , nanoseconds  => 0
	 , end_of_month => 'limit'
	 ) };
	return $dur;
 };
sub page_of_event ($$$$$) {
	my ($form, $from_date, $to_date, $name, $newevent_base) = @_;
	my $time = '';
	if ($form->field('from_hour') ne '' or $form->field('from_minute') ne '') {
		if ($from_date->hour() == $to_date->hour()
		 and $from_date->minute() == $to_date->minute()) {
			$time = sprintf('%02dh%02d', $from_date->hour(), $from_date->minute());
		 }
		else {
			$time = sprintf('%02dh%02d-%02dh%02d'
			 , $from_date->hour(), $from_date->minute()
			 , $to_date->hour(), $to_date->minute());
		 }
	 }
	return
	 ( $newevent_base
	 . ($newevent_base?'/':'').$from_date->year()
	 . '/'.sprintf('%02d', $from_date->month())
	 . '/'.sprintf('%02d', $from_date->day())
	 . '/'. ($time ne '' ? $time . '/' : '')
	 . $name
	 );
 }
sub check_cannewevent ($$$$) {
	my $dest=shift;
	my $destfile=shift;
	my $cgi=shift;
	my $session=shift;
	
	# Must be a legal filename.
	if (IkiWiki::file_pruned($destfile)) {
		error(sprintf(gettext("illegal name")));
	 }
	# Must not be a known source file.
	if (exists $pagesources{$dest}) {
		error(sprintf(gettext("%s already exists"),
			htmllink("", "", $dest
			 , linktext => $dest
			 , noimageinline => 1)));
	 }
	# Must not exist on disk already.
	if (-l "$config{srcdir}/$destfile" || -e _) {
		error(sprintf(gettext("%s already exists on disk"), $destfile));
	 }
	
	# Must be editable.
	IkiWiki::check_canedit($dest, $cgi, $session);
	
	my $can_newevent;
	IkiWiki::run_hooks(can_newevent => sub {
		return if defined $can_newevent;
		my $ret=shift->(cgi => $cgi, session => $session, dest => $dest, destfile => $destfile);
		if (defined $ret) {
			if ($ret eq "") {
				$can_newevent=1;
			 }
			elsif (ref $ret eq 'CODE') {
				$ret->();
				$can_newevent=0;
			 }
			elsif (defined $ret) {
				error($ret);
				$can_newevent=0;
			 }
		 }
	 });
	return defined $can_newevent ? $can_newevent : 1;
 }
sub post_newevent ($$$) {
	my $cgi=shift;
	my $session=shift;
	my $dest=shift;
	
	IkiWiki::redirect($cgi, urlto($dest));
	exit;
 }
sub preprocess (@) {
	#my %params =
	# ( base => ($config{newevent_base} ? $config{newevent_base} : gettext('Agenda'))
	# , @_ );
	#($form, $buttons) = newevent_form()
	#	if not defined $form;
	#my $ret = $form->render();
	return "<div class='newevent'></div>";
 }
sub sessioncgi ($$) {
	my ($cgi, $session) = @_;
	if (defined $cgi->param('do') && $cgi->param('do') eq "newevent") {
		# TOTRY: decode_cgi_utf8($cgi);
		
		my $now_date = DateTime->now
		 ( time_zone => 'local'
		 , locale    => $config{locale}
		 )->set_time_zone('floating');
		my %dows   = map { ($_ => $now_date->{locale}->day_format_wide->[ $_ ]) } (0..6);
		my %months = map { ($_ => $now_date->{locale}->month_format_wide->[ $_ - 1 ]) } (1..12);
		my $cgi_date;
		eval { $cgi_date = DateTime->new
		 ( year       => defined $cgi->param("year")   ? $cgi->param("year")   : $now_date->year()
		 , month      => defined $cgi->param("month")  ? $cgi->param("month")  : $now_date->month()
		 , day        => defined $cgi->param("day")    ? $cgi->param("day")    : $now_date->day()
		 , hour       => defined $cgi->param("hour")   ? $cgi->param("hour")   : $now_date->hour()
		 , minute     => defined $cgi->param("minute") ? $cgi->param("minute") : $now_date->minute()
		 , second     => 0
		 , nanosecond => 0
		 , time_zone  => 'local'
		 , locale     => $config{locale}
		 )->set_time_zone('floating') };
		error(sprintf(gettext("illegal date")))
			unless $cgi_date;
		
		my @years = ($cgi_date->year() .. $cgi_date->year()+5);
		my $week_start_day
		 = (defined $config{week_start_day} and $config{week_start_day} >= 0 and $config{week_start_day} <= 6)
		 ?          $config{week_start_day}
		 : 1;
		my @dow_order = ($week_start_day .. 6, 0 .. $week_start_day-1);
		
		my $page = Encode::decode_utf8($cgi->param("page"));
		my $tags = $typedlinks{$page}{tag};
		my $buttons = [qw{Preview Create}];
		my ($from_date, $to_date, $end_date, $inc_dur);
		my $form = CGI::FormBuilder->new
		 ( action => IkiWiki::cgiurl()
		 , charset => "utf-8"
		 , fields => [qw{
			 do
			 from_date from_year from_month from_day from_hour from_minute
			 to_date to_year to_month to_day to_hour to_minute
			 inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
			 end_times end_date end_year end_month end_day end_hour end_minute
			 dom name content
			 }]
		 , header => 0
		 , javascript => 0
		 , messages =>
			 {
			#   form_required_text  => 'form_required_text'
			# , form_invalid_text   => 'form_invalid_text'
			# , form_invalid_file   => 'form_invalid_file'
			# , form_invalid_input  => gettext('allowed characters: ').$config{wiki_file_chars}
			 form_invalid_select => gettext('invalid selection')
			 }
		 , method     => 'POST'
		 , name       => "newevent"
		 , stylesheet => 1
		 , params     => $cgi
		 , required   => [qw{do year month day name from_date to_date end_date inc_dur}]
		 , submit     => [qw{Preview Create}]
		 , title      => gettext("newevent")
		 , template   => { template("newevent.tmpl") }
		 , validate   =>
			 { from_date => { perl => sub {
					my (undef, $form) = @_;
					$from_date = date_of_form($form, 'from')
						unless defined $from_date;
					defined $from_date
				 } }
			 , to_date   => { perl => sub {
					my (undef, $form) = @_;
					$from_date = date_of_form($form, 'from')
						unless defined $from_date;
					if (defined $from_date) {
						$to_date = date_of_form($form, 'to'
						 , year   => $from_date->year()
						 , month  => $from_date->month()
						 , day    => $from_date->day()
						 , hour   => $from_date->hour()
						 , minute => $from_date->minute());
						defined $to_date
						and (DateTime->compare($from_date, $to_date) <= 0)
					 }
					else {return 0;}
				 } }
			 , end_date  => { perl => sub {
					my (undef, $form) = @_;
					if ( $form->field('end_year')   ne ''
					 or  $form->field('end_month')  ne ''
					 or  $form->field('end_day')    ne '' ) {
						$from_date = date_of_form($form, 'from')
							unless defined $from_date;
						if (defined $from_date) {
							$end_date = date_of_form($form, 'end'
							 , year   => $from_date->year()
							 , month  => $from_date->month()
							 , day    => $from_date->day()
							 , hour   => $from_date->hour()
							 , minute => $from_date->minute());
							(defined $from_date and defined $end_date
							and DateTime->compare($from_date, $end_date) <= 0)
						 }
						else {return 0;}
					 }
					else {
						1;
					 }
				 } }
			 , name => '/^.+$/'
			 , end_times  => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
			 , inc_year   => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
			 , inc_month  => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
			 , inc_week   => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
			 , inc_day    => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
			 , inc_hour   => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
			 , inc_minute => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
			 , inc_dur    => sub {
					my (undef, $form) = @_;
					$inc_dur = duration_of_form($form, 'inc');
					defined $inc_dur
					and ($inc_dur->is_positive() or $inc_dur->is_zero());
				 }
			 }
		 );
		$form->title(sprintf(gettext("creating new events"), pagetitle(IkiWiki::basename($page))));
		$form->field(name => "do", type => "hidden", value => 'newevent', force => 1);
		$form->field(name => "from_date", type => "hidden", value => '1', force => 1);
		$form->field(name => "to_date", type => "hidden", value => '1', force => 1);
		$form->field(name => "end_date", type => "hidden", value => '1', force => 1);
		$form->field(name => "inc_dur", type => "hidden", value => '1', force => 1);
		$form->field(name => "from_year", type => 'select', value => $cgi_date->year(), options => \@years);
		$form->field(name => "from_month", type => 'select'
		 , value => sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
		 , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
		$form->field(name => "from_day", type => 'select'
		 , value => sprintf("%02d", $cgi_date->day())
		 , options => \@days);
		$form->field(name => "from_hour", type => 'select', value => '', options => \@hours);
		$form->field(name => "from_minute", type => 'select', value => '', options => \@minutes);
		$form->field(name => "name", type => 'text', size => 60, value => gettext('New event'));
		$form->field(name => "to_year", type => 'select', value => '', options => \@years);
		$form->field(name => "to_month", type => 'select'
		 , value => ''
		 , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
		$form->field(name => "to_day", type => 'select'
		 , value => ''
		 , options => \@days);
		$form->field(name => "to_hour", type => 'select', value => '', options => \@hours);
		$form->field(name => "to_minute", type => 'select', value => '', options => \@minutes);
		$form->field(name => "end_year", type => 'select', value => '', options => \@years);
		$form->field(name => "end_month", type => 'select', value => ''
		 , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
		$form->field(name => "end_day", type => 'select', value => '', options => \@days);
		$form->field(name => "end_hour", type => 'select', value => '', options => \@hours);
		$form->field(name => "end_minute", type => 'select', value => '', options => \@minutes);
		$form->field(name => "end_times", type => 'text', value => '0', size => 2);
		$form->field(name => "inc_year", type => 'text', value => '0', size => 2);
		$form->field(name => "inc_month", type => 'text', value => '0', size => 2);
		$form->field(name => "inc_week", type => 'text', value => '0', size => 2);
		$form->field(name => "inc_day", type => 'text', value => '0', size => 2);
		$form->field(name => "inc_hour", type => 'text', value => '0', size => 2);
		$form->field(name => "inc_minute", type => 'text', value => '0', size => 2);
		my $tmpl_neweventcontent = template("neweventcontent.tmpl");
		$tmpl_neweventcontent->param(title => gettext('Title of the event'));
		$tmpl_neweventcontent->param(tags => [map {{name => $_}} (sort keys %$tags)]);
		$form->field(name => "content", type => "textarea", size => 30, rows => 20, cols => 80
		 , value => $tmpl_neweventcontent->output());
		$form->field(name => "dom", type => 'select', multiple => 1, size => 35
		 , options => [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
		
		IkiWiki::decode_form_utf8($form);
		IkiWiki::run_hooks(formbuilder_setup => sub {
			shift->(form => $form, cgi => $cgi, session => $session, buttons => $buttons);
		 });
		IkiWiki::decode_form_utf8($form);
		
		if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
			#IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
			my $newevent_base = $cgi->param('base');
			$newevent_base = $config{newevent_base}
				unless defined $newevent_base;
			$newevent_base = gettext('Agenda')
				unless defined $newevent_base;
			my $end_times
			 = $form->field('end_times') == 0
			 ? undef : $form->field('end_times');
			my $dom;
			foreach ($form->field('dom')) {
				$dom = {} if not defined $dom;
				$dom->{$_} = 1;
			 }
			my $name = $form->field('name');
			$name = IkiWiki::possibly_foolish_untaint(IkiWiki::titlepage($name));
				# NOTE: these untaints are safe because of the checks
				#       performed in check_cannewevent later.
			my $content = $form->field('content');
			$content =~ s/\r\n/\n/gs;
			$content =~ s/\n$//s;
			
			# Queue of event creations to perfom.
			my @events = ();
			my $events_try = 0;
			my $events_max
			 = defined $config{newevent_max_per_commit}
			 ? $config{newevent_max_per_commit} : (2 * 365) ;
			my $pageext = $config{default_pageext};
			while (++$events_try <= $events_max
			 and (not defined $end_times or --$end_times >= 0)
			 and (not defined $end_date or DateTime->compare($from_date, $end_date) <= 0)) {
				my $dest = page_of_event($form, $from_date, $to_date, $name, $newevent_base);
				my $week = $from_date->weekday_of_month();
				my $day = $now_date->{locale}->day_format_wide->[$from_date->day_of_week()-1];
				if (not defined $dom or exists $dom->{"$week° $day"}) {
					push @events,
					 { page => $dest
					 , file => IkiWiki::newpagefile($dest, $pageext)
					 , from => $from_date
					 , to   => $to_date
					 , name => $name
					 };
				 }
				last unless defined $inc_dur and $inc_dur->is_positive();
				$from_date = $from_date->clone->add_duration($inc_dur);
				$to_date   = $to_date->clone->add_duration($inc_dur);
			 }
			error("events try per commit overflow: $events_max")
				unless $events_try <= $events_max;
			my $tmpl_neweventpage = template("neweventpage.tmpl");
			my $i = 0;
			foreach (@events) {
				$tmpl_neweventpage->clear_params();
				$tmpl_neweventpage->param(content => $content);
				$tmpl_neweventpage->param(page => $_->{page});
				$tmpl_neweventpage->param(event => $i);
				$tmpl_neweventpage->param("event_first" => 1)
					if $i == 0;
				$tmpl_neweventpage->param("event_last" => 1)
					if $i == @events - 1;
				$tmpl_neweventpage->param(events => \@events);
				$tmpl_neweventpage->param(from_date => "$_->{from}");
				$tmpl_neweventpage->param(name => $_->{name});
				$tmpl_neweventpage->param(to_date => "$_->{to}");
				$_->{content} = $tmpl_neweventpage->output();
				$i++;
			 }
			if ($form->submitted eq 'Create') {
				@events = newevent_hook
				 ( cgi     => $cgi
				 , done    => {}
				 , events  => \@events
				 , session => $session
				 );
				require IkiWiki::Render;
				if ($config{rcs}) {
					IkiWiki::disable_commit_hook()
				 }
				foreach my $event (@events) {
					create($event, $cgi, $session, \%months, $newevent_base);
				 }
				if ($config{rcs}) {
					IkiWiki::rcs_commit_staged
					 ( message => sprintf(gettext("new event"))
					 , session => $session );
					IkiWiki::enable_commit_hook();
					IkiWiki::rcs_update();
				 }
				IkiWiki::refresh();
				IkiWiki::saveindex();
				
				post_newevent($cgi, $session, (defined $events[0] ? $events[0]->{page} : ''));
			 }
			elsif ($form->submitted eq 'Preview') {
				preview($cgi, $session, $form, \@events, \%months);
				IkiWiki::showform($form, $buttons, $session, $cgi);
			 }
		 }
		else {
			IkiWiki::showform($form, $buttons, $session, $cgi);
		 }
		
		exit 0;
	 }
 }
sub preview($$$$) {
	my ($cgi, $session, $form, $events, $months) = @_;
	$form->tmpl_param(year   => gettext("year"));
	$form->tmpl_param(month  => gettext("month"));
	$form->tmpl_param(day    => gettext("day"));
	$form->tmpl_param(hour   => gettext("hour"));
	$form->tmpl_param(min    => gettext("min"));
	$form->tmpl_param(dow    => gettext("day of week"));
	$form->tmpl_param(page   => gettext("page"));
	$form->tmpl_param(events => [
		map {
			{ from_year      => $_->{from}->year()
			, from_month     => sprintf('%02d', $_->{from}->month())
			, from_monthname => $months->{$_->{from}->month()}
			, from_day       => sprintf('%02d', $_->{from}->day())
			, from_hour      => sprintf('%02d', $_->{from}->hour())
			, from_minute    => sprintf('%02d', $_->{from}->minute())
			, from_dow       => $_->{from}->dow()
			, from_downame   => $_->{from}->day_name()
			, to_year        => $_->{to}->year()
			, to_month       => sprintf('%02d', $_->{to}->month())
			, to_monthname   => $months->{$_->{to}->month()}
			, to_day         => sprintf('%02d', $_->{to}->day())
			, to_hour        => sprintf('%02d', $_->{to}->hour())
			, to_minute      => sprintf('%02d', $_->{to}->minute())
			, to_dow         => $_->{to}->dow()
			, page           =>
				htmllink("", "", $_->{page}
				 , linktext => $_->{page}
				 , noimageinline => 1)
			}
		 } @$events
	 ]);
	if (@$events > 0) {
		my $page = @$events[0];
		# FROM: editpage.pm
		my $new = not exists $pagesources{$page};
		# temporarily record its type
		my $type = $config{default_pageext};
		$pagesources{$page} = $page.".".$type if $new;
		my %wasrendered = map { $_ => 1 } @{$renderedfiles{$page}};
		my $content = @$events[0]->{content};
		
		IkiWiki::run_hooks(editcontent => sub {
			$content = shift->
			 ( cgi     => $cgi
			 , content => $content
			 , page    => $page
			 , session => $session
			 );
		 });
		my $preview = IkiWiki::htmlize($page, $page, $type,
			IkiWiki::linkify($page, $page,
			IkiWiki::preprocess($page, $page,
			IkiWiki::filter($page, $page, $content), 0, 1)));
		IkiWiki::run_hooks(format => sub {
			$preview = shift->
			 ( content => $preview
			 , page    => $page
			 );
		 });
		$form->tmpl_param("preview", $preview);
		
		# Previewing may have created files on disk.
		# Keep a list of these to be deleted later.
		my %previews = map { $_ => 1 } @{$wikistate{editpage}{previews}};
		foreach my $f (@{$renderedfiles{$page}}) {
			$previews{$f} = 1 unless $wasrendered{$f};
		 }
		
		# Throw out any other state changes made during previewing,
		# and save the previews list.
		IkiWiki::loadindex();
		@{$wikistate{editpage}{previews}} = keys %previews;
		IkiWiki::saveindex();
	 }
	else {
		$form->tmpl_param("preview", gettext("No event"));
	 }
 }
sub create ($$$$$) {
	my ($event, $cgi, $session, $months, $newevent_base) = @_;
	check_cannewevent
	 ( $event->{page}
	 , $event->{file}
	 , $cgi
	 , $session
	 );
	my $pageext = $config{default_pageext};
	
	$config{cgi} = 0; # NOTE: avoid CGI error message
	eval { writefile($event->{file}, $config{srcdir}, $event->{content}) };
	if ($config{rcs}) {
		IkiWiki::rcs_add($event->{file});
	 }
	# month page
		my $monthpage =
		 ( $newevent_base
		 . ($newevent_base?'/':'').$event->{from}->year()
		 . '/'.sprintf('%02d', $event->{from}->month())
		 );
		my $monthfile = IkiWiki::newpagefile($monthpage, $pageext);
		if (not exists $pagesources{$monthpage}
		 and not -l $config{srcdir}.'/'.$monthfile
		 and not -e _) {
			my $tmpl_neweventmonth = template("neweventmonth.tmpl");
			$tmpl_neweventmonth->param(year      => $event->{from}->year());
			$tmpl_neweventmonth->param(month     => sprintf('%02d', $event->{from}->month()));
			$tmpl_neweventmonth->param(monthname => $months->{$event->{from}->month()});
			my $content = $tmpl_neweventmonth->output();
			eval { writefile($monthfile, $config{srcdir}, $content) };
			if ($config{rcs}) {
				IkiWiki::rcs_add($monthfile);
			 }
		 }
	# day page
		my $daypage =
		 ( $monthpage
		 . '/'.sprintf('%02d', $event->{from}->day())
		 );
		my $dayfile = IkiWiki::newpagefile($daypage, $pageext);
		if (not exists $pagesources{$daypage}
		 and not -l $config{srcdir}.'/'.$dayfile
		 and not -e _) {
			my $tmpl_neweventday = template("neweventday.tmpl");
			$tmpl_neweventday->param(year      => $event->{from}->year());
			$tmpl_neweventday->param(month     => sprintf('%02d', $event->{from}->month()));
			$tmpl_neweventday->param(monthname => $months->{$event->{from}->month()});
			$tmpl_neweventday->param(day       => sprintf('%02d', $event->{from}->day()));
			$tmpl_neweventday->param(dayname   => $event->{from}->day_name());
			my $content = $tmpl_neweventday->output();
			eval { writefile($dayfile, $config{srcdir}, $content) };
			if ($config{rcs}) {
				IkiWiki::rcs_add($dayfile);
			 }
		 }
	$config{cgi} = 1;
 }
sub newevent_hook {
	my %params  = @_;
	my @events  = @{$params{events}};
	my %done    = %{$params{done}};
	my $cgi     = $params{cgi};
	my $session = $params{session};
	return ()
		unless @events;
	my @next;
	foreach my $event (@events) {
		unless (exists $done{$event->{page}} && $done{$event->{file}}) {
			IkiWiki::run_hooks(newevent => sub {
				push @next, shift->
				 ( cgi     => $cgi
				 , event   => $event
				 , session => $session
				 );
			 });
			$done{$event->{page}} = 1;
		 }
	 }
	push @events, newevent_hook
	 ( cgi     => $cgi
	 , done    => \%done
	 , events  => \@next
	 , session => $session
	 );
	my %seen; # NOTE: insure unicity
	return grep { ! $seen{$_->{page}}++ } @events;
 }

1;
