#!/usr/bin/perl

## W3C statistics - store annotations into, and query from, a persistant RDF DB

#Copyright Massachusetts Institute of technology, 2000.
#Written by Eric Prud'hommeaux

#####
# What It Does:

#####
# set up module environment
use strict;

#BEGIN {unshift@INC,('../../..');}
use W3C::Rdf::CGIApp;
@AnnotationQueryObject::ISA = qw(W3C::Rdf::CGIApp::QueryObject);

package W3C::Annotations::CGI::statistics::LogEntry;
use W3C::Util::Exception;

sub new {
    my ($proto, $cgi, $env) = @_;

    my $class = ref $proto || $proto;
    my $self = {
	-sessionId => $cgi->getSessionId(),

	# relevent environment stuff
	-server => $env->getField('SERVER_NAME'),
	-method => $env->getField('REQUEST_METHOD'),
	-path => $env->getField('PATH_INFO'),
	-accept => $env->getField('HTTP_ACCEPT'),

	# user-related stats
	-ua => $env->getField('HTTP_USER_AGENT'),
	-user => $env->getField('REMOTE_USER'),
	-addr => $env->getField('REMOTE_ADDR'),

	# cgi parms
	-annotates => $cgi->getField('w3c_annotates'),

	# characterize this request
	-action => '- NONE -', 
	};

    bless ($self, $class);

    return $self;
}

sub getSessionId {
    my ($self) = @_;
    return $self->{-sessionId};
}

sub toString {
    my ($self) = @_;
    return "unknown";
}

package W3C::Annotations::CGI::statistics::NoActionLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'none';	

    return $self;
}

sub toString {
    my ($self) = @_;
    return "simple query";
}

package W3C::Annotations::CGI::statistics::AnnotatesLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'annotates';	

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-annotates};
}

package W3C::Annotations::CGI::statistics::DeleteLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::AnnotatesLogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'delete';	

    return $self;
}

package W3C::Annotations::CGI::statistics::ThreadLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'thread';
    $self->{-thread} = $cgi->getField('thread')|| $cgi->getField('root') || 
	$cgi->getField('w3c_thread')|| $cgi->getField('w3c_root');

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-thread};
}

package W3C::Annotations::CGI::statistics::AlgaeQueryLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'algae query';	
    $self->{-query} = $cgi->getField('w3c_algaeQuery');

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-query};
}

package W3C::Annotations::CGI::statistics::NewAnnotationLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env, $annotatee) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'new annotation';
    $self->{-annotatee} = $annotatee;

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-annotatee};
}

package W3C::Annotations::CGI::statistics::NewAnnotationBadEncodingLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::NewAnnotationLogEntry';

sub new {
    my ($proto, $cgi, $env, $annotatee) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'new badly encoded annotation';

    return $self;
}

package W3C::Annotations::CGI::statistics::ReplaceAnnotationLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::NewAnnotationLogEntry';

sub new {
    my ($proto, $cgi, $env, $annotatee) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'replace annotation';

    return $self;
}

package W3C::Annotations::CGI::statistics::AnnotationLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'annotation request';

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-path};
}

package W3C::Annotations::CGI::statistics::BodyLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'body request';

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-path};
}

package W3C::Annotations::CGI::statistics::AttributionLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'attribution request';	

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-path};
}

package W3C::Annotations::CGI::statistics::StatisticsLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub new {
    my ($proto, $cgi, $env) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($cgi, $env);
    bless ($self, $class);

    $self->{-action} = 'statistics request';	

    return $self;
}

sub toString {
    my ($self) = @_;
    return $self->{-path};
}

package W3C::Annotations::CGI::statistics::UnknownLogEntry;
use vars qw(@ISA);
@ISA = 'W3C::Annotations::CGI::statistics::LogEntry';

sub toString {
    my ($self) = @_;
    return "$self->{-sessionId} $self->{-user}";
}

sub W3C::Annotations::CGI::statistics::LogEntry::MakeLogEntry {
    my ($cgi, $env) = @_;
    my $path = $env->getField('PATH_INFO');
    my $annotateLogEntry = undef;
    if ($cgi->getField('w3c_annotates')) {
	if ($env->getField('REQUEST_METHOD') eq 'DELETE') {
	    $annotateLogEntry = new W3C::Annotations::CGI::statistics::DeleteLogEntry($cgi, $env);
	} else {
	    $annotateLogEntry = new W3C::Annotations::CGI::statistics::AnnotatesLogEntry($cgi, $env);
	}
    } elsif ($cgi->getField('thread')|| $cgi->getField('root') || 
	     $cgi->getField('w3c_thread')|| $cgi->getField('w3c_root')) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::ThreadLogEntry($cgi, $env);
    } elsif ($cgi->getField('replace_source')) {
	if ($cgi->getField('w3c_submit') =~ m/delete/) {
	    $annotateLogEntry = new W3C::Annotations::CGI::statistics::DeleteLogEntry($cgi, $env);
	} elsif ($cgi->getField('w3c_submit') =~ m/replace/) {
	    $annotateLogEntry = new W3C::Annotations::CGI::statistics::ReplaceLogEntry($cgi, $env);
	} else {
	    $annotateLogEntry = new W3C::Annotations::CGI::statistics::UnknownLogEntry($cgi, $env);
	}
    } elsif ($cgi->getField('w3c_algaeQuery')) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::AlgaeQueryLogEntry($cgi, $env);
    } elsif ($path =~ m/^\/annotation\//) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::AnnotationLogEntry($cgi, $env);
    } elsif ($path =~ m/^\/body\//) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::BodyLogEntry($cgi, $env);
    } elsif ($path =~ m/^\/attribution\//) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::AttributionLogEntry($cgi, $env);
    } elsif ($path =~ m/^\/statistics\// || defined $cgi->getField('statistics')) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::StatisticsLogEntry($cgi, $env);
    } elsif ($env->getField('POST_VARS') =~ m/annotates\s+([a-zA-Z0-9]+\:)?resource\s*=\s*\"(.*?)\"/m) {
	my $annotatee = $2;
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::NewAnnotationLogEntry($cgi, $env, $annotatee);
    } elsif ($env->getField('PUT_VARS')) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::ReplaceAnnotationLogEntry($cgi, $env, $path);
    } elsif ($env->getField('POST_VARS') =~ m/annotates\++([a-zA-Z0-9]+\%3A)?resource\+*\%3D\+*\%22(.*?)\%22/m && 
	     $env->getField('CONTENT_TYPE') eq 'application/x-www-form-urlencoded') {
	my $annotatee = CGI::unescape($2);
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::NewAnnotationLogEntry($cgi, $env, $annotatee);
    } elsif ($cgi->getField('explain') eq 'false' || $cgi->getField('w3c_showSource')) {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::NoActionLogEntry($cgi, $env);
    } elsif ((!$path || $path eq '/') && $env->getField('REQUEST_METHOD') eq 'GET' && 
	     !$env->getField('POST_VARS') && $env->getField('QUERY_STRING') eq '') {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::NoActionLogEntry($cgi, $env);
    } else {
	$annotateLogEntry = new W3C::Annotations::CGI::statistics::UnknownLogEntry($cgi, $env);
    }
    return $annotateLogEntry;
}

package BinObject;
sub new {
    my ($proto, $firstDate, $lastDate, $enums) = @_;

    my $class = ref $proto || $proto;
    my $dateRange = $lastDate - $firstDate + 1;
    my $BINS = 20;
    my $binSize = $dateRange/$BINS;
    my $self = {
	-firstDate => $firstDate, 
	-lastDate => $lastDate, 
	-bins => [[], []], 
	-binSize => $binSize, 
    };

    for (my $i = 0; $i < $BINS; $i++) {
	$self->{-bins}->[0][$i] = $i*$BINS + $firstDate;
	for (my $j = 1; $j <= $enums; $j++) {
	    $self->{-bins}->[$j][$i] = 0;
	}
    }

    bless ($self, $class);

    return $self;
}

sub add {
    my ($self, $isoDate, $enum) = @_;
    my $bin = int(($isoDate - $self->{-firstDate})/$self->{-binSize});
    $self->{-bins}->[$enum][$bin]++;
}

sub plot {
    my ($self, $legend, $name) = @_;
    my $pkg = 'GD/Graph/linespoints.pm';
    my $class = 'GD::Graph::linespoints';
    #my $pkg = 'GIFgraph/linespoints.pm';
    #my $class = 'GIFgraph::linespoints';
    require $pkg;
    my $graph = $class->new(800,600);
    $graph->set(x_label           => 'Date',
		y_label           => 'hits',
		title             => 'hit rate',
		x_tick_number     => 'auto', 
		# x_label_skip      => 2, 
		);
    if ($legend) {
	$graph->set_legend(@$legend);
    }
    my $gd = $graph->plot($self->{-bins});
    if (open(IMG, ">$name")) {
	binmode IMG;
	print IMG $gd->jpeg();
	close IMG;
    }
}

package statistics;
@statistics::ISA = qw(W3C::Annotations::AnnotationApp);

use W3C::Annotations::AnnotationApp qw($SCRIPT_HOME_URI_PROP
				       $NS_ANNOTATION $NS_HTTP $NS_DC);

use W3C::Util::W3CDebugCGI;
use W3C::Util::Exception;
use W3C::Util::Properties;
use W3C::Annotations::DBMUserRecords qw($RW_read);
use W3C::Rdf::Atoms qw($RDF_SCHEMA_URI);

use vars qw($REVISION $VERSION);
$REVISION = '$Id: statistics,v 1.9 2004/06/08 06:51:00 eric Exp $ ';
$VERSION=0.1;

$AnnotationQueryObject::AnnotatesQuery = "(ask
     '((${RDF_SCHEMA_URI}type ?annotation ${NS_ANNOTATION}Annotation) 
       (${NS_ANNOTATION}annotates ?annotation ?annotates)
      ) :collect '(?annotation ?annotates))";

$AnnotationQueryObject::SubjectQuery = "(ask
     '((?p \$self->{-subject} ?o) 
      ) :collect '(?p ?o))";

use vars qw(%ParmMapping %PathMapping);
%ParmMapping = (
		'log' => 'QUERY_LOG', 
		'db' => 'QUERY_DB', 
		'serverName' => 'SERVER_NAME', 
		'src' => 'SOURCE', 
		);

%PathMapping = (
		);

#####
# main - either statistics or show source

my ($query, $statistics);

eval {
    $W3C::Util::W3CDebugCGI::DEBUG_SESSION = $ARGV[1]; # use a session id like 957296047.909868 or -2;
    $query = new W3C::Util::W3CDebugCGI($0, $ARGV[0] eq 'DEBUG', 
					{-dieNoOpen => 1,
					 -logExt => '.log', 
					 -storeIn => '/tmp', 
					 -rerun => 'w3c_rerun', 
					 -mergeQueryAndPOST => 1});

    $statistics = new statistics($query);
    my $presenter = $statistics->execute();
    print $presenter->flush(200);
}; if ($@) {
    my $sessionId = $query ? $query->getSessionId : undef;
    if (my $ex = &catch('W3C::Http::HttpMessageException')) {
	if ($statistics && $statistics->{RDF_DB}) {
	    $statistics->{RDF_DB}->disconnect;
	    delete $statistics->{ACL_REPOSITORY};
	}
	my $message = $ex->getHttpMessage();
	$message->addHeader('Session-Id', $sessionId) if (defined $sessionId);
	print $message->toString;
    } elsif (my $ex = &catch('W3C::Rdf::CGIApp::AppException')) {
	print $ex->toString($sessionId);
    } elsif (my $ex = &catch('W3C::Util::Exception')) {
	print "Status: 500\n\n";
	print "<pre>".$ex->toString."</pre>\n";
	if ($sessionId) {
	    print "<p>Session-id: $sessionId</p>\n";
	}
    } else {
	print "Status: 500\n\n";
	print "died with $@";
	if ($sessionId) {
	    print "<p>Session-id: $sessionId</p>\n";
	}
    }
}

sub new {
    my ($proto, $read) = @_;
    my $class = ref $proto || $proto;
    my $self = $class->SUPER::new($read, {-parmMapping => \%ParmMapping, -pathMapping => \%PathMapping});
    bless ($self, $class);

    $self->{USER_RECORDS} = new W3C::Annotations::DBMUserRecords(-type => 'ndbm', -errorHandler => $self);
    $self->{FILE_users} = '/etc/apache/conf/users';
    $self->{SHORT_TITLE} => 'statistics';
    return $self;
}

sub sortKeysAndKey {
    my ($hash, $a, $b) = @_;
    my $major = scalar keys %{$hash->{$b}} <=> scalar keys %{$hash->{$a}};
    return $major == 0 ? $a cmp $b : $major;
}

sub execute {
    my ($self) = @_;
    my $annotationObject = new AnnotationQueryObject(-filter => sub {
	my ($tmpDB, $attrib, $annotation, $statements) = @_;
	push (@$statements, @{$self->addIdStatements($tmpDB, $attrib, $annotation)});
    });
    my @titles = ();

    # grab some stuff from the properties
    $self->{ADMIN_IDS} = {};
    my @ids = $self->{PROPERTIES}->getI('auth.admin.user');
    foreach my $id (@ids) {
	my $idUri = $self->mapWebIdToUri($id);
	$self->{ADMIN_IDS}{$idUri} = $idUri;
    }

    # make interface appropriate to the accept header
    $self->{PRESENTER} = $self->makePresenter(); # @presenterParms

    # get authentication parameters
    my $auth = $self->getAuthenticatedUser();
    if (my $realSession = $self->{READ}->getRealSession()) {
	my $user = $realSession->getEnv('REMOTE_USER');
	my $callersAuth = $self->mapWebIdToUri($user);
	if ($self->checkEquivOrAdminAuth($callersAuth, $auth)) {
	    # we are allowing a user or admin user to rerun a session
	} else {
	    &throw($self->failAuth($callersAuth, 'rerun', $realSession->param('w3c_rerun')));
	}
    }

    # summarize data
    my @summaryLines = ();
    if (exists $self->{QUERY_LOG}) {
	my $statsRange = $self->{QUERY_LOG};
	my $source = $self->{SOURCE} || '/tmp/';
	my $l = &W3C::Util::W3CDebugCGI::loadOrSave(undef, 'annotate.log', 0, 
						    {-readLog => $statsRange, -storeIn => $source, 
						     -ignoreBadHeaders => 1});
	my @keys = sort keys %$l;
	my $firstDate = $l->{$keys[0]}{'cgi'}->getIsoDate();
	my $lastDate = $l->{$keys[-1]}{'cgi'}->getIsoDate();
	my %enums = ('annotates' => 1, 'new annotation' => 2, 'annotation request' => 3, 'body request' => 4, 'delete' => 5);
	my @enums = ('annotates', 'new annotation', 'annotation request', 'body request', 'delete');
	my $binObject = new BinObject($firstDate, $lastDate, scalar @enums);
	push (@titles, "stats: $firstDate - $lastDate");
	my $byUser = {};
	my $annotationsByUser = {};
	my $deletesByUser = {};
	my $byAction = {};
	my $byAnnotatee = {};
	my $unknowns = {};
	foreach my $sessionKey (@keys) {
	    my $cgi = $l->{$sessionKey}{'cgi'};
	    my $env = $l->{$sessionKey}{'env'};
	    my $isoDate = $cgi->getIsoDate();
	    if (defined $self->{SERVER_NAME} && $self->{SERVER_NAME} ne $env->getField('SERVER_NAME')) {
		next;
	    }
	    my $annotateLogEntry = &W3C::Annotations::CGI::statistics::LogEntry::MakeLogEntry($cgi, $env);
	    if ($annotateLogEntry->isa('W3C::Annotations::CGI::statistics::UnknownLogEntry')) {
		$unknowns->{$annotateLogEntry} = $annotateLogEntry;
	    } else {
		$byAction->{$annotateLogEntry->{-action}}{$annotateLogEntry} = $annotateLogEntry;
		$byUser->{$annotateLogEntry->{-user}}{$annotateLogEntry} = $annotateLogEntry;
		if ($annotateLogEntry->isa('W3C::Annotations::CGI::statistics::NewAnnotationLogEntry')) {
		    $byAnnotatee->{$annotateLogEntry->{-annotatee}}{$annotateLogEntry} = $annotateLogEntry;
		    $annotationsByUser->{$annotateLogEntry->{-user}}{$annotateLogEntry} = $annotateLogEntry;
		} elsif ($annotateLogEntry->isa('W3C::Annotations::CGI::statistics::DeleteLogEntry')) {
		    $deletesByUser->{$annotateLogEntry->{-user}}{$annotateLogEntry} = $annotateLogEntry;
		}
	    }
	    if (my $enum = $enums{$annotateLogEntry->{-action}}) {
		$binObject->add($isoDate, $enum);
	    }
	}

	push (@summaryLines, $self->summarize($byAction, 'hits by action'));

	push (@summaryLines, $self->summarize($byUser, 'hits by user', [sub {$_[0] =~ m/w3\.org$/ ? 'team' : 'non-team'}]));

	push (@summaryLines, $self->summarize($annotationsByUser, 'new annotations by user', [sub {$_[0] =~ m/w3\.org$/ ? 'team' : 'non-team'}]));

	push (@summaryLines, $self->summarize($deletesByUser, 'deletes by user', [sub {$_[0] =~ m/w3\.org$/ ? 'team' : 'non-team'}]));

	push (@summaryLines, $self->summarize($byAnnotatee, 'new annotations by URI', [sub {$_[0] =~ m/^http:\/\/[^\/]+w3\.org/ ? 'w3.org' : 'non-w3.org'}]));

	push (@summaryLines, "<h3>unknowns</h3>\n  <dl>\n");
	foreach my $unknownKey (sort {$unknowns->{$a}{-sessionId} <=> $unknowns->{$b}{-sessionId}} keys %$unknowns) {
	    my $unknown = $unknowns->{$unknownKey};
	    my $sessionId = $unknown->getSessionId;
	    my $string = $unknown->toString();
	    push (@summaryLines, "    <dt>$sessionId:</dt><dd>$string</dd>\n");
	}
	push (@summaryLines, "  </dl>\n");

	eval {
	    #$binObject->plot(\@enums, '/tmp/logData.jpg');
	    push (@summaryLines, "<img src=\"/tmp/logData.jpg\" alt=\"log data\" />\n");
	}; if ($@) {die $@;}
    }

    if (exists $self->{QUERY_DB}) {
	my $statsRange = $self->{QUERY_DB};
	my $annotators = new AnnotationQueryObject();
	my $title = "annotators of $annotators->{-annotates}";
	push (@titles, $title);
	my ($results, $selects, $messages, $statements, $query) = 
	    $annotators->templateReq($self->{RDF_DB}, $AnnotationQueryObject::AnnotatesQuery);
	#$self->{PRESENTER}->printAnnotation($title, $query);
	my $annotationsByUser = {};
	my $byAnnotatee = {};
	my $bySessionId = {};
	for (my $i = 0; $i < @$results; $i++) {
	    eval {
		my $row = $results->[$i];
		my $annotation = $row->[0];
		my $annotates = $row->[1];
		my $evidence = $statements->[$i];
		my $localAuth = $evidence->[0]->getAttribution->getAuth->getUri;
		$annotation->getUri =~ m/([\d\.]+)$/ || &throw(new W3C::Util::Exception());
		my $sessionId = $1;
		my $annotateLogEntry = {-sessionId => $sessionId, 
					-annotates => $annotates->getUri, 
					-action => 'new annotation', 
					-annotatee => $annotates->getUri, 
					-user => $statements->[$i][0]->getAttribution->getAuth->getUri};
		bless ($annotateLogEntry, 'W3C::Annotations::CGI::statistics::NewAnnotationLogEntry');
		$annotationsByUser->{$annotateLogEntry->{-user}}{$annotateLogEntry} = $annotateLogEntry;
		$byAnnotatee->{$annotateLogEntry->{-annotatee}}{$annotateLogEntry} = $annotateLogEntry;
		$bySessionId->{$sessionId} = $annotateLogEntry;
		# my $added = $self->addIdStatements($tmpDB, $attrib, $annotation);
		#$annotationObject->{-subject} = $annotation->getUri;
		#$annotationObject->{-annotates} = $annotates->getUri;
		#$annotationObject->genericReq($self->{RDF_DB}, $self->{PRESENTER}, 
		#			      $AnnotationQueryObject::SubjectQuery, $annotationObject->{-annotates}, 
		#			      'an annotation for ', 'found nothing annotating ', 
		#			      [[[['?p', '?s', $self->{-annotates}]], ['?p', '?s']]],
		#			      $auth);
	    }; if ($@) {
		my $ex;
		if ($ex = &catch('W3C::Util::Exception')) {
		} else {
		    $ex = new W3C::Util::PerlException(-error => $@);
		}
		my $message = $ex->getMessage;
		my $annotStr = $results->[$i][0]->show();
		my $errorString = "Error dumping $annotStr:\n$message\n";
		push (@{$self->{PRE_MESSAGES}}, $errorString);
	    }
	}

	push (@summaryLines, $self->summarize($annotationsByUser, 'new annotations by user', [sub {$_[0] =~ m/w3\.org$/ ? 'team' : 'non-team'}]));

	push (@summaryLines, $self->summarize($byAnnotatee, 'new annotations by URI', [sub {$_[0] =~ m/^http:\/\/[^\/]+w3\.org/ ? 'w3.org' : 'non-w3.org'}]));

	my @keys = sort keys %$bySessionId;
	my $firstDate = $bySessionId->{$keys[0]}->getSessionId();
	my $lastDate = $bySessionId->{$keys[-1]}->getSessionId();
	my %enums = ('new annotation' => 1);
	my @enums = ('new annotation');
	my $binObject = new BinObject($firstDate, $lastDate, scalar @enums);
	foreach my $sessionId (@keys) {
	    $binObject->add($sessionId, 1);
	}

	eval {
	    #$binObject->plot(\@enums, '/tmp/dbData.jpg');
	    push (@summaryLines, "<img src=\"/tmp/dbData.jpg\" alt=\"db data\" />\n");
	}; if ($@) {die $@;}
    }

    $self->{PRESENTER}->printHead($self->{PRE_MESSAGES}, join ('&semi; ', @titles));
    $self->{PRESENTER}->printOK(join ('', @summaryLines));
    $self->{PRESENTER}->printFoot($annotationObject);
    return $self->{PRESENTER};
}

sub summarize {
    my ($self, $hash, $title, $matches) = @_;
    if (!defined $matches) {
	$matches = [];
    }
    my $total = 0;
    my @ret;
    my @ordered = sort {&sortKeysAndKey($hash, $a, $b)} keys %$hash;
    push (@ret, "<h3>$title</h3>\n  <dl>\n");
    my $matchResults = {};
    my $matchBins = {};
    foreach my $key (@ordered) {
	my $entries = $hash->{$key};
	my $count = scalar keys %$entries;
	$total += $count;
	foreach my $matchFunc (@$matches) {
	    my $match = &$matchFunc($key, $entries);
	    if (defined $match) {
		$matchResults->{$match} += $count;
		$matchBins->{$match} += 1;
	    }
	}
	push (@ret, "    <dt>$key:</dt><dd>$count</dd>\n");
    }
    my $bins = scalar @ordered;
    push (@ret, "  </dl>\n<p>$total entries in $bins bins.</p>");
    if (scalar keys %$matchResults) {
	my @ordered = sort {$matchResults->{$b} <=> $matchResults->{$a}} keys %$matchResults;
	push (@ret, "<dl>\n");
	foreach my $key (@ordered) {
	    push (@ret, "  <dt>$key</dt><dd>$matchResults->{$key} in $matchBins->{$key} bins</dd>\n");
	}
	push (@ret, "</dl>\n");
    }
    return join ('', @ret);
}

sub importCGIParms {
    my ($self) = @_;

    # load a properties file if available
    eval {
	$self->{PROPERTIES} = new W3C::Util::Properties('../../../Conf/annotate.prop');
	$self->{SELF_URI} = $self->{READ}->uriFromProperties($self->{PROPERTIES}, $SCRIPT_HOME_URI_PROP);
    }; if ($@) {if (my $ex = &catch('W3C::Util::FileNotFoundException')) {
	$self->{PROPERTIES} = new W3C::Util::Properties();
	$self->{SELF_URI} = $self->{READ}->url;
    } else {&throw()}}
    $self->SUPER::importCGIParms;
}

package HTMLPresenter;
use W3C::Rdf::Atoms qw($RDF_SCHEMA_URI);
use W3C::Annotations::AnnotationApp qw($NS_ANNOTATION $NS_HTTP $NS_DC);

sub printHead999 {
    my ($self, $preText, $title) = @_;
    my $tmp = <<EOF;
  <p>
    Welcome to the W3C Annotation Server statistics page!
  </p>
  <p>
    This tool reviews statistics for <a href="annotations">annotations</a>.
  </p>
EOF
    ;
    $self->printOK($tmp);
}

sub printFoot {
    my ($self, $annotationObject) = @_;
    $self->printOK($self->{RENDERER}->end_html."\n");
}

