package GO::Tool::ReferencesTool;
use strict;
use lib 'go/scratch/tools';
use base 'GO::Tool::GenericGoTool';

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

use GO::Object::Reference;
use GO::Object::Xref;
use GO::TestSet qw(dfv_test);
use GO::Utilities qw(:all);
use GO::GeneralPurposeParser;
use URI::Escape;
#use Utility::TSLParser;

sub _specification {
	my $self = shift;

	return (
		'sort', {
			test => dfv_test( 'is_in_list_p', { list => [ 'go_ref_id' ], this => 1 } ),
			default => 'go_ref_id',
		},
	
		'id', {
			view_single => {
				test => qr/^GO_REF:\d{7}$/,
				required => 1,
				allow_multiple => 1,
			},
		},
	
		'page', {
			test => qr/^\d+$/,  # must be a number
			default => '1',
		},
	
		'page_size', {
			test => qr/^(\d+|all)$/,  # must be a number or all
			default => 'all',
		},

#		rm => {
#			test => qr/^view_(all|single)$/,
#			default => sub {
#				my $dfvr = shift;
#				my $input = $dfvr->get_input_data( as_hashref => 1 );
#				if ( $input->{id})
#				{	return 'view_single'; }
#				else
#				{	return 'view_all'; }
#			},
#			dependencies => {
#				"view_single" => [ qw( id ) ],
#			}
#		},
	);
}


### stuff we're going to need
### reference db file
my $ref_db = "/Users/gwg/go/doc/GO.references";

### the references master, official adder of references to the GO references db
### new references will be sent by email to this address for checking
my $ref_master = "";


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


## override the default just to speed things up
#sub _runmode_specific_data {
#	my $self = shift;
#	return [ 'go_ref_id' ];
#}


sub set_run_mode {
	my $self = shift;
	my %cgi_h = $self->query->Vars;
	return 'view_single' if exists $cgi_h{id};
	return 'view_all';
}


# 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
sub cgiapp_prerun {
	my $self = shift;
	$self->startme;
	$self->debugme("current runmode: ". $self->get_current_runmode);

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

	if (!$results->success) 
	{	#	set the mode to 'error'
		$self->prerun_mode('fatal_error');
		#	create an error message
		my $err_str;
		if ($self->get_current_runmode eq 'view_single' && ! $results->valid('id') )
		{	if ($results->missing('id'))
			{	$err_str = 'You have not entered a query';
			}
			elsif ($results->invalid('id'))
			{	$err_str = 'You have entered an invalid ID';
			}
		}
		if (!$err_str)
		{	$err_str = join "<br>", @{ GO::Utilities::summarize_errors($results) };
		}
		$self->fatal_msg($err_str);
	}
}


sub view_single {
	my $self = shift;
	return $self->retrieve_data({ single => 1 });
}

sub view_all {
	my $self = shift;
	return $self->retrieve_data;
}


=head2 retrieve_data

Function to get data from the DB, either for a single ref or the whole lot.

input: $self, optional flag which will trigger a search for an ID if present;
otherwise, the operation will get all refs.

=cut

sub retrieve_data {
	my $self = shift;
	$self->startme;
	my $options = shift;

	my $output;

	# get the checked query
	my $query_h = $self->param('validated_query');
	# get the parser ready for action
	my $parser_args = {
		file => $ref_db,
		file_separator => "\n\n",
		inner_parser => {
			parser => 'multi_field_tag_value',
			return_record_as_object => 'GO::Object::Reference',
			check_input => 1,
			filter_field => qr/^[^!]/,
		},
	};

	if ($options->{single})  # this is a single GO REF query
	{	#	create the search query using the data in parsed query data
		#	the param we need in the query is id
		#	we should check alt_id too

		# in case we have more than one go ref, turn the query into a list
		$query_h->{id} = [ $query_h->{id} ] if ! ref($query_h->{id});
		
		# create the query
		# check for $query_h->{id} appearing as a go_ref_id or alt_id
		# ignore any record which doesn't feature $query_h->{id} somewhere
		$parser_args->{inner_parser}{filter_record} = [ map { qr/(go_ref|alt)_id: $_\b/i } @{$query_h->{id}} ];

	}

#	load the references; dies if no references present
	my $ref_arr = $self->get_data_from_file($parser_args);

	# check we found all that we wanted to find
	if ($options->{single})
	{	# a query for one (or more) references
		if (!$ref_arr || scalar @{$query_h->{id}} != scalar @$ref_arr)
		{	my $lost_and_found = GO::Utilities::check_list_for_values({ 
				query_list => $query_h->{id}, results => $ref_arr, 
				test => sub { 
					my ($r, $query) = @_; 
					return 1 if $r->go_ref_id eq $query;
					return 0 if ! $r->alt_id;
					if (!ref($r->alt_id))
					{	return 1 if $r->alt_id eq $query;
						return 0;
					}
					foreach ( $r->alt_id )
					{	return 1 if $_ eq $query;
					}
					return 0;
				},
			});
			
			$self->printerr("lost and found: ".Dumper($lost_and_found));

			# let's have a look at our error list. If the last message was the parser
			# complaining that it had no results, delete it and replace it with
			# a custom message.

			my $last_msg = $self->get_last_msg;
			if ($last_msg && $last_msg->{MSG_CODE} && $last_msg->{MSG_CODE} eq 'no_results')
			{	$self->clear_last_msg;
			}
			
			my $msg;
			my $last = pop @{$lost_and_found->{missing}};
			if (scalar (@{$lost_and_found->{missing}}) != 0)
			{	$msg = "There are no GO references with the IDs " . 
				join(", ", @{$lost_and_found->{missing}}) . " or " . $last . ".";
			}
			else
			{	$msg = "There is no GO reference with the ID " . $last . ".";
			}
			if (!$ref_arr)
			{	$self->fatal_msg({ MSG_CODE => 'no_results', MSG => $msg });
			}
			else
			{	$self->warning_msg({ MSG => $msg });
			}
		}
	}
	elsif (!$ref_arr || !@$ref_arr)
	{	# no results at all. Oh no!
		$self->printerr("FATAL: No reference data found in $ref_db");
		$self->clear_all_msgs;
		$self->fatal_err("No valid data could be found. Please check $ref_db and try again.");
	}
	
	if ($ref_arr)
	{	# get the sorted list
		my $sorted_list = GO::Utilities::sort_list({ list => $ref_arr, crit => [ $query_h->{'sort'} ] });
	
		#	get the subset of the results, if appropriate
		my $paged = get_results_chunk($sorted_list, { chunk_n => $query_h->{page}, chunk_size => $query_h->{page_size} });
		$output->{ref_arr} = $paged->{subset};
		$output->{n_pages} = $paged->{n_chunks};
		$output->{page} = $paged->{chunk_n};
	}
	
	# set up all the other stuff and output the template
	return $self->tt_process('view_reference.tmpl', $self->set_up_output($output)) || die "Template toolkit messed up, the bastard!: ", $self->tt_obj->error(), "Dying";
}


sub set_up_output {
	my $self = shift;
	$self->startme;
	my $tmpl_vars = shift || {};

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

	$tmpl_vars = 
	{	%$tmpl_vars,
		base_url => 'http://127.0.0.1/cgi-bin/references.cgi',
		install_dir => 'http://127.0.0.1/go/',
	};

#	$self->debugme("tmpl_vars: ".Dumper($tmpl_vars));

	if ($tmpl_vars->{n_pages} && $tmpl_vars->{n_pages} > 1)
	{	### set up URLs for paging, etc.
		my $url_h;
		my $spec = $self->get_spec;
		foreach my $p (keys %$spec)
		{	# if the parameter is set AND there's no default or the parameter differs
			# from the default, add the param to the URL
			if ($tmpl_vars->{query_h}{$p} && 
					(!$spec->{$p}{default} || # no default
					($spec->{$p}{default} &&  # default, param differs from default
						$spec->{$p}{default} ne $tmpl_vars->{query_h}{$p}) )
				)
			{	$url_h->{$p} = uri_escape($tmpl_vars->{query_h}{$p});
			}
		}

		# paging URLs: need everything except page number
		delete $url_h->{page} if $url_h->{page};
		$tmpl_vars->{url_for_paging} = join( "&amp;",
			map { my $k = $_;
				if (ref($url_h->{$k}) eq 'ARRAY')
				{	join( "&amp;", 
						map { $k . "=" . $_ } @{$url_h->{$k}} );
				}
				else
				{	$k . "=" . $url_h->{$k};
				}
			} keys %$url_h );
		$self->debugme("url for paging: ".Dumper($tmpl_vars->{url_for_paging}));
	}

	return $tmpl_vars;
}

1;