package GO::Tool::AddTool;
use strict;
use lib '/Users/gwg/go/scratch/tools';
use base 'GO::Tool::GenericGoTool';

### Various modules required
use Data::Dumper;
use CGI::Carp qw(fatalsToBrowser);

use Data::FormValidator::Constraints qw(:closures);

use GO::Object::Generic;
use GO::Object::Tool;
use GO::Object::Developer;
use GO::Object::ToolPreview;
use GO::TestSet qw(dfv_test);
use GO::Utilities qw(:all);
use GO::GeneralPurposeParser;
use URI::Escape;
#use Utility::TSLParser;

my $valid_run_modes = [ qw( tool_form preview submit ) ];

sub _specification {
	my $self = shift;
	
	my $spec = GO::Object::ToolPreview->get_spec();
	my $extra = {
		"developer" => {
			subtext => 'Please supply the organization name and URL',
		},
		"description" => {
			data_type => 'longtext',
			subtext => 'Please provide a brief description of the tool\'s functionality',
		},
		"other_license_text" => {
			data_type => 'longtext',
			human_name => 'Other license (please specify)',
		},
		"compatible_os" => {
			subtext => 'If the tool is standalone, please specify which operating systems it will run on',
		},
		"other_feature_text" => {
			data_type => 'longtext',
			human_name => 'Other tool features (please specify)',
			allow_multiple => 1,
		},
		"publication" => {
			subtext => 'Please give PubMed IDs or DOIs',
		},
		"is_open_source" => {
#			data_type => 'boolean',
			test => dfv_test( 'is_in_list_p', { list => [ 'true', 'false' ], this => 1 } ),
			human_name => 'Is the tool open source?',
			list_values => [ 'true', 'false' ],
			list_values_human_name_h => { 'true' => 'yes', 'false' => 'no', },
			required => 1,
			default => 'false',
		},
		"comments", {
			data_type => 'longtext',
			human_name => 'Any other information or comments',
#			allow_multiple => 1,
		},
		
		"tool_type" => {
			human_name => 'Tool type',
			allow_multiple => 1,
			test => dfv_test('is_in_list_p', { list => [ 'is_standalone_tool', 'is_online_tool' ], this => 1 } ),
			list_values => [ 'is_standalone_tool', 'is_online_tool' ],
			list_values_human_name_h => { is_standalone_tool => 'Standalone (downloadable) tool', is_online_tool => 'Web-based online tool' },
			required => 1,
			dependencies => {
				'is_standalone_tool' => [ 'compatible_os' ],
			},
		},

	## what GO data is used
		"go_data_used" => {
			human_name => 'What GO data does the tool display?',
		},

	## GO data source
		"go_data_source" => {
			human_name => 'Where does the GO data come from?',
		},
		
	## GO data update frequency
		"go_data_update_frequency" => {
			human_name => 'How often is the GO data updated?',
			# daily, weekly, monthly, n_per_week, n_per_month, n_per_year, unknown
		},
		
		## runmode
		rm => {
			test => dfv_test('is_in_list_p', { list => $valid_run_modes, this => 1 } ),
			input_type => 'hidden',
			default => 'tool_form',
			list_values => $valid_run_modes,
		},
	};

	my @field_order = qw(name url email developer license other_license_text description tool_type compatible_os feature other_feature_text publication go_data_used go_data_update_frequency is_open_source comments rm);

	my $error_text = {
		name => 'should be more than two characters long',
		url => 'should start with http:// and contain valid URL characters',
		email => 'should be in the form name@domain.com',
		developer => 'should contain an URL',
		license => 'please pick an option',
#		other_license_text => '',
		description => 'should be at least 50 characters long',
		tool_type => 'please pick one or more options',
		compatible_os => 'please pick one or more options',
		feature => 'please pick one or more options',
		publication => 'should be at least 10 characters long',
		go_data_used => 'please pick one or more options',
		go_data_source => 'should be at least 5 characters long',
		go_data_update_frequency => 'should be at least 5 characters long',
	};
	
	return (
		map {
			if ($extra->{$_})
			{	foreach my $x (keys %{$extra->{$_}})
				{	$spec->{$_}{$x} = $extra->{$_}{$x};
				}
			}
			if ($error_text->{$_})
			{	$spec->{$_}{error_text} = $error_text->{$_};
			}
			( $_, $spec->{$_} )
		} @field_order );
	
#	my @return;
#	push @return, ($_, $tool_h{$_}) foreach @field_order;
#	return @return;

#	return ( map { ( $_, $spec->{$_} ) } @field_order );
}

sub setup {
	my $self = shift;
	$self->start_mode('tool_form');
	
	$self->mode_param(\&set_run_mode);
	$self->error_mode('die_and_output_error');
	$self->run_modes(
		'tool_form' => 'tool_form',
		'preview' => 'preview',
		'submit' => 'submit',
		'fatal_error' => 'die_and_output_error',
		'AUTOLOAD' => 'tool_form',
	);
}


sub set_run_mode {
#	return 'rm';
	my $self = shift;
	my %cgi_h = $self->query->Vars;
	if (defined $cgi_h{rm} && grep { $cgi_h{rm} eq $_ } @$valid_run_modes)
	{	return $cgi_h{rm};
	}
	return 'tool_form';
}


# configure the template before we do anything else
sub cgiapp_init {
	my $self = shift;
	$self->tt_config(
		TEMPLATE_OPTIONS => {
			INCLUDE_PATH=> [ qw( /Users/gwg/go/scratch/tools/templates/ /Users/gwg/go/www/) ],
			TRIM=>1,
		},
	);
}


## stuff to do before we run the page-specific code
# if the mode is preview or submit and we don't have all the required data,
# just show the query form

# if we do have all the required data and the mode is preview,
# show a preview of the tool plus the query data in case they want
# to change the data
# if the mode is submit, verify the data and if it's OK, show a
# thank you message and send the data to the tool person
sub cgiapp_prerun {
	my $self = shift;

	$self->debugme("Starting cgiapp_prerun in run mode ". $self->get_current_runmode);

	if ($self->get_current_runmode eq 'tool_form')
	{	# get the spec and put default values into validated_query
		my $valid_h;
		my $spec = $self->get_spec();
		foreach (keys %$spec)
		{	$valid_h->{$_} = $spec->{$_}{default} if $spec->{$_}{default};
		}
		if ($valid_h)
		{	$self->param('validated_query', $valid_h);
		}
	}
	else
	{	#	check the input query, replace dodgy params with the defaults
		my $results = $self->check_input_query({ replace_with_defaults => 1, spec_profile => $self->get_current_runmode, report_multiples => 1 });

#		print STDERR "results: ".Dumper($results);
		

		if (!$results->success) 
		{	# put the tool back into the tool form mode and create an error message
			$self->prerun_mode('tool_form');
			
			$self->param('validator_results', $results);
			# summarize the errors and add a fatal message
			$self->fatal_msg({ MSG => "There were some errors with your tool submission. Please check you have entered everything correctly and try again.", results_obj => $results });
		}
	}
}


=head2 tool_form

This is the form for people to submit their tool. How exciting!
If there were lotsa errors, they will be in the param 'error'

=cut

sub tool_form {
	my $self = shift;
	$self->startme;

	#	get the spec info and the form stuff
	my $output = $self->add_tool_form;

	$output->{next_run_mode} = 'preview';

	# set up all the other stuff and output the template
	return $self->tt_process("add_tool.tmpl", $self->set_up_output($output)) || die "Template toolkit messed up, the bastard!: ", $self->tt_obj->error(), "Dying";
}


=head2 preview

The user has submitted a decent set of data - good on 'em!
Show them the tool as it would appear on the tools page, plus the tool
submission form, in case they want to make any more changes

=cut

sub preview {
	my $self = shift;
	$self->startme;

	#	get the spec info and the form stuff
	my $output = $self->add_tool_form;
	
	#	create a tool preview with the data
	my $data = $self->convert_form_values($self->param('validated_query'));
	
	my $results = GO::Object::ToolPreview->new({
		data => $data,
		return_as => 'success_hash',
		with_checks => 1,
	});
	
#	$self->debugme("results of creating a ToolPreview:\n".Dumper($results));
	
	# add the errors / messages if there are any
	$self->add_message_list($results->{ERROR_LIST}) if $results->{ERROR_LIST};

	if (! $results->{SUCCESS})
	{	# this shouldn't have happened... hmmm! What's going on?
		die "Something very strange has happened here... hmmm! Dying";
	}
	$output->{tool} = $results->{OBJECT};
	$output->{tool_spec} = GO::Object::ToolPreview->get_spec();
	$output->{validated_query} = $self->param('validated_query');

	# the data was OK, so we can allow the user to submit the data
	$output->{next_run_mode} = 'submit';

	# set up all the other stuff and output the template
	return $self->tt_process("preview_tool.tmpl", $self->set_up_output($output)) || die "Template toolkit messed up, the bastard!: ", $self->tt_obj->error(), "\nDying";
}

=head2 submit

The user has submitted a decent set of data.
Show them a thank you page, and format the data into a suitable state
for putting into the tools db.

=cut

sub submit {
	my $self = shift;
	$self->startme;

#	#	get the spec info and the form stuff
#	my $output = $self->add_tool_form;
	
	#	create a tool preview with the data
	my $data = $self->convert_form_values($self->param('validated_query'));
	
	my $results = GO::Object::ToolPreview->new({
		data => $data,
		with_checks => 1,
	});

	### where to save the file (if appropriate)
	my $save_file = $self->param('save_file') || $ENV{GO_TOOL_ADDTOOL_SAVE_FILE} || undef;

	### the tool master, official adder of tools to the GO tools db
	### new tools will be sent by email to this address for checking
	my $tool_master = $self->param('tool_master') || $ENV{GO_TOOL_ADDTOOL_TOOL_MASTER} || undef;

	# see how we're going to save our data
	if (defined $save_file && -e $save_file)
	{	my $text = $results->obj_to_text;
		# add a little extra info
		$text .= "submission_date: ".localtime()
		."\nsubmitted_from: ".$self->cgiapp_get_query->remote_host()."\n";
		$self->write_data_to_file({ file_name => $save_file, string => $text, file_separator => '\n' });
		
	}
	elsif (defined $tool_master)
	{	# send an email
	
		# the data has been checked and given a thorough working over
		# format the data and email it to the GO tools person
		# ? send an email to the tool submitter for their records
		my $message = "GO tool submission on " . localtime() . " from " . $self->cgiapp_get_query->remote_host() . "\n\nTool data:";
	
		# format the tools data for writing to a file
		$message .= $results->obj_to_text;
	
		$self->debugme("results as obj_to_text: ".Dumper($results->obj_to_text));
	
		$message .= '\nIf you do not receive a response regarding your tool within a week of submission, please forward this message to the GO helpdesk, gohelp\@geneontology.org.\nThank you!\n';
	
		# email the data to the tools master
		$self->sendmail({
			subject => 'GO Tool submission',
			from => 'gohelp@geneontology.org',
			to => $tool_master,
			message => $message,
		});
	}
	else
	{	$self->warning_msg("No save method specified: writing to STDERR");
		print STDERR Dumper($results->obj_to_text);
	}
	
	# prepare the template
	my $output = {
		message_list => {
			level => 'info',
			list => [{
				CLASS => 'info',
				MSG => 'Thank you for submitting your tool! The information has been submitted and will be added to the GO tools database once it has been checked.',
			}],
		},
		page_title => 'GO Tool Submission Successful',
		h2_title => 'Thank you!',
	};

	# set up all the other stuff and output the template
	return $self->tt_process("message.tmpl", $self->set_up_output($output)) || die "Template toolkit messed up, the bastard!: ", $self->tt_obj->error(), "Dying";
}


=head2 add_tool_form

	This is the form for people to submit their tool. How exciting!

=cut

sub add_tool_form {
	my $self = shift;
	my $arg_h = shift;
	$self->startme;
	
#	my $ordered_object_spec = $arg_h->{spec};
#	my $query = $arg_h->{query};
#	my $results = $arg_h->{validator_results};
	
	my $temp_h;

	my $q = $self->param('validated_query');
	# don't forget that if the validator didn't validate properly, $validator_results
	# will return undef unless you use 'defined'.
	my $validator_results = $self->param('validator_results');

	#	Gather up the data needed for the form
	#	read in the list of valid params
	
	my $ordered_object_spec = $self->get_spec({ 'ordered' => 1 });
	my %spec_h = ( @$ordered_object_spec );
	
#	$self->debugme("ordered object spec: ".Dumper($ordered_object_spec));

	#	get the keys from this array (every other array item)
	my $n = 0;
	my @p_list = grep { $_ if ! ( $n++ % 2 ) } @$ordered_object_spec;

	foreach my $param (@p_list)
	{	my $param_spec = $spec_h{$param};
		next if $param_spec->{constructor} && $param_spec->{constructor} eq 'automatic';
		$param_spec->{name} = $param;

		if (defined $validator_results)
		{	#$self->debugme("$param validator_results->valid: ".Dumper($validator_results->valid($param)));
			if (! $validator_results->valid($param) )
			{	$self->debugme("error with $param");
				$param_spec->{error} = 'missing' if $validator_results->missing($param);
				$param_spec->{error} = 'invalid' if $validator_results->invalid($param);
				$param_spec->{error} = 'unknown' if $validator_results->unknown($param);
			}
		}
		
		my $selected = $q->{$param} || undef;

		# the input is values from a list
		if ($param_spec->{list_values})
		{	my @list_opts;
			if ($param_spec->{allow_multiple})
			{	foreach my $val (@{$param_spec->{list_values}})
				{	push @list_opts, { value => $val , label => $param_spec->{list_values_human_name_h}{$val} };
					if ( grep { $val eq $_ } @$selected )
					{	$list_opts[-1]->{selected} = 1;
					}
				}
			}
			else
			{	foreach my $val (@{$param_spec->{list_values}})
				{	push @list_opts, { value => $val , label => $param_spec->{list_values_human_name_h}{$val} };
					if ($val eq $selected )
					{	$list_opts[-1]->{selected} = 1;
					}
				}
			}
			$param_spec->{"options"} = [ @list_opts ];
			
		}
		else # we don't know what these params might be!
		{	if ($param_spec->{allow_multiple})
			{	$param_spec->{value} = join("\n", @$selected) if $selected;
			}
			elsif ($param_spec->{data_type} && $param_spec->{data_type} eq 'longtext')
			{	$param_spec->{value} = $selected if $selected;
			}
			else  # everything else
			{	$param_spec->{value} = $selected if $selected;
			}
		}
		push @{$temp_h->{spec_list}}, $param_spec;
	}
	return $temp_h;
}


=head2 tt_pre_process {

Populates various useful variables from the environment

Args:    $template_vars    # any data to go in the template
      
=cut

#sub tt_pre_process {
sub set_up_output {
	my $self = shift;
	my $tmpl_vars = shift || {};

	# the checked query
	if ($self->param('validated_query'))
	{	$tmpl_vars->{query_h} = $self->param('validated_query');

		$tmpl_vars->{url_for_query} = $self->create_url($tmpl_vars->{query_h});

	}
	
	# validator results
	if (defined $self->param('validator_results'))
	{	$tmpl_vars->{validator_results} = $self->param('validator_results');
	}
	
	$tmpl_vars->{install_dir} = 'http://127.0.0.1/go/';

	# any error messages
	if ($self->has_msgs)
	{	$tmpl_vars->{ message } = $self->get_all_msgs('with_level');
	}

	if ($ENV{VERBOSE} || $ENV{TMPL_VERBOSE} || $self->verbosity =~ /verbose/)
	{	$tmpl_vars->{verbose} = 1;
	}

	return $tmpl_vars;
}


sub make_urls {
	my $tool_list = shift;
	
	foreach my $t (@$tool_list)
	{	if ($t->publication)
		{	foreach (@{$t->publication})
			{	if (lc($_->{db}) eq 'pmid')
				{	$_->{url} = 'http://www.ncbi.nlm.nih.gov/pubmed/' . $_->{key};
				}
				elsif (lc($_->{db}) eq 'doi')
				{	$_->{url} = 'http://dx.doi.org/'.$_->{key};
				}
				elsif (lc($_->{db}) eq 'url')
				{	$_->{url} = $_->{key};
				}
			}
		}
	}
}


# simple sub to send mail
sub sendmail {
	my $self = shift;
	my $msg = shift;
	$ENV{PATH} = "/usr/sbin";
	if (open (MAIL, "|/usr/sbin/sendmail -oi -t"))
	{	#send to GO help and the form submitter?
		print MAIL "To: " . $msg->{to} . "\n" .
		"From: " . $msg->{from} . "\n" .
		"Subject: " . $msg->{subject} . "\n\n" .
		$msg->{message};
		close(MAIL);
	}
	else
	{	$self->fatal_msg("Could not send mail: $!");
	}
}


sub convert_form_values {
	my $self = shift;
	my $data = shift;
	
	my $prvw_data;
	foreach (keys %$data)
	{	$prvw_data->{$_} = $data->{$_};
	}

	# change the data so we can create a ToolPreview object out of it
	if ($prvw_data->{tool_type})
	{	#$self->debugme("prvw_data->{tool_type}: ".Dumper($prvw_data->{tool_type}));
		foreach (@{$prvw_data->{tool_type}})
		{	$prvw_data->{$_} = 'true';
		}
		delete $prvw_data->{tool_type};
	}

	if ($prvw_data->{is_open_source} && $prvw_data->{is_open_source} == 'false')
	{	delete $prvw_data->{is_open_source};
	}

	return $prvw_data;
}


1;