#!/usr/bin/perl
package ParagraphCurationPage;

#######################################################################
##### Author :  Kane Tse
##### Date   :  August 2001
##### Description : This package contains all necessary methods for dictyBase
#####               curators to display, update, insert or delete paragraph
#####               related info in the Oracle database.
#####
#######################################################################
use strict;
use DBI;
use CGI qw/:all :html3/;
use CGI::Carp qw(fatalsToBrowser);
#use CGI::Pretty;
use lib "/usr/local/dicty/www_dictybase/db/lib/common";
use Login qw (ConnectToDatabase);
use lib "/usr/local/dicty/www_dictybase/db/lib/dictyBase";
use dictyBaseCentralMod qw(:formatPage :getInfo);
use dictyBaseObject;
# use Pubmed; # <-- unused
use lib "/usr/local/dicty/www_dictybase/db/lib/dictyBase/Objects";
use ConfigURLdictyBase;
# use Go_locus_goev; # <-- unused
# use Go_feat_goev; # <-- unused
use Reference;
use Reflink;
use Feature; # <-- unused
use Locus;
use Paragraph;
use Colleague;
use Phrase_category;
use Go;
use ParagraphUI;
use ParagraphDS;

#######################################################################
#################### global variables #################################
#######################################################################

my $domain = "\@genome.stanford.edu";
my $programmerEmail = "mkaloper\@genome.stanford.edu";

my $dbh;
my $dblink; 
my $configUrl;
my %hiddenVars;

my $debugMode = 0;
my $scriptURL;
my $locusURL;
my $colleagueURL;
my $goURL;
my $colleagueSearchURL;
my $referenceSearchURL;
my $goBrowserURL;
my $helpURL = "http:///usr/local/dicty/www_dictybase/db/lib/staff/dictyBase/curator/paragraph_instr2.html";
my $uiModule;

# debugging variables
#   (altering the values of these variables should not affect operation
#    of the script presented to the user)
my $hiddenFieldsDisplayCount = 0;

#######################################################################
sub new {      ############ constructor ###############################
#######################################################################

	my ($self, %args) = @_;

#	push @CGI::Pretty::AS_IS,qw( B );
#	$CGI::Pretty::INDENT = "  ";

	$self = {};
	bless $self;

      	$self->{'_database'} = $args{'database'};
	$self->{'_help'}     = defined($args{'help'}) ? 
	                       $args{'help'} : "";
	$self->{'_title'}    = defined($args{'title'}) ? 
	                       $args{'title'} : "GO Curation Page";
	$self->{'_user'}     = defined($args{'user'}) ? $args{'user'} : "";
	$self->{'_mode'}     = defined($args{'mode'}) ? $args{'type'} : "";
	# $dbh = &ConnectToDatabase($self->database);

	$dblink = ($self->{'_database'} =~ m/dev/i) ? "dictyBaseDEV" : "dictyBase";

	$uiModule = ParagraphUI->new();

    	return $self;
}

sub help { $_[0]->{_help} }
sub database { $_[0]->{_database} }
sub title { $_[0]->{_title} }
sub user { $_[0]->{_user} }

######################################################################
sub DESTROY {   ############ destructor ##############################
######################################################################
    	if (defined $dbh) {
		$dbh->disconnect;
    	}
}

######################################################################
sub start {
######################################################################
    	my ($self) = @_;

	$configUrl = ConfigURLdictyBase->new;

	if (!$self->user) {
	    print "location: ", $configUrl->dictyBaseCGIRoot, 
		 $dblink,"/curatorLogin\n";
	    print "Content-type: text/html\n\n";
	    exit;
	}
	my ($dbuser, $dbpasswd) = &getUsernamePassword(uc($self->user), 
						       $self->database);
	if (!$dbuser || !$dbpasswd) {
	    print "location: ", $configUrl->dictyBaseCGIRoot, 
		 $dblink,"/curatorLogin\n";
	    print "Content-type: text/html\n\n";
	    exit;
	}

	# configure URLs
	$dblink = $configUrl->dblink($self->database);

	$scriptURL = $configUrl->dictyBaseCGIRoot
	     ."$dblink/curation/paragraphCuration";
	$locusURL = $configUrl->dictyBaseCGIRoot
	     ."$dblink/locus.pl?locusNo=";
	$colleagueURL = $configUrl->dictyBaseCGIRoot
	     .$dblink."/colleague/colleagueSearch?id=";
	$goURL = $configUrl->dictyBaseCGIRoot
	     .$dblink."/GO/go.pl?goid=";
	$colleagueSearchURL = $configUrl->dictyBaseCGIRoot
	     .$dblink."/colleague/colleagueIdSearch";
	$referenceSearchURL = $configUrl->dictyBaseCGIRoot
	     .$dblink."/curation/refCuration?user=". $dbuser;
	$goBrowserURL = $configUrl->dictyBaseServerRoot
	     ."/GO/main.html";


	### read and parse form input parameters from the HTML page
	$self->{'_feat'} =~ s/^ *//;
	$self->{'_feat'} =~ s/ *$//;
	$self->{'_feat'} =~ s/[\t\r\f\n]+//g;

	my $buttonClicked = param('submit') || "";
	my $previousScreen = param('currentMode') || "";
	$hiddenVars{'locus_no'} = param('locus_no');
	$hiddenVars{'paragraph_no'} = param('paragraph_no');
	$hiddenVars{'user'} = param('user');

	my $pageBodyHTML = "";

	# debug -->
	# &printStartPage($self->database, $self->title, $self->help);
	# <-- debug

	eval {


	    &printStartPage($self->database, $self->title, $self->help);


	    # the following screen/button combinations require write access to
	    #   the database, and thus should be logged on using the current
	    #   user's name and password
	    if (
		(($previousScreen eq "EditPhrase") 
		 && (
		     ($buttonClicked eq "Update")
		     || ($buttonClicked eq "Merge with Previous Phrase")
		     || ($buttonClicked eq "Merge with Next Phrase")
		     || ($buttonClicked eq "Split this Phrase")
		    )
		)
		||
		(($previousScreen eq "EditParagraph") 
		 && ($buttonClicked eq "Update"))
		||
		(($previousScreen eq "DelinkParagraphVerification") 
		 && ($buttonClicked eq "Yes"))
		||
		(($previousScreen eq "DeletePhraseVerification")
		 && ($buttonClicked eq "Yes"))
		||
		(($previousScreen eq "DeleteParagraphVerification")
		 && ($buttonClicked eq "Yes"))
		||
		(($previousScreen eq "LoadParagraph")
		 && ($buttonClicked eq "Submit"))
	       ) {
		$dbh = &ConnectToDatabase($self->database, $dbuser, $dbpasswd);
	    }
	    else {
		# connect as CGM_DDB, since all other interfaces do not need to
		#   write to the database
		$dbh = &ConnectToDatabase($self->database);
	    }

	    if ($buttonClicked eq "Report Problem") {
		$pageBodyHTML = $self->emailBugReport();
		$previousScreen = "";
	    }
	    ### interpret the command buttons pressed, depending on which
	    ###   screen was displayed when the button was pressed
	    elsif ($previousScreen eq "EntryForm") {
		if ($buttonClicked eq "Load") {
		    $pageBodyHTML = $self->printCheckLoadParagraph();
		}
		elsif ($buttonClicked eq "Edit") {
		    $pageBodyHTML = $self->printEditParagraph();
		}
		elsif ($buttonClicked eq "List") {
		    $pageBodyHTML = $self->printParagraphList();
		}
		else {
		    $pageBodyHTML = $self->printEditParagraph();

		}
	    }
	    elsif ($previousScreen eq "DelinkParagraphVerification") {
		if ($buttonClicked eq "Yes") {
		    $pageBodyHTML = $self->printDelinkParagraphResult();
		}
		elsif ($buttonClicked eq "Back") {
		    $pageBodyHTML = $self->printEntryForm();
		}
		else {
		    $pageBodyHTML = $self->printButtonError($previousScreen,
							    $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "CheckLoadParagraph") {
		if ($buttonClicked eq "Back") {
		    $pageBodyHTML = $self->printEntryForm();
		}
		elsif ($buttonClicked eq "Replace All") {
		    $hiddenVars{'replaceAll'} = "Yes";
		    $pageBodyHTML = $self->printLoadParagraph();
		}
		elsif (($buttonClicked eq "Replace Current")
		       || ($buttonClicked eq "Replace")) {
		    $hiddenVars{'replaceAll'} = "No";
		    $pageBodyHTML = $self->printLoadParagraph();
		}
		elsif ($buttonClicked eq "Edit") {
		    $pageBodyHTML = $self->printEditParagraph();
		}
		else {
		    $pageBodyHTML = $self->printButtonError($previousScreen, 
							    $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "LoadParagraph") {
		my $paragraphText = param('paragraphText');
		if (defined param('replaceAll')) {
		    $hiddenVars{'replaceAll'} = param('replaceAll');
		}
		if ($buttonClicked eq "Read File") {
		    my $filename = param('filename');
		    my $fileLoad = $self->getFileText(param('filename'));
		    my ($fileText, $errorCode) = split(/\t/, $fileLoad);
		    if (!defined $errorCode) {
			param('filename', "");
			$pageBodyHTML = $self->printLoadParagraph($fileText);
		    }
		    else {
			$pageBodyHTML 
			     .= $self->blueTitleBar("Message: ".
						    font({-color=>'red'}, 
							 "FILE ERROR"));
			$pageBodyHTML 
			     .= ("Script was unable to locate or read the file: ".
				 code($filename) . br .
				 "The following error message was returned: ".
				 code($errorCode) . p
				);
			$pageBodyHTML .= $self->printLoadParagraph($fileText);
		    }
		}
		elsif ($buttonClicked eq "Submit") {
		    $pageBodyHTML 
			 .= $self->checkParagraphSubmission($paragraphText);
		}
		elsif ($buttonClicked eq "Preview") {
		    $pageBodyHTML 
			 .= $self->printPreviewParagraph($paragraphText);
		}
		elsif ($buttonClicked eq "Back") {
		    $pageBodyHTML 
			 .= $self->printEntryForm();
		}
		else {
		    $pageBodyHTML = $self->printButtonError($previousScreen, 
							    $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "EditParagraph") {
		my $paragraphObj;
		if (defined $hiddenVars{'paragraph_no'}) {
		    $paragraphObj = ParagraphDS->new
			 (dbh=>$dbh,
			  paragraphNo=>$hiddenVars{'paragraph_no'});
		    delete $hiddenVars{'locus_no'};
		}
		elsif (defined $hiddenVars{'locus_no'}) {
		    $paragraphObj = ParagraphDS->new
			 (dbh=>$dbh,
			  locusNo=>$hiddenVars{'locus_no'});
		    # where possible, we want to use paragraph_no to identify
		    #   a paragraph, rather than a locus_no, because a locus can
		    #   be dissociated from a paragraph easily.
		    delete $hiddenVars{'locus_no'};
		    $hiddenVars{'paragraph_no'} = $paragraphObj->getParagraphNo();
		}

		if (!defined param('phrase_no')) {
		    # add or remove new primary loci #
		    {
			my $resultRef 
			     = $self->addRemovePrimaryLoci
				  (
				   paragraphObj=>$paragraphObj
				  );
			($paragraphObj, my $message) = @$resultRef;
			$pageBodyHTML .= $message;
		    }
		    # add or remove new colleagues #
		    {
			my $resultRef 
			     = $self->addRemoveColleagues
				  (
				   paragraphObj=>$paragraphObj
				  );
			($paragraphObj, my $message) = @$resultRef;
			$pageBodyHTML .= $message;
		    }
		}

		if ($buttonClicked eq "Back") {
		    $pageBodyHTML .= $self->printEntryForm();
		}
		elsif ($buttonClicked eq "Update") {
		    $pageBodyHTML .= $self->printCommitParagraphVerification
			 (paragraphObj=>$paragraphObj);
		}
		elsif (($buttonClicked eq "Add/Remove") 
		       && ((defined param('locus')) 
			   || (defined param('colleague')))) {
		    $pageBodyHTML
			 .= $self->printEditParagraph(paragraphObj=>$paragraphObj);
		}
		elsif (defined param('phrase_no')) {
		    $hiddenVars{'phrase_no'} = param('phrase_no');
		    $pageBodyHTML 
			 .= $self->printEditPhrase
			      (phrase_no=>$hiddenVars{'phrase_no'},
			       paragraph_no=>$hiddenVars{'paragraph_no'});
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen, 
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "ShowReferences") {
		my $paragraph_no = param('paragraph_no');
		print "Content-type: text/html\n\n";
		print $self->printParagraphReferences(paragraphNo=>$paragraph_no);
		exit();
	    }
	    elsif ($previousScreen eq "EditPhrase") {
		my $locus_no = param('locus_no');
		my $paragraph_no = param('paragraph_no');
		my $phrase_no = param('phrase_no');
		my $newPhraseNo = param('oldPhrasePosition');
		my $paragraphObj = ParagraphDS->new
		     (dbh=>$dbh,
		      paragraphNo=>$hiddenVars{'paragraph_no'});
		if ($buttonClicked eq "Back") {
		    $pageBodyHTML 
			 .= $self->printEditParagraph(paragraphNo=>$paragraph_no);
		}
		elsif ($buttonClicked eq "Insert New Phrase Before") {
		    $pageBodyHTML
			 .= $self->printEditPhrase
			      (newPhraseOrder=>$newPhraseNo,
			       paragraphObj=>$paragraphObj);
		}
		elsif ($buttonClicked eq "Insert New Phrase After") {
		    $newPhraseNo += 1;
		    $pageBodyHTML .= $self->printEditPhrase
			 (newPhraseOrder=>$newPhraseNo,
			  paragraphObj=>$paragraphObj);
		}
		elsif ($buttonClicked eq "ListAutoTags") {
		    $pageBodyHTML .= $uiModule->displayAutoMarkedupTerms();
		}
		elsif ($buttonClicked eq "Merge with Previous Phrase") {
		    $pageBodyHTML =
			 $self->mergePhrase(type=>"previous",
					    paragraphNo=>$paragraph_no,
					    currPhraseNo=>$phrase_no);
		}
		elsif ($buttonClicked eq "Merge with Next Phrase") {
		    $pageBodyHTML
			 = $self->mergePhrase(type=>"next",
					    paragraphNo=>$paragraph_no,
					    currPhraseNo=>$phrase_no);
		}
		elsif ($buttonClicked eq "Split this Phrase") {
		    $pageBodyHTML
			 = $self->splitPhrase(
					      paragraphNo=>$paragraph_no,
					      phraseNo=>$phrase_no
					     );
		}
		elsif (defined $phrase_no) {
		    # load the phrase & paragraph objects from the database and
		    #   update them with data specified by the curator
		    my $objectRef;
		    $objectRef = $self->updatePhraseObject();
		    my ($phraseObj, $paragraphObj, $message) = @$objectRef;
		    $pageBodyHTML .= $message;
		    if (($buttonClicked eq "Preview Markup Text") 
			|| ($buttonClicked eq "Preview")) {
			$pageBodyHTML .= $self->printEditPhrase
			     (phraseObj=>$phraseObj,
			      paragraphObj=>$paragraphObj);
		    }
		    elsif ($buttonClicked eq "Update") {
			$pageBodyHTML .= $self->printCommitPhraseVerification
			     (phraseObj=>$phraseObj,
			      paragraphObj=>$paragraphObj);
		    }
		    elsif ($buttonClicked eq "Delete") {
			$pageBodyHTML .= $self->printDeletePhraseVerification
			     (phraseObj=>$phraseObj,
			      paragraphObj=>$paragraphObj);
		    }
		    else {
			$pageBodyHTML .= $self->printEditPhrase
			     (phraseObj=>$phraseObj,
			      paragraphObj=>$paragraphObj);
		    }
		}
		elsif ((defined $locus_no) 
		       || (defined $paragraph_no)) {
		    $pageBodyHTML 
			 .= $self->printEditParagraph(paragraphNo=>$paragraph_no);
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen, 
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "CommitPhraseVerification") {
		if ($buttonClicked eq "Continue") {
		    my $phrase_no = param('phrase_no');
		    my $paragraph_no = param('paragraph_no');
		    $pageBodyHTML .= $self->printEditPhrase
			 (phrase_no=>$phrase_no,
			  paragraph_no=>$paragraph_no);
		}
		elsif ($buttonClicked eq "Edit Paragraph") {
		    my $paragraph_no = param('paragraph_no');
		    $pageBodyHTML .= $self->printEditParagraph
			 (paragraphNo=>$paragraph_no);
		}
		elsif ($buttonClicked eq "Select New Paragraph") {
		    $pageBodyHTML .= $self->printEntryForm();
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen, 
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "CommitParagraphVerification") {
		if ($buttonClicked eq "Select New Paragraph") {
		    $pageBodyHTML .= $self->printEntryForm();
		}
		elsif ($buttonClicked eq "Continue") {
		    my $paragraph_no = param('paragraph_no');
		    $pageBodyHTML .= $self->printEditParagraph
			 (paragraphNo=>$paragraph_no);
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen, 
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "DeletePhraseVerification") {
		my $objectRef = $self->updatePhraseObject();
		my ($phraseObj, $paragraphObj, $message) = @$objectRef;
		$pageBodyHTML .= $message;
		
		if ($buttonClicked eq "Yes") {
		    $pageBodyHTML .= $self->printDeletePhraseResult
			 (phraseObj=>$phraseObj,
			  paragraphObj=>$paragraphObj);
		}
		elsif ($buttonClicked eq "Back") {
		    $pageBodyHTML .= $self->printEditPhrase
			 (phraseObj=>$phraseObj,
			  paragraphObj=>$paragraphObj);
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen, 
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "DeletePhraseResult") {
		if ($buttonClicked eq "Edit Paragraph") {
		    my $paragraph_no = param('paragraph_no');
		    $pageBodyHTML .= $self->printEditParagraph
			 (paragraphNo=>$paragraph_no);
		}
		elsif ($buttonClicked eq "Select New Paragraph") {
		    $pageBodyHTML .= $self->printEntryPage();
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen, 
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "DelinkParagraphResult") {
		if ($buttonClicked eq "Select New Paragraph") {
		    $pageBodyHTML .= $self->printEntryForm();
		}
		elsif ($buttonClicked eq "List All Paragraphs") {
		    $pageBodyHTML = $self->printParagraphList();
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen,
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "ListParagraph") {
		if ($buttonClicked eq "Delete") {
		    my $paragraph_no = param('paragraph_no');
		    $pageBodyHTML .= $self->printDeleteParagraphVerification
			 (paragraphNo=>$paragraph_no);
		}
		elsif ($buttonClicked eq "Delink") {
		    $pageBodyHTML = $self->printDelinkParagraphVerification();
		}
		elsif ($buttonClicked eq "Return") {
		    $pageBodyHTML = $self->printEntryForm();
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen,
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "DeleteParagraphVerification") {
		if ($buttonClicked eq "Yes") {
		    my $paragraph_no = param('paragraph_no');
		    $pageBodyHTML .= $self->printDeleteParagraphResult
			 (paragraphNo=>$paragraph_no);
		}
		elsif ($buttonClicked eq "No") {
		    $pageBodyHTML .= $self->printParagraphList();
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen,
							     $buttonClicked);
		}
	    }
	    elsif ($previousScreen eq "DeleteParagraphResult") {
		if ($buttonClicked eq "List all Paragraphs") {
		    $pageBodyHTML .= $self->printParagraphList();
		}
		elsif ($buttonClicked eq "Select New Paragraph") {
		    $pageBodyHTML .= $self->printEntryForm();
		}
		else {
		    $pageBodyHTML .= $self->printButtonError($previousScreen,
							     $buttonClicked);
		}
	    }
	    else {
		$pageBodyHTML = $self->printEntryForm();
	    }
	};
	if ($@) {
	    my $errorMessage = $@;

	    my $output = "";
	    $output .= $self->blueTitleBar("Error Encountered: ");
	    $output .= code($errorMessage);
	    $output .= p;


	    $output .= $self->blueTitleBar("What to do next: ");
	    $output .= $self->printEMailBugReport($errorMessage 
						  . "\n\n" . "");
	    $pageBodyHTML .= $output;
	}
	print $pageBodyHTML;
	&printEndPage;
    }

#######################################################################
sub updatePhraseObject {
#######################################################################
    my ($self) = @_;

    my $locus_no = param('locus_no');
    my $paragraph_no = param('paragraph_no');
    my $phrase_no = param('phrase_no');
    my $newPhraseNo = param('oldPhrasePosition');
    my $loadFromDBOnly = param('loadFromDBOnly');
    my $newPhraseOrder = param('phraseOrder');
    my $newPhraseText = param('phraseText');

    my $returnMessage = "";

    my $paragraphObj = ParagraphDS->new
	 (dbh=>$dbh,
	  paragraphNo=>$hiddenVars{'paragraph_no'});

    my $phraseObj;
    if ($phrase_no == -1) { # a phrase_no of -1 means create a new phrase
	$phraseObj
	     = $paragraphObj->insertPhrase(new_phrase_text=>$newPhraseText,
					   new_phrase_order=>$newPhraseOrder-1);
    }
    else {
	$phraseObj = PhraseDS->new(dbh=>$dbh,
				   phrase_no=>$phrase_no
				  );
    }

    $hiddenVars{'phrase_no'} = $phrase_no;

    if ($loadFromDBOnly eq "yes") {
	### this section is to be executed when loading a fresh, new phrase
	#   from the database; thus, any user-selections from the previous
	#   web page should be ignored.
	my @rtnObj = ($phraseObj, $paragraphObj, $returnMessage);
	return \@rtnObj;
    }

    ### this next section updates the phrase object with selections indictaed
    #     by the user via the user-interface
    {
	my $resultRef = $self->addRemovePhraseCategories
	     (phraseObj=>$phraseObj);
	($phraseObj, my $message) = @$resultRef;
	$returnMessage .= $message;
    }

    if ((defined $newPhraseOrder) && ($phrase_no != -1)) {

	# Check, if $newPhraseOrder is higher than the number of
	#   phrases
	if ($newPhraseOrder > $paragraphObj->getPhraseCount()) {
	    $newPhraseOrder = $paragraphObj->getPhraseCount();
	}

	my $resultRef = $self->movePhrase
	     (phraseObj=>$phraseObj,
	      newPhraseOrder=>$newPhraseOrder,
	      paragraphObj=>$paragraphObj);
	($phraseObj, my $message, $paragraphObj) = @$resultRef;
	$returnMessage .= $message;
    }

    if ((defined $newPhraseText) && ($phrase_no != -1)) {
	my $message;
	my $resultRef 
	     = $self->modifyPhraseText(phraseObj=>$phraseObj,
				       paragraphObj=>$paragraphObj,
				       phraseText=>$newPhraseText);
	($phraseObj, $message, $paragraphObj) = @$resultRef;

	$returnMessage .= $message;
    }

    $paragraphObj->updatePhrase($phraseObj, 
				$phraseObj->getPhraseOrder());

    my @rtnObj = ($phraseObj, $paragraphObj, $returnMessage);

    return \@rtnObj;
}

#######################################################################
sub printEntryForm {
#######################################################################
    my ($self) = shift;

    $self->{'_title'} = uc($self->database) . " Paragraph Curation Page";

    my %typeLabels = ('Locus'=>'Locus or locus_no',
		      'Feature'=>'Feature or feature_no'
		     );
    my @typeValues = qw/Locus Feature/;
    my $defaultType = 'Locus';

    $hiddenVars{'currentMode'} = 'EntryForm';

    param('locus_no', undef);
    delete($hiddenVars{'locus_no'});

    ##########################
    my @buttons = ();
    push(@buttons, ("Load" 
		    . "\tLoad a paragraph from a flat-file "
		    . "or paste a paragraph in plain-text. "));
    push(@buttons, ("Edit"
		    . "\tEdit an existing paragraph or its phrases. "
		    . ""));
    my $options
	 = $self->buttons(undef, \@buttons);



    @buttons = ();
    push(@buttons, ("List"
		    . "\tLists all paragraphs stored in the database, also "
		    . "locate orphaned paragraphs, delete a paragraph, "
		    . "delink a locus and a paragraph"
		   ));
    push(@buttons, ("Curator Central"
		    . "\tReturn to Curator Central"
		    . "\t\*".$configUrl->dictyBaseCGIRoot()."$dblink/"
		    ."curatorLogin"));

    my $options2
	 = $self->buttons("... or select a different action", \@buttons);



    ##########################
    my $output 
	 = (startform.
	    b("This form is for dictyBase curators to display, ".
	      "update, insert or delete Summary Paragraph ".
	      "related info in the ".
	      uc $self->database, " database").
	    p.
	    $self->blueTitleBar("Choose a paragraph to load or edit").
	    b(font({-size=>"+1"},
		   'Enter ').
	      popup_menu(-name=>'type',
			 -'values'=>\@typeValues,
			 -default=>$defaultType,
			 -labels=>\%typeLabels
			).
	      ": ").
	    textfield(-name=>'feat', -size=>30).
	    p.
	    $options.
	    p.
	    $options2.
	    endform
	 );

    return $output;
}

#######################################################################
sub printCheckLoadParagraph {
#######################################################################
    my ($self) = @_;

    $self->{'_title'} = (uc($self->database) . " Load Paragraph");
    $hiddenVars{'currentMode'} = 'CheckLoadParagraph';

    my $searchType = param('type');
    my $searchString = param('feat');

    my @paragraphObjRef = $self->findParagraphObject($searchString, 
						     $searchType);
    my ($dictyBaseObject, $errorMsg) = @paragraphObjRef;
    if (!defined $dictyBaseObject) {
	my $errorHTML = $self->printReturnHTML($errorMsg);
	return $errorHTML;
    }

    my $paragraphObj;
    eval {
	$hiddenVars{'locus_no'} = $dictyBaseObject->locusNo();
	$paragraphObj = ParagraphDS->new(dbh=>$dbh,
					   locusNo=>$dictyBaseObject->locusNo());
    };
    if ($@) {
	# if script execution gets here, it probably means that there is
	#  no summary paragraph for this locus_no
	return $self->printLoadParagraph();
    }

    my $primaryLociRef = $paragraphObj->getPrimaryLoci();
    my %primaryLoci = %$primaryLociRef;
    ##########################
    my @buttons = ();

    if (scalar keys %primaryLoci > 1) {
	push(@buttons, ("Replace All" 
			. ("\tYes, replace this paragraph ".
			   "with a new one.  If any other loci also ".
			   "use this paragraph, their paragraphs will ".
			   "also be replaced with the new paragraph"))
	    );
	push(@buttons, ("Replace Current"
			. ("\tYes, replace this paragraph, ".
			   "but for this locus only.")));
    }
    else {
	push(@buttons, ("Replace"
			. ("\tYes, replace this paragraph with a new one.")
		       ));
    }
    push(@buttons, ("Edit"
		    . ("\tEdit paragraph displayed above.")));
    push(@buttons, ("Back"
		    . ("\tSelect a different locus/paragraph.")));

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);
    ##########################

    my $output = "";

    $output .= $self->blueTitleBar("Message: ".
				   font({-color=>'red'}, "WARNING!"));

    $output .= ("This locus already has a paragraph associated with it! ".
		"Loading another paragraph may overwrite the existing ".
		"paragraph.". p
		);

    $output .= $self->blueTitleBar("Current Paragraph ");

    my $paragraphText = $uiModule->displayParagraph(dictyBaseObject=>$dictyBaseObject);

    $output .= table({-border=>10},
		     Tr(td(
			   $paragraphText
			  )));
    $output .= (startform .
		$options .
		endform
	       );
    return $output;
}

#######################################################################
sub printParagraphList {
#######################################################################
    my ($self) = @_;

    $self->{'_title'} = (uc($self->database) . " List of Paragraphs");
    $hiddenVars{'currentMode'} = "ListParagraph";

    my $output = "";

    delete $hiddenVars{'locus_no'};
    delete $hiddenVars{'paragraph_no'};

    ##########################
    my @buttons = ();
    push(@buttons, ("Return"
		    . ("\tReturn to the main paragraph curation interface.")));

    my $options
	 = $self->buttons("Select an action", \@buttons);
    ##########################

    my %allParagraphs;
    {
	my $sql = ("SELECT paragraph_no, date_written, written_by " .
		   "FROM   CGM_DDB.paragraph ");
	my $sth = $dbh->prepare($sql);
	$sth->execute();
	while (my $array_ref = $sth->fetchrow_arrayref) {
	    my $paragraph_no = $array_ref->[0];
	    my $creation_info = $array_ref->[1] . " " . $array_ref->[2];
	    $allParagraphs{$paragraph_no} = $creation_info;
	}
    }

    my %locusParagraphs;
    {
	my $sql = ("SELECT paragraph_no, locus_no, locus_name " .
		   "FROM   CGM_DDB.locus " .
		   "WHERE  paragraph_no IS NOT null ");
	my $sth = $dbh->prepare($sql);
	$sth->execute();
	while (my $array_ref = $sth->fetchrow_arrayref) {
	    my $paragraph_no = $array_ref->[0];
	    my $locus_no = $array_ref->[1];
	    my $locus_name = $array_ref->[2];

	    if (!defined $locusParagraphs{$paragraph_no}) {
		$locusParagraphs{$paragraph_no} = "$locus_no\t$locus_name";
	    }
	    else {
		$locusParagraphs{$paragraph_no} .= "\n$locus_no\t$locus_name";
	    }

	}
    }

    # $output .= $self->blueTitleBar("All paragraphs:");

    my @bgcolor = ("#FFFFFF", "#F5F5F5");
    my $row_count = 0;

    my $paragraph_row = "";
    foreach my $paragraph_no (sort keys %allParagraphs) {
	my $pLocusList;
	my $delinkList;
	foreach my $pLocusPair (split(/\n/, $locusParagraphs{$paragraph_no})) {
	    my ($locus_no, $locus_name) = split(/\t/, $pLocusPair);
	    if (defined $pLocusList) {
		$pLocusList .= ", ";
		$delinkList .= " ";
	    }

	    $pLocusList .= a({-href=>("$locusURL".$locus_no)},
			     $locus_name
			    );

	    $delinkList 
		 .= input({-type=>"button",
			   -value=>$locus_name,
			   -onClick=>("window.location\='".
				      "$scriptURL".
				      "?user=".$hiddenVars{'user'}.
				      "&locus_no=".$locus_no.
				      "&currentMode=".$hiddenVars{'currentMode'}.
				      "&submit=Delink".
				      "';"
				     )
			  }
			 );


	}
	if (!defined $pLocusList) {
	    $pLocusList = i("(Orphaned Paragraph)");
	}
	# eliminate extraneous spaces at the beginning
	#   and end of hyperlinks
	$pLocusList =~ s/\n *//g;

	my $bgcolor = $row_count++ % 2 ? $bgcolor[0] : $bgcolor[1];

	$paragraph_row .= Tr({-bgcolor=>$bgcolor},
			     td({-align=>"center"},
				a({-href=>("$scriptURL".
					   "?user=".$hiddenVars{'user'}.
					   "&paragraph_no=".$paragraph_no.
					   "&currentMode=EditPhrase"
					  )
				  },
				  $paragraph_no
				 )
				. br
			       ).
			     td (
				 $pLocusList
				).
			     td (
				 $delinkList
				).
			     td (
				 input({-type=>"button",
					-value=>"Delete Paragraph",
					-onClick=>("window.location\='".
						   "$scriptURL".
						   "?user=".$hiddenVars{'user'}.
						   "&paragraph_no=".$paragraph_no.
						   "&currentMode=".$hiddenVars{'currentMode'}.
						   "&submit=Delete".
						   "';"
						  )
				       }
				      )
				).
			     td (
				 "<NOBR>" 
				 . $allParagraphs{$paragraph_no}
				 . "</NOBR>"
				)
			  );
    }

    $output .= (
		b("List of all Paragraphs in the Database, Total Number =".
		  font({-color=>"red"},
		       $row_count
		      )
		 ).
		startform.
		table({-align=>"center",
		       -border=>"0"},
		      Tr({-valign=>"top",
			  -bgcolor=>"#a4abc2"},
			 th({-align=>"center"},
			    "Paragraph No", br
			    font({-size=>"-1"},
				 "(Edit)")),
			 th("Primary Loci", br
			    font({-size=>"-1"},
				 "(View Locus Page)")
			   ),
			 th("Delink from Locus"
			   ),
			th("Delete Paragraph"
			  ),
			 th("Date/Author")
			).
		      $paragraph_row
		     )
		.endform
	       );
    $output .= (startform.
		$options.
		endform);

    return $output;

}

#######################################################################
sub printPreviewParagraph {
#######################################################################
    my ($self, $paragraphText) = @_;

    $self->{'_title'} = (uc($self->database) . " Preview Paragraph");
    $hiddenVars{'currentMode'} = "PreviewParagraph";

    my $output = "";
    my $locusNo = param('locus_no');

    my $locusObj = Locus->new(dbh=>$dbh,
			      locus_no=>$locusNo);
    my $locusName = $locusObj->locus_name();

    # add these lines to imitate the ACE-longtext format for the parser
    if ($paragraphText !~ m/LongText : \"Gene Summary Paragraph for/) {
	$paragraphText 
	     = ("LongText : \"Gene Summary Paragraph for $locusName\"\n"
		. $paragraphText);
    }

    if ($paragraphText !~ m/\*\*\*LongTextEnd\*\*\*/) {
	$paragraphText = $paragraphText . "***LongTextEnd***\n";
    }

    eval {
	my $newParagraphObj;

	$newParagraphObj = $self->parseParagraph(text=>$paragraphText,
						 locus=>$locusNo);

	$newParagraphObj->validate();

	$output .= $self->blueTitleBar("Previewing Paragraph Text");
	$output .= $uiModule->displayParagraph(locusNo=>0,
					       dbh=>$dbh,
					       paragraphObj=>$newParagraphObj);

	$output .= $self->printPhrases(clickable=>undef,
				       paragraphObj=>$newParagraphObj,
				       'title'=>"Previewing Identified Phrases:"
				      );
    };
    if ($@) {
	my $errorMessage = $@;

	$output .= $self->blueTitleBar("Messages");

	$output .= ("While attempting to parse the paragraph for preview, ".
		    "the following errors were encountered: ".p
		    code($errorMessage));
    }

    $output .= $self->printLoadParagraph($paragraphText);

    return $output;
}



#######################################################################
sub printLoadParagraph {
#######################################################################
    my ($self, $fileText) = @_;

    $self->{'_title'} = (uc($self->database) . " Load Paragraph");
    $hiddenVars{'currentMode'} = "LoadParagraph";

    if (!defined $fileText) {
	$fileText = "";
    }

    ##########################
    my @buttons = ();
    push(@buttons, ("Preview"
		    . ("\tPreview Paragraph Text.")));
    push(@buttons, ("Submit"
		    . ("\tLoad into the database and edit the paragraph.")));
    push(@buttons, ("Back"
		    . ("\tSelect a different locus/paragraph.")));

    my $options
	 = $self->buttons("Select an action", \@buttons);
    ##########################

    my $output = "";


    if (length $fileText == 0) {
	$output .= $self->blueTitleBar("Read Paragraph from a Text File");

	$output .= (startform .
		    textfield(-name=>"filename",
			      -size=>75) .
		    br .
		    $self->embedHiddenFields() . 
		    submit({-name=>'submit',
			    -value=>"Read File",
			   }
			  ).
		    endform
		   );
    }


    $output .= $self->blueTitleBar("Use marked-up paragraph from text below");

    my $seeTagsPopup .= font({-size=>"-1",
			      -align=>"center"},
			     " [ " .
			     a({-href=>("$scriptURL".
					"?user=".$hiddenVars{'user'}.
					"&currentMode=EditPhrase".
					"&submit=ListAutoTags"),
				-target=>"_blank"},
			       "See a list of automatically bolded/italicized terms")
			     . " ]"
			    );

    $output .= (startform .
		textarea({-cols=>75,
			  -rows=>20,
			  -name=>"paragraphText",
			  -wrap=>"yes",
			  -default=>$fileText
			 }
			) . br
		$seeTagsPopup. p.
		$options . 
		endform
	       );
    return $output;
}

#######################################################################
sub printEditParagraph {
#######################################################################
    my ($self, %args) = @_;

    $self->{'_title'} = (uc($self->database) . " Edit Paragraph");
    $hiddenVars{'currentMode'} = 'EditParagraph';

    ### load the paragraph from the datbase
    my $paragraphObj;
    my $dictyBaseObject;

    if (defined $args{'paragraphObj'}) { # a paragraph object was passed to
	                                 #   to this method
	$paragraphObj = $args{'paragraphObj'};

	my $locus_no;
	if (defined $hiddenVars{'locus_no'}) {
	    $locus_no = $hiddenVars{'locus_no'};
	}
	else {
	    my $primaryLociRef = $paragraphObj->getPrimaryLoci();
	    my %primaryLoci = %$primaryLociRef;

	    $locus_no = (keys %primaryLoci)[0];
	}

	$dictyBaseObject = dictyBaseObject->new(dbh=>$dbh,
				    locusNo=>$locus_no);
    }
    elsif (defined $args{'paragraphNo'}) {
	my $paragraphNo = $args{'paragraphNo'};
	$paragraphObj = ParagraphDS->new(dbh=>$dbh,
					   paragraphNo=>$paragraphNo);

	my $primaryLociRef = $paragraphObj->getPrimaryLoci();
	my %primaryLoci = %$primaryLociRef;

	if ((defined $hiddenVars{'locus_no'}) 
	    && (defined $primaryLoci{$hiddenVars{'locus_no'}})) {
	    $dictyBaseObject = dictyBaseObject->new
		 (dbh=>$dbh,
		  locusNo=>$hiddenVars{'locus_no'});
	}
	else {
	    foreach my $locus_no (keys %primaryLoci) {
		$dictyBaseObject = dictyBaseObject->new
		     (dbh=>$dbh,
		      locusNo=>$locus_no);
		if (defined $dictyBaseObject) {
		    last;
		}
	    }
	}
    }
    elsif (defined $args{'loadText'}) {
	if (defined param{'replaceAll'}) {
	    my $replaceAll = param{'replaceAll'};

	    if ($replaceAll == 0) {
		# create a new paragraph object to replace the paragraph
		#   for the current locus only

		# delete the association between the current paragraph
		#   object and the current paragraph
		$paragraphObj
		     = ParagraphDS->new(dbh=>$dbh,
					  locusNo=>$hiddenVars{'locus_no'});

		$paragraphObj->unsetPrimaryLoci($hiddenVars{'locus_no'});

		undef $paragraphObj;

		# create the new paragraph object
		$paragraphObj = ParagraphDS->new(dbh=>$dbh);

		$paragraphObj->setPrimaryLoci($hiddenVars{'locus_no'});

	    }
	    else {
		# edit the existing paragraph object to replace the paragraph
		#   for all loci that use this paragraph
		$paragraphObj 
		     = ParagraphDS->new(dbh=>$dbh,
					  locusNo=>hiddenVars{'locus_no'});
	    }
	}
    }
    else {
	my @paragraphObjRef;

	if (defined $hiddenVars{'locus_no'}) {
	    @paragraphObjRef
		 = $self->findParagraphObject($hiddenVars{'locus_no'},
					      "locus");
	}
	else {
	    my $searchType = param('type');
	    my $searchString = param('feat');

	    @paragraphObjRef = $self->findParagraphObject(($searchString, 
							   $searchType));
	}

	($dictyBaseObject, my $errorMsg) = @paragraphObjRef;
	if (!defined $dictyBaseObject) {
	    my $errorHTML = $self->printReturnHTML($errorMsg);
	    return $errorHTML;
	}

	eval {
	    $hiddenVars{'locus_no'} = $dictyBaseObject->locusNo();
	    $paragraphObj = ParagraphDS->new(dbh=>$dbh,
					       locusNo=>$dictyBaseObject->locusNo());
	};
	if ($@) {
	    # if script execution gets here, it probably means that there is
	    #  no summary paragraph for this locus_no
	    my $output = "";
	    $output .= $self->blueTitleBar("Message: ".
					   font({-color=>'red'}, 
						"Unable to find paragraph"));
	    $output .= ("The script was able to find the locus ".
			$dictyBaseObject->locusName().
			" requested, ".
			"but there is no associated paragraph to edit.  ".
			"Please load new paragraph text below, or click ".
			"the BACK button to type a new locus name."
			. p
		       );
	    $output .= $self->printLoadParagraph();

	    return $output;
	}
    }

    $hiddenVars{'paragraph_no'} = $paragraphObj->getParagraphNo();

    #########################
    ### define buttons and their actions

    my @buttons = ();

    push(@buttons, ("Update"
		    . ("\tUpdate Paragraph Information in the database.")));
    push(@buttons, ("Back"
		    . ("\tDiscard changes to Paragraph Information")));

    my $options
	 = $self->buttons("Commit Changes to the database", \@buttons, 150);

    ##########################
    ##### create the web page #####

    my $output = "";

    my $currentParagraph = "";
    $currentParagraph 
	 .= $self->blueTitleBar("Current Paragraph [" .
				font({-color=>"red"},
				     $paragraphObj->getParagraphNo(),
				    ).
				"]"
			       );
    $currentParagraph
	 .= $uiModule->displayParagraph(
					paragraphObj=>$paragraphObj
#					dictyBaseObject=>$dictyBaseObject
				       );

    ### create the paragraph information sidebar
    #
    #
    my $paragraphInformation = "";
    $paragraphInformation .= $self->blueTitleBar("Paragraph&nbsp;Information");

    { # primary loci
	$paragraphInformation .= $self->blueTitleBar("Primary&nbsp;Loci");

	my $primaryLociRef = $paragraphObj->getPrimaryLoci();
	my $primaryLociList = "";
	foreach my $primaryLocus (keys %$primaryLociRef) {
	    my $primaryLocusObject = dictyBaseObject->new(locusNo=>$primaryLocus);
	    $primaryLociList 
		 .= li(a({-href=>"$locusURL".$primaryLocus."&win=1",
			  -onclick=>"open_win()",
			  -target=>"infowin"
			 },
			 $primaryLocusObject->locusName()
			)
		       . " [$primaryLocus]");
	}
	if (length($primaryLociList) < 1) {
	    $primaryLociList = i("(No primary loci)");
	}

	$paragraphInformation .= $primaryLociList . p;

	my $input = (
		     startform .
		     font({-size=>1},
			  "LOCUS_NO / LOCUS_NAME ") . br .
		     input({-type=>'text',
			    -name=>'locus',
			    -size=>10
			   }).br.
		     $self->embedHiddenFields().
		     submit({-name=>'submit',
			     -value=>"Add/Remove"
			    }
			   ).
		     endform
		    );

	$paragraphInformation .= center($input);
    }

    { # contributors
	$paragraphInformation .= $self->blueTitleBar("Contributors");

	my $colleaguesRef = $paragraphObj->getColleagues();
	my $colleagueList = "";
 	foreach my $colleagueNo (keys %$colleaguesRef) {
	    my $colleagueObject = Colleague->new(dbh=>$dbh,
						 colleague_no=>$colleagueNo);
	    $colleagueList .= li(a({-href=>"$colleagueURL".$colleagueNo},
				   $colleagueObject->last_name() . ", " .
				   $colleagueObject->first_name()
				  )
				 . " [$colleagueNo]");
	}
	if (length($colleagueList) < 1) {
	    $colleagueList = i("(No contributors)");
	}

	$paragraphInformation .= $colleagueList . p;

	$paragraphInformation 
	     .= center("[" . 
		       font({-size=>"-1",
			     -align=>"center"},
			    a({-href=>$colleagueSearchURL,
			       -target=>"_blank"},
			      "Search colleague_no")
			   )
		       . " ]" 
		       . p);


	my $input = (
		     startform .
		     font({-size=>1},
			  "COLLEAGUE_NO ") . br .
		     $self->embedHiddenFields().
		     input({-type=>'text',
			    -name=>'colleague',
			    -size=>10
			   }).br.
		     submit({-name=>'submit',
			     -value=>'Add/Remove'
			    }
			   ).
		     endform
		    );

	$paragraphInformation .= center($input);

    }

    $paragraphInformation .= $self->blueTitleBar("&nbsp;");
    #
    #
    ###

    ### generate a list of phrases, each individually clickable
    #
    #
    my $phraseListSection 
	 = $self->printPhrases(clickable=>1,
			       paragraphObj=>$paragraphObj,
			       uiModule=>$uiModule,
			       'title'=>"Edit Phrase Text (click a phrase)"
			      );
    #
    #
    ###

    ### locate modification history
    #
    #
    my $modificationHistory ="";
    $modificationHistory .= $self->blueTitleBar("Modification History");


    $modificationHistory .= ("Original Author:". br
			     . li($paragraphObj->getWrittenBy() . " " 
				  .$paragraphObj->getDateWritten())
			     . p
			     );

    my $updateList = "";
    my $updatesRef = $paragraphObj->getModificationHistory();

    my %updateCount;

    foreach my $update (@$updatesRef) {
	my ($modifiedBy, $modifiedDate) = @$update;

	if (defined $updateCount{"$modifiedBy$modifiedDate"}) {
	    my $count = ++$updateCount{"$modifiedBy$modifiedDate"};

	    $updateList =~ s/$modifiedBy $modifiedDate( \(\d+ updates\))*/$modifiedBy $modifiedDate \($count updates\) /;
	}
	else {
	    $updateList .= li("$modifiedBy $modifiedDate");
	    $updateCount{"$modifiedBy$modifiedDate"} = 1;
	}

	
    }

    if (length($updateList) < 1) {
	$updateList = i("(No Updates)");
    }

    $modificationHistory .= ("Update Log:".br
			     $updateList
			    );
    #
    #
    ###


    my $commitToDatabase = "";
    $commitToDatabase = (startform(-action=>$scriptURL) 
			 . $options 
			 . endform(-action=>$scriptURL));

    $output .= table({-border=>0},
		     Tr({-valign=>"top"},
			td($currentParagraph).
			td($paragraphInformation)).
		     Tr(
			td({-colspan=>2},
			   $phraseListSection
			   . $modificationHistory
			   . $commitToDatabase
			  )
		       )
		    );
    return $output;
}

#######################################################################
sub printPhrases {
#######################################################################

    my ($self, %args) = @_;

    my $clickable = $args{'clickable'} || undef;
    my $paragraphObj = $args{'paragraphObj'};
    my $title = $args{'title'};

    my @bgcolor = ("#FFFFFF", "#F5F5F5");

    my $phraseListSection = "";
    $phraseListSection 
	 .= $self->blueTitleBar($title);

    my $phrasesRef = $paragraphObj->getPhraseTexts();

    my $phraseList = "";
    my $phraseCount = 1;

    foreach my $phraseObj (@$phrasesRef) {
	my ($phraseNo, $phraseText, $phraseCategoryRef) = @$phraseObj;

	$phraseText = $uiModule->renderText($phraseText, 
					    $hiddenVars{'locus_no'});

	# print the phrase, and (optionally) create a clickable link to the 
	#  phrase
	$phraseText =~ s/\<P\>/\<HR\>/ig;

	if (defined $clickable) {
	    $phraseText =~ s/\<A HREF=\"[\#\ \-\=\"\?\.\&A-Za-z0-9\/\:]+\>//ig;
	    $phraseText =~ s/\<\/A\>//ig;

	    $phraseText 
		 = (a({-href=>"$scriptURL?user=" . $hiddenVars{'user'}
		       . "&currentMode=" . $hiddenVars{'currentMode'}
		       . "&paragraph_no=" . $hiddenVars{'paragraph_no'}
		       . "&phrase_no=" . $phraseNo},
		      $phraseText));
	}
	else {
	    $phraseText = $phraseText;
	}

	# expand the list of phrase categories
	my $phraseCategoryList = "";
	{
	    my %phraseCategories = %$phraseCategoryRef;
	    foreach my $phraseCategory (sort keys %phraseCategories) {
		$phraseCategory =~ s/ $//;
		$phraseCategory =~ s/ /\&nbsp\;/g;
		$phraseCategoryList .= b($phraseCategory) . ", ";
		$phraseCategoryList =~ s/[ \n]\,/\,/g;
	    }
	    
	    $phraseCategoryList =~ s/\,\W*$//;
	}

	my $currBgcolor = (($phraseCount % 2) == 1) ? $bgcolor[1] : $bgcolor[0];

	$phraseList .= Tr({-bgcolor=>$currBgcolor,
			   -valign=>"top"}, 
			 td($phraseCount . ".&nbsp;"),
			 td($phraseText),
			 td($phraseCategoryList));
	$phraseCount++;

    }
    $phraseList = table({-border=>"0",
			 -cellspacing=>"0",
			 -cellpadding=>"5"
			},
			$phraseList
		       );
    $phraseListSection .= $phraseList;

    return $phraseListSection;
}

#######################################################################
sub printEditPhrase {
#######################################################################
    my ($self, %args) = @_;

    $self->{'_title'} = (uc($self->database) . " Edit Phrase");
    $hiddenVars{'currentMode'} = 'EditPhrase';

    my $output = "";

    ### load the paragraph from the database
    my $paragraphObj;
    if (defined $args{'paragraphObj'}) {
	$paragraphObj = $args{'paragraphObj'};
    }
    elsif (defined $args{'paragraph_no'}) {
	$paragraphObj = ParagraphDS->new(dbh=>$dbh,
					   paragraphNo=>$args{'paragraph_no'});
    }

    ### load the phrase from the database, or, if specified, from the
    #   arguments passed to this method
    my $phraseObj;
    if (defined $args{'phraseObj'}) {
	$phraseObj = $args{'phraseObj'};
    }
    elsif (defined $args{'phrase_no'}) {
	$phraseObj = PhraseDS->new(dbh=>$dbh,
				     phrase_no=>$args{'phrase_no'});
    }
    elsif (defined $args{'newPhraseOrder'}) {
	$phraseObj = PhraseDS->new(dbh=>$dbh);

	my $newPhraseOrder = $args{'newPhraseOrder'};

	$phraseObj->setPhraseOrder($newPhraseOrder);
	$paragraphObj->insertPhrase(new_phrase_object=>$phraseObj, 
				    new_phrase_order=>$newPhraseOrder);
    }
    else {
	# create a new phrase object
	my $output = "";


	$output .= $self->blueTitleBar("Message: ".
				       font({-color=>'red'}, 
					    "Unable to find phrase"));
	$output .= ("Error: Unable to locate phrase or no phrase order ".
		    "number specified for a new phrase.");

	return $output;
    }

    if ((!defined $paragraphObj) && (!defined $args{'newPhraseNo'})) {
	# since we still don't have a paragraph object at this point, try
	#   to retrieve the paragraph object associated with the current
	#   phrase (if the current phrase is not a new phrase)
	$paragraphObj = ParagraphDS->new(dbh=>$dbh,
					   $phraseObj->getParagraphNo());
    }

    #########################
    ### define buttons and their actions

    my @buttons = ();

    push (@buttons, ("Preview"
		    . ("\tPreview Phrase Modifications.")
		    )
	 );

    push(@buttons, ("Update"
		    . ("\tUpdate Phrase Information in the database.")
		   ));

    if ((defined $phraseObj->getPhraseNo())
	&& ($phraseObj->getPhraseNo() != -1)) {
	push(@buttons, ("Delete"
			. ("\tDelete this phrase from the database.")
		       ));
    }

    push(@buttons, ("Back"
		    . ("\tDiscard changes to this phrase since update ".
		       "button was last pressed.  Return to paragraph page.")
		   ));

    my $options
	 = $self->buttons("Commit Changes to the database", \@buttons, 150);

    ##########################
    # create the web page

    my $navigation = "";
    $navigation .= $self->blueTitleBar("Quick Phrase Navigation");

    my $previousPhrase = "";

    if ($phraseObj->getPhraseOrder() > 0) {
	my $previousPhraseObj 
	     = $paragraphObj->getPhrase($phraseObj->getPhraseOrder() - 1);

	my $prevPhraseNo = $previousPhraseObj->getPhraseNo();

	param('phrase_no', undef);
	$hiddenVars{'phrase_no'} = $prevPhraseNo;
	$previousPhrase = (
			   input({-type=>"button",
				  -value=>'Previous Phrase',
				  -onClick=>"window.location='$scriptURL?user="
				  . $hiddenVars{'user'}
				  . "&currentMode=" 
				  . $hiddenVars{'currentMode'}
				  . "&paragraph_no=" . $hiddenVars{'paragraph_no'}
				  . "&phrase_no=" . $prevPhraseNo 
				  . "&loadFromDBOnly=yes"
				  . "';"

				 })
			  );
    }

    my $nextPhrase = "";
    if ($phraseObj->getPhraseOrder() < $paragraphObj->getPhraseCount() - 1) {
	my $nextPhraseObj
	     = $paragraphObj->getPhrase($phraseObj->getPhraseOrder() + 1);

	my $nextPhraseNo = $nextPhraseObj->getPhraseNo();

	param('phrase_no', undef);
	$hiddenVars{'phrase_no'} = $nextPhraseNo;
	$nextPhrase = (
		       input({-type=>"button",
			      -value=>'Next Phrase',
			      -onClick=>"window.location='$scriptURL?user="
			      . $hiddenVars{'user'}
			      . "&currentMode=" 
			      . $hiddenVars{'currentMode'}
			      . "&paragraph_no=" . $hiddenVars{'paragraph_no'}
			      . "&phrase_no=" . $nextPhraseNo 
			      . "&loadFromDBOnly=yes"
			      . "';"
			     })
		      );
    }

    $navigation .= (
		    startform . 
		    input({-type=>"button",
			   -value=>'Back to Paragraph',
			   -onClick=>"window.location='$scriptURL?"
			   . "user=" . $hiddenVars{'user'}
			   . "&currentMode=" . $hiddenVars{'currentMode'}
			   . "&paragraph_no=" . $hiddenVars{'paragraph_no'}
			   . "';"})
		    .br.
		    $previousPhrase . 
		    $nextPhrase . endform . 
		    font({-size=>"-2"},
			 b("Note: ").
			 "Using the buttons provided above will result in ".
			 "the loss of any changes made to this phrase since ".
			 "the update button was last pressed."
		     )
		   );

    $hiddenVars{'phrase_no'} = $phraseObj->getPhraseNo();

    ### display current phrase
    #
    #

    # This next line actaully belongs in the "preview entire paragraph"
    #   section, below, however, it done here so that references can be
    #   numbered in a consistent fashion between the "current phrase" display
    #   and the "preview entire paragraph" and the "Edit Paragraph" screen.

    #Mira Jan/22/02 should set dbh before using it
    $uiModule->setDbh($dbh);

    my $renderedParagraph
	 .= $uiModule->displayPhrases(paragraphObj=>$paragraphObj,
				      locusNo=>$hiddenVars{'locus_no'},
				      highlight=>$phraseObj->getPhraseOrder()
				     );


    my $currentPhrase = "";
    $currentPhrase 
	 .= $self->blueTitleBar("Current Phrase".
				" [".
				font({-color=>"red"},
				     $phraseObj->getPhraseNo(),
				    ).
				"]"
			       );

    my $parsedPhraseText = $phraseObj->getPhraseText();

    #Mira Jan/22/02 should set dbh before using it
    #$uiModule->setDbh($dbh);

    my @ref_nums = ();
    $currentPhrase .= $uiModule->renderText($parsedPhraseText, 0, \@ref_nums);

    if (length($parsedPhraseText) == 0) {
	$currentPhrase .= i("(No phrase text)");
    }

    if ((!defined $args{'newPhraseOrder'})
	&& ($hiddenVars{'phrase_no'} != -1)
	&& (defined $hiddenVars{'phrase_no'})
       ) {

	# we don't want the phrase_no of the current phrase to be passed
	#  onto future phrases
	my $tempPhraseNo = $hiddenVars{'phrase_no'};
	$hiddenVars{'phrase_no'} = -1;

	param('phrase_no', undef);

	$currentPhrase 
	     .= (startform(-action=>$scriptURL) .
		 $self->embedHiddenFields().
		 table({-width=>'100%'},
		       Tr(
			  td({-align=>'left'},
			     hidden({-name=>"oldPhrasePosition",
				     -value=>$phraseObj->getPhraseOrder()}),
			     submit({-name=>"submit",
				     -value=>'Insert New Phrase Before'}
				   ),
			    ),
			  td({-align=>'right'},
			     $self->embedHiddenFields(),
			     submit({-name=>"submit",
				     -value=>'Insert New Phrase After'}
				   ),
			    )
			 )
		      ).
		 endform .
		 font({-size=>"-2"},
		      b("Note: ").
		      "Please ensure that you have updated any changes ".
		      "to this phrase by clicking the \"update\" ".
		      "button before inserting any new phrases.  ".
		      "Otherwise, changes made to this phrase will be ".
		      "lost."
		     )
		);

	$hiddenVars{'phrase_no'} = $tempPhraseNo;
    }
    #
    #
    ###

    ### phrase order
    #
    #
    my $phraseOrder = "";
    $phraseOrder .= $self->blueTitleBar("Phrase Order");
    $phraseOrder .= ("This phrase is currently in position "
		     . b($phraseObj->getPhraseOrder()+1)
		     . " of " 
		     . b($paragraphObj->getPhraseCount())
		     . "." .br
		    );
    $phraseOrder .= (
		     "Move this phrase to position: "
		     . input({-type=>"text",
			      -name=>"phraseOrder",
			      -value=>$phraseObj->getPhraseOrder()+1,
			      -size=>"5"})
		     . " (" 
		     . ($paragraphObj->getPhraseCount()+1)
		     . " = end of the paragraph)"
		    );

    # merge phrase buttons
    my $mergeButtons = "";
    if (($phraseObj->getPhraseOrder() > 0) 
	 && (defined $hiddenVars{'phrase_no'})) {
	my $mergeWithPrevious 
	     = td({-align=>'left'},
		  submit({-name=>"submit",
			  -value=>'Merge with Previous Phrase'}
			),
		 );
	$mergeButtons = $mergeWithPrevious;
    }

    if (($phraseObj->getPhraseOrder()+1 < $paragraphObj->getPhraseCount())
	&& (defined $hiddenVars{'phrase_no'})) {
	my $mergeWithNext
	     = td({-align=>'right'},
		  submit({-name=>"submit",
			  -value=>'Merge with Next Phrase'}
			),
		 );
	$mergeButtons .= $mergeWithNext;
    }

    if (length($mergeButtons) > 0) {
	$mergeButtons = table({-width=>'100%',
			       -border=>'0'},
			      Tr($mergeButtons));
	$phraseOrder .= $mergeButtons;
    }

    $phraseOrder .= p;
    #
    #
    ###

    ### references
    #
    #
    my $references = "";

    my $referenceTitleBarText = ("References");

    $references .= $self->blueTitleBar($referenceTitleBarText);

    # load references from the database
    my $referenceListRef = $phraseObj->getOrderedReferences();
    my @referenceList = @$referenceListRef;
    my $referencesOnly = "";

    my $refno = 1;
    my @bgcolor = ("#FFFFFF", "#F5F5F5");

    foreach my $refKey (@referenceList) {
	my $refObj;
	$refObj = Reference->new(dbh=>$dbh,
				 reference_no=>$refKey);

        my $currBgcolor = (($refno % 2) == 1) ? $bgcolor[1] : $bgcolor[0];

        $referencesOnly .= Tr(td({bgcolor=>$currBgcolor,
				  valign=>"top",
				  align=>"left"},
				 $ref_nums[$refno-1] . ")" ),
			      td({bgcolor=>$currBgcolor},
				 a(
				   {name=>"$refKey"}
				  ).
				 $refObj->formatedCitation(),
				 font({-size=>"-1"},
				      "[Reference No: ", b($refKey), "]")
				)
			     );
        $refno++;
    }

    if (length($referencesOnly) == 0) {
	$referencesOnly .= i("(No references)");
    }

    $references .= (table({border=>0},
			  $referencesOnly,
			 ).
		    p.
		    "Markup syntax to add a reference is ".
		    code("&lt;ref_no:####&gt;") .
		    " or " .
		    code("&lt;ref_pm:########&gt; ").
		    "(no closing tag)". br

		    font({-size=>"-1",
			  -align=>"center"},
			 " [ " .
			 a({-href=>"$scriptURL?user=" . $hiddenVars{'user'}
			    . "&currentMode=ShowReferences"
			    . "&paragraph_no=" . $hiddenVars{'paragraph_no'},
			    -target=>"_blank"},
			   "List all references used by this paragraph")
			 . " ] (Pop-up)"
			)
		    . br . 
		    font({-size=>"-1",
			  -align=>"center"},
			 " [ " .
			 a({-href=>$referenceSearchURL,
			    -target=>"_blank"},
			   "Look up a Reference No")
			 . " ]"
			)
		    . br .
		    font({-size=>"-1",
			  -align=>"center"},
			 " [ " .
			 a({-href=>"$helpURL#phrase_References",
			    -target=>"_blank"},
			   "How do I add or remove a reference?")
			 . " ]"
			)
		    .p
		   );
    #
    #
    ###

    ### create the phrase information sidebar
    #
    #
    my $phraseCategories = "";
    $phraseCategories .= $self->blueTitleBar("Phrase&nbsp;Category");

    {
	my $sql = ("SELECT code_value ".
		   "FROM   CGM_DDB.code ".
		   "WHERE  tab_name = 'PHRASE_CATEGORY' ");
	my $sth = $dbh->prepare($sql);
	$sth->execute();
	my $arrayRef = $sth->fetchall_arrayref();

	my $phraseCategoryRef = $phraseObj->getPhraseCategories();
	my %phraseCategory  = %$phraseCategoryRef;

	my $phraseCategoriesChk = "";
	foreach my $category_row (@$arrayRef) {
	    my ($category_name) = @$category_row;

	    if (defined $phraseCategory{$category_name}) {
		my $display_category_name = $category_name;
		$display_category_name =~ s/ /\&nbsp\;/g;
		$display_category_name = font({size=>"-1"},
					      $display_category_name);

		$phraseCategoriesChk .= (
				      input({-name=>"Category: $category_name",
					     -type=>"checkbox",
					     -checked=>"Yes"
					    }
					   ).
				      b($display_category_name).
				      br

				     );
	    }
	    else {
		my $display_category_name = $category_name;
		$display_category_name =~ s/ /\&nbsp\;/g;
		$display_category_name = font({size=>"-1"},
					      $display_category_name);

		$phraseCategoriesChk .= (
				      input({-name=>"Category: $category_name",
					     -type=>"checkbox"}
					   ).
				      $display_category_name. 
				      br
				     );
	    }
	}

	# this next substitution will eliminate any extraneous linebreaks that
	#   may appear between the checkboxes and their corresponding label
	$phraseCategoriesChk
	     =~ s/(TYPE=\"checkbox\"\>\<FONT SIZE=\"-1\"\>)\n +([\w\&\;]+)\n/$1&nbsp;$2/g;

	# this substitution will eliminate any extraneous linebreaks (usually
	#   resulting in blank lines) between a checkbox-label and the
	#   next checkbox-label
	$phraseCategoriesChk =~ s/\n//g;

	$phraseCategories .= $phraseCategoriesChk;
    }

    #
    #
    ###

    ### Secondary Loci
    #
    #
    my $secondaryLoci = "";
    $secondaryLoci = $self->blueTitleBar("Secondary Loci");

    my $secondaryLociRef = $phraseObj->getSecondaryLoci();

    my $secondaryLociList = "";
    foreach my $secondaryLocus (@$secondaryLociRef) {
	my $locusName = dictyBaseObject->new(dbh=>$dbh,
					  locusNo=>$secondaryLocus)->locusName();
	$secondaryLociList .= (a({-href=>"$locusURL".$secondaryLocus},
				 "$locusName"
				).
			       font({-size=>"-2"},
				    "[$secondaryLocus]").
			       ", "
			      );
    }

    if (length $secondaryLociList == 0) {
	$secondaryLociList = i("(No secondary loci present)");
    }
    else {
	$secondaryLociList =~ s/, $//g;
    }
    $secondaryLoci .= $secondaryLociList;

    my $primaryLociRef = $paragraphObj->getPrimaryLoci();

    my $primaryLociList = "";
    foreach my $primaryLocus (keys %$primaryLociRef) {
	my $locusName = dictyBaseObject->new(dbh=>$dbh,
					  locusNo=>$primaryLocus)->locusName();
	$primaryLociList .= (a({-href=>"$locusURL".$primaryLocus."&win=1",
				-onclick=>"open_win()",
				-target=>"infowin"
			       },
			       "$locusName" 
			       ).
			     font({-size=>"-2"},
				  "[$primaryLocus]").
			     ", "
			    );
    }

    if (length $primaryLociList == 0) {

    }
    else {
	$primaryLociList = p. "Primary Loci: ". $primaryLociList;
	$primaryLociList =~ s/, $//g;
	$primaryLociList .= p;
    }
    $secondaryLoci .= ($primaryLociList.
		       "Markup syntax to add a primary locus is ".
		       code("&lt;LOCUS01:####&gt;XXX#&lt;/LOCUS&gt;") . br
		       " or to add a secondary locus is " .
		       code("&lt;LOCUS02:####&gt;XXX#&lt;/LOCUS&gt;") .
		       br .
		       font({-size=>"-1",
			     -align=>"center"},
			    " [ " .
			    a({-href=>$locusURL,
			       -target=>"_blank"},
			      "Look up a Locus No")
			    . " ]"
			   )
		       . br .
		       font({-size=>"-1",
			     -align=>"center"},
			    " [ " .
			    a({-href=>"$helpURL#phrase_secondaryLoci",
			       -target=>"_blank"},
			      "How do I add or remove a Locus?")
			    . " ]"
			   )
		       .p);
    #
    #
    ### end Locus lists

    ### GO Terms
    #
    #
    my $goTerms = "";
    $goTerms = $self->blueTitleBar("GO Terms");

    my $goTermRef = $phraseObj->getGOTerms();

    my $goTermList = "";
    foreach my $goID (@$goTermRef) {
	my $goObj = Go->new(dbh=>$dbh,
			    goid=>$goID);

	if (defined $goObj) {
	    $goTermList .= li(a({-href=>"$goURL".$goID},
				$goObj->go_term()).
			      font({-size=>"-2"},
				   "[$goID]")
			     );
	}
	else {
	    $goTermList .= li("Unknown GO ID: ".
			      a({-href=>"$goURL".$goID},
				"$goID"));
	}
    }

    if (length $goTermList == 0) {
	$goTermList = i("(No GO Terms)");
    }
    else {
	$goTermList =~ s/, $//g;
	$goTermList .= p;
    }
    $goTerms .= ($goTermList . 
		 "Markup syntax to add a GO term is ".
		 code("&lt;GO:####&gt;relevant term&lt;/GO&gt;") . br
		 font({-size=>"-1",
		       -align=>"center"},
		      " [ " .
		      a({-href=>$goBrowserURL,
			 -target=>"_blank"},
			"Open the GO Browser")
		      . " ]"
		     )
		 . br .
		 font({-size=>"-1",
		       -align=>"center"},
		      " [ " .
		      a({-href=>"$helpURL#phrase_GO",
			 -target=>"_blank"},
			"How do I add or remove a GO Term?")
		      . " ]"
			   )
		 .p);


    #
    #
    ### end GO Terms

    ### edit phrase text
    #
    #
    my $editPhraseText = "";
    $editPhraseText = $self->blueTitleBar("Edit phrase mark-up text");

    my $phraseText = $phraseObj->getPhraseText();

    param('phraseText', undef);
    param('phraseText', $phraseText);
    $editPhraseText .= (textarea({-name=>"phraseText",
				  -rows=>10,
				  -cols=>75,
				  -wrap=>"yes",
				  -default=>$phraseText
				 }) .
			br
			);

    $editPhraseText .= (
			$self->embedHiddenFields() .
			submit({-name=>'submit',
				-value=>'Preview Markup Text'
			       })
		       );

    $editPhraseText .= (
			submit({-name=>'submit',
				-value=>'Split this Phrase'
			       })
		       );


    $editPhraseText .= (p.
			"Insert three pipe symbols, |||, at the position of the intended split. " . br .
		   font({-size=>"-2"},
		   "Note: Please ensure that you have updated any changes to this phrase by clicking the \"update\" button before clicking the 'Split this Phrase' button. Otherwise, changes made to this phrase will be lost.") . br.

                       font({-size=>"-1",
			     -align=>"center"},
			    " [ " .
			    a({-href=>("$scriptURL".
				       "?user=".$hiddenVars{'user'}.
				       "&currentMode=EditPhrase".
				       "&submit=ListAutoTags"),
			       -target=>"_blank"},
			      "See bolded/italicized tags")
			    . " ]"
			   ). br
);

    $editPhraseText .= font({-size=>"-1",
			     -align=>"center"},
			    " [ " .
			    a({-href=>("$helpURL"),
			       -target=>"_blank"},
			      " See help documentation")
			    . " ]"
			   );
    #
    #
    ### end edit phrase text

    ### preview entire paragraph
    #
    #
    my $paragraphPreview = "";
    $paragraphPreview = $self->blueTitleBar("Preview Entire Paragraph");

    $paragraphPreview 
	 .= $renderedParagraph;
    #
    #
    ### end preview entire paragraph

    my $commitToDatabase = "";
    $commitToDatabase = $options;

    $output .= (center($navigation) .
		table({-border=>0,
		       -width=>"100%"},
		      Tr(
			 {-valign=>"top"},
			 td($currentPhrase.p.
			    startform({-action=>$scriptURL}) . # <- start form
			    $phraseOrder.p
			    $references).
			 td($phraseCategories.p)).
		      Tr(
			 td({-colspan=>2},
			    table({-width=>"100%"},
				  Tr({-valign=>"top"},
				     td({-width=>"50%"},
					$secondaryLoci).
				     td({-width=>"50%"},
					$goTerms))).p.
			    $editPhraseText.p
			    $paragraphPreview.p
			    $commitToDatabase
			   )
			)
		     )
		.endform # <- end form

	       );

    return $output;
}

#######################################################################
sub printReturnHTML {
#######################################################################
    my ($self, $errorMessage) = @_;

    $hiddenVars{'currentMode'} = 'CheckLoadParagraph';

    ##########################
    my @buttons = ();
    push(@buttons, ("Back" 
		    . ("\tReturn to the previous page. ".
		       ""))
	);

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);
    ##########################

    $errorMessage =~ s/\n/\<BR\>/g;

    my $output = $self->blueTitleBar(
				   font({-color=>"RED"},
					"MESSAGE")
				  );
    $output .= ($errorMessage.
		startform.
		$options.
		endform
	       );
    return $output;
}

#######################################################################
sub checkParagraphSubmission {
#######################################################################
    my ($self, $loadText) = @_;

    if (length($loadText) < 1) {

	# trim both leading- and trailing- whitespace.
	$loadText =~ s/^\s+//g;
	$loadText =~ s/\s+$//g;

	my $output = "";
	$output .= $self->blueTitleBar(
				       font({-color=>"RED"},
					    "MESSAGE")
				      );
	$output .= ("This script did not receive any text to load.  If you ".
		    "are attempting to load a paragraph from a file, please ".
		    "click the \'Read File\' button before attempting to ".
		    "submit the paragraph into the database.  Otherwise, you ".
		    "have encountered a bug in the script.  Thanks!\n") . p;

	$output .= $self->printLoadParagraph();

	return $output;
    }
    $hiddenVars{'currentMode'} = "PreviewParagraph";


    my $output = "";
    my $locusNo = param('locus_no');

    my $locusObj = Locus->new(dbh=>$dbh,
			      locus_no=>$locusNo);
    my $locusName = $locusObj->locus_name();


    # add these tags to imitate the ACE-longtext format
    if ($loadText !~ m/LongText : \"Gene Summary Paragraph for/) {
	$loadText = ("LongText : \"Gene Summary Paragraph for $locusName\"\n"
		      . $loadText);
    }

    if ($loadText !~ m/\*\*\*LongTextEnd\*\*\*/) {
	$loadText = $loadText . "***LongTextEnd***\n";
    }

    # read and parse the flat text file
    my $newParagraphObj = $self->parseParagraph(text=>$loadText,
						locus=>$locusNo);

    my $replaceAll = $hiddenVars{'replaceAll'};
    my $oldParagraphObj;
    if (defined $replaceAll) {

	my $locusObj = Locus->new(dbh=>$dbh,
				  locus_no=>$locusNo
				 );
	my $oldParagraphNo = $locusObj->paragraph_no();
	$oldParagraphObj = ParagraphDS->new(dbh=>$dbh,
					      paragraphNo=>$oldParagraphNo
					     );
	my $pLociRef = $oldParagraphObj->getPrimaryLoci();
	my %pLoci = %$pLociRef;

	foreach my $pLocusNo (keys %pLoci) {
	    if (lc($replaceAll) eq "yes") {
		# replace existing paragraph for all loci using this paragraph
		$newParagraphObj->setPrimaryLoci($pLocusNo);
		$oldParagraphObj->unsetPrimaryLoci($pLocusNo);
		$oldParagraphObj->delete();
	    }
	    elsif (lc($replaceAll) eq "no") {
		# replace existing paragraph for the current locus only.

		$newParagraphObj->unsetPrimaryLoci($pLocusNo);

		if ($pLocusNo ne $locusNo) {
		    $oldParagraphObj->setPrimaryLoci($pLocusNo);
		}
	    }
	}
    }
    $newParagraphObj->setPrimaryLoci($locusNo);

    ### insert the text (represented as a paragraphObject and phraseObjects
    #     stored within that paragraphObject) into the database
    # $output .= $self->blueTitleBar("Loading Paragraph Text");

    # $output .= ("Now inserting paragraph and phrases into the database. ".
    #		"Any problems encountered will be noted in the space ".
    #		"below.") . p;

    $output .= $self->blueTitleBar
	 ("Messages from Oracle or Paragraph/Phrase objects:");

    eval {
	$newParagraphObj->update();

	$output .= ("No problems were encountered while ".
		    "inserting this paragraph into the database.". p);

	if (defined $oldParagraphObj) {
	    $oldParagraphObj->update();
	    $output .= ("No problems were encountered while ".
			"deleting the old paragraph from the database.". p);
	}
	$dbh->commit();
    };
    if ($@) {
	my $errorMessage = $@;
	my $dbiErrorString = $DBI::errstr;

	$output .= "Error Message:". br;
	$output .= code($errorMessage);
	$output .= code($dbiErrorString);
	$dbh->rollback();

	$output .= $self->blueTitleBar("What to do next: ");

	$output .= (p . "Because an error was encountered, all changes have "
		    . "rolled back as a precautionary measure." . br
		    );

	if (defined $dbiErrorString) {
	    $output .= $self->printEMailBugReport($errorMessage 
						  . "\n\n" . $dbiErrorString);
	}

	$output .= $self->printLoadParagraph($loadText);
    }
    else {
	$output .= $self->printEditParagraph(locus_no=>$locusNo);
    }

    return $output;
}

#######################################################################
sub findParagraphObject {
#######################################################################
    my ($self, $input, $type) = @_;

    # trim leading and trailing blank spaces
    $input =~ s/^\s+//g;
    $input =~ s/\s+$//g;

    my $locusObj;
    my $dictyBaseObject;
    my $errorMsg = "";

    if (($type =~ /feature/i) && (length($input) > 0) ){
	my $featureObj;
	if ($input =~ /^(\d+)$/) {
	    # input is a FEATURE_NO
	    $featureObj = Feature->new(dbh=>$dbh, feature_no=>$1);
	}
	else {
	    # input is a FEATURE_NAME
	    $featureObj = Feature->new(dbh=>$dbh, feature_name=>$input)
	}

	if (defined $featureObj) {
	    if (defined $featureObj->locus_no) {
		$locusObj 
		     = Locus->new(dbh=>$dbh, locus_no=>$featureObj->locus_no);
	    }
	    else {
		$errorMsg 
		     = ("Unable to determine LOCUS_NO for FEATURE_NAME ".
			$featureObj->feature_name.
			" (FEATURE_NO ". $featureObj->feature_no . ")\n");
	    }
	}
	else {
	    $errorMsg = "FEATURE_NO or FEATURE_NAME: \"$input\" not found.\n";
	}
    }
    elsif (($type =~ /locus/i) && (length($input) > 0)) {
	if ($input =~ /^(\d+)$/) {
	    # input is a LOCUS_NO
	    $locusObj = Locus->new(dbh=>$dbh, locus_no=>$1);
	}
	else {
	    # input is a LOCUS_NAME
	    # uc $input;
	    $locusObj = Locus->new(dbh=>$dbh, locus_name=>$input);
	}
    }
    else {
	$errorMsg = "No LOCUS or FEATURE specified.\n";
    }

    if (!defined $locusObj) {
	$errorMsg .= "Could not determine which LOCUS to be edited.\n";
    }
    else {
	my $locus_no = $locusObj->locus_no;
	$dictyBaseObject = dictyBaseObject->new(dbh=>$dbh, 
				       locusNo=>$locus_no
				      );

    }
    return ($dictyBaseObject, $errorMsg);
}

#######################################################################
sub printCommitParagraphVerification {
#######################################################################
    my ($self, %args) = @_;

    $self->{'_title'} = (uc($self->database) . " Commit Paragraph");
    my $output = "";

    $hiddenVars{'currentMode'} = 'CommitParagraphVerification';
    ##########################
    my @buttons = ();
    push(@buttons, ("Continue" 
		    . ("\tContinue editing the current paragraph. ".
		       ""))
	);

    push(@buttons, ("Select New Paragraph" 
		    . ("\tSelect a new paragraph to edit. ".
		       ""))
	);

    push(@buttons, ("Curator Central"
		    . "\tReturn to Curator Central"
		    . "\t\*".$configUrl->dictyBaseCGIRoot()."$dblink/"
		    ."curatorLogin"));

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);

    ##########################

    $output .= $self->blueTitleBar(
			     font({-color=>"RED"},
				"MESSAGE")
			   );

    $output .= ("Now committing changes to the database. ".
		"Any problems encountered will be noted in the space ".
		"below.") . p;

    $output .= $self->blueTitleBar("Messages from Oracle or Phrase/Paragraph objects:");

    eval {
	if (defined $args{'paragraphObj'}) {
	    my $paragraphObj = $args{'paragraphObj'};

	    $paragraphObj->update();

	    $output .= ("No problems were encountered while ".
			"updating the paragraph.");

	    $dbh->commit();
	}
    };
    if ($@) {
	my $errorMessage = $@;
	my $dbiErrorMessage = $DBI::errstr;

	$output .= code($errorMessage);
	$output .= code($dbiErrorMessage);
	$dbh->rollback();

	$output .= $self->blueTitleBar("What to do next: ");

	$output .= (p . "Because an error was encountered, all changes have "
		    . "rolled back as a precautionary measure." . br
		    );

	$output .= $self->printEMailBugReport($errorMessage 
					      . "\n\n" . $dbiErrorMessage);
    }

    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= startform . $options . endform;

    return $output;

}

#######################################################################
sub printCommitPhraseVerification {
#######################################################################
    my ($self, %args) = @_;

    $self->{'_title'} = (uc($self->database) . " Commit Phrase");
    my $output = "";

    $hiddenVars{'currentMode'} = 'CommitPhraseVerification';

    ##########################
    my @buttons = ();
    push(@buttons, ("Continue" 
		    . ("\tEdit the Current Phrase. ".
		       ""))
	);

    push(@buttons, ("Edit Paragraph" 
		    . ("\tEdit the current paragraph. ".
		       ""))
	);

    push(@buttons, ("Select New Paragraph" 
		    . ("\tSelct a new paragraph to edit. ".
		       ""))
	);

    push(@buttons, ("Curator Central"
		    . "\tReturn to Curator Central"
		    . "\t\*".$configUrl->dictyBaseCGIRoot()."$dblink/"
		    ."curatorLogin"));

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);

    ##########################

    $output .= $self->blueTitleBar(
			     font({-color=>"RED"},
				"MESSAGE")
			   );

    $output .= ("Now committing changes to the database. ".
		"Any problems encountered will be noted in the space ".
		"below.") . p;

    $output .= $self->blueTitleBar("Messages from Oracle or Phrase/Paragraph objects:");

    my $phrase_no;

    eval {
	if (defined $args{'paragraphObj'}) {
	    my $paragraphObj = $args{'paragraphObj'};
	    $paragraphObj->update();

	    $output .= ("No problems were encountered while ".
			"updating the paragraph.");

	    if (($phrase_no == -1)  || (!defined $phrase_no)) {
		# if this is a new phrase, we need its newly-assigned phrase_no
		#   so that we can send users back to that phrase
		my $phrase_order = param('phraseOrder');

		my $phraseObj = $paragraphObj->getPhrase($phrase_order-1);
		$phrase_no = $phraseObj->getPhraseNo();

		$hiddenVars{'phrase_no'} = $phrase_no;
	    }
	    $dbh->commit();
	}
    };
    if ($@) {
	my $errorMessage = $@;
	my $dbiErrorMessage = $DBI::errstr;

	$output .= code($errorMessage);
	$output .= code($dbiErrorMessage);
	$dbh->rollback();

	$output .= $self->blueTitleBar("What to do next: ");

	$output .= (p . "Because an error was encountered, all changes have "
		    . "rolled back as a precautionary measure." . br
		    );

	$output .= $self->printEMailBugReport($errorMessage 
					      . "\n\n" 
					      . $dbiErrorMessage);

	$output .= $self->printEditPhrase(paragraphObj=>$args{'paragraphObj'},
					  phraseObj=>$args{'phraseObj'});

	return $output;
    }

    return $output . $self->printEditPhrase(phraseObj=>$args{'phraseObj'},
					    paragraphObj=>$args{'paragraphObj'}
					   );


    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= startform . $options . endform;

    return $output;

}

#######################################################################
sub printDeletePhraseResult {
#######################################################################
    my ($self, %args) = @_;

    my $output = "";

    $self->{'_title'} = (uc($self->database) . " Phrase Deletion");
    $hiddenVars{'currentMode'} = 'DeletePhraseResult';

    my $paragraphObj = $args{'paragraphObj'};
    my $phraseObj = $args{'phraseObj'};

    ##########################
    my @buttons = ();

    push(@buttons, ("Edit Paragraph" 
		    . ("\tEdit the current paragraph. ".
		       ""))
	);

    push(@buttons, ("Select New Paragraph" 
		    . ("\tSelect a new paragraph to edit. ".
		       ""))
	);

    push(@buttons, ("Curator Central"
		    . "\tReturn to Curator Central"
		    . "\t\*".$configUrl->dictyBaseCGIRoot()."$dblink/"
		    ."curatorLogin"));

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);

    ##########################
    $output .= $self->blueTitleBar(
			     font({-color=>"RED"},
				"MESSAGE")
			   );

    $output .= ("Now committing changes to the database. ".
		"Any problems encountered will be noted in the space ".
		"below.") . p;

    $output .= $self->blueTitleBar("Messages from Oracle or Paragraph/Phrase objects:");

    eval {
	$paragraphObj->deletePhrase($phraseObj->getPhraseNo());
	$paragraphObj->update();

	$output .= ("No problems were encountered while ".
		    "deleting this phrase from the paragraph.");

	$dbh->commit()
    };
    if ($@) {
	my $errorMessage= $@;
	my $dbiErrorMessage = $DBI::errstr;


	$output .= code($errorMessage);
	$output .= code($dbiErrorMessage);
	$dbh->rollback();

	$output .= $self->blueTitleBar("What to do next: ");

	$output .= (p . "Because an error was encountered, all changes have "
		    . "rolled back as a precautionary measure." . br
		    );

	$output .= $self->printEMailBugReport($errorMessage 
					      . "\n\n" . $dbiErrorMessage);

    }

    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= startform . $options . endform;

    return $output;

}

#######################################################################
sub printDeleteParagraphResult {
#######################################################################
    my ($self, %args) = @_;

    my $output = "";

    $self->{'_title'} = (uc($self->database) . " Paragraph Deletion");
    $hiddenVars{'currentMode'} = 'DeleteParagraphResult';

    my $paragraph_no = $args{'paragraphNo'};
    my $paragraphObj = ParagraphDS->new(dbh=>$dbh,
					  paragraphNo=>$paragraph_no);


    ##########################

    my @buttons = ();

    push(@buttons, ("List all Paragraphs" 
		    . ("\tView the list of all paragraphs. ".
		       ""))
	);

    push(@buttons, ("Select New Paragraph" 
		    . ("\tSelect a new paragraph to edit. ".
		       ""))
	);

    push(@buttons, ("Curator Central"
		    . "\tReturn to Curator Central"
		    . "\t\*".$configUrl->dictyBaseCGIRoot()."$dblink/"
		    ."curatorLogin"));

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);

    ##########################
    $output .= $self->blueTitleBar(
			     font({-color=>"RED"},
				"MESSAGE")
			   );

    $output .= ("Now committing changes to the database. ".
		"Any problems encountered will be noted in the space ".
		"below.") . p;

    $output .= $self->blueTitleBar("Messages from Oracle or Paragraph/Phrase objects:");

    eval {
	$paragraphObj->delete();
	$paragraphObj->update();

	$output .= ("No problems were encountered while ".
		    "deleting this paragraph from the database.");

	# $dbh->rollback();
	$dbh->commit();
    };
    if ($@) {
	my $errorMessage= $@;
	my $dbiErrorMessage = $DBI::errstr;


	$output .= code($errorMessage);
	$output .= code($dbiErrorMessage);
	$dbh->rollback();

	$output .= $self->blueTitleBar("What to do next: ");

	$output .= (p . "Because an error was encountered, all changes have "
		    . "rolled back as a precautionary measure." . br
		    );

	$output .= $self->printEMailBugReport($errorMessage 
					      . "\n\n" . $dbiErrorMessage);

    }

    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= startform . $options . endform;

    return $output;

}

#######################################################################
sub printDelinkParagraphResult {
#######################################################################
    my ($self) = @_;

    my $output = "";

    $hiddenVars{'currentMode'} = 'DelinkParagraphResult';
    $self->{'_title'} = (uc($self->database) . " Delink Paragraph");

    ##########################
    my @buttons = ();
    push(@buttons, ("Select New Paragraph" 
		    . ("\tSelect a new paragraph to edit. ".
		       ""))
	);

    push(@buttons, ("List All Paragraphs" 
		    . ("\tDisplay all paragraphs in the database. ".
		       ""))
	);

    push(@buttons, ("Curator Central"
		    . "\tReturn to Curator Central"
		    . "\t\*".$configUrl->dictyBaseCGIRoot()."$dblink/"
		    ."curatorLogin"));

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);

    ##########################
    $output .= $self->blueTitleBar(
			     font({-color=>"RED"},
				"MESSAGE")
			   );

    my $paragraphObj = ParagraphDS->new(dbh=>$dbh,
					  locusNo=>$hiddenVars{'locus_no'});

    my $primaryLociRef = $paragraphObj->getPrimaryLoci();
    my %primaryLoci = %$primaryLociRef;

    if (0) {
	if (scalar keys %primaryLoci <= 1) {
	    $output
		 .= ("You cannot delink this paragraph from the locus.  This ".
		     "is the only primary locus for this paragraph and delinking ".
		     "would create an orphaned paragraph in the database (a ".
		     "paragraph with no primary loci).". p);
	    $output 
		 .= ("If you truly wish to delete this paragraph from the ".
		     "database, please use the paragraph deletion tool. (Not ".
		     "currently available.)");
	}
	else {
	}
    }

    $output .= ("Now committing changes to the database. ".
		"Any problems encountered will be noted in the space ".
		"below.") . p;

    $output .= $self->blueTitleBar("Messages from Oracle or Phrase/Paragraph objects:");

    eval {
	$paragraphObj->unsetPrimaryLoci($hiddenVars{'locus_no'});
	$paragraphObj->update();

	$output .= ("No problems were encountered while ".
		    "delinking this locus from the paragraph.");

	$dbh->commit();
    };
    if ($@) {
	my $errorMessage= $@;
	my $dbiErrorMessage = $DBI::errstr;

	$output .= code($errorMessage);
	$output .= code($dbiErrorMessage);
	$dbh->rollback();

	$output .= $self->blueTitleBar("What to do next: ");

	$output .= (p . "Because an error was encountered, all changes have "
		    . "rolled back as a precautionary measure." . br
		    );

	$output .= $self->printEMailBugReport($errorMessage 
					      . "\n\n" . $dbiErrorMessage);
    }

    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= startform . $options . endform;

    return $output;

}


#######################################################################
sub printDelinkParagraphVerification {
#######################################################################
    my ($self) = @_;

    $hiddenVars{'currentMode'} = 'DelinkParagraphVerification';
    $self->{'_title'} = (uc($self->database) . " Confirm Delink Paragraph");

    ### load the paragraph from the datbase

    my @paragraphObjRef;

    if (defined $hiddenVars{'locus_no'}) {
	@paragraphObjRef = $self->findParagraphObject($hiddenVars{'locus_no'},
						      "locus");
    }
    else {
	my $searchType = param('type');
	my $searchString = param('feat');

	@paragraphObjRef = $self->findParagraphObject(($searchString, 
						       $searchType));
    }

    my ($dictyBaseObject, $errorMsg) = @paragraphObjRef;
    if (!defined $dictyBaseObject) {
	my $errorHTML = $self->printReturnHTML($errorMsg);
	return $errorHTML;
    }

    my $paragraphObj;
    eval {
	$hiddenVars{'locus_no'} = $dictyBaseObject->locusNo();
	$paragraphObj = ParagraphDS->new(dbh=>$dbh,
					   locusNo=>$dictyBaseObject->locusNo());
    };
    if ($@) {
	# if script execution gets here, it probably means that there is
	#  no summary paragraph for this locus_no
	my $output = "";

	$output 
	     .= $self->printReturnHTML("The script was able to find the locus ".
				       $dictyBaseObject->locusName().
				       " requested, ".
				       "but there is no associated paragraph to delink.");

	return $output;
    }
    ##########################
    my @buttons = ();
    push(@buttons, ("Yes" 
		    . ("\tRemove the association between this locus (".
		       $dictyBaseObject->locusName() .
		       ") and the paragraph."))
	);

    push(@buttons, ("Back" 
		    . ("\tReturn to the paragraph selection page without changing the database.".
		       ""))
	);


    my $options
	 = $self->buttons("Select an action", \@buttons, 150);

    ##########################

    my $output = "";

    $output .= $self->blueTitleBar("Verification Requested");

    $output .= (b("Are you sure you want to delink this locus from this ".
		  "paragraph?"). br.
		"The paragraph will not be deleted from the database; " .
		"however, it will no longer be displayed when this locus ".
		"is shown." . p);

    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= startform . $options . endform;

    return $output;

}

#######################################################################
sub printDeleteParagraphVerification {
#######################################################################
    my ($self) = @_;

    $hiddenVars{'currentMode'} = 'DeleteParagraphVerification';
    $self->{'_title'} = (uc($self->database) . " Comfirm Delete Paragraph");

    ##########################
    my @buttons = ();
    push(@buttons, ("Yes" 
		    . ("\tDelete this paragraph now.")));

    push(@buttons, ("No" 
		    . ("\tReturn to the paragraph list page without changing the database.".
		       "")));

    my $options
	 = $self->buttons("Really delete this paragraph?", \@buttons, 150);

    ##########################

    my $output = "";

    $output .= $self->blueTitleBar("Verification Requested");

    $output .= (b("Are you sure you want to delete this paragraph from the ".
		  "database?"). br.
		"If you click YES below, this action cannot be undone.  ".
		"The paragraph will be permanently deleted from the database.".
		 p);

    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= startform(-action=>$scriptURL) . $options . endform;

    return $output;

}

#######################################################################
sub printDeletePhraseVerification {
#######################################################################
    my ($self, %args) = @_;

    $hiddenVars{'currentMode'} = 'DeletePhraseVerification';
    $self->{'_title'} = (uc($self->database) . " Comfirm Delete Phrase");

    my $phraseObj = $args{'phraseObj'};
    my $paragraphObj = $args{'paragraphObj'};

    ##########################
    my @buttons = ();
    push(@buttons, ("Yes" 
		    . ("\tDelete this phrase permanently from the database.")
		   ));

    push(@buttons, ("Back" 
		    . ("\tReturn to the edit phrase page without altering "
		       . "the database."))
	);

    my $options
	 = $self->buttons("Select an action", \@buttons, 150);

    ##########################

    # embed all hidden field values so that if the user clicks "back", then
    #  any changes made to the object will not be forgotten
    my $query = new CGI;
    my @names = $query->param();
    foreach my $pName (@names) {
	my $value = param($pName);
	$hiddenVars{$pName} = $value;
    }

    my $output = "";

    $output .= $self->blueTitleBar("Verification Requested");

    my $parsedPhraseText = $phraseObj->getPhraseText();

    $output .= (b("Are you sure you want to delete the following phrase ".
		  "from the database?  This action cannot be undone!").
		p.
		table({-width=>"100%"},
		      Tr(td({-bgcolor=>"#d8d8d8"},
			    "&nbsp;", br,
			    $uiModule->renderText($parsedPhraseText, 0),
			    p
			   )))
	       );

    $options
	 = $self->buttons("Select an action", \@buttons, 150);
    $output .= (
		startform 
		. $options 
		. $self->embedHiddenFields()
		. endform)
	 ;

    return $output;

}

#######################################################################
sub printButtonError {
#######################################################################
    my ($self, $previousScreen, $buttonClicked) = @_;

    my $output = "";
    $self->{'_title'} = (uc($self->database) . " Paragraph Curation");

    $output .= $self->blueTitleBar(
			     font({-color=>"RED"},
				"MESSAGE")
			   );

    $output .= ("It looks like you have found a bug.  There is no action ".
		"assigned to the button that you pressed.  Please report/".
		"email this page to an dictyBase programmer.  Thanks!\n") . p;

    $output .= code("File = $0") . br;
    $output .= code("Screen = $previousScreen") . br;
    $output .= code("Button Clicked = $buttonClicked") . br;

    return $output;

}


########################################################################
sub blueTitleBar {
########################################################################
    my ($self, $text) = @_;

    my $html = table({-border=>0,
		      -width=>'100%'},
		     Tr(td({-bgcolor=>"#a4abc2"},
			   b($text))));
    return $html;
}

########################################################################
sub buttons {
########################################################################
    my ($self, $title, $buttonsRef, $btnWidth) = @_;

    my @buttons = @$buttonsRef;

    my $html = "\n";

    if (defined $title) {
	$html = $self->blueTitleBar($title);
    }

    if (!defined $btnWidth) {
	$btnWidth = 100;
    }

    my $allButtons = "";
    foreach my $buttonObj (@buttons) {

	my ($btnLbl, $btnText, $btnURL) = split(/\t/, $buttonObj);

	my $action = "";
	if ($btnURL =~ /^\*/) {

	    $btnURL =~ s/^\*//;

	    $action .= input({-type=>"button",
			      -value=>"$btnLbl",
			      -'style'=>"width: $btnWidth;",
			      -onClick=>"window.location\='$btnURL?return=curator&user=".$self->{'_user'}."';"
			     }
			    );
	}
	else {
	    $action .= submit({-name=>'submit',
			       -value=>"$btnLbl",
			       -'title'=>"$btnURL",
			       -'style'=>"width: $btnWidth;"
			     }
			    );
	}

	my $singleButton = Tr(td({-align=>"center"},
				 $action
				)
			      .
			      td({-align=>"left"},
				 $btnText
				)
			     );
	$allButtons .= $singleButton;
    }
    $allButtons = table($allButtons);
    $html .= $allButtons . "\n";
    $html .= $self->embedHiddenFields() . "\n";
    return $html;
}

########################################################################
sub embedHiddenFields {
########################################################################
    my ($self) = @_;

    my $output = "";

    # debugging code -->
    if (($debugMode == 1) && ($hiddenFieldsDisplayCount == 0)) {
	print "<!-- Hidden Variables passed from previous page -->\n";
	print $self->blueTitleBar("Hidden Variables");
	foreach my $hiddenKey (keys %hiddenVars) {
	    print code($hiddenKey, " = ", $hiddenVars{$hiddenKey} . ";". br);
	}
	print "<!-- End Hidden Variables -->\n";
	$hiddenFieldsDisplayCount++;
    }
    # <-- debugging code

    foreach my $hiddenKey (keys %hiddenVars) {
	param($hiddenKey, $hiddenVars{$hiddenKey});
	$output .= hidden($hiddenKey,
			  $hiddenVars{$hiddenKey}
			 ) . "\n";
    }
    return $output;
}


########################################################################
sub getFileText {
########################################################################
    my ($self, $filename) = @_;

    my $currentUser = lc($self->user);

    $filename =~ s/^\~\//\~$currentUser\//;

    $filename =~ s{ ^ ~ ( [^/]* ) }
	 { $1 ? (getpwnam($1))[7]
		: ( $ENV{HOME} || $ENV{LOGDIR} 
		    || (getpwuid($>))[7]
		  )
       }ex;

    open(IN, $filename)
	 || return ("\t$filename $!");

    my $fileText = "";
    while (<IN>) {
	$fileText .= $_;
    }

    close (IN);

    return ($fileText);
}

########################################################################
sub addRemovePrimaryLoci {
########################################################################
    # This method makes the assumption that a valid locus_no is available
    #  in $hiddenVars{'locus_no'}

    my ($self, %args) = @_;

    my $addedLoci = (length(param('addLoci')) > 0 ? 
		     param('addLoci')
		     : undef
		    );

    my $removedLoci = (length(param('removeLoci')) > 0 ? 
		      param('removeLoci')
		      : undef
		     );

    my $paragraphObj = $args{'paragraphObj'};

    my $userMessage = "";

    $userMessage = $self->blueTitleBar("Add or Remove New Primary Loci");

    # previously-added or removed loci...
    #   previously = before the current pressing of the "Add/Remove" loci
    #                button

    $hiddenVars{'addLoci'} = "";
    my @addedLoci = split(/\|/, $addedLoci);
    foreach my $addedLocus (@addedLoci) {
	$paragraphObj->setPrimaryLoci($addedLocus);
	$hiddenVars{'addLoci'} .= "|$addedLocus";
    }

    $hiddenVars{'removeLoci'} = "";
    my @removedLoci = split(/\|/, $removedLoci);
    foreach my $removedLocus (@removedLoci) {
	$paragraphObj->unsetPrimaryLoci($removedLocus);
	$hiddenVars{'removeLoci'} .= "|$removedLocus";
    }

    my $changedLoci = param('locus');
    my @changedLoci = split(/\|/, $changedLoci);

    foreach my $locus (@changedLoci) {
	my $locus_no;
	my $dictyBaseObject;

	if ($locus !~ /^\d+$/) {
	    $dictyBaseObject = dictyBaseObject->new(dbh=>$dbh,
					locusName=>$locus);
	}
	else {
	    $dictyBaseObject = dictyBaseObject->new(dbh=>$dbh,
					locusNo=>$locus);
	}
	$locus_no = $dictyBaseObject->locusNo();

	if (! $dictyBaseObject->isLocusName()) {
	    $userMessage .= "Could not identify locus: $locus".br;
	    next;
	}

	if ($paragraphObj->isPrimaryLocus($locus_no) == 0) {
	    # add the user-specified locus to the list of primary loci for this
	    #   paragraph

	    # test to see if this locus_no is already assigned to a different
	    #   paragraph or not
	    my $locusObj = Locus->new(dbh=>$dbh,
				      locus_no=>$locus_no);
	    my $existingParagraphNo = $locusObj->paragraph_no;

	    if ((defined $existingParagraphNo) 
		&&
		($existingParagraphNo != $paragraphObj->getParagraphNo())
	       ) {
		$userMessage 
		     .= ("$locus [$locus_no] is already currently assigned ".
			 "to paragraph: $existingParagraphNo.  Please delink ".
			 "this paragraph from $locus before attempting to add "
			 . " it as a primary locus to this paragraph.  ".
			 a({-href=>("$scriptURL".
				    "?user=".$hiddenVars{'user'}.
				    "&currentMode=EntryForm".
				    "&locus_no=".$locus_no.
				    "&submit=Edit"),
			   -target=>"_blank"},
			   "Edit paragraph $existingParagraphNo in a new window"
			  )
			);
		 next;
	    }

	    $paragraphObj->setPrimaryLoci($locus_no);
	    $hiddenVars{'addLoci'} .= "|$locus_no";
	    $userMessage 
		 .= "$locus [$locus_no] added as a primary locus.".br;

	    $hiddenVars{'removeLoci'} =~ s/\|*$locus_no//g;
	}
	else {
	    # remove the user-specific locus from the list of primary loci
	    #   for this paragraph

	    my $primaryLociRef = $paragraphObj->getPrimaryLoci();
	    my %primaryLoci = %$primaryLociRef;

	    if (scalar keys %primaryLoci == 1) {
		# don't allow the user to delete the last primary loci
		$userMessage 
		     .= ("Cannot remove requested locus - this would create ".
			 "an orphaned paragraph in the database that is not ".
			 "associated with any loci.  If this action is truly ".
			 "intended, please use the DELINK option from the ".
			 "main paragraph curation page."
			);
	    }
	    else {
		$paragraphObj->unsetPrimaryLoci($locus_no);
		$hiddenVars{'removeLoci'} .= "|$locus_no";
		$userMessage 
		     .= "$locus [$locus_no] removed as a primary locus.".br;
		$hiddenVars{'addLoci'} =~ s/\|*$locus_no//g;
	    }
	}
    }
    $hiddenVars{'addLoci'}    =~ s/^\|//g;
    $hiddenVars{'removeLoci'} =~ s/^\|//g;

    $userMessage .= (p.
		     "Any changes will not be made to dictyBase until the update ".
		     "button is pressed."
		     .p);

    if ((!defined $changedLoci) || (length($changedLoci) < 1)) {
	$userMessage = "";
    }

    my @returnArray = ($paragraphObj, $userMessage);

    return \@returnArray;
}


########################################################################
sub addRemovePhraseCategories {
########################################################################
    my ($self, %args) = @_;

    my $phraseObj = $args{'phraseObj'};

    my $userMessage = "";
    {
	# retreive a list of all phrase categories from the database
    	my $sql = ("SELECT code_value ".
		   "FROM   CGM_DDB.code ".
		   "WHERE  tab_name = 'PHRASE_CATEGORY' ");
	my $sth = $dbh->prepare($sql);
	$sth->execute();
	my $arrayRef = $sth->fetchall_arrayref();

	# retreive a list of all phrase categories assigned to this phrase
	#   as currently stored in the database
	my $phraseCategoryRef = $phraseObj->getPhraseCategories();
	my %phraseCategory  = %$phraseCategoryRef;

	# compare the list of checked phrase categories to the list of
	#  previoiusly-checked phrase categories to see which one is new
	my $addPCs = param('addedPhraseCategories');
	my $remPCs = param('removedPhraseCategories');
	my %oldAPCs;
	my %oldRPCs;
	if (defined $addPCs) {
	    foreach my $phraseCategory (split(/\|/, $addPCs)) {
		$oldAPCs{$phraseCategory} = 1;
	    }
	}

	if (defined $remPCs) {
	    foreach my $phraseCategory (split(/\|/, $remPCs)) {
		$oldRPCs{$phraseCategory} = 1;
	    }
	}

	# these next two arrays are used to keep track of which phrase
	#   categories were just-added to the list, so that feedback can be
	#   provided to the user, indicating that a phrase category was added/
	#   or removed.
	my @newlyAddedPCs = ();
	my @newlyRemovedPCs = ();

	$hiddenVars{'addedPhraseCategories'} = "";
	$hiddenVars{'removedPhraseCategories'} = "";

	# add or remove phrase categories to/from the phrase object as defined
	#   by the checkboxes passed to this script
	foreach my $category_row (@$arrayRef) {
	    my ($category_name) = @$category_row;
	    if (defined param("Category: $category_name")) {

		if (
		    (!defined $phraseCategory{$category_name})
		    &&
		    (!defined $oldAPCs{$category_name})
		   ) {
		    push(@newlyAddedPCs, $category_name);
		}
		elsif ((defined $phraseCategory{$category_name})
		       &&
		       (defined $oldRPCs{$category_name})
		      ) {
		    push(@newlyAddedPCs, $category_name);
		}

		if (!defined $phraseCategory{$category_name}) {
		    $hiddenVars{'addedPhraseCategories'} .= "|$category_name";
		}

		$phraseObj->setPhraseCategory($category_name);
	    }
	    else {
		if (
		    (defined $phraseCategory{$category_name})
		    &&
		    (!defined $oldRPCs{$category_name})
		   ) {
		    push(@newlyRemovedPCs, $category_name);
		}
		elsif (
		    (!defined $phraseCategory{$category_name})
		    &&
		    (defined $oldAPCs{$category_name})
		   ) {
		    push(@newlyRemovedPCs, $category_name);
		}

		if (defined $phraseCategory{$category_name}) {
		    $hiddenVars{'removedPhraseCategories'} .= "|$category_name";
		}
		$phraseObj->unsetPhraseCategory($category_name);
	    }
	}

	if ((scalar (@newlyAddedPCs) != 0) 
	    || (scalar (@newlyRemovedPCs) != 0)) {

	    $userMessage 
		 .= $self->blueTitleBar("Add or Remove Phrase Categories");

	    foreach my $addedPC (@newlyAddedPCs) {
		$userMessage
		     .= "The phrase category: $addedPC has been added.". br;
	    }

	    foreach my $removedPC (@newlyRemovedPCs) {
		$userMessage
		     .= "The phrase category: $removedPC has been removed.". br;
	    }

	    $userMessage .= p;
	}
    }
    $hiddenVars{'addedPhraseCategories'} =~ s/^\|//;
    $hiddenVars{'removedPhraseCategories'} =~ s/^\|//;

    my @returnArray = ($phraseObj, $userMessage);

    return \@returnArray;
}

########################################################################
sub movePhrase {
########################################################################
    my ($self, %args) = @_;

    my $phraseObj = $args{'phraseObj'};
    my $paragraphObj = $args{'paragraphObj'};
    my $newPhraseOrder = $args{'newPhraseOrder'};
    my $previousPhraseOrder = param('previousPhraseOrder');

    my $userMessage = "";

    if ((defined $previousPhraseOrder)
	&& ($previousPhraseOrder != $phraseObj->getPhraseOrder())) {

	# update the phrase_order number because it was done in a previous
	#  operation by the user
	$paragraphObj->movePhrase($phraseObj->getPhraseOrder(),
				  $previousPhraseOrder);
	$phraseObj->setPhraseOrder($previousPhraseOrder);
	$hiddenVars{'previousPhraseOrder'} = $previousPhraseOrder;

    }
    if ($newPhraseOrder != $phraseObj->getPhraseOrder()) {

	if (($newPhraseOrder =~ /^(\d+)$/) || ($newPhraseOrder == "-1")) {
	    # only process $newPhraseOrder values that are numbers-only

	    # phrase order values are zero-indexed, however, in the curator
	    #   interface phrases are numbered started at 1, therefore, we must
	    #   subtract 1 from the curator-specified newPhraseOrder number to
	    #   ensure that the two numbering systems coincide

	    if ($newPhraseOrder == 0) {
		$newPhraseOrder = 0;
	    }
	    else {
		$newPhraseOrder -= 1;
	    }

	    # we only need to move the phrase if it is has a new phrase order
	    #   number
	    if ($phraseObj->getPhraseOrder() ne $newPhraseOrder) {

		$userMessage .= $self->blueTitleBar("Updating Phrase Order");

		eval {
		    if (param('phrase_no') ne -1) {
			$paragraphObj->movePhrase($phraseObj->getPhraseOrder(),
						  $newPhraseOrder);
			$phraseObj->setPhraseOrder($newPhraseOrder);
		    }
		    else { # this is new phrase
			$paragraphObj->insertPhrase($phraseObj,
						    $newPhraseOrder
						   );
			$phraseObj->setPhraseOrder($newPhraseOrder);
			$phraseObj->getParagraphNo
			     ($paragraphObj->getParagraphNo());
		    }

		    $userMessage 
			 .= ("The phrase has been moved successfully to ".
			     "position ". ($newPhraseOrder+1) . "." . p);

		    $hiddenVars{'previousPhraseOrder'} = $newPhraseOrder;
		};
		if ($@) {
		    $userMessage
			 .= ("An error was encountered while attempting "
			    ."to move/place the phrase within the paragraph."
			    .p);
		}
	    }
	}
	else {
	    $userMessage .= $self->blueTitleBar("Updating Phrase Order: ".
						font({-color=>"RED"},
						     "ERROR"
						    ));
	    $userMessage .= ("Unable to determine the phrase_order value "
			     . "specified: \"$newPhraseOrder\".\n"
			     . p);
	}
    }

    my @returnArray = ($phraseObj, $userMessage, $paragraphObj);

    return \@returnArray;
}

########################################################################
sub modifyPhraseText {
########################################################################
    my ($self, %args) = @_;

    my $phraseObj = $args{'phraseObj'};
    my $paragraphObj = $args{'paragraphObj'};
    my $newPhraseText = $args{'phraseText'};
    my $phraseOrder = $phraseObj->getPhraseOrder();
    my $previousText = param('previousText');
    my $userMessage = "";

    if (defined $previousText) {
	$phraseObj->setPhraseText($newPhraseText);
	$paragraphObj->updatePhrase($phraseObj, $phraseOrder);
	$hiddenVars{'previousText'} = $newPhraseText;
    }
    else {

	if ($phraseObj->getPhraseText() ne $newPhraseText) {

	    $userMessage .= $self->blueTitleBar("Updating Phrase Text");

	    eval {
		$phraseObj->setPhraseText($newPhraseText);
		$paragraphObj->updatePhrase($phraseObj, $phraseOrder);
		$hiddenVars{'previousText'} = $newPhraseText;

		$userMessage .= ("The phrase has been successfully updated."
				 .p);
	    };
	    if ($@) {
		my $errorMessage = $@;

		$userMessage .= ("An error was encountered while attempting "
				 ."to update the phrase text."
				 .p
				 .code($errorMessage)
				 .p
				 ."The changes made to phrase_text could "
				 ."not be committed due to the errors above. "
				 ."Please correct the phrase text (provided "
				 ."below) and try again."
				 .p);
	    }
	}
    }

    my @returnArray = ($phraseObj, $userMessage, $paragraphObj);

    return \@returnArray;
}

########################################################################
sub addRemoveColleagues {
########################################################################
    my ($self, %args) = @_;

    my $addedColleagues = (length(param('addColleagues')) > 0 ? 
			   param('addColleagues')
			   : undef
			  );

    my $removedColleagues = (length(param('removeColleagues')) > 0 ? 
			    param('removeColleagues')
			    : undef
			   );

    my $paragraphObj = $args{'paragraphObj'};

    my $userMessage = "";

    $userMessage = $self->blueTitleBar("Add or Remove Colleague Contributors");

    # previously-added or removed colleagues...
    #   previously = before the current pressing of the "Add/Remove" colleagues
    #                button

    $hiddenVars{'addColleagues'} = "";
    my @addedColleagues = split(/\|/, $addedColleagues);
    foreach my $addedColleague (@addedColleagues) {
	$paragraphObj->setColleague($addedColleague);
	$hiddenVars{'addColleagues'} .= "|$addedColleague";
    }

    $hiddenVars{'removeColleagues'} = "";
    my @removedColleagues = split(/\|/, $removedColleagues);
    foreach my $removedColleague (@removedColleagues) {
	$paragraphObj->unsetColleague($removedColleague);
	$hiddenVars{'removeColleagues'} .= "|$removedColleague";
    }

    my $changedColleagues = param('colleague');
    my @changedColleagues = split(/\|/, $changedColleagues);

    foreach my $colleague_no (@changedColleagues) {
	my $collObject;
	my $colleagueName;

	$collObject = Colleague->new(dbh=>$dbh,
				     colleague_no=>$colleague_no);
	if (!defined $collObject) {
	    $userMessage .= "Could not identify colleague: $colleague_no".br;
	    next;
	}
	$colleague_no = $collObject->colleague_no();

	$colleagueName = ($collObject->last_name() .
			  ", " .
			  $collObject->first_name());

	if ($paragraphObj->isColleague($colleague_no) == 0) {
	    # add the user-specified colleague to the list of 
	    #   colleague-contributors for this paragraph

	    $paragraphObj->setColleague($colleague_no);
	    $hiddenVars{'addColleagues'} .= "|$colleague_no";
	    $userMessage 
		 .= "$colleagueName [$colleague_no] added as a contributor.".br;

	    $hiddenVars{'removeColleagues'} =~ s/\|*$colleague_no//g;
	}
	else {
	    # remove the user-specific colleague from the list of 
	    #   colleague-contributors for this paragraph

	    $paragraphObj->unsetColleague($colleague_no);
	    $hiddenVars{'removeColleagues'} .= "|$colleague_no";
	    $userMessage 
		 .= "$colleagueName [$colleague_no] removed as a ".
		      "contributor.".br;
	    $hiddenVars{'addColleagues'} =~ s/\|*$colleague_no//g;

	}
    }
    $hiddenVars{'addColleagues'} =~ s/^\|//g;
    $hiddenVars{'removeColleagues'} =~ s/^\|//g;

    $userMessage .= (p.
		     "This change will not be made to dictyBase until the update ".
		     "button is pressed."
		     .p);

    if ((!defined $changedColleagues) || (length($changedColleagues) < 1)) {
	$userMessage = "";
    }

    my @returnArray = ($paragraphObj, $userMessage);

    return \@returnArray;
}

########################################################################
sub parseParagraph {
########################################################################
    my ($self, %args) = @_;

    my $locusName = "";
    my $paragraph = "";
    my $inParagraph = 0;

    my $text = $args{'text'};

    my $forcePrimaryLocus = $args{'locus'}; 
    # for web-interface where only a single primary loci should be allowed
    #   when parsing entire paragraphs

    # ACE initials to Oracle name translation table
    my %curators = qw { DGF FISK
			JW  WITHEE
			KD  KARA
			LIT LAURIE
			MAH MIDORI
			TR  TROE
		      };

    # phrase category translation to two-digit abbreviations
    my %phraseCategories = qw { history HI
				gene_product GP
				function FU
				process PR
				cellular_component CC
				phenotype PH
				related_genes RG
				genetic_interactions GI
				physical_interactions PI
				regulatory_role RR
				regulated_by RB
				nucleic_acid_features NA
				protein_features PF
				pathway PA
				editorial ED
			      };

    my %pcAbbrev = (
		    '#01' => '<history>',
		    '#2' => '<gene_product>',
		    '#3' => '<function>',
		    '#4' => '<process>',
		    '#5' => '<cellular_component>',
		    '#6' => '<phenotype>',
		    '#7' => '<related_genes>',
		    '#8' => '<genetic_interactions>',
		    '#9' => '<physical_interactions>',
		    '#10' => '<regulatory_role>',
		    '#11' => '<regulated_by>',
		    '#12' => '<nucleic_acid_features>',
		    '#13' => '<protein_features>',
		    '#14' => '<pathway>',
		    '#15' => '<editorial>',
		    '#16' => '<i><human_gene symbol=XXX>',
		    '@01' => '</history>',
		    '@2' => '</gene_product>',
		    '@3' => '</function>',
		    '@4' => '</process>',
		    '@5' => '</cellular_component>',
		    '@6' => '</phenotype>',
		    '@7' => '</related_genes>',
		    '@8' => '</genetic_interactions>',
		    '@9' => '</physical_interactions>',
		    '@10' => '</regulatory_role>',
		    '@11' => '</regulated_by>',
		    '@12' => '</nucleic_acid_features>',
		    '@13' => '</protein_features>',
		    '@14' => '</pathway>',
		    '@15' => '</editorial>',
		    '@16' => '</human_gene></i>',
		    '#p' => '<name>',
		    '@p' => '</name>',
		    '#g' => '<i><name>',
		    '@g' => '</name></i>',

		    '\$HI' => '<PC_HI>', # history
		    '\$GP' => '<PC_GP>', # gene_product
		    '\$FU' => '<PC_FU>', # function
		    '\$PR' => '<PC_PR>', # process
		    '\$CC' => '<PC_CC>', # cellular_component
		    '\$PH' => '<PC_PH>', # phenotype
		    '\$RG' => '<PC_RG>', # related_genes
		    '\$GI' => '<PC_GI>', # genetic_interactions
		    '\$PI' => '<PC_PI>', # physical_interaction
		    '\$RR' => '<PC_RR>', # regulatory_role
		    '\$RB' => '<PC_RB>', # regulatry_by
		    '\$NA' => '<PC_NA>', # nucleic_acid_features
		    '\$PF' => '<PC_PF>', # protein_features
		    '\$PA' => '<PC_PA>', # pathway
		    '\$ED' => '<PC_ED>', # editorial
		   );


    # retrieve a list of locus names and locus no. from the database
    my %locusNo;

    my $dbOff = 0;
    if ($dbOff != 1) {
	my $sqlGetLoci = ("SELECT locus_no, locus_name "
			  . "FROM CGM_DDB.locus");
	my $sthGetLoci = $dbh->prepare($sqlGetLoci);

	$sthGetLoci->execute();

	while (my $array_ref = $sthGetLoci->fetchrow_arrayref) {
	    my $locus_no = $array_ref->[0];
	    my $locus_name = $array_ref->[1];

	    if (!defined $locusNo{$locus_name}) {
		$locusNo{$locus_name} = $locus_no;
	    }
	    else {
		$locusNo{$locus_name} .= ",$locus_no";
	    }
	}
	$sthGetLoci->finish();
    }

    # retrieve a list of alias names and locus no from the database.
    if ($dbOff != 1) {
	my $sqlGetLoci = (  "SELECT la.locus_no, a.alias_name "
			    .  "FROM CGM_DDB.locus_alias la, CGM_DDB.alias a "
			    . "WHERE la.alias_no = a.alias_no ");

	my $sthGetLoci = $dbh->prepare($sqlGetLoci);
	$sthGetLoci->execute();
	while (my $array_ref = $sthGetLoci->fetchrow_arrayref) {
	    my $locus_no = $array_ref->[0];
	    my $locus_name = $array_ref->[1];

	    if (!defined $locusNo{$locus_name}) {
		$locusNo{$locus_name} = $locus_no;
	    }
	    else {
		$locusNo{$locus_name} .= ",$locus_no";
	    }
	}
	$sthGetLoci->finish();
    }

    # translate ace_papers/references to dictyBase format
    my %ace2oracle;
    if ($dbOff != 1) {
	my $sth
	     = $dbh->prepare("select unique(ACE_OBJECT), PRIMARY_KEY "
				    . "from CGM_DDB.ACELINK "
				    . "where ACE_CLASS = 'Paper' "
				    . "and TAB_NAME = 'REFERENCE'");

	$sth->execute();

	while (my $array_ref = $sth->fetchrow_arrayref) {
	    my $ace_paper_name = $array_ref->[0];
	    my $reference_no = $array_ref->[1];
	    $ace2oracle{$ace_paper_name} = $reference_no;

	    if ($reference_no eq "REFERENCE_NO") {
		# print $ace_paper_name . "\t" . $reference_no . "\n";
	    }

	}
	$sth->finish();
    }

    my %paragraphs; # stores the paragraphs
    my $paragraphCount = 0; # count of how many paragraphs read from flat file

    my $aceParagraphCount = 0; # count of how many paragraphs read from flat file
    my $duplicateCount = 0; # count of loci with identical paragraphs
    my $badRefsCount = 0; # count of paragraphs not loaded because could not find
# a REFERENCE_NO for their ACE_paper_name (papers not yet loaded into Oracle?)
    my $loadedParagraphsCount= 0; # count of paragraphs loaded into Oracle

    my %paragraphUseCount; # number of times a given paragraph is used
    # used to track identical paragraphs, word for word

    my %identicalParagraphs; # lists all the loci that share the exact same
    # paragraph text

    my %primaryLoci; # lists all the primary loci for a given paragraph

    # iterate through flat-file, locating paragraphs and associating them with 
    #   loci

    # Some browsers transmit line breaks as \r instead of the traditional
    #   \n.  This will convert all \r line breaks to \n
    $text =~ s/\r\n/\n/g;
    $text =~ s/\r/\n/g;

    foreach my $line (split(/\n/, $text)) {

	$line .= "\n";

	if ($line =~ /^\/\//) { next; }

	if ($line =~ 
	    /^LongText : \"Gene Summary Paragraph for ([\w\-\'\,]+)\"/ ) {

	    $locusName = $1;
	    $locusName =~ tr/[a-z]/[A-Z]/;

	    $inParagraph = 1;
	    $aceParagraphCount++;
	    next;
	}

	# translate ACE-style initials into Oracle user-names
	if (($line =~ m/(\d{4}-\d{1,2}-\d{1,2}) (\w+)/) 
	    && (defined $curators{$2})) {
	    $line =~ s/(\d{4}-\d{1,2}-\d{1,2}) (\w+)/$1 $curators{$2}/
	}

	if ($line =~ /\*\*\*LongTextEnd\*\*\*/) {

	    $paragraph =~ s/\n{2,}/<P>/g;
	    $paragraph =~ s/[\n +]/ /g;

	    $inParagraph = 0;

	    my $locusNo = $locusNo{$locusName};

	    # keep track of how many other loci use the current paragraph
	    if (defined $paragraphUseCount{$paragraph}) {

		my $originalLocusName = $paragraphUseCount{$paragraph};

		# $primaryLoci{$originalLocusName}{$locusNo} = 1;

		foreach my $individualLocusNo(split(/,/, $locusNo)) {
		    $primaryLoci{$originalLocusName}{$individualLocusNo} = 1;
		}

		if (defined $identicalParagraphs{$paragraphUseCount{$paragraph}}) {
		    $identicalParagraphs{$originalLocusName} .= "," . $locusNo;
		}
		else {
		    $identicalParagraphs{$originalLocusName} = $locusNo;
		}
		$duplicateCount++;
	    }
	    else {
		$paragraphs{$locusName} = $paragraph;
		$paragraphUseCount{$paragraph} = $locusName;

		foreach my $individualLocusNo(split(/,/, $locusNo)) {
		    $primaryLoci{$locusName}{$individualLocusNo} = 1;
		}
	    }
	    $locusName = "";

	    $paragraph = "";
	    $paragraphCount++;
	}

	if ($inParagraph == 1) {
	    $paragraph .= $line;
	}
    }

    my $noDCStampCount = 0;

    my %phraseCategoryFull = reverse %phraseCategories;

    my %badRefs;

    # enables flushing of standard out without a \n character;
    $|=1;

    # find the longest phrase, and the loci whose paragraph contains that
    #   longest phrase
    my $maxPhraseLength = 0;
    my $maxPhraseLocus = "";

 parag:
    foreach my $locus (sort keys %paragraphs) {

	my %secondaryLoci;
	my $paragraph = $paragraphs{$locus};
	my $locusNo = $locusNo{$locus}; # translate locus_name to a locus_no
	my $date;
	my $curator;
	my $noDCStamp = 0;

	if ($paragraph =~ /((\d{4}-\d{1,2}-\d{1,2}) (\w+))/) {
	    $date = $2;
	    $curator = $3;

	    # remove the date/curator stamp from the raw-text
	    $paragraph =~ s/$1//;
	}
	else {
	    $noDCStampCount++;
	    $noDCStamp = 1;
	}

	# Correcting any variants of "Homolog" to "related_genes"
	$paragraph =~ s/\<(\/*)Homologs*/\<$1related_genes/g;
	$paragraph =~ s/\<(\/*)homologs*/\<$1related_genes/g;

	# do not allow consecutive tags separated by a comma without a space
	$paragraph =~ s/\>,\</\>, \</g;

	# substitute abbreviations for common tags
	foreach my $abbrev (keys %pcAbbrev) {
	    my $full = $pcAbbrev{$abbrev};

	    $paragraph =~ s/$abbrev/$full/g;
	}

	# error check: ensure that the first phrase is marked off with a 
	#  phrase category
	if ($paragraph !~ /^\</) {
	    die ("The submitted paragraph does not start with a valid "
		 . "phrase category tag.\n"
		);
	}

	foreach my $word (split(/[ \t\n\,]/, $paragraph)) {

	    # remove leading spaces
	    $word =~ s/\n/ /g;

	    # replace ACE paper references with Oracle reference no.
	    if ($word =~ /\<([a-z_]{1,5}_+\d{4}_+[a-z_]{5})\>/) {
		if (defined $ace2oracle{$1}) {
		    my $ref_no = $ace2oracle{$1};
		    $paragraph =~ s/\<$1\>/\<ref_no:$ref_no\>/g;
		}
		else {
		    # look for references that cannot be translated into
		    #   Oracle reference numbers
		    $badRefsCount++;

		    if (defined $badRefs{$1}) {
			$badRefs{$1} .= ",$locus";
		    }
		    else {
			$badRefs{$1} = $locus;
		    }

		    die ("Unable to parse paragraph because it contains "
			   . "the following ACE paper reference that is not "
			   . "yet loaded into Oracle: " . $1 . "\n"
			  );

		    next parag;
		}
	    }

	    # mark off locus names in the text
	    my $poss_locusName = $word;

	    if ($word =~ m/^\(*((\<[a-zA-Z\_\ ]*\>)*([A-Za-z\-\,\'0-9]+)\)*)/) {
		$poss_locusName = $3;

		$poss_locusName =~ s/^\W//; # remove leading whitespace
		$poss_locusName =~ s/[,\.]$//; # remove trailing , or .
		$poss_locusName =~ s/p$//i; # remove trailing p or P

		my $poss_locusNameUC = $poss_locusName;
		$poss_locusNameUC =~ tr/[a-z]/[A-Z]/;

		if (
		    (defined $locusNo{$poss_locusNameUC})
		    &&
		    ($paragraph !~ /\<locus\d{2}:.*\>$poss_locusName<\/locus>/)
		   ) {
		    # get the list of locus_no's corresponding to this locus
		    #  name
		    my $locusNo = $locusNo{$poss_locusNameUC};

		    my $locusNoTmp = "";

		    # locus_name maps to multiple loci, don't embed a single
		    #  locus number into the tag, use a hash symbol
		    #  (The hash symbol tells the paragraph render function
		    #     to do a search by locus_name)
		    if ($locusNo =~ /,/) {
			$locusNoTmp = "#";
		    }
		    else {
			$locusNoTmp = $locusNo;
		    }

		    (my $firstLocusNo, undef) = split(/,/, $locusNo);

		    if (defined $primaryLoci{$locus}{$firstLocusNo}) {
			$paragraph =~ s/(\W)$poss_locusName([\Wp])/$1\<locus01:$locusNoTmp\>$poss_locusName<\/locus>$2/g;
		    }
		    else {
			if (!defined $secondaryLoci{$locusNo}) {
			    $paragraph =~ s/(\W)$poss_locusName([\Wp])/$1\<locus02:$locusNoTmp\>$poss_locusName<\/locus>$2/g;

			    foreach my $locusNo (split(/,/, $locusNo)) {
				$secondaryLoci{$locusNo} = 1;
			    }
			}
			else {
			}
		    }
		}
		# locate new primary loci
		elsif ($word =~ /locus01:(\S+)/) {
		    my $locusNo = $1;

		    my $primaryLocusName = $paragraphUseCount{$paragraph};

		    my $otherPrimaryLoci = 
			 $identicalParagraphs{$primaryLocusName};

		    if ($otherPrimaryLoci !~ /$locusNo/) {
			 $identicalParagraphs{$primaryLocusName} = ", $locusNo";
		    }
		}

	    }
	    $poss_locusName = "";
	}

	# location tags - convert to 'cellular_component'
	$paragraph =~ s/\<(\/*)location/\<$1cellular_component/g;

	$paragraph =~ s/\n\n/\<P\>/g; # try to locate paragraph breaks
	$paragraph =~ s/\n/ /g;     # remove forced line breaks

	$paragraph =~ s/(\<P\>)*$//; # remove trailing <P> tags
	$paragraph =~ s/^(\<P\>)*//; # remove leading <P> tags
	$paragraph =~ s/\<P\>\<P\>//g; # remove multiple <P> tags

	# tag corections
	$paragraph =~ s/\>,\</\>, \</g; # put a space between a two successive
	                                #  tags that are separated by a comma
	$paragraph =~ s/\< +(\w)/\<$1/g; # eliminating extraneous spaces
                                         #  between a tag opening angle bracket
	                                 #  and the tag-name following it
	                                 #  ex: "< locus" becomes "<locus"

	$paragraph =~ s/\<name\>//g; # remove <name> tags
	$paragraph =~ s/\<\/name\>//g; # remove </name> tags


	# replace phrase categories with 2-letter phrase category abbreviations
	foreach my $phraseCategory (sort keys %phraseCategories) {
	    my $abbrev = $phraseCategories{$phraseCategory};
	    $paragraph =~ s/\<$phraseCategory\>/\<PC_$abbrev\>/g;
	    $paragraph =~ s/\<\/$phraseCategory\>/\<\/PC_$abbrev\>/g;
	}

	# remove spaces from phrases that have one or more spaces between the 
	# last start phrase category tag and the first word of the phrase
	$paragraph =~ s/\<PC_(\w\w)\> +(\w)/\<PC_$1>$2/g;

	# insert a space between ending and starting phrase category markers
	$paragraph =~ s/\<\/PC_(\w\w)\>\<PC_(\w\w)\>/\<\/PC_$1\> \<PC_$2\>/g;

	# remove spaces from between start pharse category tags
	$paragraph =~ s/\<PC_(\w\w)\>\W+\<PC_(\w\w)\>/\<PC_$1\>\<PC_$2\>/g;
	$paragraph =~ s/\<\/*name\>//g; # remove <name> and </name> tags
	# $paragraph =~ s/\<\/*i\>//g; # remove <i> and </i> tags
	# $paragraph =~ s/\<\/*b\>//g; # remove <b> and </b> tags

	# attempt to locate the end of a phrase if it is not terminated by
	#  an end phrase category tag
	$paragraph =~ s/[\.\s+](\<PC_\w\w\>)/\<\/PC_ZZ\> $1/g;

	# put each phrase on its own line, each phrase is prefixed with %
	$paragraph =~ s/(\<\/PC_\w\w\>) +(\<PC_\w\w\>)/$1\n\%\%\%\t$2/g;

	# look for phrases that are separated by a <P> and place that tag
	#   inside previous phrase, and then break the phrases onto separate
	#   lines

	# $paragraph =~ s/(\<\/PC_\w\w\>)<P>(\<PC_\w\w\>)/<P>$1\n\%\%\%\t$2/g;
	$paragraph =~ s/(\<\/PC_\w\w\>)*<P>(\<PC_\w\w\>)/<P>$1\n\%\%\%\t$2/g;

	# locate the first phrase of the paragraph and place it on its own
	#   line, prefixed with a % symbol
	$paragraph =~ s/^ *(\<PC_\w\w\>)/\%\%\%\t$1/;

	# remove spaces between > and ) symbols
	$paragraph =~ s/\>\W+\)/\>\)/g;

	# look for paragraphs whose loci we cannot identify locus_no for
	if (defined $locusNo) {
	}
	else {
	    $locusNo = "***** $locus *****";
	}

	# retrieve a list of all other loci that share an identical paragraph
	#  with the current paragraph
	my $otherPrimaryLoci = "";
	if (defined $identicalParagraphs{$locus}) {
	    $otherPrimaryLoci = "," . $identicalParagraphs{$locus};
	}

	# this line adds a list of primary loci to the line
	$paragraph =~ s/\%\%\%/\%\%\%\t$locusNo$otherPrimaryLoci/g;

	# remove any remaining end-phrase category tags
	$paragraph =~ s/\<\/PC_\w\w\>//g;

	# insert a tab between start phrase category tags and the first word
	#   of the phrase
	$paragraph =~ s/(\<PC_\w\w\>)(\w)/$1\t$2/g;

	# insert a tab between start phrase category tag and the first tag
	#   provided that the second tag:
	#   (is not a phrase category tag/does not start with P)
	$paragraph =~ s/(\<PC_\w\w\>)\s*(\<[^P])/$1\t$2/g;

	# strip off the angle-bracks and PC_ prefix of the start phrase
	#   category tag
	$paragraph =~ s/\<PC_(\w\w)\>/$1,/g;

	$paragraph =~ s/\s*$//g;      # remove trailing spaces

	$paragraphCount--;

	# error check: ensure that the first phrase is marked off with a 
	#  phrase category
	if ($paragraph !~ /^\%\%\%\W+\d+\W\w+\W/) {
	    die ("The submitted paragraph does not start with a valid "
		 . "phrase category tag (2).\n"
		);
	}


	#### CREATE PARAGRAPH & PHRASE OBJECTS and LOAD INTO THE DATABASE ####
	my $paragraph_obj = ParagraphDS->new(dbh=>$dbh);

	# If there is no curator or date stamp, then just use today's date
	#  and the current curator as a default curator/date stamp.
	if ($noDCStamp == 1) {
	    $curator = $self->user;
	    (my $DAY, my $MONTH, my $YEAR) = (localtime)[3,4,5];
	    if ($MONTH < 10) { $MONTH = "0" . $MONTH; }
	    if ($DAY < 10) { $DAY = "0" . $DAY; }
	    $date = ($YEAR+1900) . "-" . ($MONTH+1) . "-" . $DAY;
	}

	$paragraph_obj->setWrittenBy($curator);
	$paragraph_obj->setDateWritten($date);

	my $phraseCount = 0;

	foreach my $phrase (split(/\n/, $paragraph)) {

	    (undef, my $locusNo, my $phraseCatAbbrev, my $phraseText)
		 = split(/\t/, $phrase);

	    foreach my $primaryLocus (split(/,/, $locusNo)) {
		if ((!defined $secondaryLoci{$primaryLocus}) &&
		    (!defined $forcePrimaryLocus))
		     {
		    $paragraph_obj->setPrimaryLoci($primaryLocus);
		}
	    }

	    if (defined $forcePrimaryLocus) {
		$paragraph_obj->setPrimaryLoci($forcePrimaryLocus);
	    }

	    my $new_phrase 
		 = $paragraph_obj->insertPhrase(new_phrase_text=>$phraseText, 
						new_phrase_order=>9999);


	    $phraseCatAbbrev =~ s/ +//g;
	    foreach my $phraseCat (split(/,/, $phraseCatAbbrev)) {
		my $phraseCatFull = $phraseCategoryFull{$phraseCat};
		$phraseCatFull =~ s/_/ /g;

		$new_phrase->setPhraseCategory($phraseCatFull);
	    }
	    $phraseCount++;
	}
	$loadedParagraphsCount++;

	return $paragraph_obj;
    }
}

########################################################################
sub printEMailBugReport {
########################################################################
    my ($self, $message) = @_;

    ##########################
    my @buttons = ();
    push(@buttons, ("E-Mail"
		    . ("\tSend bug report.")));
    my $options
	 = $self->buttons("Select an action", \@buttons);
    ##########################

    my $query = new CGI;
    my @names = $query->param();
    my $cgiFieldValues = "";


    foreach my $pName (@names) {
	$cgiFieldValues .= $pName . " = \"" . param($pName) . "\"\n";
    }

    my $output = "";

    $output .= (p
		"To correct this error, use the BACK button on your browser "
		. "and make the appropriate corrections."
		. p
		. "If you do not understand or know how to correct the error "
		. "above, this error may require the attention of an dictyBase "
		. " programmer.  "
		. "The form below may be useful in reporting this problem: "
	       );

    my $userEmail = lc($hiddenVars{'user'}) . $domain;

    $output .= (startform .
		table(Tr(td(
			    "Programmer's E-mail address: "
			   ).
			 td(
			    textfield(-name=>"To",
				      -size=>75,
				      -value=>$programmerEmail)
			   )
			 ).
		      Tr(td(
			    "Your e-mail address: "
			   ).
			 td(
			    textfield(-name=>"From",
				      -size=>75,
				      -value=>$userEmail
				     )
			   )
			).
		      Tr(td({-valign=>"top"},
			    "Your comments").
			 td(
			    textarea({-name=>'comments',
				      -cols=>78,
				      -wrap=>"yes",
				      -rows=>10})
			   )
			).
		      Tr(td({-valign=>"top"},
			    "Error Message").
			 td(
			    textarea({-name=>'error',
				      -value=>$message,
				      -cols=>78,
				      -wrap=>"yes",
				      -rows=>5})
			   )
			).
		      Tr(td({-valign=>"top"},
			    "Script Data").
			 td(
			    textarea({-name=>'cgiFields',
				      -value=>$cgiFieldValues,
				      -wrap=>"yes",
				      -cols=>78,
				      -rows=>5})
			   )
			)
		     ).
		$self->embedHiddenFields() .
		br.
		submit({-name=>'submit',
			-value=>"Report Problem",
		       }
		      ) .
		endform
	       );

    return $output;
}

########################################################################
sub emailBugReport {
########################################################################
    my ($self) = @_;

    my $destEMail = param('To');
    my $origEMail = param('From');
    my $comments = param('comments');
    my $errorMsg = param('error');
    my $cgiFields = param('cgiFields');

    my $output = "";

    eval {
	open (MAIL, "|/usr/lib/sendmail -oi -t");

	my $mailHeaders =
	     ("From: $origEMail\n"
	      . "To: $destEMail\n"
	      . "Subject: $self->{'_database'} Paragraph Curation Page "
	      . "-- problem report\n");

	print MAIL $mailHeaders;

	my $separator = "==========================================\n";

	print MAIL ("This message was generated by a user who encountered an "
		    . "error while using the Paragraph Curation script.  It "
		    . "was most likely generated during a database commit "
		    . "operation.  \n\n");

	print MAIL $separator;

	print MAIL ("The user has provided the following comments:\n\n"
		    . $comments  . "\n\n"
		   );

	print MAIL $separator;

	print MAIL ("The script generated the following error message:\n\n"
		    . $errorMsg . "\n\n"
		   );

	print MAIL $separator;

	print MAIL ("The following data was passed to the script:\n\n"
		    . $cgiFields . "\n\n"
		   );

	print MAIL $separator;

	print MAIL ("This is the end of the problem report message, generated "
		    . "on " . (scalar localtime) . "\n\n");

	close(MAIL);

	$output .= "Your bug report has been sent to " . $destEMail . p;
    };
    if ($@) {
	my $errorMessage = $@;

	$output .= "An error was encountered while e-mailing your bug report.";
	$output .= p. code($@) . p;

	$output .= ("Your bug report was not sent.  Please contact a " 
		    . "programmer via other means."
		    . p);
    }

    return $output;

}

########################################################################
sub mergePhrase {
########################################################################
    my ($self, %args) = @_;

    my $mergeType = $args{'type'};
    my $paragraphNo = $args{'paragraphNo'};
    my $currPhraseNo = $args{'currPhraseNo'};

    my $phrase1Obj;
    my $phrase2Obj;
    my $phrase1No;
    my $phrase2No;

    my $paragraphObj = ParagraphDS->new(dbh=>$dbh,
					  paragraphNo=>$paragraphNo);

    if ($mergeType eq "previous") {
	$phrase2No = $currPhraseNo;
	$phrase2Obj = PhraseDS->new(dbh=>$dbh,
				      phrase_no=>$phrase2No
				     );
	$phrase1Obj = $paragraphObj->getPhrase($phrase2Obj->getPhraseOrder() - 1);
	$phrase1Obj = $paragraphObj->getPhrase(0);
	$phrase1No = $phrase1Obj->getPhraseNo();
    }
    else {
	$phrase1No = $currPhraseNo;
	$phrase1Obj = PhraseDS->new(dbh=>$dbh,
				    phrase_no=>$phrase1No
				   );
	$phrase2Obj = $paragraphObj->getPhrase($phrase1Obj->getPhraseOrder() + 1);
	$phrase2No = $phrase2Obj->getPhraseNo();
    }

    $phrase1Obj->setPhraseText($phrase1Obj->getPhraseText() 
			       . " "
			       . $phrase2Obj->getPhraseText());

    # merge phrase categories
    my $phrase2PCRef = $phrase2Obj->getPhraseCategories();
    my %phrase2PC = %$phrase2PCRef;

    foreach my $p2PC (sort keys %phrase2PC) {
	$phrase1Obj->setPhraseCategory($p2PC);
    }

    # delete the phrase from the paragraph object
    $paragraphObj->updatePhrase($phrase1Obj,
				$phrase1Obj->getPhraseOrder());
    $paragraphObj->deletePhrase($phrase2Obj->getPhraseNo);

    my $output = "";

    # commit the changes to the database.
    eval {
	$paragraphObj->update();
	$dbh->commit();
	# $dbh->rollback();

	$output .= $self->blueTitleBar("Messages: ");
	$output .= ("The phrase has been succesully merged."
		    ." All phrase data has been updated and committed into "
		    ." the database."
		    .p);

	$output.= $self->printEditPhrase(
					 phraseObj=>$phrase1Obj,
					 paragraphObj=>$paragraphObj
					);

    };
    if ($@) {
	my $errorMessage = $@;
	$output .= $self->blueTitleBar("Error Encountered: ");
	$output .= code($errorMessage);
	$output .= p;

	$output .= $self->blueTitleBar("What to do next: ");
	$output .= $self->printEMailBugReport($errorMessage 
					      . "\n\n" . "");

	$output .= $self->printEditPhrase(
					  phrase_no=>$currPhraseNo,
					  paragraph_no=>$paragraphNo
					 );
    }

    return $output;
}


########################################################################
sub splitPhrase {
########################################################################
    my ($self, %args) = @_;

    my $paragraphNo = $args{'paragraphNo'};
    my $phraseNo = $args{'phraseNo'};

    my $phraseObj = PhraseDS->new(dbh=>$dbh,
				    phrase_no=>$phraseNo);
    my $paragraphObj = ParagraphDS->new(dbh=>$dbh,
					  paragraphNo=>$paragraphNo);

    my $phraseText = param('phraseText');

    if ($phraseText !~ m/\w*\|\|\|\w*/) {
	die "Unable to determine where this phrase should be split";
    }

    my ($oldPhraseText, @newPhraseTexts) = split(/\|\|\|/, $phraseText);

    $phraseObj->setPhraseText($oldPhraseText);
    my $oldPhraseOrder = $phraseObj->getPhraseOrder();

    $paragraphObj->updatePhrase($phraseObj,
				$oldPhraseOrder);

    my $splitPhraseInsertPosition = $oldPhraseOrder + 1;

    # retrieve phrase categories
    my $pcRef = $phraseObj->getPhraseCategories();
    my %existingPhraseCategories = %$pcRef;


    foreach my $newPhraseText(@newPhraseTexts) {

	# print "Working on phrase: $newPhraseText", br;

	my $newPhraseObj = PhraseDS->new(dbh=>$dbh);

	$newPhraseObj->setPhraseText($newPhraseText);

	# print "The phrase contains this text: " . $newPhraseObj->getPhraseText() . br;

	$newPhraseObj->validate();

	# print "Phrase validated.", br;

	foreach my $phraseCategory (sort keys %existingPhraseCategories) {
	    $newPhraseObj->setPhraseCategory($phraseCategory);
	}

	# print "Attempting to insert the phrase in to the paragraph.", br;

	$paragraphObj->insertPhrase
	     (new_phrase_object=>$newPhraseObj, 
	      new_phrase_order=>$splitPhraseInsertPosition++);

	# print "The phrase insertion was successful.", br;
    }

    my $output = "";

    # commit the changes to the database.
    eval {

	# print "Updating the paragraph object.", br;

	$paragraphObj->update();

	# print "Update of paragraph completed.", br;

	$dbh->commit();
	# $dbh->rollback();

	$output .= $self->blueTitleBar("Messages: ");
	$output .= ("The phrase has been succesully split. "
		    . "All phrase data has been updated and committed into "
		    . "the database."
		    .p);

	$output.= $self->printEditPhrase(
					 phraseObj=>$phraseObj,
					 paragraphObj=>$paragraphObj
					);

    };
    if ($@) {
	my $errorMessage = $@;
	$output .= $self->blueTitleBar("Error Encountered: ");
	$output .= code($errorMessage);
	$output .= p;

	$output .= $self->blueTitleBar("What to do next: ");
	$output .= $self->printEMailBugReport($errorMessage 
					      . "\n\n" . "");

	$output .= $self->printEditPhrase(
					  phrase_no=>$phraseNo,
					  paragraph_no=>$paragraphNo
					 );
    }

    return $output;
}

########################################################################
sub printParagraphReferences {
########################################################################
    my ($self, %args) = @_;

    my $paragraphNo = $args{'paragraphNo'};

    $self->{'_title'} = (uc($self->database) . " Paragraph References");

    my $paragraphObj;
    if (defined $paragraphNo) {
	$paragraphObj = ParagraphDS->new(dbh=>$dbh,
					paragraphNo=>$paragraphNo);
    }
    else {
	return;
    }

    #### DUPLICATED CODE FROM ParagraphUI.pm::displayParagraph()

    my $referenceListRef = $paragraphObj->getReferences();
    my @referenceList = @$referenceListRef;
    my $references = "";

    my $refno = 1;
    my @bgcolor = ("#FFFFFF", "#F5F5F5");

    my %references;

    foreach my $ref (@referenceList) {

	my $refObj;
	my $reference_no;
	if ($ref =~ /RF:(\d+)/) {
	    $reference_no = $1;
	    $refObj = Reference->new(dbh=>$dbh,
				     reference_no=>$reference_no);
	    $references{$reference_no} = 1;
	}
	elsif ($ref =~ /PM:(\d+)/) {
	    my $pubmed_id = $1;
	    $refObj = Reference->new(dbh=>$dbh,
				     pubmed=>$pubmed_id);
	    $reference_no = $refObj->reference_no();

	    if (defined $references{$reference_no}) {
		# don't print a reference line for this value if it has already
		#  been displayed previously (i.e. it was found as a REF_NO
		#  value, and now the same reference is listed a second time
		#  as a PUBMED id)
		next;
	    }

	}

	my $currBgcolor = (($refno % 2) == 1) ? $bgcolor[1] : $bgcolor[0];

	$references .= Tr(td({bgcolor=>$currBgcolor,
			      valign=>"top",
			      align=>"left"},
			     "$refno)"),
			  td({bgcolor=>$currBgcolor},
			     a(
			       {name=>"$reference_no"}
			      ).
			     $refObj->formatedCitation()
			    )
			 );
	$refno++;
    }

    $references = (b("Summary References:") . 
		   table({border=>0},
			 $references
			)
		  );

    #### END OF DUPLICATED CODE FROM ParagraphUI.pm::displayParagraph()

    return $references;

}

########################################################################
1;
########################################################################





























