# -------------------------------------------------------------------------------------
# flo::plugin::Search
# -------------------------------------------------------------------------------------
#
#       Author : Jean-Michel Hiver (jhiver@cisedi.com).
#    Copyright : (c) MKDoc Holdings Ltd, 2001
# 
#      Unauthorized modification, use, reuse, distribution or redistribution
#      of this module is stricly forbidden
#
#    Description:
#
#      A plugin that deals with search requests and builds search result pages
#
# -------------------------------------------------------------------------------------
package flo::plugin::Search;
use flo::Object;
use lib::sql::Table;
use lib::Exception;
use lib::Config;
use lib::cgi::CGI;
use flo::Standard;
use flo::Template;
use flo::HTTP::Header;
use flo::CGI;
use strict;
use vars qw /@ISA/;
@ISA = qw /flo::Object/;


##
# $class->main;
# -------------
#   Processes the request
##
sub main
{
    my $class = shift;
    my $cgi   = cgi;
    
    # if this module is not concerned, let's return ASAP
    my $config = $class->config;
    my $http_name = $config->{http_name};
    my $q_http_name = quotemeta ($http_name);
    my $raw_path_info = raw_path_info;
    return unless ( ($raw_path_info =~ /.*\/$q_http_name$/o) );
    
    # we want absolute URIs for the search results
    $flo::CGI::FULL_URI = 1;
 
    # otherwise, let's process the request
    $raw_path_info =~ s/$q_http_name$//;
    
    # grabs the current document we're working on. If the document doesn't
    # exist, let's return (which should trigger the flo::plugin::Error module)
    my $document_table = table ('Document');
    my $document = $document_table->get ( Full_Path => $raw_path_info ) or return;
    
    # $hash will be passed to the template module
    # initializes $hash->{Document} with the current document we just grabbed
    my $hash = {};
    $hash->{Document} = $document;
    
    # computes the 'home' URI
    my $cgix = $cgi->new;
    $cgix->path_info ('/');
    foreach ($cgix->param) { $cgix->delete ($_) }
    $hash->{URI} = {};
    $hash->{URI}->{Home} = $cgix->self_url;
    
    # computes 'search' URI
    $hash->{URI}->{Search} = $cgi->self_url;
    $hash->{Is_Root} = ($document->{Full_Path} eq '/') ? 1 : 0;
    
    # computes keywords for that search
    my $keywords = $cgi->param ('q') || "";
    $keywords .= " " . $cgi->param ('narrow') if ($cgi->param ('narrow'));
    
    # retrieve results sorted by relevance order
    my @search = sort { $b->[1] <=> $a->[1] } $document_table->fast_search ($keywords);
    
    # number of document which matched
    my $number_of_matches = @search;
    
    # that is to initialize the search form
    $hash->{Query} = $cgi->param ('q') || "";
    
    # total number of matching documents
    $hash->{Number_Of_Matches} = $number_of_matches;
    
    $class->narrow_option ($hash);
    
    if ($number_of_matches)
    {
	# used to computes scores (0-100%)
	my $max_weight = $search[0]->[1];
	
	# number of records per page
	my $number_of_records_per_page = $cgi->param ('per_page') || $config->{'per_page'};
	$number_of_records_per_page = $config->get ('per_page') if ($number_of_records_per_page !~ /^[0-9]+$/);
	$hash->{Number_Of_Records_Per_Page} = $number_of_records_per_page;
	
	# number of result pages
	my $number_of_result_pages = undef;
	if ($number_of_matches % $number_of_records_per_page)
	{
	    $number_of_result_pages = int ($number_of_matches / $number_of_records_per_page) + 1;
	}
	else
	{
	    $number_of_result_pages = $number_of_matches / $number_of_records_per_page;
	}
	$hash->{Number_Of_Result_Pages} = $number_of_result_pages;
	
	# current page
	my $current_page = $cgi->param ('page') || '';
	$current_page = 1 if ($current_page !~ /^[0-9]+$/);
	$current_page = $number_of_result_pages if ($current_page > $number_of_result_pages);
	$hash->{Current_Page_Number} = $current_page;
	
	# grabs the results in which we are interested into
	$class->get_results ($hash, $max_weight, \@search);
	
	# constructs the navigation bar between the results
	$class->navigation_bar ($hash, \@search);
    }
    
    my @per_page_loop = ();
    for my $ppl (qw /10 20 50/)
    {
	if (defined $cgi->param ('per_page') and $cgi->param ('per_page') == $ppl)
	{
	    unshift ( @per_page_loop, { __SELECTED__ => 1, value => $ppl } );
	}
	else
	{
	    push ( @per_page_loop, { __SELECTED__ => 0, value => $ppl } );
	}
    }
    $hash->{per_page_loop} = \@per_page_loop;
    
  flo::HTTP::Header->out
      ( new flo::Template ( file => template ('search', $document->{Lang}),
			    hash => $hash ) );
    return 'TERMINATE';
}


##
# $class->narrow_option ($hash);
# ------------------------------
#   When we are on a document, we want to be able to narrow the
#   searches from this document, or from its parent, or from its
#   grand-parent, etc...
#
#   This subroutine creates the structure that is used by the
#   template to build the drop-down form which allows that
##
sub narrow_option
{
    my $class = shift;
    my $hash  = shift;
    
    my $config = $class->config();
    my $http_name = $config->{http_name};
    my $q_http_name = quotemeta ($http_name);
    my $raw_path_info = raw_path_info;
    $raw_path_info =~ /.*\/$q_http_name$/o;
    my $path  = $raw_path_info;
    $path =~ s/^\///;
    $path =~ s/\/$//;
    my @path  = split /\//, $path;
    pop (@path) if ($path[$#path] =~ /\./);
    my @result = ();
    while (@path)
    {
	unshift (@result, { Path => '/' . (join '/', @path) . '/' });
	pop (@path);
    }
    
    $hash->{Narrow_Options} = \@result;
}


##
# $class->navigation_bar ($hash);
# -------------------------------
#   Adds a structure to $hash that will let the template build
#   a navigation bar which the user will be able to use in order
#   to browse through the results pages (i.e page: 1 2 3 ... xx)
##
sub navigation_bar
{
    my $class = shift;
    my $hash  = shift;
    
    my $number_of_pages = $hash->{Number_Of_Result_Pages};
    my $current_page    = $hash->{Current_Page_Number};
    
    my $left_space  = $current_page - 1;
    my $right_space = $number_of_pages - $current_page;
    $left_space  = 10 if ($left_space > 10);
    $right_space = 10 if ($right_space > 10);
    
    my $cgix = cgi->new;
    my @res  = ();
    
    foreach my $page ( $current_page - $left_space .. $current_page - 1)
    {
	$cgix->param ('page', $page);
	push @res, { Page => $page, URI => $cgix->self_url };
    }
    
    push @res, { Page => $current_page, URI => '' };
    
    foreach my $page ( $current_page + 1 .. $current_page + $right_space )
    {
	$cgix->param ('page', $page);
	push @res, { Page => $page, URI => $cgix->self_url };
    }
    
    if (@res == 1) { $hash->{Navigation_Bar} = [] }
    else { $hash->{Navigation_Bar} = \@res }
}


##
# $class->get_results ($hash, $max_weight);
# ------------------------------------------
#   Grabs the results for the search, the best result having
#   a maximum weight of $max_weight, and adds the results into
#   $hash for the template to play with
##
sub get_results
{
    my $class = shift;
    my $hash = shift;
    my $max_weight = shift;
    my @search = @{shift()};
    
    my $page_number = $hash->{Current_Page_Number};
    my $thickness   = $hash->{Number_Of_Records_Per_Page};
    
    # computes start and stop indexes, which represent
    # the portion of search results which we're interested
    # in displaying.
    my $start_index = ($page_number - 1) * $thickness;
    my $stop_index  = $start_index + $thickness - 1;
    $stop_index = $#search if ($stop_index >= @search);
    
    # list of documents which we want to grab for our
    # result array.
    my @to_grab = @search[$start_index..$stop_index];
    
    # retrieves all the records which we're interested into.
    my $document_table = table ('Document');
    my $condition = new lib::sql::Condition;
    $condition->boolean ('OR');
    foreach my $to_grab (@to_grab)
    {
	$condition->add ('ID', '=', $to_grab->[0]);
    }
    
    my @records = $document_table->select ( cols => [ qw /ID Description Full_Path Title Lang/ ],
					    where => $condition )->fetch_all;
    
    my %records = map { $_->{ID} => $_ } @records;
    
    my @res  = ();
    my $cgix = cgi->new;
    for ($cgix->param) { $cgix->delete ($_) }
    
    foreach my $to_grab (@to_grab)
    {
	# pulls the record from the result pool.
	my $record = $records{$to_grab->[0]};
	
	# computes the document URI.
	$cgix->path_info ($record->{Full_Path});
	$record->{URI} = $cgix->self_url;
	
	# computes the document score.
	$record->{Score} = int (100 * ($to_grab->[1] / $max_weight));
	push @res, $record;
    }
    
    $hash->{Result} = \@res;
}


1;


