#!/usr/bin/perl

use strict;
use warnings;
use CGI;
use HTML::Entities;
use Net::LDAP;

my $CGI = new CGI;
my $s = $CGI->param("s");
my $prefix = "";

if ($s =~ /^(\S+\@[\w.-]+,\s*)+/)
{
	$prefix = $&;
	$s = $';
}
$s = lc $s;

my $LDAP_SERVER = "ldap.example.com";
my $LDAP_BASE = "dc=example,dc=com";
my $LDAP_USER = "cn=admin,dc=example,dc=com";
my $LDAP_PASS = "secret";

print $CGI->header("text/xml");
print qq|<body>
<div id="popup">
|;

sub do_error
{
	print qq|<div class="error">|;
	print encode_entities(join("", @_));
	print qq|</div>\n|;
	print qq|</div>\n|;
	exit;
}

my $results = build_results($s);

use Cache::FileCache;
our $cache;

sub build_results
{
	my $s = shift;

	$cache ||= new Cache::FileCache( {
		namespace => "RT_AutoComplete",
		default_expires_in => "7 days" } );
	my $results = $cache->get($s);

	if (not defined $results)
	{
		$results = perform_query($s);
		$cache->set($s, $results);
	}
	return $results;
}

#print "<!-- filter: $filter -->\n";
print qq|<table>
	<tr>
	<th align="left">Email</th>
	<th align="left">Full Name</th>
	<th align="left">Phone</th>
	</tr>
|;

my $last_class;
foreach my $entry (@{$results})
{
	$last_class ||= $entry->{class};
	my $row_extra = "";
	if ($entry->{class} ne $last_class)
	{
		$row_extra = qq| style="border-top: 1px solid gray"|;

		$last_class = $entry->{class};
	}

	print qq|
	<tr>
	<td$row_extra><li value="|
		. encode_entities($prefix . $entry->{email}) . qq|">|
		. encode_entities($entry->{email}) . qq|</li></td>
	<td$row_extra>| . highlight_match($s, $entry->{full_name}) . qq|</td>
	<td$row_extra>| . highlight_match($s, $entry->{phone}) . qq|</td>
	</tr>
|;
}

print qq|</table>
</div>
</body>
|;

sub perform_query
{
	my $s = shift;

	my $ldap = Net::LDAP->new($LDAP_SERVER)
		or do_error "Cannot connect to ldap server $LDAP_SERVER.\n";

	my $result = $ldap->bind($LDAP_USER, password => $LDAP_PASS);
	$result->code == 0
		or do_error "Cannot bind... " . $result->error . "\n";

	$s =~ s/[^a-zA-Z0-9., @-]//g;
	if (length($s) < 2)
	{
		do_error "Query not long enough";
	}

	my $filter = "(|(mail=$s*)(fullName=$s*)(sn=$s*))";
	if ($s =~ /^([^,]+),\s*(.*)$/)
	{
		$filter = "(&(sn=$1)(fullName=$2*))";
	}

	#my $filter = "(|(mail=$s*)(fullName=$s*)(sn=$s*)(telephoneNumber=x$s*))";
	#my $filter = "(mail=$s*)";
	$result = $ldap->search(
			base => $LDAP_BASE,
			filter => $filter,
			attrs => [ 'dn', 'mail', 'fullName', 'telephoneNumber' ]);
	$result->code == 0
		or do_error "Search error... " . $result->error . "\n";

	my $entry;
	my @results;
	while (defined($entry = $result->pop_entry))
	{
		my $class = ($entry->dn =~ /,ou=student,o=mc$/i) ? "student" : "other";
		my $email = $entry->get_value("mail")
			or next;
		my $full_name = $entry->get_value("fullName") || "";
		my $phone = $entry->get_value("telephoneNumber") || "";
		$phone =~ s/^x//;
		$phone =~ s/\s\{.*\}$//;

		push @results, {
			class => $class,
			email => $email,
			full_name => $full_name,
			phone => $phone,
			};
	}

	$ldap->unbind;

	@results = sort { $a->{class} cmp $b->{class}
				or $a->{email} cmp $b->{email} } @results;
	return \@results;
}

sub highlight_match
{
	my ($query, $value) = @_;

	my $len = length($query);
	if (lc(substr($value, 0, $len)) eq lc($query))
	{
		return qq|<span class="matched">|
			. encode_entities(substr($value, 0, $len))
			. qq|</span>|
			. encode_entities(substr($value, $len));
	}
	return encode_entities($value);
}

sub matches
{
	my ($query, $entry) = @_;

	my $len = length($query);
	my @fields = qw(email name sn phone);
	foreach my $f (@fields)
	{
		return 1 if (lc(substr($entry->{$f}, 0, $len)) eq lc($query));
	}
	return;
}
