/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.servlet.facilities;

import org.apache.log4j.Logger;

import java.io.*;
import java.util.*;

import javax.servlet.*;
import javax.servlet.http.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;


import org.bodington.servlet.*;
import org.bodington.database.PrimaryKey;
import org.bodington.server.*;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.ResourceUtils;
import org.bodington.server.resources.UploadedFile;
import org.bodington.server.resources.UploadedFileSummary;
import org.bodington.server.events.*;
import org.bodington.server.realm.Acl;
import org.bodington.server.realm.AclEntry;
import org.bodington.server.realm.Permission;
import org.bodington.server.realm.User;
import org.bodington.util.DateFormatter;
import org.bodington.assessment.*;


public class PigeonHoleFacility extends Facility
	{
    
    private static Logger log = Logger.getLogger(PigeonHoleFacility.class);
	

	static final BigDecimal max_weight = new BigDecimal( "100.0" );
	
    public boolean canCopy( Resource resource )
    {
        return true;
    }
    
	public Resource newResource()
		{
		return new PigeonHole();
		}
	
	
	public List initResource(HttpServletRequest breq, Resource new_resource)
    {
        List errors = new LinkedList();
        if (new_resource instanceof PigeonHole)
        {
            PigeonHole ph = (PigeonHole) new_resource;
            
            try 
            {
                String maxMark = breq.getParameter("maxmark");
                if (maxMark != null && maxMark.length() > 0) 
                {
                    ph.setMaximumMark(Integer.parseInt(maxMark));
                }
            }
            catch (NumberFormatException nfe)
            {
                errors.add("Maximum mark isn't a number.");
            }
            
            ph.setDateOpen(parseDate(breq, errors, "date_open", "Opening Date"));
            ph.setDateDeadline(parseDate(breq, errors, "date_deadline", "Deadline"));
            ph.setDatePenalty(parseDate(breq, errors, "date_penalty", "Penalty Date"));
        }
        else
        {
            throw new IllegalArgumentException("Resource must be a PigeonHole");
        }
        errors.addAll(super.initResource(breq, new_resource));
        return errors;
    }

    public boolean initResource( Resource original_resource, Resource new_resource )
		throws BuildingServerException
         	{
	         if ( !(new_resource instanceof PigeonHole) )
		   throw new BuildingServerException( "Error: The wrong type of resource was created." );

		 PigeonHole original = (PigeonHole)original_resource;
		 PigeonHole new_pigeonhole = (PigeonHole)new_resource;

		 new_pigeonhole.setDateOpen(original.getDateOpen());
		 new_pigeonhole.setDateDeadline(original.getDateDeadline());
		 new_pigeonhole.setDatePenalty(original.getDatePenalty());
		 new_pigeonhole.setMaximumMark(original.getMaximumMark());

		 return true;
		}
	
	/**
	 * Setup the PigeonHole resource to a sensible state. We add Record, Mark 
	 * and Review Permissions to the owner for this.
	 */
	public List postCreate(Request request, Resource resource) throws Exception
    {
	    List errors = super.postCreate(request, resource);
		Permission grant[] = {Permission.RECORD, Permission.MARK, Permission.REVIEW};
		if (ResourceUtils.grantPermissions(resource, grant))
		{
			try {
				resource.getAcl().save();
				return errors;
			} catch (BuildingServerException e) {
				log.error("Failed to save resource ACL.");
			}
		}
		errors = new ArrayList(errors);
		errors.add("Failed to add extra permissions.");
		return errors;
    }


    /**
     * Copy the content of one resource to another. <p>
     * <h4>Content copied</h4>
     * <ul>
     * <li>authored questions.
     * </ul>
     * <h4>Content <em>not</em> copied</h4>
     * <ul>
     * <li>user entries.
     * <li>user responses.
     * </ul>
     * </p>
     * @param original_resource the resource whose contents are to be copied.
     * @param new_resource the resource to copy content to.
     * @param breq the building request object.
     * @throws BuildingServerException if there is any problem copying the
     *         resource content.
     */
        public void copyContent( Resource original_resource, Resource new_resource, Request breq )
                throws BuildingServerException
	        {
//	  authored : questions
//	  userdata : entries, responses? /** @todo  */

		 BuildingSession session = BuildingSessionManagerImpl.getSession( original_resource );
		 if ( !(session instanceof PigeonHoleSession) )
		   throw new BuildingServerException( "Unable to access appropriate resource session." );

		 try
		 {
		   if ( breq.getParameter( "authored" ) != null )
		   {
		     copyAuthoredContent( original_resource, new_resource );
//		     if ( breq.getParameter( "userdata" ) != null )
//                       copyUserContent( original_resource, new_resource );
		   }
		 }
		 catch (Exception ex)
		 {
		   throw new BuildingServerException( ex.getMessage() );
		 }
	}


    /**
     * Copy the authored content of an existing Resource to another Resource. <p>
     * <i>(WebLearn modification (method added): 02/12/2003 Colin Tatham)</i>
     * @param original_resource The resource with content to copy.
     * @param new_resource The resource to copy content to.
     * @exception BuildingServerException Thrown if there is any problem copying the resource content.
     *
     */
	private void copyAuthoredContent( Resource original_resource, Resource new_resource )
		throws BuildingServerException
		{
		 PigeonHoleSession session;
		 Vector questions;
		 PigeonHoleQuestion orig_question, new_question;

		 session = (PigeonHoleSession)BuildingSessionManagerImpl.getSession( original_resource );
		 questions = session.getPigeonHoleQuestionsInOrder();

		 for (int i=0; i<questions.size(); i++ )
		 {
		   orig_question = (PigeonHoleQuestion)questions.elementAt( i );
		   new_question = new PigeonHoleQuestion();
		   new_question.setResourceId( new_resource.getResourceId() );
		   new_question.setAvailable( orig_question.getAvailable() );
		   new_question.setNotes( orig_question.getNotes() );
		   new_question.setWeight( orig_question.getWeight() );
		   new_question.setOrdinal( orig_question.getOrdinal() );

		   new_question.save();
		 }
	}


	public List validate( Resource newResource ) throws BuildingServerException
		{
            PigeonHole pigeonHole = (PigeonHole)newResource;
            List errors = new LinkedList();
			int n;

			validateDate(errors, pigeonHole.getDateOpen(), pigeonHole
            .getDateDeadline(), pigeonHole.getDatePenalty());

			n = pigeonHole.getMaximumMark();
			if ( n<1 || n>100 )
				{
				errors.add( "The maximum mark must be between 1 and 100." );

				}
            errors.addAll(super.validate(newResource));
			return errors;  
		}

	/**
	 * Finds the three dates used by pigeon holes in the request and attempts to
	 * parse them. Currenly this is called twice when creating a new pigeon hole
	 * and we should really save the values from the first calling.
	 * @param breq The request in which to find the dates.
	 * @return An array of 3 dates (begin, end, penalty).
     * @deprecated {@link #parseDate(HttpServletRequest, List, String, String)}
	 */
	private java.util.Date[] parseDates(HttpServletRequest breq)
	{
		String strdate;
		String dateFields[] = { "date_open", "date_deadline", "date_penalty" };

		java.util.Date[] date = new java.util.Date[dateFields.length];

		for ( int i=0; i<dateFields.length; i++ )
			{
			strdate=breq.getParameter( dateFields[i] );
			if ( strdate!=null )
				{
				strdate=strdate.trim();
				if ( strdate.length()>0 )
					{
					date[i]=DateParser.parse( strdate );
					}
				}
			}
		return date;
	}

	/**
	 * Check that the supplied dates make sense. This does not check the size of the
	 * ranges or how far they are in the past or future.
	 * @param begin The start of the submission period. Can be null.
	 * @param end The end of the submission period. Can be null.
	 * @param penalty The end of the penalty period. Can be null.
	 * @return <code>false</code> if this set of date is invalid otherwise
	 * <code>true</code>.
	 */
	void validateDate(List errors, Date begin, Date end, Date penalty)
		{
		// If we don't have an end date we can't do any real comparisons.
		if (end == null)
		{
			if (penalty != null)
			{
				errors.add("You can't have a penalty date without an end date.");
			}
		}
		else
		{
			// Compare begin and end
			if (begin != null)
			{
				if (!begin.before(end))
				{
					errors.add("Your beginning date isn't before your end date");
				}
			}
			
			// Compare end and penalty
			if (penalty != null)
			{
				if(!end.before(penalty))
				{
					errors.add("Your end date should be before your penalty date");

				}
			}
		}
		}
	

	public void insert( Request req, PrintWriter out, String command, String insertname )
		throws ServletException, IOException
		{
		log.debug( Thread.currentThread().getName() + " PigeonHoleFacility insert()" );

		try
			{
			BuildingSession session;
			PigeonHoleSession ph_session;

			session = BuildingSessionManagerImpl.getSession( req.getResource() );
			if ( !(session instanceof PigeonHoleSession) )
		    	{
		    	out.println( "<HR>Technical problem: unable to access appropriate tool session.<HR>" );
		    	return;
		    	}
			ph_session = (PigeonHoleSession)session;
			

			if ( command.equalsIgnoreCase( "upload" ) )
				{
				upload( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "entrylist" ) )
				{
				entrylist( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "entrystatuslist" ) )
				{
				entrystatuslist( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "entrystatusconfirm" ) )
				{
				entrystatusconfirm( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "markpaper" ) )
				{
				markpaper( req, out, ph_session, insertname );
				return;
				}

			if ( command.equalsIgnoreCase( "markitemedit" ) )
				{
				markitemedit( req, out, ph_session, insertname );
				return;
				}

			if ( command.equalsIgnoreCase( "newq" ) )
				{
				newq( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "markdeleteq" ) )
				{
				markdeleteq( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "markeditqconfirm" ) )
				{
				markeditqconfirm( req, out, ph_session );
				return;
				}
	/*
	   Template calling this method should be removed too: pigeonremark.html
			if ( command.equalsIgnoreCase( "remarkconfirm" ) )
				{
				remark( req, out, ph_session );
				return;
				}
	*/

			if ( command.equalsIgnoreCase( "markpaperconfirm" ) )
				{
				markpaperconfirm( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "adjustedmarkconfirm" ) )
				{
				adjustedmarkconfirm( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "overwrite" ) )
				{
				overwrite( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "reviewlink" ) )
				{
				reviewlink( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "reviewmark" ) )
				{
				reviewmark( req, out, ph_session );
				return;
				}

			if ( command.equalsIgnoreCase( "noupmessage" ) )
				{
				noupmessage( req, out, ph_session );
				return;
				}
				
			if ( command.equalsIgnoreCase( "ifcanup" ) )
				{
				ifcanup( req, out, ph_session );
				return;
				}
				
			if ( command.equalsIgnoreCase( "ifcanseemark" ) )
				{
				ifcanseemark( req, out, ph_session );
				return;
				}
				
			if ( command.equalsIgnoreCase( "ifupentry" ) )
				{
				ifentry( req, out, ph_session );
				return;
				}
				
	//		if ( command.equalsIgnoreCase( "eventlog" ) )
	//			{
	//			eventlog( req, out, ph_session );
	//			return;
	//			}
				
			if ( command.equalsIgnoreCase( "pigeonmodify" ) )
				{
				pigeonmodify( req, out, ph_session );
				return;
				}
				
			if ( command.equalsIgnoreCase( "uplink" ) )
				{
				uplink( req, out, ph_session, insertname );
				return;
				}
				
			if ( command.equalsIgnoreCase( "pigeonfield" ) )
				{
				pigeonfield( req, out, ph_session, insertname );
				return;
				}
			}		
		catch ( BuildingServerException bsex )
			{
			out.println( bsex.toString() );
			return;
			}

		super.insert( req, out, command, insertname );
		}


	private void ifcanseemark( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		try
			{
			if ( ph_session.canSeeMark() )
				req.setSwitchedOff(  false );
			else
				req.setSwitchedOff(  true );
			}
		catch ( Exception ex )
			{
			req.setSwitchedOff(  false );
			logException( null, "PigeonHoleFacility", "ifcanseemark", 
			    "Technical error looking for an entry in the database.",
			    ex );
			return;
			}
		}	

	
	private void ifcanup( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		try
			{
			if ( ph_session.canUpload() )
				req.setSwitchedOff(  false );
			else
				req.setSwitchedOff(  true );
			}
		catch ( Exception ex )
			{
			logException( null, "PigeonHoleFacility", "ifcanup", 
			    "Technical error looking for an entry in the database.",
			    ex );
			return;
			}
		}	

	
	private void noupmessage( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		try
			{
			if ( !ph_session.canUpload() )
				out.print( ph_session.denyUploadMessage() );
			}
		catch ( Exception ex )
			{
			logException( null, "PigeonHoleFacility", "noupmessage", 
			    "Technical error looking for an entry in the database.",
			    ex );
			return;
			}
		}	



	private void ifentry( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		try
			{
			PigeonHoleEntry entry=ph_session.getPigeonHoleEntry();
			if ( entry!=null )
				{
				PrimaryKey fid = entry.getUploadedFileId();
				if ( fid==null )
					{
					req.setSwitchedOff(  true );
					return;
					}
				UploadedFileSummary finfo = ph_session.getUploadedFileSession().getFileSummary( fid );
				req.setSwitchedOff(  finfo.isDeleted() );
				}
			else
				req.setSwitchedOff(  true );
				
			}
		catch ( Exception ex )
			{
			logException( null, "PigeonHoleFacility", "ifentry", 
			    "Technical error looking for an entry in the database.",
			    ex );
			return;
			}
		}	
	
	private void upload( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		String name, param, value, avalue=null, bvalue=null, cvalue=null;
		String file, file_name, mime_type;
		PigeonHoleEntry entry;

		avalue=req.getParameter( "a" );
		bvalue=req.getParameter( "b" );
		cvalue=req.getParameter( "c" );
		if ( cvalue!=null )
			{
			//a form for deleting was included so we have to check
			// values.
			if ( avalue==null || bvalue!=null )
				{
				out.println( "<H1>File Rejected.</H1><P>  There is already a file " +
					"in the pigeon hole and your form selections suggest that " +
					"you may want to keep it.  You can't put two files in the " +
					"pigeon hole.</P>" );
				return;
				}
			}

		file = req.getParameterFileLocation( "file" );
		file_name = req.getParameterFileName( "file" );

		if ( file_name == null || file_name.length() == 0 )
			{
			out.println( "Upload failed because no file name was supplied in the form." );
			return;
			}

		if ( file_name.indexOf( '/' )>=0 )
			{
			out.println( "File names may not contain any slash characters." );
			return;
			}

        file_name = UploadedFile.nameToURL( file_name );
		
		try
			{
			if ( file!=null && file_name!=null )
				{
				ph_session.transferFileForAssessment( file, file_name, null );
				out.println( "<HR>Upload succeeded.<HR>" );
				}
			}
		catch ( Exception ex )
			{
			logException( out, "PigeonHoleFacility", "upload", 
			    "Technical problem: ex.toString()",
			    ex );
			}
		}


	
	private void overwrite( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		PigeonHoleEntry entry;

		if ( out==null )
		    return;
		    
		try
			{
			entry=ph_session.getPigeonHoleEntry();

			//no entry, no point showing an overwrite form
			if ( entry==null )
				return;

			out.println( 
				"<INPUT TYPE=CHECKBOX NAME=a>I've thought about it and I want " +
				"to overwrite the current entry.<BR>" +
				"<INPUT TYPE=CHECKBOX NAME=b CHECKED>Actually, I want to keep the " +
				"the current entry." +
				"<INPUT TYPE=HIDDEN NAME=c VALUE=dummy>"
				);
			}
		catch ( Exception ex )
			{
			logException( null, "PigeonHoleFacility", "overwrite", 
			    "Technical error looking for an entry in the database.",
			    ex );
			return;
			}
		
		}


	private void reviewlink( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		PigeonHoleEntry entry=new PigeonHoleEntry();
		try
			{
			entry=ph_session.getPigeonHoleEntry();
			if ( entry==null )
				{
				//no entry no link
				return;
				}

			if ( entry.getStatus() == PigeonHoleEntry.STATUS_UPLOAD_RETURNED )
				{
				out.println( 
					"<UL><LI><A TARGET=docmain HREF=bs_template_pigeonreview.html>Review feedback and/or mark</A></LI></UL>" );
				return;
				}
			}
		catch ( Exception ex )
			{
			out.println( "<HR>Technical error looking for an entry in the database.<HR>"+ex );
			return;
			}
		}
	
	private void reviewmark( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException
		{
		PigeonHole ph;
		PigeonHoleEntry entry=new PigeonHoleEntry();
		boolean seen=false;

		try
			{
			ph = ph_session.getPigeonHole();
			entry=ph_session.getPigeonHoleEntry();
			if ( entry==null )
				{
				return;
				}

			if ( entry.getStatus() == PigeonHoleEntry.STATUS_UPLOAD_RETURNED )
				{
				Integer mark = (entry.getAdjustedMark() == null)? entry.getMark():entry.getAdjustedMark();
				if ( mark != null )
					{
					seen=true;
					out.print( "<CENTER><H1>Mark = " );
					out.print( mark.toString() );
					out.print( " out of " );
					out.print( ph.getMaximumMark() );
					out.println( "</H1></CENTER>" );
					}
				else
					out.println( "<CENTER><P>There is no mark for your work.</P></CENTER>" );
				if ( entry.getFeedback()!=null && entry.getFeedback().trim().length()>0 )
					{
					seen=true;
					out.println( "<H4>Feedback relating to the file you uploaded:</H4>" );
					out.println( "<TABLE CLASS=bs-table-opaque ALIGN=CENTER><TR><TD><I>" );
					out.println( entry.getFeedback() );
					out.println( "</I></TD></TR></TABLE>" );
					}
				else
					out.println( "<CENTER><P>There is no feedback for your work.</P></CENTER>" );

				if ( seen )
					{
        			AssessmentEvent event = new AssessmentEvent( 
        							AssessmentEvent.EVENT_VIEW_ASSESSMENT,
        							req.getResource().getResourceId(), 
        							entry.getUserId(), 
      								null,
        							new Integer( entry.getPigeonHoleEntryId().intValue() ), 
        							mark==null?null:new BigDecimal( mark.toString() )    );
        			event.save();
					}
				return;
				}

			out.println( "<P>Feedback and/or a mark are not available.</P>" );
			}
		catch ( Exception ex )
			{
			out.println( "<HR>Technical error looking for an entry in the database.<HR>"+ex );
			return;
			}
		
		}
	
	
	
	
	private void entrylist(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException, BuildingServerException
		{
		int i, j, status;
		Vector users;
		Hashtable entries;
		User user;
		PigeonHoleEntry entry;
		
		
		users = ph_session.getPigeonHoleUsers();
		entries = ph_session.getPigeonHoleEntries();
		
		out.println( "<TABLE BORDER><TR><TD>Entry</TD><TD>Mark</TD><TD>Mod</TD></TR>" );
		
		for ( i=0; i<users.size(); i++ )
			{
			user = (User)users.elementAt( i );
			entry = (PigeonHoleEntry)entries.get( user.getUserId() );
			
			if ( user==null || entry==null )
				{
				out.println( "<TR><TD COLSPAN=3>Problem fetching entry details.</TD></TR>" );
				continue;
				}
				
			out.print(  "<TR><TD><A TARGET=docmain HREF=" + 
						entry.getUserId() +
						"/bs_template_pigeonmarkpaper.html>" );

			if ( user.getName() == null )
				out.print( "no name" );
			else
				out.print( user.getName() );

			out.println( "</A></TD>" );
			
			out.print( "<TD>" );
			if ( entry.getMark() != null )
				out.print( entry.getMark().toString() );
			else
				out.print( "~" );
			out.print( "</TD>" );
			
			out.print( "<TD>" );
			if ( entry.getAdjustedMark() != null )
				out.print( entry.getAdjustedMark().toString() );
			else
				out.print( "~" );
			out.print( "</TD></TR>" );
			}

		out.println( "</TABLE>" );
		}


	private void entrystatuslist(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException, BuildingServerException
		{
		int i, j, status;
		Vector users;
		Hashtable entries;
		User user;
		PigeonHoleEntry entry;
		
		
		users = ph_session.getPigeonHoleUsers();
		entries = ph_session.getPigeonHoleEntries();
		
		out.println( "<TABLE BORDER=1>");
                out.print(  "<TR VALIGN=TOP><TD>Set all entries" );
                out.println( "</TD><TD><SELECT NAME=\"status\">" );
                out.println("<OPTION VALUE=\"-1\">Set individually");
                for ( j=0; j<PigeonHoleEntry.status_message.length; j++ )
		{
			out.print( "<OPTION VALUE=\"" + j + "\"" );
			out.print( " >CHANGE ALL TO: " );
			out.println( PigeonHoleEntry.status_message[j] );
		}
                out.println("</SELECT>");
                out.println("</TD></TR>");
                out.println("</TABLE>");
                out.println("<TABLE BORDER=1>");
                out.println("<TR><TD>Name</TD><TD>Status</TD><TD>Lock</TD>" +
			         "<TD>If set to date dependent would user be able to upload now?</TD></TR>" );
		for ( i=0; i<users.size(); i++ )
			{
			user = (User)users.elementAt( i );
			entry = (PigeonHoleEntry)entries.get( user.getUserId() );
			
			if ( user==null || entry==null )
				{
				out.println( "<TR><TD COLSPAN=3>Problem fetching entry details.</TD></TR>" );
				continue;
				}
				
			out.print(  "<TR VALIGN=TOP><TD>" );
				
			if ( user.getName() == null )
				out.print( "no name" );
			else
				out.print( user.getName() );

			out.println( "</TD><TD><SELECT NAME=\"status" + entry.getPigeonHoleEntryId() + "\">" );
			for ( j=0; j<PigeonHoleEntry.status_message.length; j++ )
				{
				out.print( "<OPTION VALUE=\"" + j + "\"" );
				if ( j==entry.getStatus() )
					{
					out.print( " SELECTED>" );
					out.println( PigeonHoleEntry.status_message[j] );
					}
				else
					{
					out.println( " >CHANGE TO: " );
					out.println( PigeonHoleEntry.status_message[j] );
					}
				}

			status=entry.getStatus();
			entry.setStatus( PigeonHoleEntry.STATUS_UPLOAD_BY_DATE );
			if ( ph_session.canUpload( user.getUserId() ) )
                            out.println( "</SELECT></TD><TD><INPUT NAME=\"lock" + entry.getPigeonHoleEntryId() + "\" TYPE=CHECKBOX VALUE=\"TRUE\"></TD><TD>Yes</TD>" );
			else
			    out.println( "</SELECT></TD><TD><INPUT NAME=\"lock" + entry.getPigeonHoleEntryId() + "\" TYPE=CHECKBOX VALUE=\"TRUE\"></TD><TD>No, because " + 
								ph_session.denyUploadMessage( user.getUserId() ) +
								".</TD>" );
			entry.setStatus( status );

			out.println( "</TR>" );
			}

		out.println( "</TABLE>" );
		}

	private void entrystatusconfirm(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException, BuildingServerException
		{
		int i, j, status;
		boolean changeall=false;
                boolean locked = false;
		String param;
                String lock;
		Vector users;
		Hashtable entries;
		User user;
		PigeonHoleEntry entry;
		
		//check to see if change must be made to all entries
		
                param = req.getParameter("status");
                if (param != null)
                {
                    if (param.equalsIgnoreCase("-1")) 
                         changeall = false;
                    else changeall = true;
                }
                
		users = ph_session.getPigeonHoleUsers();
		entries = ph_session.getPigeonHoleEntries();
		
		out.println( "<TABLE BORDER><TR><TD>Name</TD><TD>Status</TD></TR>" );
		for ( i=0; i<users.size(); i++ )
			{
			user = (User)users.elementAt( i );
			entry = (PigeonHoleEntry)entries.get( user.getUserId() );
			
			if ( user==null || entry==null )
				{
				out.println( "<TR><TD COLSPAN=3>Problem fetching entry details.</TD></TR>" );
				continue;
				}
		
                        //check to see if this entry is locked
                        lock = req.getParameter("lock"+entry.getPigeonHoleEntryId().toString());
                        if (lock == null) locked=false; else locked=true;
                   
                        if ((changeall==false)||locked)
			param=req.getParameter( "status" + entry.getPigeonHoleEntryId().toString() );
                        else
                            param=req.getParameter("status");
               
			if ( param==null ) continue;
			
			try
				{
				status=Integer.parseInt( param );
				}
			catch ( Exception numex )
				{
				continue;
				}
			if ( status == entry.getStatus() ||
					status < 0 ||
					status > PigeonHoleEntry.STATUS_MAX )
				continue;
				
			// got this far so entry must need changing	
			
			ph_session.recordStatus( entry.getPigeonHoleEntryId(), status );
			
			out.print(  "<TR VALIGN=TOP><TD>" );
				
			if ( user.getName() == null )
				out.print( "no name" );
			else
				out.print( user.getName() );

			out.println( "</TD><TD>" );
			out.println( PigeonHoleEntry.status_message[status] );
			out.println( "</TD></TR>" );
			}

		out.println( "</TABLE>" );
		}



	private void newq( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException
		{
		try
			{
			ph_session.createQuestion();
			}
		catch ( Exception ex )
			{
			out.println( "<HR>A technical problem occurred while trying to create a new question.<HR>" + ex.getMessage() );
			}
		}	

	
	private void markdeleteq(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException, BuildingServerException
		{
		int n;
		String id, param, notparam;
		PrimaryKey qid;

		if ( !BuildingContext.getContext().checkPermission( Permission.EDIT ) )
			{
			out.println( "<PRE>You don't have permission to delete (edit) questions in this location of the building.</PRE>\n" );
			return;
			}

		//must have an id in the parameter part of the URL
		if ( req.getTemplateParameterCount() < 1 )
			{
			out.println( "<I>Can't delete question.</I>" );
			return;
			}
		id=(String)req.getTemplateParameter( 0 );
		qid= new PrimaryKey( Integer.parseInt( id ) );

		
		param=req.getParameter( "confirm" );
		if ( param==null ) param="";
		notparam=req.getParameter( "notconfirm" );
		if ( notparam==null ) notparam="";
		if ( param.length()<1 || notparam.length()>0 )
			{
			out.println( "<HR>Wrong checkboxes selected - question not deleted.<HR>" );
			return;
			}

		ph_session.removeQuestion( qid );
		}


	private void markeditqconfirm(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException, BuildingServerException
		{
		int n;
		String id, param;
		PrimaryKey qid;
		Hashtable questions;
		PigeonHoleQuestion q;
		
		if ( !BuildingContext.getContext().checkPermission( Permission.EDIT) )
			{
			out.println( "<PRE>You don't have permission to edit questions in this location of the building.</PRE>\n" );
			return;
			}


		//must have an id in the parameter part of the URL
		if ( req.getTemplateParameterCount() < 1 )
			{
			out.println( "<I>Can't display edit field.</I>" );
			return;
			}
		id=(String)req.getTemplateParameter( 0 );
		qid= new PrimaryKey( Integer.parseInt( id ) );

		
		questions = ph_session.getPigeonHoleQuestions();
		q=(PigeonHoleQuestion)questions.get( qid );

		if ( q==null )
			{
			out.println( "<I>Can't display edit field.</I>" );
			return;
			}

		param=req.getParameter( "ordinal" );
		try
			{
			q.setOrdinal( Integer.parseInt( param ) );
			}
		catch ( NumberFormatException ex )
			{
			out.println( "<HR>Invalid number in ordinal field.<HR>" );
			return;
			}

		param=req.getParameter( "available" );
		try
			{
			int a = Integer.parseInt( param );
			if ( a<1 || a>100 )
				{
				out.println( "Available Mark must be between 1 and 100" );
				return;
				}
			q.setAvailable( a );
			}
		catch ( NumberFormatException ex )
			{
			out.println( "<HR>Invalid number in available marks field.<HR>" );
			return;
			}

		param=req.getParameter( "weight" );
		try
			{
			BigDecimal big = new BigDecimal( param );
			if ( big.scale()>3 )
				{
				out.println( "The weighting will be rounded because a maximum of 3 decimal places are allowed." );
				big = big.setScale( 3, BigDecimal.ROUND_HALF_UP );
				}
			if ( big.compareTo( max_weight )>0 )
				{
				out.println( "A weight greater than 100.0 is not allowed - the weight has been adjusted to 100.0." );
				big = max_weight;
				}
			q.setWeight( big );
			}
		catch ( NumberFormatException ex )
			{
			out.println( "<HR>Invalid number in weight field.<HR>" );
			return;
			}


		
		ph_session.changeQuestionText( q.getPigeonHoleQuestionId(), 
										q.getNotesBigStringId(), 
										req.getParameter( "notes" ) );


		ph_session.changeQuestion( q.getPigeonHoleQuestionId(), q );
		
		out.println( "<HR>Question Saved<HR>" );
		}


	private void markitemedit(  Request req, PrintWriter out, PigeonHoleSession ph_session, String insertname )
		throws IOException, BuildingServerException
		{
		String id;
		Hashtable questions;
		PigeonHoleQuestion question;
		
		if ( !BuildingContext.getContext().checkPermission( Permission.EDIT ) )
			{
			out.println( "<PRE>You don't have permission to edit questions in this location of the building.</PRE>\n" );
			return;
			}


		//must have an id in the parameter part of the URL
		if ( req.getTemplateParameterCount() < 1 )
			{
			out.println( "<I>Can't display edit field.</I>" );
			return;
			}
		id=(String)req.getTemplateParameter( 0 );

		questions=ph_session.getPigeonHoleQuestions();
		if ( questions == null )
			{
			out.println( "<I>Can't display edit field.</I>" );
			return;
			}
			
		question = (PigeonHoleQuestion)questions.get( new PrimaryKey( Integer.parseInt( id ) ) );
		
		if ( question==null )
			{
			out.println( "<I>Can't display edit field.</I>" );
			return;
			}

		if ( insertname.equalsIgnoreCase( "ordinal" ) )
			{
			out.println( "<INPUT NAME=ordinal VALUE=\"" +question.getOrdinal() + "\">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "available" ) )
			{
			out.println( "<INPUT NAME=available VALUE=\"" +question.getAvailable() + "\">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "weight" ) )
			{
			out.println( "<INPUT NAME=weight VALUE=\"" +question.getWeight().toString() + "\">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "notes" ) )
			{
			out.print( "<SPAN CLASS=bs-textarea><TEXTAREA NAME=notes COLS=45 ROWS=8>" );
			out.print( question.getNotes() );
			out.println( "</TEXTAREA></SPAN>" );
			return;
			}
		}

	private void questionToHTML( PigeonHoleQuestion question, PrintWriter out, String style, PigeonHoleResponse response )
		throws IOException, BuildingServerException
		{

		out.println( "<TR><TD><B>Notes</B></TD></TR><TR><TD>" );
		out.println( question.getNotes() );
		out.println( "</TD></TR>" );

		if ( style.equalsIgnoreCase("mark") )
			{
			out.println( "<TR><TD><B>Mark</B></TD></TR><TR><TD>" );
			int a = question.getAvailable();
			int r=-1;

			if ( response!=null && response.getMark() != null )
				r = response.getMark().intValue();
				
			out.print( "<SELECT NAME=QR" );
			out.print( question.getPigeonHoleQuestionId().toString() );
			out.println( ">" );
			if ( r==-1 )
				out.println( "<OPTION VALUE=\"-1\" SELECTED>Not Marked" );
			else
				out.println( "<OPTION VALUE=\"-1\">Not Marked" );
				
			for ( int i=0; i<=a; i++ )
				{
				if ( r==i )
					out.println( "<OPTION VALUE=\"" + i + "\" SELECTED>" + i );
				else
					out.println( "<OPTION VALUE=\"" + i + "\">" + i );
				}
			out.println( "</SELECT>" );
			}
		else
			{
			if ( style.equalsIgnoreCase( "markreview" ) )
				{
				out.println( "<TR><TD><B>Mark</B></TD></TR><TR><TD>" );
				if ( response!=null && response.getMark() != null )
					{
					out.print( response.getMark().intValue() );
					out.print( " out of " );
					out.print( question.getAvailable() );
					out.println( "." );
					}
				else
					out.println( "No mark assigned." );
				out.println( "</TD></TR>" );
				}
			else
				{
				out.println( "<TR><TD><B>Available Marks</B></TD></TR><TR><TD>" );
				out.println( question.getAvailable() );
				out.println( "</TD></TR>" );
				}
			}
		
		if ( style.equalsIgnoreCase("edit") )
			{
			out.println( "<TR><TD><B>Weight</B></TD></TR><TR><TD>" );
			out.println( question.getWeight().toString() );
			out.println( "</TD></TR>" );
			}
		}
	


	private void markpaper(  Request req, PrintWriter out, PigeonHoleSession ph_session, String mode )
		throws IOException, BuildingServerException
		{
		int i;
		boolean edit, mark=false, canmark=false;
		String param;
		PrimaryKey uid;
		
		Vector questions_in_order;
		Hashtable responses=null;
		PigeonHoleQuestion q;
		PigeonHoleEntry entry=null;
		PigeonHoleResponse response=null;
		Resource resource;

		edit = ( mode!=null && mode.equalsIgnoreCase( "edit" ) );
		mark = ( mode!=null && mode.equalsIgnoreCase( "mark" ) );

		if ( !edit && !mark )
			{
			out.println( "Unkown presentation format selected." );
			return;
			}
			
		if ( edit )
			{
			if ( !BuildingContext.getContext().checkPermission( Permission.EDIT ) )
				{
				out.println( "<PRE>You don't have permission to edit assessment categories here.</PRE>\n" );
				return;
				}
			uid=null;
			}

		if ( mark )
			{
			if ( !BuildingContext.getContext().checkPermission( Permission.MARK ) )
				{
				out.println( "<PRE>You don't have permission to mark papers here.</PRE>\n" );
				return;
				}

			if ( req.getTemplateParameterCount()<1 )
				{
				out.println( "Invalid page address - no user specified" );
				return;
				}

			param=(String)req.getTemplateParameter( 0 );
			if ( param==null )
				{
				out.println( "Invalid page address - no user specified" );
				return;
				}
			uid = new PrimaryKey( Integer.parseInt( param ) );

			entry=ph_session.getPigeonHoleEntry( uid );
			
			if ( entry==null )
				{
				out.println( "<HR>No database entry for this student.</HR>" );
				return;
				}

			if ( entry.getStatus() == PigeonHoleEntry.STATUS_UPLOAD_BY_DATE || 
				entry.getStatus() == PigeonHoleEntry.STATUS_UPLOAD_CHANGEABLE  )
				{
				out.println( "<HR><CENTER><H1>This student's work is not ready for assessment.</H1></CENTER>" );
				return;
				}
				
			canmark=true;
			if ( entry.getStatus() != PigeonHoleEntry.STATUS_UPLOAD_LOCKED &&
				 entry.getStatus() != PigeonHoleEntry.STATUS_UPLOAD_PROCESSED )
				{
				out.println( "<HR><CENTER><H1>Assessment of this student's work is completed.</H1></CENTER>" );
				canmark=false;
				}
				
			responses=ph_session.getPigeonHoleResponses( entry.getPigeonHoleEntryId() );
			if ( responses==null )
				responses = new Hashtable();
			
			if ( canmark )
				out.println( "<FORM ACTION=bs_template_pigeonmarkconfirm.html METHOD=POST>" );
			}
		
		resource = BuildingContext.getContext().getResource();

		questions_in_order = ph_session.getPigeonHoleQuestionsInOrder();
			
			
		for ( i=0; i<questions_in_order.size(); i++ )
			{
			q=(PigeonHoleQuestion)questions_in_order.elementAt( i );
				
			out.println( "<P></P><TABLE BORDER><TR><TD><H4>Category " + 
					(i+1) + " (" + q.getOrdinal() + ")</H4>" );
						
			if ( edit )
				{
				out.print( "<P><FORM METHOD=POST ACTION=\"" );
				out.print( req.getContextPath() );
				out.print( req.getServletPath() );
				out.print( resource.getFullName()  );
				out.print( q.getPigeonHoleQuestionId() + "/bs_template_markeditq.html\">" );
				out.println( "<INPUT TYPE=SUBMIT VALUE=\"Edit This Question\"></FORM></P>" );
				}
					
			out.println( "</TD></TR>" );
				
			if ( mark )
				{
				response = (PigeonHoleResponse)responses.get( q.getPigeonHoleQuestionId() );
				if ( canmark )
					questionToHTML( q, out, mode, response );
				else
					questionToHTML( q, out, "markreview", response );
				}
			else
				questionToHTML( q, out, mode, null );
				
			out.println( "</TABLE>" );
			}

		if ( i==0 )
			out.println( "<P>There are currently no assessment categories for this pigeon hole.</P>" );

		if ( mark )
			{
			if ( canmark )
				{
				out.println( "<H4>General Feedback</H4>" );
				out.print( "<SPAN CLASS=bs-textarea><TEXTAREA COLS=50 ROWS=5 NAME=feedback>" );
				if ( entry.getFeedback()!=null )
					out.print( entry.getFeedback() );
				out.println( "</TEXTAREA></SPAN><BR>" );
				out.println( "<H4>Private Comments</H4>" );
				out.print( "<SPAN CLASS=bs-textarea><TEXTAREA COLS=50 ROWS=5 NAME=comments>" );
				if ( entry.getComments()!=null )
					out.print( entry.getComments() );
				out.println( "</TEXTAREA></SPAN><BR>" );
				out.println( "<INPUT TYPE=SUBMIT VALUE=\"Save Changes\">" );
				out.println( "</FORM>" );
				}
			else
				{
				if ( BuildingContext.getContext().checkPermission( Permission.MANAGE )  && !ph_session.canSeeMark( entry.getUserId() ))
					{
					out.println( "<HR><H4>Assign Adjusted Mark for Release</H4>" );
					out.println( "<P>The final released mark may be adjusted.  E.g." );
					out.println( " a penalty for handing in late or extra marks " );
					out.println( " because the student was absent or suffered some  " );
					out.println( " other impediment.</P>" );
					out.println( "<FORM NAME=adjustedform METHOD=POST ACTION=bs_template_adjustedmarkconfirm.html>" );
					out.println( "<TABLE CLASS=bs-table-opaque><TR><TD><B>Uploaded:</B></TD><TD><H4>" );
					if ( entry.getWhenSaved()!=null )
						{
						String d=DateFormatter.formatDate( entry.getWhenSaved(), DateFormatter.SHORT );
						out.println( d );
						}
					else
						out.println( "No date." );
					out.println( "</H4></TR><TR><TD><B>Total Mark:</B></TD><TD><H2>" );
					if ( entry.getMark()!=null )
						{
						out.println( entry.getMark().toString() );
						out.print( "<INPUT TYPE=BUTTON VALUE=\"Set\" ONCLICK=" );
						out.print( "\"adjustedform.adjustedmark.value=" );
						out.print( entry.getMark().toString() );
						out.println( "\">" );
						}
					else
						out.println( "No Mark" );
					out.println( "</H2></TD></TR><TR><TD><B>Adjusted Mark</B></TD><TD>" );
					out.print( "<INPUT NAME=adjustedmark VALUE=\"" );
					if ( entry.getAdjustedMark()!=null )
						out.print( entry.getAdjustedMark().toString() );
					out.println( "\"></TD></TR><TR><TD><B>General Feedback</B></TD><TD>" );
					out.print( "<SPAN CLASS=bs-textarea><TEXTAREA COLS=50 ROWS=5 NAME=feedback>" );
					if ( entry.getFeedback()!=null )
						out.print( entry.getFeedback() );
					out.println( "</TEXTAREA></SPAN>" );
					out.println( "</TD></TR><TR><TD><B>Private Comments</B></TD><TD>" );
					out.print( "<SPAN CLASS=bs-textarea><TEXTAREA COLS=50 ROWS=5 NAME=comments>" );
					if ( entry.getComments()!=null )
						out.print( entry.getComments() );
					out.println( "</TEXTAREA></SPAN></TD></TR><TR><TD></TD><TD>" );
					out.println( "<INPUT TYPE=SUBMIT VALUE=\"Save Adjusted Mark\">" );
					out.println( "</TD></TR></TABLE></FORM>" );
					}
				else
					{
					out.println( "<P>It isn't possible to edit marks or to save a final adjusted mark.</P>" );
					}
				}
			}			
		}
	
	private void markpaperconfirm(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException, BuildingServerException
		{
		int i;
		Vector questions_in_order;
		PrimaryKey[] qids;
		int[] marks;
		String feedback, comments, uid;
		PrimaryKey user_id;
		PigeonHoleQuestion q;
		PigeonHoleEntry entry;

		
		if ( !BuildingContext.getContext().checkPermission( Permission.MARK ) )
			{
			out.println( "<PRE>You don't have permission to mark papers here.</PRE>\n" );
			return;
			}

		if ( req.getTemplateParameterCount()<1 )
			{
			out.println( "Invalid page address - no user specified" );
			return;
			}

		uid=(String)req.getTemplateParameter( 0 );
		if ( uid==null )
			{
			out.println( "Invalid page address - no user specified" );
			return;
			}

		user_id = new PrimaryKey( Integer.parseInt( uid ) );
		entry = ph_session.getPigeonHoleEntry( user_id );
		if ( entry == null )
			{
			out.println( "Unable to find database entry." );
			return;
			}
		
		
		questions_in_order = ph_session.getPigeonHoleQuestionsInOrder();
		qids = new PrimaryKey[questions_in_order.size()];
		marks = new int[questions_in_order.size()];

		feedback=req.getParameter( "feedback" );
		comments=req.getParameter( "comments" );
		if ( feedback==null || comments==null )
			{
			out.println( "<HR>Invalid form input.<HR>" );
			return;
			}
		
		for ( i=0; i<questions_in_order.size(); i++ )
			{
			q=(PigeonHoleQuestion)questions_in_order.elementAt( i );
			
			qids[i] = q.getPigeonHoleQuestionId();
			
			marks[i] = Integer.parseInt( req.getParameter( "QR" + q.getPigeonHoleQuestionId() ) );
			
			if ( marks[i]<0 )
				out.println( "Warning: no mark assigned for category " + (i+1) + "<BR>" );
			}
			
		entry = ph_session.recordMarks( entry.getPigeonHoleEntryId(), qids, marks, feedback, comments );
			
		out.println( "<P>Entries were recorded." );
		
		if ( entry.getMark() != null )
			{
                        //set a provisional adjusted mark
                        entry.setAdjustedMark(entry.getMark());    
           
			out.print( "<BR>The total mark, before moderation is " );
			out.print( entry.getMark().toString() );
			out.print( " out of " );
			PigeonHole ph = ph_session.getPigeonHole();
			out.print( ph.getMaximumMark() );
			out.println( "." );
			}
		else
			out.println( "Marking is incomplete for this work." );
			
		out.print( "</P>" );
		/*
		String uid, feedback, comments, m;
		ResultSet results;
		Connection con;
		PigeonHoleEntry entry;
		PigeonHoleQuestion question;
		PigeonHoleResponse response;
		Hashtable responses;
		double total;
		boolean complete;
		

		if ( req.getTemplateParameterCount()<1 )
			{
			out.println( "Invalid page address - no user specified" );
			return;
			}

		uid=(String)req.getTemplateParameter( 0 );
		if ( uid==null )
			{
			out.println( "Invalid page address - no user specified" );
			return;
			}

		User user = (User)BuildingContext.getContext().getUser();
		
		try
			{
			con=db.getConnection();
			entry=getEntry( req.getResource().getResourceId().intValue(), uid );
			
			if ( entry==null )
				{
				out.println( "<HR>No database entry for that student.</HR>" );
				return;
				}

			responses=getResponses( entry );
			
			Enumeration enumeration=ph.getQuestions();
			total=0;
			complete=true;
			for ( int q=1; enumeration.hasMoreElements(); q++ )
				{
				out.println( "<H4>Assessment Category " + q + "</H4>" );
				question=(PigeonHoleQuestion)enumeration.nextElement();
				response=(PigeonHoleResponse)responses.get( question.question_id );
				if ( response==null )
					{
					response=new PigeonHoleResponse();
					response.result_id=entry.id;
					response.question_id=question.question_id;
					response.feedback=null;
					response.mark=null;
					response.insert( con );
					}
				
				m=req.getParameter( "QR" + question.question_id );
				if ( m==null )
					{
					out.println( "<P>WARNING - no mark given in category.</P> " + q );
					response.mark=null;
					complete=false;
					}
				else
					{
					out.println( "<P>Mark: " + m + "</P>");
					try
						{
						response.mark=new Integer( m );
						total+=
							question.weight.doubleValue() *
							(double)response.mark.intValue()
							/(double)question.available.intValue();
						}
					catch ( NumberFormatException numex )
						{
						response.mark=null;
						out.println( "<P>Technical problem saving mark.</P>" );
						}
					}
				response.update( con );
				}

			feedback=req.getParameter( "feedback" );
			comments=req.getParameter( "comments" );
			if ( feedback==null || comments==null )
				{
				out.println( "<HR>Invalid form input.<HR>" );
				return;
				}
			entry.marker=req.string_user_id;
			java.util.Date d=new java.util.Date();
			entry.when_marked=new java.sql.Timestamp( d.getTime() );
			entry.feedback=feedback;
			entry.comments=comments;

			if ( complete )
				entry.mark=new Integer( Math.round( (float)(total*10.0) ) );
			else
				entry.mark=null;

			if ( entry.status_upload.intValue() == PigeonHoleEntry.STATUS_UPLOAD_LOCKED )
				entry.status_upload = new Integer( PigeonHoleEntry.STATUS_UPLOAD_PROCESSED );
			entry.update( con );
			
			
        	AssessmentEvent event = new AssessmentEvent( 
        					AssessmentEvent.EVENT_RECORD_ASSESSMENT,
        					req.getResource().getResourceId(), 
        					user.getUserId(), 
							null,
        					new Integer( entry.id.intValue() ), 
        					entry.mark==null?null:new BigDecimal( entry.mark.toString() )
        					);
        	event.save();
			}
		catch ( Exception ex )
			{
			out.println( "</TABLE>" );
			out.println( ex.toString() );
			}
		*/
		}

	private void adjustedmarkconfirm(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException, BuildingServerException
		{
		int i, amark;
		Vector questions_in_order;
		PrimaryKey[] qids;
		String feedback, comments, uid;
		PrimaryKey user_id;
		PigeonHoleQuestion q;
		PigeonHoleEntry entry;

		
		if ( !BuildingContext.getContext().checkPermission( Permission.MARK ) )
			{
			out.println( "<PRE>You don't have permission to mark papers here.</PRE>\n" );
			return;
			}

		if ( req.getTemplateParameterCount()<1 )
			{
			out.println( "Invalid page address - no user specified" );
			return;
			}

		uid=(String)req.getTemplateParameter( 0 );
		if ( uid==null )
			{
			out.println( "Invalid page address - no user specified" );
			return;
			}

		user_id = new PrimaryKey( Integer.parseInt( uid ) );
		entry = ph_session.getPigeonHoleEntry( user_id );
		if ( entry == null )
			{
			out.println( "Unable to find database entry." );
			return;
			}
		
		
		feedback=req.getParameter( "feedback" );
		comments=req.getParameter( "comments" );

		if ( feedback==null || comments==null )
			{
			out.println( "<HR>Invalid form input.<HR>" );
			return;
			}
		
		//general marks.
		amark=-1;
		if ( req.getParameter( "adjustedmark" )!=null && req.getParameter( "adjustedmark" ).length()>0 )
			{
			try
				{
				amark = Integer.parseInt( req.getParameter( "adjustedmark" ) );
				}
			catch ( NumberFormatException nfex )
				{
				out.println( "The value entered in the adjusted mark box was not recognized as a number." );
				return;
				}
			}
			
		
		entry = ph_session.recordAdjustedMark( entry.getPigeonHoleEntryId(), amark, feedback, comments );
			
		out.println( "<P>Your entries were recorded.</P>" );
		
			
		}
/*
	private void remark(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException
		{
		String uid, feedback, comments, m;
		ResultSet results;
		Connection con;
		PigeonHoleEntry entry;
		PigeonHoleQuestion question;
		PigeonHoleResponse response;
		Hashtable responses=new Hashtable();
		Vector questions=new Vector();
		Vector entries=new Vector();
		

		try
			{
			con=db.getConnection();

			results=db.select( 
				"SELECT * FROM pigeon_hole_questions WHERE (location = " + 
				req.location.id +
				")" );
			while ( results.next() )
				{
				question = new PigeonHoleQuestion();
				question.load( results );
				questions.addElement( question );
				}

			results=db.select( 
				"SELECT id, location, user_id, up_changed, down_changed, " +
				"deleted, marker, when_marked, mark, penalty, adjusted_mark, " +
				"feedback, comments, DATALENGTH(upload) up_length, " +
				"DATALENGTH(download) down_length " +
				"FROM pigeon_hole_entries WHERE (location = " +
				req.location.id +
				")" );
			while ( results.next() )
				{
				entry=new PigeonHoleEntry();
				entry.load( results );
				entries.addElement( entry );
				}


			for ( int s=0; s<entries.size(); s++ )
				{
				entry=(PigeonHoleEntry)entries.elementAt( s );

				responses=new Hashtable();
				results=db.select( 
					"SELECT * FROM pigeon_hole_responses WHERE (result_id = " + 
					entry.id +
					")" );
				while ( results.next() )
					{
					response = new PigeonHoleResponse();
					response.load( results );
					responses.put( response.question_id, response );
					}

				entry.mark=new Integer( 0 );
				for ( int q=0; q<questions.size(); q++ )
					{
					question=(PigeonHoleQuestion)questions.elementAt( q );
					response=(PigeonHoleResponse)responses.get( question.question_id );
					if ( response==null || response.mark==null )
						{
						out.println( "<HR>Missing response for: " + entry.user_id + "<BR>" );
						entry.mark=null;
						break;
						}
					else
						{
						entry.mark=new Integer( 
								entry.mark.intValue() + 
								response.mark.intValue() );
						}
				 	}
				entry.update( con );
				}
			}
		catch ( Exception ex )
			{
			out.println( "</TABLE>" );
			out.println( ex.toString() );
			}
		}
*/	

/*
	private void eventlog(  Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws IOException
		{
		int j;
		String sql;
		PreparedStatement statement;
		Connection con;
		ResultSet results;
		java.util.Date jdate;
		Event event;

		if ( !BuildingContext.getContext().checkPermission( "manage" ) )
			{
			out.println( "<HR>You need manage access to view the log.<HR>" );
			return;
			}

		try
			{
			sql="SELECT * " +
				"FROM events_detail " +
				"WHERE (location = ? AND detail_level <= 1000 ) " +
				"ORDER BY happened ASC";
			con=db.getConnection();
			statement=con.prepareStatement( sql );
			statement.setInt( 1, req.location.id.intValue() );
			results=statement.executeQuery();
			
			for ( j=0; results.next();  )
				{
				event=new Event( );
				event.load( results );
				jdate=new java.util.Date( event.happened.getTime() );
				
				if ( j==0 )
					out.println( "</A></H3><TABLE CLASS=bs-table-opaque>" );

				out.println( "<TR><TD><I>" );
				outputDate( req, out, jdate, 0 );
				out.println( "</I></TD><TD>" );
				printEvent( out, event );
				out.println( "</TD></TR>" );
				j++;
				}
			if ( j>0 )
				out.println( "</TABLE>" );
			}
		catch ( Exception ex )
			{
			out.println( "<HR>There was a technical problem trying to get a list of events.<HR>" + ex.getMessage() );
			return;
			}
		
		}
*/
	
	private void uplink( Request req, PrintWriter out, PigeonHoleSession ph_session, String url )
		throws ServletException, IOException, BuildingServerException
		{
		PigeonHoleEntry entry;
		UploadedFileSummary uf;
		PrimaryKey uid=null;
		
	    if ( req.getTemplateParameterCount()>0 )
	    	{
	    	uid = new PrimaryKey( Integer.parseInt( (String)req.getTemplateParameter( 0 ) ) );
			entry = ph_session.getPigeonHoleEntry( uid );
			}
		else
			{
			entry = ph_session.getPigeonHoleEntry();
			if ( entry!=null )
			    uid = entry.getUserId();
			}
		
		if ( entry == null || uid == null )
			return;
			
		uf = ph_session.getUploadedFileSession().getFileSummary( entry.getUploadedFileId() );
		if ( uf == null )
			return;
		
		out.print( "<A HREF=\"" + req.absoluteURL() );
        out.print( uid.toString() );
        out.print( "/" );
        out.print( uf.getUrl() );
        out.print( "\">" );
        out.print( uf.getName() );
        out.print( "</A>" );
        
		
		}
	
	private void pigeonfield( Request req, PrintWriter out, PigeonHoleSession ph_session, String name )
		throws ServletException, IOException, BuildingServerException
		{
		if ( !BuildingContext.getContext().checkPermission( Permission.MANAGE ) )
			{
			out.println( "<PRE>You don't have permission to manage this pigeon hole.</PRE>\n" );
			return;
			}


		PigeonHole ph = ph_session.getPigeonHole();

		if ( name.equalsIgnoreCase( "maximum_mark" ) )
			{
			out.print( "<INPUT NAME=maximum_mark VALUE=\"" );
			out.print( ph.getMaximumMark() );
			out.println( "\">" );
			return;
			}
		if ( name.equalsIgnoreCase( "date_open" ) )
			{
			out.print( "<INPUT CLASS=\"datetime\" NAME=date_open VALUE=\"" );
			if ( ph.getDateOpen()!=null ) out.print( DateFormatter.formatDate( ph.getDateOpen(), DateFormatter.MEDIUM ) );
			out.println( "\">" );
			return;
			}
		if ( name.equalsIgnoreCase( "date_deadline" ) )
			{
			out.print( "<INPUT CLASS=\"datetime\" NAME=date_deadline VALUE=\"" );
			if ( ph.getDateDeadline()!=null ) out.print( DateFormatter.formatDate( ph.getDateDeadline(), DateFormatter.MEDIUM ) );
			out.println( "\">" );
			return;
			}
		if ( name.equalsIgnoreCase( "date_penalty" ) )
			{
			out.print( "<INPUT CLASS=\"datetime\" NAME=date_penalty VALUE=\"" );
			if ( ph.getDatePenalty()!=null ) out.print( DateFormatter.formatDate( ph.getDatePenalty(), DateFormatter.MEDIUM ) );
			out.println( "\">" );
			return;
			}

		
		return;
		}


	private void pigeonmodify( Request req, PrintWriter out, PigeonHoleSession ph_session )
		throws ServletException, IOException, BuildingServerException
		{
		String strdate;
		int max;


		Timestamp timestamps[] = convertToTimestamps(parseDates(req));

		strdate=req.getParameter( "maximum_mark" );
		try
			{
			max = Integer.parseInt( strdate );
			}
		catch ( NumberFormatException nfex )
			{
			out.println( "The entry in the maximum mark field could not be understood as a valid number - changes have not been saved." );
			return;
			}
			
		ph_session.setDates( timestamps[0], timestamps[1], timestamps[2] );
		
		out.println( "<p>Dates saved.</P>" );
		
		if ( max<1 )
			{
			max = 1;
			out.println( "The maximum mark field has been adjusted up to 1." );
			}

		if ( max>100 )
			{
			max = 100;
			out.println( "The maximum mark field has been adjusted down to 100." );
			}
		ph_session.setMaximumMark( max )	;
		
		out.println( "<p>Maximum mark saved.</P>" );
		}
	
	
	/**
	 * Utility method to convert an array of Dates to an array of Timestamps.
	 * If a date is null the timestamp is also null.
	 * @param dates The array of dates to convert.
	 * @return An array of Timestamps.
	 */
	private Timestamp[] convertToTimestamps(Date dates[])
	{
		Timestamp timestamps[] = new Timestamp[dates.length];
		
		for (int i = 0; i < dates.length; i++)
		{
			if (dates[i] == null)
				timestamps[i] = null;
			else
				timestamps[i] = new Timestamp(dates[i].getTime());
		}
		return timestamps;
	}
	
	public boolean canDownload(String path)
	{
		return false;
	}


	}

