=head1 NAME

EPrints::Plugin::Screen::BatchEdit

=cut

package EPrints::Plugin::Screen::BatchEdit;

use EPrints::Plugin::Screen;

@ISA = ( 'EPrints::Plugin::Screen' );

use strict;

sub new
{
	my( $class, %params ) = @_;

	my $self = $class->SUPER::new(%params);
	
	$self->{actions} = [qw/ edit remove cancel /];

	# is linked to by the BatchEdit export plugin
	$self->{appears} = [];

	return $self;
}

sub allow_edit { $_[0]->can_be_viewed }
sub allow_remove { $_[0]->can_be_viewed }
sub allow_cancel { $_[0]->can_be_viewed }

sub can_be_viewed
{
	my( $self ) = @_;

	return $self->allow( "eprint/archive/edit" );
}

sub redirect_to_me_url
{
	my( $self ) = @_;

	return undef;

	my $cacheid = $self->{processor}->{cacheid};

	return $self->SUPER::redirect_to_me_url."&cache=$cacheid";
}

sub hidden_bits
{
	my( $self ) = @_;

	return(
		$self->SUPER::hidden_bits,
		cache => $self->get_searchexp->get_cache_id,
	);
}

sub properties_from
{
	my( $self ) = @_;

	$self->SUPER::properties_from;

	$self->{processor}->{cacheid} = $self->{session}->param( "cache" );
}

sub get_cache
{
	my( $self ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	return $session->dataset( "cachemap" )->dataobj( $processor->{cacheid} );
}

sub get_searchexp
{
	my( $self ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	my $cacheid = $processor->{cacheid};

	my $cache = $self->get_cache();
	return if !defined $cache;

	my $searchexp = EPrints::Search->new(
		session => $session,
		dataset => $session->dataset( "eprint" ),
		keep_cache => 1,
	);

	if( $searchexp )
	{
		$searchexp->from_string_raw( $cache->get_value( "searchexp" ) );
		$searchexp->{"cache_id"} = $cacheid;
	}

	return $searchexp;
}

sub action_edit { }
sub action_remove { }
sub action_cancel { }

sub wishes_to_export
{
	my( $self ) = @_;

	return defined $self->{session}->param( "ajax" );
}

sub export
{
	my( $self ) = @_;

	my $session = $self->{session};

	my $action = $self->{processor}->{action};
	return unless defined $action;

	if( $action eq "add_change" )
	{
		$self->ajax_add_change( scalar($session->param( "field_name" )) );
	}
	elsif( $action eq "edit" )
	{
		$self->ajax_edit();
	}
	elsif( $action eq "remove" )
	{
		$self->ajax_remove();
	}
	elsif( $action eq "list" )
	{
		$self->ajax_list();
	}
}

sub ajax_list
{
	my( $self ) = @_;

	my $max = 8;

	my $session = $self->{session};

	my $searchexp = $self->get_searchexp;
	return if !defined $searchexp;

	my $list = $searchexp->perform_search;

	$session->send_http_header( content_type => "text/xml; charset=UTF-8" );
	binmode(STDOUT, ":utf8");

	my $div = $session->make_element( "div" );

	my @records = $list->get_records( 0, $max );
	if( !scalar @records )
	{
		$div->appendChild( $session->render_message( "error", $session->html_phrase( "lib/searchexpression:noresults" ) ) );
		print $session->xml->to_string( $div, undef, 1 );
		$session->xml->dispose( $div );
		return;
	}

	$div->appendChild( $self->html_phrase( "applying_to",
		count => $session->make_text( $list->count ),
		showing => $session->make_text( $max ) ) );

	my $ul = $session->make_element( "ul" );
	$div->appendChild( $ul );

	foreach my $record (@records)
	{
		my $li = $session->make_element( "li" );
		$ul->appendChild( $li );
		$li->appendChild( $record->render_citation_link() );
	}

	print $session->xml->to_string( $div, undef, 1 );
	$session->xml->dispose( $div );
}

# generate a new action line
sub ajax_add_change
{
	my( $self, $name ) = @_;

	my $prefix = APR::UUID->new->format;

	my $session = $self->{session};

	my $searchexp = $self->get_searchexp;
	EPrints->abort( "Missing cache parameter" ) if !defined $searchexp;

	my $dataset = $searchexp->get_dataset;
	EPrints->abort( "'$name' is not a valid field" ) if !$dataset->has_field( $name );

	my $field;
	foreach my $f ($self->get_fields( $dataset ))
	{
		$field = $f, last if $f->get_name eq $name;
	}
	EPrints->abort( "'$name' is not a valid field" ) if !defined $field;

	$field = $field->clone;
	my @options;
	if( $field->get_property( "multiple" ) )
	{
		@options = qw( clear delete insert append );
	}
	else
	{
		@options = qw( clear replace );
	}

	# construct a new field called the action number
	# this first sub-field is the action to perform (append, clear etc.)
	my $custom_field = {
		name => $prefix,
		type => "compound",
		fields => [{
			sub_name => "action",
			type => "set",
			options => \@options,
			title_xhtml => $session->make_doc_fragment,
			render_option => sub {
				my( undef, $option ) = @_;
				return $self->html_phrase( "actionopt:" . ($option||"") );
			},
		}],
	};

	# add the field or sub-fields that represent the field
	# the sub name of the field is the field's name
	# the title is the real field's title
	if( $field->isa( "EPrints::MetaField::Compound" ) )
	{
		for(@{$field->property( "fields" )})
		{
			my $inner_field = EPrints::Utils::clone( $_ );
			$inner_field->{sub_name} = join('_', $name, $inner_field->{sub_name});
			push @{$custom_field->{fields}}, $inner_field;
		}
	}
	else
	{
		$field->{sub_name} = $field->{name};
		push @{$custom_field->{fields}}, $field;
	}

	# and lastly a button to remove action entries and a hidden that tells us
	# the field name of the action name
	push @{$custom_field->{fields}}, {
			sub_name => "remove",
			type => "text",
			title_xhtml => $session->make_doc_fragment,
			render_input => sub {
				my $frag = $session->make_doc_fragment;
				
				# button to remove the action
				$frag->appendChild( $session->make_element( "input",
					type => "image",
					alt => "Remove",
					src => $session->get_url( path => "static", "style/images/action_remove.png" ),
					onclick => "document.getElementById('$prefix').remove(); return false;",
				) );

				# hint so we can work out how to retrieve the values
				$frag->appendChild( $session->make_element( "input",
					type => "hidden",
					name => $self->get_subtype."_change_".$prefix,
					value => $name
				) );

				return $frag;
			},
		};

	# do some tidying up and turn custom_field into a temporary field object
	$custom_field = $self->custom_field_to_field( $dataset, $custom_field );

	my $div = $session->make_element( "div", id => $prefix );
	$div->appendChild( $custom_field->render_input_field( $session ) );

	$session->send_http_header( content_type => "text/html; charset=UTF-8" );
	binmode(STDOUT, ":utf8");
	print $session->xhtml->to_xhtml( $div );
	$session->xml->dispose( $div );
}

sub ajax_edit
{
	my( $self ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	my $progressid = $session->param( "progressid" );

	my $searchexp = $self->get_searchexp;
	EPrints->abort( "Missing search expression" ) if !defined $searchexp;

	my $list = $searchexp->perform_search;
	my $progress = $session->dataset( "upload_progress" )->create_dataobj({
		progressid => $progressid,
		received => 0,
		size => $list->count
	}) or EPrints->abort( "Error creating progress object" );

	my $request = $session->get_request;

	$request->content_type( "text/html; charset=UTF-8" );
	my $html = $session->make_element( "html" );
	my $body = $html->appendChild( $session->make_element( "body" ) );

	my $dataset = $searchexp->get_dataset;

	my @actions = $self->get_changes( $dataset );

	if( !@actions )
	{
		$progress->remove();
		$body->appendChild( $session->render_message( "warning", $self->html_phrase( "no_changes" ) ) );
		print $session->xhtml->to_xhtml( $html );
		$session->xml->dispose( $html );
		return;
	}

	$list->map(sub {
		my( $session, $dataset, $dataobj ) = @_;

		foreach my $act (@actions)
		{
			my $field = $act->{"field"};
			my $action = $act->{"action"};
			my $value = $act->{"value"};
			my $orig_value = $field->get_value( $dataobj );

			if( $field->get_property( "multiple" ) )
			{
				if( $action eq "clear" )
				{
					$field->set_value( $dataobj, [] );
				}
				elsif( $action eq "delete" )
				{
					my $values = EPrints::Utils::clone( $orig_value );
					@$values = grep { cmp_deeply($value, $_) != 0 } @$values;
					$field->set_value( $dataobj, $values );
				}
				elsif( $action eq "insert" )
				{
					my @values = ($value, @$orig_value);
					$field->set_value( $dataobj, \@values );
				}
				elsif( $action eq "append" )
				{
					my @values = (@$orig_value, $value);
					$field->set_value( $dataobj, \@values );
				}
			}
			else
			{
				if( $action eq "clear" )
				{
					$field->set_value( $dataobj, undef );
				}
				elsif( $action eq "replace" )
				{
					$field->set_value( $dataobj, $value );
				}
			}
		}

		$dataobj->commit;

		$progress->set_value( "received", $progress->value( "received" ) + 1 );
		$progress->commit;
	});

	my $ul = $session->make_element( "ul" );
	foreach my $act (@actions)
	{
		my $field = $act->{"field"};
		my $action = $act->{"action"};
		my $value = $act->{"value"};
		my $li = $session->make_element( "li" );
		$ul->appendChild( $li );
		$value = defined($value) ?
			$field->render_single_value( $session, $value ) :
			$session->html_phrase( "lib/metafield:unspecified" );
		$li->appendChild( $self->html_phrase( "applied_$action",
			value => $session->make_text( EPrints::Utils::tree_to_utf8( $value ) ),
			fieldname => $field->render_name,
		) );
		$session->xml->dispose( $value );
	}
	$body->appendChild( $session->render_message( "message", $self->html_phrase( "applied",
		changes => $ul,
	) ) );

	$progress->remove;

	print $session->xhtml->to_xhtml( $html );
	$session->xml->dispose( $html );
}

sub ajax_remove
{
	my( $self ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	my $progressid = $session->param( "progressid" );

	my $searchexp = $self->get_searchexp;
	EPrints->abort( "Missing search expression" ) if !defined $searchexp;

	my $list = $searchexp->perform_search;
	my $progress = $session->dataset( "upload_progress" )->create_dataobj({
		progressid => $progressid,
		received => 0,
		size => $list->count
	}) or EPrints->abort( "Error creating progress object" );

	my $request = $session->get_request;
	my $html = $session->make_element( "html" );
	my $body = $html->appendChild( $session->make_element( "body",
		onload => "window.top.window.ep_batchedit_finished()"
	) );

	$request->content_type( "text/html; charset=UTF-8" );

	my $dataset = $searchexp->get_dataset;

	$list->map(sub {
		my( $session, $dataset, $dataobj ) = @_;

		$dataobj->remove;

		$progress->set_value( "received", $progress->value( "received" ) + 1 );
		$progress->commit;
	});

	$body->appendChild( $session->render_message( "message", $self->html_phrase( "removed" ) ) );

	$progress->remove;

	print $session->xhtml->to_xhtml( $html );
	$session->xml->dispose( $html );
}

sub render
{
	my( $self ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	my $prefix = $self->get_subtype;

	my( $page, $p, $div, $link );

	$page = $session->make_doc_fragment;

	my $searchexp = $self->get_searchexp;
	if( !defined $searchexp )
	{
		$processor->add_message( "error", $self->html_phrase( "invalid_cache" ) );
		return $page;
	}

	my $list = $searchexp->perform_search;
	if( $list->count == 0 || !$list->slice(0,1) )
	{
		$processor->add_message( "error", $session->html_phrase( "lib/searchexpression:noresults" ) );
		return $page;
	}

	$page->appendChild( $session->make_element( "iframe",
			id => "${prefix}_iframe",
			name => "${prefix}_iframe",
			title => $self->phrase( 'title' ) . " iframe",
			width => "0px",
			height => "0px",
			style => "border: 0px;",
		) );

	$div = $session->make_element( "div" );
	$page->appendChild( $div );
	$div->appendChild( $searchexp->render_description );

	$div = $session->make_element( "div", id => "${prefix}_sample" );
	$page->appendChild( $div );

	$div = $session->make_element( "div", id => "${prefix}_progress" );
	$page->appendChild( $div );

	my $form = $self->render_form;
	$page->appendChild( $form );

	$form->setAttribute( target => "${prefix}_iframe" );
	$form->setAttribute( id => "${prefix}_form" );
	$form->appendChild( $session->xhtml->hidden_field(
			ajax => 1,
		) );
	$form->appendChild( $session->xhtml->hidden_field(
			progressid => APR::UUID->new->format,
		) );

	$form->appendChild( $session->xhtml->tabs(
		[
			$self->html_phrase( "edit_title" ),
			$self->html_phrase( "remove_title" )
		],
		[
			$self->render_changes_form( $searchexp ),
			$self->render_remove_form( $searchexp ),
		],
	) );

	$page->appendChild( $session->make_javascript( <<EOJ ) );
window.onload = () => {
	new Screen_BatchEdit ('$prefix');
};
EOJ

	return $page;
}

sub get_fields
{
	my( $self, $dataset ) = @_;

	my @fields;

	my %fieldnames;

	foreach my $field ($dataset->get_fields)
	{
		next if defined $field->{sub_name};
		next if $field->get_name eq $dataset->get_key_field->get_name;
		next if
			!$field->isa( "EPrints::MetaField::Compound" ) &&
			!$field->get_property( "show_in_fieldlist" );

		push @fields, $field;
		my $name = $field->render_name( $self->{session} );
		$fieldnames{$field} = lc(EPrints::Utils::tree_to_utf8( $name ) );
		$self->{session}->xml->dispose( $name );
	}

	@fields = sort { $fieldnames{$a} cmp $fieldnames{$b} } @fields;

	return @fields;
}

sub get_changes
{
	my( $self, $dataset ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	my @actions;

	my $prefix = $self->get_subtype . "_change_";

	my @uuids = map { /^$prefix(.+)$/ ? $1 : () } $session->param;

	my %fields = map { $_->get_name => $_ } $self->get_fields( $dataset );

	foreach my $uuid (@uuids)
	{
		my $name = $session->param( $prefix . $uuid );
		next if !EPrints::Utils::is_set( $name );
		my $action = $session->param( $uuid . "_action" );
		next if !EPrints::Utils::is_set( $action );
		my $field = $fields{$name};
		next if !defined $field;
		do {
			local $field->{multiple} = 0;
			my $value = $field->form_value( $session, undef, $uuid );
			push @actions, {
				action => $action,
				field => $field,
				value => $value,
			};
		};
	}

	return @actions;
}

sub render_changes_form
{
	my( $self, $searchexp ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	my $prefix = $self->get_subtype;

	my $dataset = $searchexp->get_dataset;

	my( $page, $p, $div, $link );

	$page = $session->make_doc_fragment;

	$div = $session->make_element( "div", id => "${prefix}_changes" );
	$page->appendChild( $div );

	$page->appendChild( $div = $session->make_element( "div" ) );

	my $select = $session->make_element( "select", id => "${prefix}_field_name", 'aria-labelledby' => join('_', $self->get_subtype, "action_add_change") );
	$div->appendChild( $select );

	foreach my $field ($self->get_fields( $dataset ))
	{
		my $option = $session->make_element( "option", value => $field->get_name );
		$select->appendChild( $option );

		$option->appendChild( $field->render_name( $session ) );
	}

	$div->appendChild( $session->xhtml->action_button(
			add_change => $self->phrase( "action:add" ),
			id => join('_', $self->get_subtype, "action_add_change"),
		) );

	$page->appendChild( $session->xhtml->action_button(
			edit => $self->phrase( "action:edit" ),
			id => join('_', $self->get_subtype, "action_edit"),
		) );

	return $page;
}

sub render_remove_form
{
	my( $self, $searchexp ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	my( $page, $p, $div, $link );

	$page = $session->make_doc_fragment;

	$div = $session->make_element( "div", class => "ep_block" );
	$page->appendChild( $div );

	$div->appendChild( $self->html_phrase( "remove_help" ) );

	$div = $session->make_element( "div", class => "ep_block" );
	$page->appendChild( $div );

	$div->appendChild( $session->xhtml->action_button(
			remove => $session->phrase( "lib/submissionform:action_remove" ),
			id => join('_', $self->get_subtype, "action_remove"),
			_phrase => $self->phrase( "confirm_remove" ),
		) );

	return $page;
}

sub custom_field_to_field
{
	my( $self, $dataset, $data ) = @_;

	my $processor = $self->{processor};
	my $session = $processor->{session};

	$data->{fields_cache} = [];

	my $field = EPrints::MetaField->new(
		dataset => $dataset,
		%{$data},
	);

	foreach my $inner_field (@{$field->property( "fields_cache" )})
	{
		next if $inner_field eq $field->property( "fields_cache" )->[0];
		next if $inner_field eq $field->property( "fields_cache" )->[-1];

		delete $inner_field->{multiple};
		local $inner_field->{name} = $inner_field->{sub_name};
		$inner_field->{title_xhtml} = $inner_field->render_name( $session );
		$inner_field->{render_option} = sub {
			my( undef, $option ) = @_;

			local $inner_field->{name} = $inner_field->{sub_name};
			local $inner_field->{render_option};

			return $inner_field->render_option( $session, $option );
		};
	}

	return $field;
}

sub cmp_deeply
{
	my( $var_a, $var_b ) = @_;

	if( !EPrints::Utils::is_set($var_a) )
	{
		return 0;
	}
	elsif( !EPrints::Utils::is_set($var_b) )
	{
		return -1;
	}

	my $rc = 0;

	$rc ||= ref($var_a) cmp ref($var_b);
	$rc ||= _cmp_hash($var_a, $var_b) if( ref($var_a) eq "HASH" );
	$rc ||= $var_a cmp $var_b if( ref($var_a) eq "" );

	return $rc;
}

sub _cmp_hash
{
	my( $var_a, $var_b ) = @_;

	my $rc = 0;

	for(keys %$var_a)
	{
		$rc ||= cmp_deeply( $var_a->{$_}, $var_b->{$_} );
	}

	return $rc;
}

sub render_links
{
	my( $self ) = @_;

	my $frag = $self->SUPER::render_links;

	$frag->appendChild( $self->{session}->make_javascript( undef,
		src => $self->{session}->current_url( path => "static", "javascript/screen_batchedit.js" ) )
	);

	return $frag;
}

1;


=head1 COPYRIGHT

=for COPYRIGHT BEGIN

Copyright 2022 University of Southampton.
EPrints 3.4 is supplied by EPrints Services.

http://www.eprints.org/eprints-3.4/

=for COPYRIGHT END

=for LICENSE BEGIN

This file is part of EPrints 3.4 L<http://www.eprints.org/>.

EPrints 3.4 and this file are released under the terms of the
GNU Lesser General Public License version 3 as published by
the Free Software Foundation unless otherwise stated.

EPrints 3.4 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with EPrints 3.4.
If not, see L<http://www.gnu.org/licenses/>.

=for LICENSE END

=cut

