#!/usr/bin/perl
#
# Catch22 (llama@osoal.org.nz)
# One Short Of A Llama [http://web.osoal.org.nz]

#
use strict;
use DBI;
use OneShortOfALlama;

#
my($load, %methods, %finger);

#
$load    = 50;
%methods = (
			'Comments'   => \&search_comments,
			'News'       => \&search_news,
			'Polls',     => \&search_polls,
			'History'    => \&search_history,
			'Manifest'   => \&search_manifest,
			'Finger'     => \&search_finger,
			'Horoscopes' => \&search_horoscopes,
			'Bottles'    => \&search_bottles
            );
%finger = (
		   'DeadBeef'    => 'lincoln',
		   'Kismet'      => 'david',
		   'Catch22'     => 'llama',
		   'maharG'      => 'graham',
		   'ZenSandwich' => 'colin'
		   );

#
sub parse
{
    my($patterns, @buf, $i, $ptr, $head, $length, $delim);

    #
    @buf      = split(//, shift(@_));
    $patterns = shift(@_);

    $length = scalar(@buf);
    $head   = 0;

    for ($i = 0; $i < $length; $i++)
    {
		# quote char
		if ($buf[$i] eq '\\')
		{
			$i++;

			if (!$ptr && $buf[$i] =~ m/\S/)
			{
				$ptr = $patterns->{'optional'};
				$delim = ' ';
			}
		}

		# end of pattern
		elsif ($buf[$i] eq $delim)
		{
			if ($delim ne ' ')
			{
				$i++;
			}

			$delim = '';
			next;
		}

		# new pattern
		elsif (!$delim)
		{
			if ($buf[$i] =~ m/\s/)
			{
				# eat whitespace
				next;
			}

			if ($buf[$i] eq '+')
			{
				$ptr = $patterns->{'required'};
				$i++;
			}
			elsif ($buf[$i] eq '-')
			{
				$ptr = $patterns->{'exclude'};
				$i++;
			}
			else
			{
				$ptr = $patterns->{'optional'};
			}
			
			$head = scalar(@$ptr);
			
			if ($buf[$i] eq '"' || $buf[$i] eq '\'')
			{
				$delim = $buf[$i];
				next;
			}
			else
			{
				$delim = ' ';
			}
		}

		if ($ptr)
		{
			$ptr->[$head] .= $buf[$i];
		}
    }

	#
    if ($delim && $delim ne ' ')
    {
		print("(Unterminated delimiter: ($delim))<br>");
    }
}

sub search
{
    my($patterns, $matches, $buf);

	#
    $buf      = shift(@_);
	$patterns = shift(@_);
    $matches  = 0;

    study($buf);

    #
    if (scalar(@{$patterns->{'required'}}))
    {
		$matches = match($buf, $patterns->{'required'});

		unless ($matches == scalar(@{$patterns->{'required'}}))
		{
			return (0);
		}
    }

    #
    if (scalar(@{$patterns->{'exclude'}}) && match($buf, $patterns->{'exclude'}))
    {
		return (0);
    }

    #
    if (scalar(@{$patterns->{'optional'}}))
    {
		$matches += match($buf, $patterns->{'optional'});
    }

    return ($matches);
}

sub match
{
    my($buf, $i, $pattern, $length, $matches);

    #
    $buf     = shift(@_);
    $pattern = shift(@_);
    $length  = scalar(@$pattern);
    $matches = 0;

    #
    for ($i = 0; $i < $length; $i++)
    {
		if ($$buf =~ m/\Q$pattern->[$i]\E/i)
		{
			$matches++;
		}
    }

    return ($matches);
}


#
#
#

sub search_comments
{
	my($dbh, $patterns, $load,
	   $sth, $matches, $i, $buf, $thread, $pid, $nickname, $time, $subject, $comment);

	#
	$dbh      = shift(@_);
	$patterns = shift(@_);
	$load     = shift(@_);
	$matches  = 0;

	#
    $sth = $dbh->prepare('select pid, thread, nickname, from_unixtime(time, "%W %D %M %l:%i %p, %Y"),
                                subject, comment
                          from osoal.comments
                          order by pid desc' . ($load > -1 ? " limit $load" : ''));
    $sth->execute();

    #
    if ($sth->rows())
    {
		$sth->bind_columns(undef(), \$pid, \$thread, \$nickname, \$time, \$subject, \$comment);

		while ($sth->fetch())
		{
			$comment =~ tr/\n/ /;
			$buf     = join(' ', $nickname, $subject, $comment);

			if ($i = search(\$buf, $patterns))
			{
				$matches++;
				$load = $dbh->selectrow_array("select count(*) from osoal.comments
                                               where (thread = $thread && pid >= $pid)");
				
				print('<small><b>', $matches, '.</b></small>
                       <a href="/comments.pl?pid=', $pid, '&load=', ($load > 10) ? $load : 10, '#', $pid,
					  '"><b>', $nickname, '</b></a> <em>', $subject, '</em> <small>on ',
					  $time, '</small> <b>(', $i, ')</b><br><br>', "\n");
			}
		}
    }

    #
    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);

}

sub search_news
{
    my($patterns, $dbh, $load, $sth, $matches, $i,
	   $buf, $pid, $nickname, $time, $subject, $news);

	#
	$dbh      = shift(@_);
	$patterns = shift(@_);
	$load     = shift(@_);
	$matches  = 0;

    #
    $sth = $dbh->prepare('select pid, nickname, from_unixtime(time, "%W %D %M %l:%i %p, %Y"),
                          subject, news from osoal.news
                          order by pid desc' . ($load > -1 ? " limit $load" : ''));
    $sth->execute();

    if ($sth->rows())
    {
		$sth->bind_columns(undef(), \$pid, \$nickname, \$time, \$subject, \$news);

		while ($sth->fetch())
		{
			$news =~ tr/\n/ /;
			$buf  = join(' ', $nickname, $subject, $news);

			if ($i = search(\$buf, $patterns))
			{
				$matches++;
				
				print('<small><b>', $matches, '.</b></small>
                       <a href="/news.pl#', $pid, '"><b>', $nickname, '</b></a>
                       <em>', $subject, '</em> <small>on ', $time, '</small> <b>(', $i, ')</b><br>
                       <br>', "\n");
			}
		}
    }

    #
    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);

}

sub search_polls
{
    my($dbh, $patterns, $load, $qsth, $asth, $matches, $i,
	   $buf, $answer, $pid, $question, $qvotes, $time, @answers);

	#
	$dbh      = shift(@_);
	$patterns = shift(@_);
	$load     = shift(@_);
	$matches  = 0;

	#
    $qsth = $dbh->prepare('select pid, question, votes, from_unixtime(time, "%D %M, %Y")
                           from osoal.pollquestions
                           order by time desc' . ($load > -1 ? " limit $load" : ''));
    $qsth->execute();

    if ($qsth->rows())
    {
		while (($pid, $question, $qvotes, $time) = $qsth->fetchrow_array())
		{
			$asth = $dbh->prepare("select answer from osoal.pollanswers
                                   where (pid = '$pid') order by aid asc");
			$asth->execute();

			#
			unless ($asth->rows())
			{
				next;
			}

			$buf = $question;

			#
			while ($answer = $asth->fetchrow_array())
			{
				$buf .= " $answer";
			}

			if ($i = search(\$buf, $patterns))
			{
				$matches++;
				$question =~ s/<[^>]*>//g;

				print('<small><b>', $matches, '.</b></small>
                       <a href="/vote.pl?pid=', $pid, '"><b>', $question, '</b></a>
                       (', $qvotes, ' vote', ($qvotes == 1) ? '' : 's', ') <small>on ', $time, '</small> <b>(', $i , ')</b><br>
                       <br>', "\n");
			}
		}
    }

    #
    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);
}

sub search_history
{
    my($dbh, $patterns, $load, $sth, $matches, $i, $buf,
       $title, $url, $author, $subject, $time, $history);

	#
	$dbh      = shift(@_);
	$patterns = shift(@_);
	$load     = shift(@_);
	$matches  = 0;

	#
    $sth = $dbh->prepare('select title, url, author, subject, from_unixtime(time, "%D %M, %Y"), history
                          from osoal.history' . ($load > -1 ? " limit $load" : ''));
    $sth->execute();

    if ($sth->rows())
    {
		$sth->bind_columns(undef(), \$title, \$url, \$author, \$subject, \$time, \$history);

		while ($sth->fetch())
		{
			$buf = join(' ', $title, $author, $history);

			if ($i = search(\$buf, $patterns))
			{
				$matches++;

				print('<small><b>', $matches, '.</b></small> <a href="', $url, '">
                       <b>', $title, '</b></a> by ', $author, ' on ', $time, ' 
                       <b>(', $i, ')</b><br>  <br>', "\n");
			}
		}
    }

    #
    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);
}

sub search_manifest
{
    my($dbh, $patterns, $load, $sth, $matches, $i, $buf,
       $msth, $psth, $location, $title, $description, $time, $filename, $filedesc);

	#
	$dbh      = shift(@_);
	$patterns = shift(@_);
	$load     = shift(@_);
	$matches  = 0;

	#
    $msth = $dbh->prepare('select location, title, description, from_unixtime(time, "%M %D, %Y")
                           from osoal.manifest
                           order by time desc' . ($load > -1 ? " limit $load" : ''));
    $msth->execute();

    if ($msth->rows())
    {
		$msth->bind_columns(undef(), \$location, \$title, \$description, \$time);

		while ($msth->fetch())
		{
			$psth = $dbh->prepare("select file, description from osoal.manifestpackages
                                   where (location = '$location')");
			$psth->execute();
			
			unless ($psth->rows())
			{
				next;
			}

			$buf = "$title $description";

			while (($filename, $filedesc) = $psth->fetchrow_array())
			{
				$buf .= " $filename $filedesc";
			}

			if ($i = search(\$buf, $patterns))
			{
				$matches++;
				
				print('<small><b>', $matches, '.</b></small>
                       <a href="/manifest.pl#', $location, '"><b>', $title, '</b></a> ', $time, ' 
                       <b>(', $i, ')</b><br><br>', "\n");
			}
		}
    }

    #
    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);
}

sub search_finger
{
	my($dbh, $patterns, $load, $sth, $matches, $i, $buf,
	   $nickname, $dir, $time, $day, $date, $month, $year, $ending);
    local(*PLAN);

    #
	$dbh      = shift(@_);
	$patterns = shift(@_);
    $load     = shift(@_);
    $matches  = 0;

    #
    while (($nickname, $dir) = each(%finger))
    {
		unless (-e "/home/$dir/.plan")
		{
			next;
		}

		$buf = $nickname;

		open(PLAN, "/home/$dir/.plan")
			or next;
		
		for ($i = $load; $i; $i--)
		{
			if (eof(PLAN))
			{
				last;
			}
			
			$buf .= ' ' . readline(*PLAN);
		}

		close(PLAN);

		$buf =~ tr/\n/ /;
		$buf =~ s/ +/ /g;

		if ($i = search(\$buf, $patterns))
		{
			$matches++;
			
			(undef, undef, undef, undef, undef, undef, undef,
			 undef, undef, $time, undef, undef, undef) = stat("/home/$dir/.plan");
			(undef, undef, undef, $date, $month, $year, $day, undef, undef) = localtime($time);

			if ($date < 11 || $date > 13)
			{
				$ending = $date % 10;

				if ($ending > 3)
				{
					$ending = 0;
				}
			}
			else
			{
				$ending = 0;
			}

			$day   = ('Sunday', 'Monday', 'Tueday', 'Wednesday', 'Thursday', 'Friday', 'Saturday')[$day];
			$month = ('January', 'Febuary', 'March', 'April', 'May', 'June', 'July', 'August',
					  'Octpber', 'September', 'November', 'December')[$month];
			$ending = ('th', 'st', 'nd', 'rd')[$ending];


			$time = sprintf("%s %d%s %s, %d", $day, $date, $ending, $month, $year + 1900);

			#
			print('<small><b>', $matches, '.</b></small>
                   <a href="/finger.pl?user=', $nickname, '"><b>', $nickname, '</b></a> ', $time, ' 
                   <b>(', $i, ')</b><br><br>', "\n\n");
		}
    }

    #
    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);
}

sub search_horoscopes
{
    my($dbh, $patterns, $load, $matches, $id,
	   $date, $i, $buf, $sth, @horoscopes);

	#
	$dbh      = shift(@_);
	$patterns = shift(@_);
	$load     = shift(@_);
	$matches  = 0;

	#
	$sth = $dbh->prepare('select id, from_unixtime(time, "%W %D %M, %Y"), aries, libra, taurus, scorpio,
                                 gemini, sagittarius, cancer, capricorn, leo, aquarius, virgo, pisces
                          from osoal.horoscopes
                          order by id desc' . ($load > -1 ? " limit $load" : ''));
	$sth->execute();

	#
	if ($sth->rows())
	{
		while (($id, $date, @horoscopes) = $sth->fetchrow_array())
		{
			$buf = join(' ', @horoscopes);
			$buf =~ s/\n/ /g;

			if ($i = search(\$buf, $patterns))
			{
				$matches++;

				print('<small><b>', $matches, '.</b></small>
                       <a href="/horoscopes.pl?id=', $id, '"><b>', $date, '</b></a> <b>(', $i, ')</b><br>
                       <br>', "\n\n");
			}
		}
	}

    #
    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);
}

sub search_bottles
{
	my($dbh, $patterns, $load, $matches, $i, $sth, $bottle, $drink, $alcohol, $comment, $date, $buf);

	$dbh      = shift(@_);
	$patterns = shift(@_);
	$load     = shift(@_);
	$matches  = 0;

	$sth = $dbh->prepare('select bottle, from_unixtime(time, "%W %D %M, %Y"), drink, alcohol, comment
                          from osoal.bottles
                          order by bottle desc' . ($load > -1 ? " limit $load" : ''));
	$sth->execute();
	
	if ($sth->rows())
	{
		$sth->bind_columns(\$bottle, \$date, \$drink, \$alcohol, \$comment);

		while ($sth->fetch())
		{
			$buf = join(' ', $drink, sprintf('%.1f%%', $alcohol), $comment);

			if ($i = search(\$buf, $patterns))
			{
				$matches++;

				print('<small><b>', $matches, '.</b></small>
                       <a href="/bottles.pl?option=Focus&bottle=', $bottle, '"><b>', $drink, '</b></a>
                       (', sprintf('%.1f%%', $alcohol), ') on ', $date, ' <b>(', $i, ')</b><br><br>', "\n\n");
			}
		}
	}

    unless ($matches)
    {
		print('(nothing)', "\n");
    }

    return ($matches);
}

#
#
#

sub main
{
	my($param, $matched, $buf, $dbh, %patterns, @required, @optional, @exclude);

	#
	$param    = variables();
	$matched  = 0;
	%patterns = (
		'required' => \@required,
		'optional' => \@optional,
		'exclude'  => \@exclude
	);

	#
	header('Search', 'search.png', '/');
    print('<div align="center">
           <table width="600" cellpadding="0" cellspacing="0" border="0">
           <tr>
           <td>', "\n\n");

        #
    unless ($param->{'load'} =~ m/^\d+$/ || $param->{'load'} == -1)
    {
		$param->{'load'} = $load;
    }

    if ($param->{'search'})
    {
		parse($param->{'search'}, \%patterns);
		$param->{'search'} =~ s/\"/&quot;/g;
    }

    #
    #
    #

    if ($dbh = DBI->connect("dbi:mysql:$MySQLserver;database=osoal",
				$MySQLuser, $MySQLpasswd))
    {
		print('<form method="POST" action="', $ENV{'SCRIPT_NAME'}, '">
               <div align="center">
               <table>
               <tr>

               <td>
                   <input type="text" name="search" value="', $param->{'search'}, '" size="35"><br>
                   <b><input type="submit" value="Search"></b>
                   <input type="text" name="load" value="', $param->{'load'}, '" size="3">
                   <b>Articles In Each Location</b>
               </td>

               <td>
               <select name="location" multiple>', "\n");

        #
        for $buf (keys(%methods))
        {
            print('<option');

            if ($param->{'location'} =~ m/$buf/)
            {
				print(' selected');
            }

            print('>', $buf, '</option>', "\n");
        }

        #
        print('</select>
               </td>
               </tr>
               </table>
               </div>
               </form>', "\n\n");

        #
		if ($param->{'search'} && $param->{'location'})
		{
			if (@required || @optional)
			{
				for $buf (keys(%methods))
				{
					if ($param->{'location'} =~ m/$buf/)
					{
						print('<li><b>', $buf, '</b></li>
                               <ul>', "\n\n");

						$matched += $methods{$buf}->($dbh, \%patterns, $param->{'load'});
						print('</ul>', "\n\n");
					}
				}

				if ($matched)
				{
					print('<br>
                           (', $matched, ' match', ($matched  == 1 ? '' : 'es'), ')', "\n\n");
				}
			}

			else
			{
				print('(Atleast one positive match field is required)');
			}
		}
		else
		{
			print('<li>Search patterns are supplied in the form:</li>
                   <ul>
                        <code>[+|-]["|\']text["|\']</code>
                   </ul>

                   Where the characters inbetween the <code>[]</code> brackets are optional. You can supply as
                   many patterns as you want, patterns are treated as cases insensitive so <b>beer</b>
                   will match <b>Beer</b> and <b>bEeR</b> etc.
                   <br><br>
                   <li>Operators:</li>

                   <ul>
                       <b>(")</b> or <b>(\')</b> The grouping operator;
                           By default search patterns are parsed as whitespace delimited strings,
                           to group words together to form a phrase surround the text with quotes.
                           For example; to search for the phrase <b>One Short Of A Llama</b>, enter:
                           <b>"One Short Of A Llama"</b> into the search field.
                           <br><br>

                       <b>(+)</b> The require operator; If you dont specify an operator for a pattern
                           it is considered to be optional, to require a pattern to be in the article
                           prefix it with this operator. For example, to get articles containing the
                           words <b>Linux</b> and <b>Apache</b>  enter <b>+Linux +Apache</b> into the
                           search field.
                           <br><br>

                       <b>(-)</b> The exclude operator; all articles returned will not contain this
                           pattern, ie; to find occurances of the phrase <b>One Short Of A Llama</b>
                           without the word <b>drinking</b>, enter:
                           <b>"One Short Of A Llama" -drinking</b> into the search field.
                           <br><br>

                       <b>(\\)</b> The quote operator; To remove the special meanings from any of the
                       operators, prefix it with a <b>\\</b>.
                          
                   </ul>', "\n\n");
		}

		#
		$dbh->disconnect();
    }

	#
    print('</td>
           </tr>
           </table>
           </div>', "\n\n");
	footer();
}

#
main();
