/* ======================================================================
The Bodington System Software License, Version 1.0
  
Copyright (c) 2001 The University of Leeds.  All rights reserved.
  
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES 
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO 
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR 
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary 
contributions from others.  For more information on the Bodington Open Source Project, please 
see http://bodington.org/

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

package org.bodington.servlet.facilities;

import java.util.logging.*;


import org.bodington.servlet.*;

import java.util.*;
import java.sql.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.math.BigDecimal;

import org.bodington.server.*;
import org.bodington.server.realm.User;
import org.bodington.server.realm.Group;
import org.bodington.server.events.*;
import org.bodington.server.resources.Resource;
import org.bodington.database.PrimaryKey;
import org.bodington.assessment.*;

public class MCQFacility extends org.bodington.servlet.facilities.Facility
	{
	

	static final int EVENT_RECORD  =0;

	public String defaultIcon()
		{
		return "mcq.gif";
		}
	public String defaultSmallIcon()
		{
		return "mcq-small.gif";
		}


	public Resource newResource()
		{
		return new McqPaper();
		}
	
	
	public boolean initResource( Request breq, Resource new_resource )
		throws Exception
		{
		McqPaper mcq_paper;

		String once, paper, mark_witheld, dead;
		java.util.Date deaddate;
		java.sql.Timestamp sqldeaddate=null;

		once=breq.getParameter( "once_only" );
		mark_witheld = breq.getParameter( "mark_witheld" );
		paper=breq.getParameter( "paper_test" );
		dead=breq.getParameter( "deadline" );
		if ( dead!=null )
			{
			dead=dead.trim();
			if ( dead.length()>0 )
				{
				deaddate=DateParser.parse( dead );
				if ( deaddate==null )
					throw new Exception( "The deadline date couldn't be understood." );
				else
					sqldeaddate=new java.sql.Timestamp( deaddate.getTime() );
				}
			}
		
		if ( !(new_resource instanceof McqPaper) )
			throw new Exception( "Technical problem: An incorrect type of resource was created." );
		
		mcq_paper = (McqPaper)new_resource;
		mcq_paper.setOnceOnly( once!=null && once.length()>0 );
		mcq_paper.setMarkWitheld( mark_witheld!=null && mark_witheld.length()>0 );
		mcq_paper.setDeadline( sqldeaddate );

		return true;
		}

	

		
	public boolean createCheck( Request breq, PrintWriter out )
		{
		//default creation does nothing but other facilities can
		//check user input print errors and prevent resource creation
		//by returning false.
		
		Logger.getLogger( "org.bodington" ).fine( "Checking MCQ creation input." );
		
//		try
//			{
			java.util.Date deaddate, now;
			now = new java.util.Date();
			String dead=breq.getParameter( "deadline" );
			if ( dead!=null )
				{
				dead=dead.trim();
				if ( dead.length()>0 )
					{
					//there is date input so check it now
					deaddate=DateParser.parse( dead );
					Logger.getLogger( "org.bodington" ).fine( deaddate.toString() );
					if ( deaddate==null )
						{
						out.println( "<HR>The date was not in a format that could be understood.  " +
						"Please use d/m/yyyy format.  (d = 1 or 2 digit date, m = 1 or two digit month, " +
						"yyyy= four digit year)" );
						return false;
						}
					if ( now.after( deaddate ) )
						{
						out.println( "<HR>The date was not acceptable as a deadline because it has already passed.  " );
						return false;
						}
					if ( deaddate.getYear() > (now.getYear()+100) )
						{
						out.println( "<HR>The date was not acceptable as a deadline because it is in the very distant future.  " );
						return false;
						}
					}
				}
//			}
//		catch ( IOException ioex )
//			{
//			return false;
//			}
		
		return true;
		}


	public void insert( Request req, PrintWriter out, String command, String insertname )
		throws ServletException, IOException
		{
		Logger.getLogger("org.bodington").fine( Thread.currentThread().getName() + " MCQFacility insert()" );
		
		try
			{
			BuildingSession session;
			McqPaperSession mcq_session;
			McqPaper mcq;

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


			if ( command.equalsIgnoreCase( "importmcqcards" ) ||
		    	command.equalsIgnoreCase( "mcqpaper" ) ||
		    	command.equalsIgnoreCase( "mcqrecord" ) ||
		    	command.equalsIgnoreCase( "mcqitemedit" ) ||
		    	command.equalsIgnoreCase( "mcqeditqconfirm" ) ||
		    	command.equalsIgnoreCase( "mcqdeleteq" ) ||
		    	command.equalsIgnoreCase( "mcqmarks" ) ||
		    	command.equalsIgnoreCase( "mcqrunbutton" ) ||
		    	command.equalsIgnoreCase( "mcqfield" ) ||
		    	command.equalsIgnoreCase( "mcqmodify" ) ||
		    	command.equalsIgnoreCase( "mcqanalysis" ) ||
		    	command.equalsIgnoreCase( "ifmcqonceonly" ) ||
		    	command.equalsIgnoreCase( "ifmcqonceonlydone" ) ||
		    	command.equalsIgnoreCase( "ifmcqdeadline" ) ||
				command.equalsIgnoreCase( "ifmcqpassed" ) ||
		    	command.equalsIgnoreCase( "mcqnewq" ) ||
		    	command.equalsIgnoreCase( "import" ) ||
		    	command.equalsIgnoreCase( "export" )	)
				{
				if ( mcq==null )
					{
					out.println( "<HR>Unable to load MCQ from database.</HR>" );
					return;
					}
				if ( command.equalsIgnoreCase( "import" ) )
					importq( req, out, mcq_session );
				if ( command.equalsIgnoreCase( "export" ) )
					exportXml( req, out, mcq_session );
				if ( command.equalsIgnoreCase( "importmcqcards" ) )
					importmcqcards( req, out, mcq_session, mcq,insertname );
				if ( command.equalsIgnoreCase( "mcqpaper" ) )
					mcqpaper( req, out, mcq_session, mcq,insertname );
				if ( command.equalsIgnoreCase( "mcqrecord" ) )
					mcqrecord( req, out, mcq_session, mcq,insertname );
				if ( command.equalsIgnoreCase( "mcqnewq" ) )
					mcqnewq( req, out, mcq_session, mcq,insertname );
				if ( command.equalsIgnoreCase( "mcqitemedit" ) )
					mcqitemedit( req, out, mcq_session, mcq,insertname );
				if ( command.equalsIgnoreCase( "mcqeditqconfirm" ) )
					mcqeditqconfirm( req, out, mcq_session, mcq,insertname );
				if ( command.equalsIgnoreCase( "mcqdeleteq" ) )
					mcqdeleteq( req, out, mcq_session, mcq );
				if ( command.equalsIgnoreCase( "mcqmarks" ) )
					mcqmarks( req, out, mcq_session, mcq );
				if ( command.equalsIgnoreCase( "mcqrunbutton" ) )
					mcqrunbutton( req, out, mcq_session, mcq );
				if ( command.equalsIgnoreCase( "mcqfield" ) )
					mcqfield( req, out, mcq_session, mcq,insertname );
				if ( command.equalsIgnoreCase( "mcqmodify" ) )
					mcqmodify( req, out, mcq_session, mcq );
				if ( command.equalsIgnoreCase( "mcqanalysis" ) )
					mcqanalysis( req, out, mcq_session, mcq );
				if ( command.equalsIgnoreCase( "ifmcqonceonly" ) )
					req.setSwitchedOff(  !mcq.isOnceOnly() );
				if ( command.equalsIgnoreCase( "ifmcqonceonlydone" ) )
					{
					if ( !mcq.isOnceOnly() || req.getUserId()==null || BuildingContext.getContext().checkPermission( "edit" ) )
						req.setSwitchedOff( true );
					else
						req.setSwitchedOff(  !( onceonlydone( req, mcq_session ) ) );
					}
				if ( command.equalsIgnoreCase( "ifmcqdeadline" ) )
					req.setSwitchedOff(  (mcq.getDeadline()==null) );
				if ( command.equalsIgnoreCase( "ifmcqpassed" ) )
					{
					if ( mcq.getDeadline()==null || req.getUserId()==null || BuildingContext.getContext().checkPermission( "edit" ) )
						req.setSwitchedOff( true );
					//only if there is a deadline and the user is a student
					else
						{
						java.util.Date now=new java.util.Date();
						out.println( "<P>comparing " + now.toString() + " with " + mcq.getDeadline().toString() + "</P>" );
						req.setSwitchedOff(  !now.after( mcq.getDeadline() ) );
						}
					}
				
				
				return;
				}
			}		
		catch ( BuildingServerException bsex )
			{
			out.println( bsex.toString() );
			return;
			}
		
		super.insert( req, out, command, insertname );
		}
	

	private void importmcqcards(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq, String insertname )
		throws IOException
		{
		/*
		BRealm realm=getRealm();
		Database db=realm.getDatabase();
		Connection con=null;
		ResultSet results;
		
		String sql, param;
		int total_available=0, total_right=0, total_wrong=0, total_lost=0;
		int available, right, wrong, lost;
		boolean answer[] = new boolean[5];
		boolean response[] = new boolean[5];
		char letter;
		double mark;
		
		
		Enumeration enum; //questions in order
		McqQuestion q;
		McqResult qresult;
		McqResponse qresponse;
		MCQCard card;
		
		Vector cards=new Vector();
		
		if ( !BuildingContext.getContext().checkPermission( "manage" ) )
			{
			out.println( "<PRE>You don't have permission to import questions in this location of the building.</PRE>\n" );
			return;
			}
		
		//takes mcqcards in database and
		//creates entries in McqResults and mcqresponses
		out.println( "<PRE>" );
		
		try
			{
			con=db.getConnection();
			con.setAutoCommit( false );

			sql="SELECT * FROM " + DBObject.schema + "mcq_cards WHERE location = (" + req.getResource().getResourceId() + ")";
			results=db.select( sql );
			
			if ( results==null )
				{
				out.println( "<HR>A problem occured trying to retrieve the cards.<HR>" );
				return;
				}
			
			while ( results.next() )
				{
				card = new MCQCard();
				card.load( results );
				out.println( card.student_id );
				cards.addElement( card );
				}
			
			for ( int k=0; k<cards.size(); k++ )
				{
				total_available=0;
				total_right=0;
				total_wrong=0;
				total_lost=0;
				
				card=(MCQCard)cards.elementAt( k );
				if ( card==null )
					continue;

				out.print( card.student_id + "\t" );
				
				qresult=new McqResult();
				qresult.user_id=new String( card.student_id );
				qresult.location=new Integer( req.getResource().getResourceId().intValue() );
				qresult.insert( con );

				enum=mcq.getQuestions(); //questions in order
				for ( int i=0; enum.hasMoreElements(); i++ )
					{
					q=(McqQuestion)enum.nextElement();

					answer[0]=q.answer_a;
					answer[1]=q.answer_b;
					answer[2]=q.answer_c;
					answer[3]=q.answer_d;
					answer[4]=q.answer_e;
				
					//get responses in card image
					for ( int j=0; j<5; j++ )
						{
						response[j]=  
							(((int)card.card_image.charAt( i*2 ) - (int)'0') & 
								(1 << (4-j))  ) != 0;
						}

					available=0;
					right=0;
					wrong=0;
					for ( int j=0; j<5; j++ )
						{
						if ( answer[j] )
							{
							available++;
							if ( response[j] ) right++;
							}
						else
							{
							if ( response[j] ) wrong++;
							}
						}
					lost=(wrong>right) ? right : wrong;
					
					total_available+=available;
					total_right+=right;
					total_wrong+=wrong;
					total_lost+=lost;
					

					qresponse=new McqResponse();
					qresponse.result_id=qresult.result_id;
					qresponse.question_id=q.question_id;
					
					qresponse.response_a=response[0];
					qresponse.response_b=response[1];
					qresponse.response_c=response[2];
					qresponse.response_d=response[3];
					qresponse.response_e=response[4];

					qresponse.insert( con );
					}

				qresult.available=new Integer( total_available );
				qresult.deductions=new Integer( total_lost );
				qresult.wrong=new Integer( total_wrong );
				qresult.right=new Integer( total_right );
				java.util.Date now= new java.util.Date();
				qresult.when_done=new java.sql.Timestamp( now.getTime() );
				qresult.when_marked=new java.sql.Timestamp( now.getTime() );
				qresult.update( con );
				
				//card.result_id=qresult.result_id;
				//card.update( con );
				
				mark=	(  ((double)total_right-(double)total_lost)
						   /(double)total_available );

				out.println( card.student_id + "\t" + (Math.round( mark*1000.0 )/10.0) );
				}
			con.commit();
			return;
			}
		catch ( Exception ex )
			{
			out.println( "<HR>A problem occured trying to store your responses.<HR>" + ex.getMessage() );
			}
		
		try
			{
			if ( con!=null )
				con.rollback();
			}
		catch ( SQLException sqlex )
			{
			out.println( "<HR>A problem occured trying to roll back database edits." );
			}
		
		*/
		}
	
	private void questionToHTML( McqQuestion question, McqQuestionAnalysis q_analysis, PrintWriter out, String style )
		throws IOException, BuildingServerException
		{
		char letter;
		boolean answer[]=new boolean[5];
		String statement[]=new String[5];
		
		out.println( "<TR><TD COLSPAN=3>" );
		if ( style.equalsIgnoreCase( "run" ) )
			{
			out.println( "<INPUT TYPE=HIDDEN VALUE=TRUE NAME=Q" + question.getMcqQuestionId() + ">" );
			}
		out.println( question.getQuestion() );
		out.println( "</TD></TR>" );
		
		for ( int i=0; i<5; i++ )
			{
			letter=(char)('A'+i);
			if ( style.equalsIgnoreCase( "run" ) )
				{
				out.println( "<TR><TD VALIGN=TOP WIDTH=20><B>" );
				out.println( letter );
				out.println( "</B></TD><TD VALIGN=TOP WIDTH=20>" );
				if ( question.isOneTrueAnswer() )
					{
					out.print( "<INPUT TYPE=RADIO " );
					out.println( "NAME=QR" + question.getMcqQuestionId() + " VALUE=" + letter + ">" );
					}
				else
					{
					out.print( "<INPUT TYPE=CHECKBOX " );
					out.println( "NAME=" + letter + question.getMcqQuestionId()  + " VALUE=TRUE>" );
					}
				out.println( "</TD><TD VALIGN=TOP WIDTH=1000>" );
				out.println( question.getStatement( i ) );
				out.println( "</TD></TR>" );
				}
			else
				{
				out.println( "<TR><TD VALIGN=TOP WIDTH=20><B>" );
				out.println( letter );
				out.println( "</B></TD><TD VALIGN=TOP WIDTH=40><I>" );
				out.println( question.getAnswer( i )?"TRUE":"FALSE" );
				out.println( "</I></TD><TD VALIGN=TOP WIDTH=1000>" );
				out.println( question.getStatement( i ) );
				out.println( "</TD></TR>" );
				}
			}
		
		if ( style.equalsIgnoreCase( "edit" ) )
			{
			out.println( "<TR><TD COLSPAN=3>" );
			out.println( question.getExplanation() );
			out.println( "</TD></TR>" );
			}

		if ( style.equals( "analysis" ) && q_analysis!=null )
			{
			out.print( "<TR><TD COLSPAN=3><P>Statistical Analysis</P>" );
			PrintWriter pout = new PrintWriter( out );
			q_analysis.dumpAnalysis( pout );
			pout.flush();
			out.println( "</TD></TR>" );
			}
		}
	
	private void questionDebriefToHTML( McqQuestion question, Request request, PrintWriter out, McqResponse response )
		throws IOException, BuildingServerException
		{
		char letter;
		String guessadvice, certainadvice;
		
		out.println( "<TABLE BORDER><TR><TD COLSPAN=3 BGCOLOR=e0e0e0>" );
		out.println( question.getQuestion() );
		out.println( "</TD></TR>" );
		
		int right=0, wrong=0, deducted=0, available=0;
		for ( int i=0; i<5; i++ )
			{
			if ( question.getAnswer(i) )
				{
				available++;
				if ( response.getResponse(i) )
					right++;
				}
			else
				{
				if ( response.getResponse(i) )
					wrong++;
				}
			}
		if ( wrong>right )
			deducted=right;
		else
			deducted=wrong;
		
		wrong=0;
		for ( int i=0; i<5; i++ )
			{
			letter=(char)('A'+i);
			if ( question.getAnswer(i) )
				{
				out.println( "<TR BGCOLOR=c0ffc0>" );
				if ( response.getResponse(i) )
                                {
                                    out.print( "<TD>" );
                                    writeTemplateGifTag( request, out, "", "img", "bs_template_grn-star.gif", "alt=\"Green star icon.\"" );
                                    out.println( "</TD>" );
                                }
				else
                                {
                                    out.print( "<TD>" );
                                    writeTemplateGifTag( request, out, "", "img", "bs_template_blank.gif", "alt=\"Blank spacer icon.\"" );
                                    out.println( "</TD>" );
                                }
				}
			else
				{
				out.println( "<TR BGCOLOR=ffc0c0>" );
				if ( response.getResponse(i) )
					{
					wrong++;
					if ( wrong<=deducted )
                                {
                                    out.print( "<TD>" );
                                    writeTemplateGifTag( request, out, "", "img", "bs_template_red-tri.gif", "alt=\"Bright red triangle icon.\"" );
                                    out.println( "</TD>" );
                                }
					else
                                {
                                    out.print( "<TD>" );
                                    writeTemplateGifTag( request, out, "", "img", "bs_template_gry-tri.gif", "alt=\"Dark triangle icon.\"" );
                                    out.println( "</TD>" );
                                }
					}
				else
                                {
                                    out.print( "<TD>" );
                                    writeTemplateGifTag( request, out, "", "img", "bs_template_blank.gif", "alt=\"Blank spacer icon.\"" );
                                    out.println( "</TD>" );
                                }
				}

			
			out.println( "<TD VALIGN=TOP WIDTH=20><B>" );
			out.println( letter );
			out.println( "</B></TD>" );
			out.println( "<TD VALIGN=TOP WIDTH=1000>" );
			out.println( question.getStatement(i) );
			out.println( "</TD></TR>" );
			}
		
		out.println( "<TR BGCOLOR=e0e0e0><TD COLSPAN=3>" );
		out.println( question.getExplanation() );
		out.println( "</TD></TR></TABLE>" );


		//put score in separate table
		out.println( "<TABLE ALIGN=CENTER BORDER BGCOLOR=e0e0e0><TR ALIGN=CENTER>" );
		out.println( "<TD><B>Available</B></TD><TD><B>Correct</B></TD>" );
		out.println( "<TD><B>Incorrect</B></TD><TD><B>Deducted</B></TD>" );
		out.println( "<TD><B>Total</B></TD></TR>" );
		out.println( "<TR ALIGN=CENTER VALIGN=TOP>" );
		out.println( "<TD>" + available + "</TD>" );
		out.println( "<TD>" + right    + "</TD>" );
		out.println( "<TD>" + wrong    + "</TD>" );
		out.println( "<TD>" + deducted + "</TD>" );
		out.println( "<TD><B>" + (right-deducted) + "</B></TD></TR></TABLE>" );
		
		
		//give advice if appropriate
		guessadvice="";
		certainadvice="";
		if ( question.isOneTrueAnswer() )
			{
			if ( wrong==0 && right==0 ) //i.e. no selection
				{
				guessadvice=
					"<P>You didn't select one of statements.  If you didn't know " +
					"the answer it wouldn't have done any harm to guess.  This "+
					"question was the type that always has one true statement so "+
					"you had a one in five change of getting a mark. </P>";
				certainadvice=
					"<P>There are never questions with no true statements.  So if "+
					"you were certain all statements are false you should have "+
					"realised you were wrong and guessed a statement.</P>";
				}
			if ( wrong==1 )
				{
				certainadvice=
					"<P>You selected the wrong statement.  If you were "+
					"certain about the answer, you ought to do some study to "+
					"see where you went wrong.</P>";
				guessadvice=
					"<P>If you didn't know the answer, "+
					"it was a good idea to guess - you might have gained a mark.</P>";
				}
			if ( right==1 )
				{
				certainadvice=
					"You selected the right statement.  Well done!";
				guessadvice=
					"It was a good idea to guess - you picked up a mark.";
				}
			}
		else
			{
			if ( wrong==0 )
				{
				if ( right==0 )
					{
					guessadvice=
						"You didn't select one of statements.  If you didn't know " +
						"the answer it wouldn't have done any harm to guess one.  The " +
						"marking scheme doesn't deduct marks if there are none to deduct.";
					certainadvice=
						"There are never questions with five false statements.  So if " +
						"you were certain all statements are false you should have " +
						"realised you were wrong and guessed one statement.";
					}
				if ( right==1 )
					{
					if ( available==1 )
						{
						guessadvice=
							"You were lucky to select the true statement and you were wise " +
							"to avoid guessing more because you might have lost your mark " +
							"by selecting a false statement. ";
						certainadvice=
							"Well done.";
						}
					if ( available>1 && available<5 )
						{
						guessadvice=
							"You were lucky to select a true statement and you were wise " +
							"to avoid guessing more because you might have lost your mark " +
							"by selecting a false statement. ";
						certainadvice=
							"Well done for gaining a mark.  It's just as well " +
							"you didn't guess at the other statements because " +
							"you might have lost your well earned mark.";
						}
					if ( available==5 )
						{
						guessadvice=
							"On this question you coudn't loose marks because " +
							"all the statements were true, BUT there is no way " +
							"to know this in advance so you were right to just " +
							"guess one statement.";
						certainadvice=
							"If you knew in advance that all the statements were " +
							"true you could have got full marks on guesswork.  It's " +
							"just as well that you only selected the statement you " +
							"were sure about though.";
						}
					}
				if ( right>1 )
					{
					if ( available==right && available<5 )
						{
						guessadvice=
							"You were very lucky to select the all the true statements " +
							"but you were running a risk. " +
							"You didn't know how many true statements there were " +
							"and with most questions guessing two or more statements " +
							"will results in a score of zero.";
						certainadvice=
							"Well done.";
						}
					if ( available>right && available<5 )
						{
						guessadvice=
							"You were very lucky to select more than one true statement " +
							"but you were running a risk. " +
							"You didn't know how many true statements there were " +
							"and with most questions guessing two or more statements " +
							"will results in a score of zero.";
						certainadvice=
							"Well done for gaining marks.  It's just as well " +
							"you didn't guess at the other statements because " +
							"you might have lost one or more of your well earned marks.";
						}
					if ( available==5 )
						{
						guessadvice=
							"You were very lucky to select more than one true statement " +
							"but you were running a risk. " +
							"You didn't know how many true statements there were " +
							"and with most questions guessing two or more statements " +
							"will results in a score of zero.";
						certainadvice=
							"If you knew in advance that all the statements were " +
							"true you could have got full marks on guesswork.  It's " +
							"just as well that you only selected the statements you " +
							"were sure about though.";
						}
					}
				}
			else
				{//one or more wrong marks
				if ( right==0 )
					{
					if ( wrong==1 )
						{
						guessadvice=
							"You were unlucky to gain no marks but it was " +
							"reasonable to guess.  ";
						certainadvice=
							"You should do some study to see where you went wrong.";
						}
					if ( wrong>1 )
						{
						guessadvice=
							"You were unwise to guess two statements.  Even if one " +
							"had been true, the false statement would have cancelled " +
							"the mark you gained.";
						certainadvice=
							"You should do some study to see where you went wrong.";
						}
					}
				if ( right>0 ) // one or more wrong and one or more right
					{
					if ( wrong>=right )
						{
						guessadvice=
							"If you gained marks on statements you were sure about " +
							"you lost them by guessing others. ";
						certainadvice=
							"You were wrong this time but you shouldn't be afraid to " +
							"select multiple statements when you are sure about it.";
						}
					if ( wrong<right )
						{
						guessadvice=
							"If you gained marks on statements you were sure about " +
							"you lost them by guessing others. ";
						certainadvice=
							"You were partly wrong this time but you shouldn't be afraid to " +
							"select multiple statements when you are sure about it.";
						}
					}
				}
			}
		
		if ( guessadvice.length()>0 && certainadvice.length()>0 )
			{
			out.println( "<TABLE BORDER ALIGN=CENTER BGCOLOR=e0e0e0><TR>" );
			out.println( "<TD><B>Were you confident?</B></TD>" );
			out.println( "<TD><B>Were you unsure?</B></TD></TR>" );
			out.println( "<TD>" + certainadvice + "</TD><TD>" + guessadvice + "</TD>" );
			out.println( "</TR></TABLE>" );
			}
		}
	
	private void mcqpaper(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq, String insertname )
		throws IOException
		{
		int i;
		
		Vector questions_in_order;
		McqQuestion q;
		Resource resource;
		
		try
			{
			resource = BuildingContext.getContext().getResource();
			if ( insertname.equalsIgnoreCase( "debrief" ) )
				{
				mcqdebrief( req, out, mcq_session, mcq );
				return;
				}

			
			questions_in_order = mcq_session.getMcqQuestionsInOrder();
			
			
			if ( insertname.equalsIgnoreCase( "run" ) )
				{
				String can=canrun( req, mcq_session, mcq );
				if ( can!=null )
					{
					out.println( can );
					return;
					}
				out.println( "<FORM METHOD=POST ACTION=bs_template_mcqrecord.html>" );
				}
			
			for ( i=0; i<questions_in_order.size(); i++ )
				{
				q=(McqQuestion)questions_in_order.elementAt( i );
				
				if ( insertname.equalsIgnoreCase( "run" ) )
					{
					out.println( "<HR><TABLE><TR><TD COLSPAN=3><H4>Question " + (i+1) + "</H4></TD></TR>" );
					}
				else
					{
					out.println( "<P></P><TABLE BORDER><TR><TD COLSPAN=2><H4>Question " + 
						(i+1) + " (" + q.getOrdinal() + ")</H4></TD>" );
					out.print( "<TD ALIGN=CENTER><FORM METHOD=POST ACTION=\"" );
					out.print( req.getContextPath() );
					out.print( req.getServletPath() );
					out.print( resource.getFullName()  );
					out.print( q.getMcqQuestionId() + "/bs_template_mcqeditq.html\">" );
					out.println( "<INPUT TYPE=SUBMIT VALUE=\"Edit This Question\"></FORM></TD></TR>" );
					}
				
				questionToHTML( q, null, out, insertname );
				
				out.println( "</TABLE>" );
				}

			if ( i==0 )
				out.println( "<P>There are currently no questions in this test paper.</P>" );
			else
				{
				if ( insertname.equalsIgnoreCase( "run" ) )
					{
					out.println( "<P><INPUT TYPE=SUBMIT VALUE=\"Record Answers Now\"></FORM></P>" );
					}
				}
			}
		catch ( Exception ex )
			{
			out.println( "<HR>A technical problem occured while trying to fetch marks.<HR>" + ex.getMessage() );
			}
		}


	private void mcqdebrief(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq )
		throws IOException
		{
		String id;
		PrimaryKey resid;
		McqResult mcq_result;
		Hashtable mcq_responses;
		McqResponse mcqresponse;
		McqQuestion q;
		Vector questions_in_order;
		
		
		//must have an id in the parameter part of the URL
		if ( req.getTemplateParameterCount() < 1 )
			{
			out.println( "<HR><I>Improper URL.</I></HR" );
			return;
			}
		id=(String)req.getTemplateParameter( 0 );
		resid= new PrimaryKey( Integer.parseInt(id) );
		
		try
			{
			mcq_result = mcq_session.getMcqResult( resid );
		
			if ( !BuildingContext.getContext().checkPermission( "manage" ) )
				{
				if ( !mcq_result.getUserId().equals( req.getUserId() ) || 
						!BuildingContext.getContext().checkPermission( "review" )  )
					{
					out.println( "<P>Sorry, you don't have access to the requested debrief information.</P>" );
					return;
					}
				}
				
			questions_in_order = mcq_session.getMcqQuestionsInOrder();
			
			mcq_responses = mcq_session.getMcqResponses( resid );

			for ( int i=0; i<questions_in_order.size(); i++ )
				{
				q = (McqQuestion)questions_in_order.elementAt( i );
				if ( q==null )
					{
					out.println( "This question wasn't found." );
					continue;
					}
				
				mcqresponse=(McqResponse)mcq_responses.get( q.getMcqQuestionId() );
				if ( mcqresponse == null )
					continue;
					
				out.println( "<HR><H4>Question " + (i+1) + "</H4>" );
				
				questionDebriefToHTML( q, req, out, mcqresponse );
				}
			}
		catch ( Exception ex )
			{
			logException( out, "MCQFacility", "mcqdebrief", 
			    "There was a technical problem retrieving the " +
			             "answers you gave to the questions.",
			    ex );
			}
		
		
		}


	
	private void mcqrecord(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq, String insertname )
		throws IOException, BuildingServerException
		{
		char letter;
		int i, j;
		String param;
		Vector questions_in_order;
		PrimaryKey[] list;
		McqQuestion q;
		boolean responses[][];
		
		questions_in_order = mcq_session.getMcqQuestionsInOrder();
			
		responses = new boolean[questions_in_order.size()][];
		list = new PrimaryKey[questions_in_order.size()];
		
		for ( i=0; i<questions_in_order.size(); i++ )
			{
			q=(McqQuestion)questions_in_order.elementAt( i );
			list[i] = q.getMcqQuestionId();
		
			responses[i] = new boolean[5];
			//if not found in form input treat it as not attempted
			for ( j=0; j<5; j++ )
				responses[i][j]=false;
				
			param=req.getParameter( "Q" + q.getMcqQuestionId() );
			if ( param==null || !param.equalsIgnoreCase( "true" ) )
				continue;
				

			if ( q.isOneTrueAnswer() )
				{
				param=req.getParameter( "QR" + q.getMcqQuestionId() );
				if ( param!=null && param.length()>0)
					{
					for ( j=0; j<5; j++ )
						{
						letter=(char)('A'+j);
						if ( param.charAt( 0 )==letter )
							{
							responses[i][j]=true;
							break;
							}
						}
					}
				}
			else
				{
				for ( j=0; j<5; j++ )
					{
					letter=(char)('A'+j);
					param=req.getParameter( letter + q.getMcqQuestionId().toString() );
					responses[i][j]=  ( param!=null && param.length()>0 );
					}
				}
			
			}
			
			
		McqResult result =  mcq_session.record( list, responses );
		BigDecimal bigmark = result.mark();
		
		if ( mcq.isMarkWitheld() )
			out.println( "<p>You mark has been recorded but the manager of this test has chosen to withold it at this time. " );
		else
			out.println( "<P>Your mark was <B>" + bigmark + "%</B>." );
			
		if ( req.getUserId()==null )
			out.println( "It was recorded anonymously.</P>" );
		else
			{
			User user = (User)BuildingContext.getContext().getUser();
			out.println( "It was recorded under the name " + user.getName() + "</P>" );
			}
			
        Event event = new AssessmentEvent( 
        				AssessmentEvent.EVENT_SUBMISSION,
        				req.getResource().getResourceId(), 
        				req.getUserId(), 
						null,
        				new Integer( result.getMcqResultId().intValue() ), 
        				bigmark  );
        event.save();
		}
	
	private void mcqnewq( Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq, String insertname )
		throws IOException
		{
		try
			{
			mcq_session.createQuestion();
			}
		catch ( Exception ex )
			{
			out.println( "<HR>A technical problem occured while trying to create a new question.<HR>" + ex.getMessage() );
			}
		}	

	private void mcqitemedit(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq, String insertname )
		throws IOException, BuildingServerException
		{
		String id, field, statement;
		PrimaryKey qid;
		Hashtable questions;
		McqQuestion q;
		
		if ( !BuildingContext.getContext().checkPermission( "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 = mcq_session.getMcqQuestions();
		q=(McqQuestion)questions.get( qid );
		
		if ( q==null )
			{
			out.println( "<I>Can't display edit field.</I>" );
			return;
			}

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

		for ( int i=0; i<5; i++ )
			{
			field = "statement_" + (char)('a' + i );
			statement = q.getStatement( i );
			if ( insertname.equalsIgnoreCase( field ) )
				{
				out.print( "<SPAN CLASS=bs-textarea><TEXTAREA NAME=" );
				out.print( field );
				out.print( " COLS=30 ROWS=4>" );
				out.print( statement==null?"":statement );
				out.println( "</TEXTAREA></SPAN>" );
				return;
				}
			}

		if ( insertname.equalsIgnoreCase( "explanation" ) )
			{
			out.print( "<SPAN CLASS=bs-textarea><TEXTAREA NAME=explanation COLS=45 ROWS=4>" );
			out.print( q.getExplanation() );
			out.println( "</TEXTAREA></SPAN>" );
			return;
			}
		
		if ( insertname.equalsIgnoreCase( "answer_a" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=answer_a VALUE=true " );
			out.println( q.getAnswer(0)?"CHECKED>":">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "answer_b" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=answer_b VALUE=true " );
			out.println( q.getAnswer(1)?"CHECKED>":">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "answer_c" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=answer_c VALUE=true " );
			out.println( q.getAnswer(2)?"CHECKED>":">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "answer_d" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=answer_d VALUE=true " );
			out.println( q.getAnswer(3)?"CHECKED>":">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "answer_e" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=answer_e VALUE=true " );
			out.println( q.getAnswer(4)?"CHECKED>":">" );
			return;
			}

		if ( insertname.equalsIgnoreCase( "one_true" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=one_true VALUE=true " );
			out.println( q.isOneTrueAnswer()?"CHECKED>":">" );
			return;
			}
		}

	
	private void mcqeditqconfirm(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq, String insertname )
		throws IOException, BuildingServerException
		{
		int n;
		String id, param;
		PrimaryKey qid;
		Hashtable questions;
		McqQuestion q;
		
		if ( !BuildingContext.getContext().checkPermission( "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 = mcq_session.getMcqQuestions();
		q=(McqQuestion)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;
			}

		n=0;
		for ( int i=0; i<5; i++ )
			{
			param=req.getParameter( "answer_" + (char)('a' + i) );
			q.setAnswer( i, param!=null && param.length()>0 );
			if ( q.getAnswer(i) )
				n++;
			}

		param=req.getParameter( "one_true" );
		q.setOneTrueAnswer( param!=null && param.length()>0 );
	
		if ( n==0 )
			{
			out.println( 
				"<HR>The question wasn't saved because you " +
				"have no true statements.  Backtrack to continue editing." );
			return;
			}
		if ( q.isOneTrueAnswer() && n>1 )
			{
			out.println( 
				"<HR>You selected 'one true' but there were more than one " +
				"true statement so this option was ignored.<HR>" );
			q.setOneTrueAnswer( false );
			}
		
		mcq_session.changeQuestionText( q.getMcqQuestionId(), 
										q.getQuestionBigStringId(), 
										req.getParameter( "question" ) );
		for ( int i=0; i<5; i++ )
			mcq_session.changeQuestionText( q.getMcqQuestionId(), 
											q.getStatementBigStringId( i ), 
											req.getParameter( "statement_" + (char)('a' + i) ) );
											
		mcq_session.changeQuestionText( q.getMcqQuestionId(), 
										q.getExplanationBigStringId(), 
										req.getParameter( "explanation" ) );


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

	
	private void mcqmarks( Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq )
		throws IOException, BuildingServerException
		{
		Vector results;
		McqResult result;
		
		results = mcq_session.getResultsOfUser();
		if ( results == null || results.size()<1 )
			{
			out.println( "<P>There is no record of attempts for you on this test.</P>" );
			return;
			}
		
		for ( int i=0; i < results.size(); i++ )
			{
			result=(McqResult)results.elementAt( i );

    		Facility.outputDate( out, result.getWhenDone(), 0 );
			out.println( "</H3>" );
			out.println( "<TABLE ALIGN=CENTER CLASS=bs-table-opaque>" );
			out.println( "<TR><TD></TD>" );
			out.println( "<TD><B>Available</B></TD>" );
			out.println( "<TD><B>Gained</B></TD>" );
			out.println( "<TD><B>Deducted</B></TD><TR>" );

			out.println( "<TR><TD><B>Marks:</B></TD>" );
			out.println( "<TD>" + result.getAvailable() + "</TD>" );
			out.println( "<TD>" + result.getRight() + "</TD>" );
			out.println( "<TD>" + result.getDeductions() + "</TD></TR>" );
			out.println( "<TR VALIGN=TOP><TD><B>Percentage:</B></TD>" );
			out.println( "<TD COLSPAN=3 ALIGN=CENTER><H2>" + result.mark() + "%</H2></TD></TR>" );

			out.println( "<TR VALIGN=TOP><TD COLSPAN=4>A " );
			out.print( "<A HREF=" );
			out.print( req.getContextPath() );
			out.print( req.getServletPath() );
			out.print( req.getResource().getFullName() + result.getMcqResultId() );
			out.print( "/bs_template_mcqdebrief.html>detailed debrief</A> is available." );
			out.println( "</TD></TR>" );

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

	private void mcqdeleteq(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq )
		throws IOException, BuildingServerException
		{
		int n;
		String id, param, notparam;
		PrimaryKey qid;

		if ( !BuildingContext.getContext().checkPermission( "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;
			}

		mcq_session.removeQuestion( qid );
		}

	private String canrun( Request req, McqPaperSession mcq_session, McqPaper mcq )
		throws IOException
		{
		if ( req.getUserId()!=null )
			if ( !BuildingContext.getContext().checkPermission( "edit" )  )
				{
				if ( mcq.getDeadline()!=null )
					{
					java.util.Date now=new java.util.Date();
					if ( now.after( mcq.getDeadline() ) )
						return "<P>The deadline has passed and you can't run the test</P>";
					}
				if ( mcq.isOnceOnly() )
					if ( onceonlydone( req, mcq_session ) )
						return "<P>You have done the test once and you can't do it again.</P>";
				}
				
		//null indicates that the test can be run
		return null;
		}

	private void mcqrunbutton(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq )
		throws IOException
		{
		String can=canrun( req, mcq_session, mcq );
		
		if ( can!=null )
			out.println( can );
		else	
			out.println( "<INPUT NAME=run TYPE=BUTTON ONCLICK=\"launchtest()\" VALUE=\"Run Test Now\">" );
		
		return;
		}

	
	private void mcqmodify(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq )
		throws IOException, BuildingServerException
		{
		String once, mark_witheld, dead;
		java.util.Date deaddate;
		java.sql.Timestamp sqldeaddate=null;

		once=req.getParameter( "once_only" );
		mark_witheld=req.getParameter( "mark_witheld" );
		dead=req.getParameter( "deadline" );
		if ( dead!=null )
			{
			dead=dead.trim();
			if ( dead.length()>0 )
				{
				deaddate=DateParser.parse( dead );
				if ( deaddate==null )
					{
					out.println( "<HR>The deadline date couldn't be understood.  Please backtrack and review the form.<HR>" );
					return;
					}
				else
					sqldeaddate=new java.sql.Timestamp( deaddate.getTime() );
				}
			}

		mcq_session.setOnceOnly( once!=null && once.length()>0 );
		mcq_session.setMarkWitheld( mark_witheld!=null && mark_witheld.length()>0 );
		mcq_session.setDeadline( sqldeaddate );

		out.println( "<CENTER><P>The requested changes were stored.</P></CENTER>" );
		}

	
	private void resultToHTML(  PrintWriter out, McqResult result, User user, boolean format_table, boolean detail )
		throws IOException
		{
		if ( format_table )
			out.print( "<TR><TD><A HREF=\"" + result.getMcqResultId() + "/bs_template_mcqdebrief.html\">" );
			
		
			
		//out.print( result.name==null?"<I>unidentified</I>":McqResult.name );
		if ( user == null || user.getName() == null )
			out.print( "<I>name unknown</I>" );
		else
			out.print( user.getName() );
		
		
		out.print( format_table?"</A></TD><TD>":"\t" );
		out.print( result.getUserId().toString() );
		out.print( format_table?"</TD><TD>":"\t" );
		out.print( " " + result.mark() + "%");
		if ( detail )
			{
			out.print( format_table?"</TD><TD>":"\t" );
			out.print( result.getRight() );
			out.print( format_table?"</TD><TD>":"\t" );
			out.print( result.getWrong() );
			out.print( format_table?"</TD><TD>":"\t" );
			out.print( result.getDeductions() );
			out.print( format_table?"</TD><TD>":"\t" );
			Facility.outputDate( out, result.getWhenDone(), 2 );
			}
		out.println( format_table?"</TD></TR>":"" );
		}
		
	private void mcqanalysis(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq )
		throws IOException, BuildingServerException
		{
		McqAnalysis mcq_analysis=null;
		
		int i=0, j, q, n=0;
		double qmark, mark, sum=0.0, sumsq=0.0;
		boolean filter1, filter2, filter3, itemanalysis, questionanalysis,
			format_print, format_table, format_detail, alt_correlation;
		StringBuffer sql = new StringBuffer();
		String sid, before, after, format, analysis, previd;
		java.util.Date beforedate, afterdate;
		java.sql.Timestamp sqlbeforedate=null, sqlafterdate=null;
		Calendar calendar = Calendar.getInstance();
		Vector sidlist=null;
		Vector altmarklist=null;
		Group allstudents=null;
		PrimaryKey student;
		Vector results;
		McqResult result;

		try
			{
			allstudents = Group.findGroup( "name = 'allstudents'" );
			}
		catch ( BuildingServerException bsex )
			{
			allstudents=null;
			}

		filter1=req.getParameter( "filter1" )!=null;

		if ( filter1 && allstudents==null )
			{
			out.println( "Unable to find the 'allstudents' group." );
			return;
			}
		
		filter2=req.getParameter( "filter2" )!=null;
		filter3=req.getParameter( "filter3" )!=null;
		
		sid=req.getParameter( "sid" );
		before=req.getParameter( "before" );
		after=req.getParameter( "after" );
		format=req.getParameter( "format" );
		format_print = format!=null && !format.equals("a");
		format_table  = format==null || format.equals( "b" ) || format.equals( "d" );
		format_detail = format==null || format.equals( "d" ) || format.equals( "e" );
		analysis=req.getParameter( "analysis" );
		questionanalysis= ( analysis!=null && (analysis.equals( "c" ) || analysis.equals( "d" )) );
		itemanalysis= ( analysis!=null && analysis.equals( "d" ) );
		alt_correlation=req.getParameter( "alt_correlation" )!=null;
		
		if ( before!=null )
			{
			before=before.trim();
			if ( before.length()>0 )
				{
				beforedate=DateParser.parse( before );
				if ( beforedate==null )
					{
					out.println( "<HR>The first date couldn't be understood.  Please backtrack and review the form.<HR>" );
					return;
					}
				else
					sqlbeforedate=new java.sql.Timestamp( beforedate.getTime() );
			    calendar.setTime( beforedate );
			    if ( calendar.get( Calendar.YEAR ) < 1000 )
					out.println( "<HR>The first date had insufficient digits in the year.  Please use the full date.<HR>" );
			        
				}
			}
		if ( after!=null )
			{
			after=after.trim();
			if ( after.length()>0 )
				{
				afterdate=DateParser.parse( after );
				if ( afterdate==null )
					{
					out.println( "<HR>The second date couldn't be understood.  Please backtrack and review the form.<HR>" );
					return;
					}
				else
					sqlafterdate=new java.sql.Timestamp( afterdate.getTime() );
			    calendar.setTime( afterdate );
			    if ( calendar.get( Calendar.YEAR ) < 1000 )
					out.println( "<HR>The second date had insufficient digits in the year.  Please use the full date.<HR>" );
				}
			}


		if ( sid!=null )
			{
			String tmp, straltmark;
			Double altmark;
			StringTokenizer tok=new StringTokenizer( sid, "\n" );
			StringTokenizer linetok;
			sidlist=new Vector();
			altmarklist=new Vector();
			Logger.getLogger("org.bodington").fine( "Check sids" );
			for ( int line=1; tok.hasMoreTokens(); line++ )
				{
				tmp=tok.nextToken();
				tmp=tmp.trim();
				if ( tmp.length()==0 ) continue;

				linetok = new StringTokenizer( tmp );
				if ( linetok.hasMoreTokens() )
					{
					try
						{
						student=new PrimaryKey( Integer.parseInt( linetok.nextToken() ) );
						}
					catch ( NumberFormatException nfex )
						{
						out.println( "<P><I>Problem: invalid ID in form input (line " + line + ").</I></P>" );
						return;
						}

					sidlist.addElement( student );
					altmark=null;
					if ( alt_correlation )
						{
						straltmark=linetok.nextToken();
						try 
							{altmark = new Double( straltmark );}
						catch ( NumberFormatException nfex )
							{
							out.println( "<P><I>Problem: invalid number in form input (line " + line + ").</I></P>" );
							return;
							}
						altmarklist.addElement( altmark );
						}
					}
				}
			if ( sidlist.size()==0 )
				{
				sidlist=null;
				altmarklist = null;
				}
			}



		sql.append( "((resource_id = " );
		sql.append( req.getResource().getResourceId().toString() );
		sql.append( ") " );
		if ( filter1 )
			{
			sql.append( " AND user_id IN (SELECT user_id FROM users WHERE " );
			sql.append( allstudents.whereMembers() );
			sql.append( " ) " );
			}
		if ( filter2 )
			sql.append( " AND ((wrong + correct) > 0) " );
		if ( sidlist!=null )
			{
			sql.append( " AND (user_id IN ( " );
			for( i=0; i< sidlist.size(); i++ )
				{
				if ( i>0 )
					sql.append( ", " );
				sql.append( sidlist.elementAt( i ).toString() );
				}
			sql.append( " )) " );
			}
		if ( sqlbeforedate!=null )
			{
			sql.append( " AND (when_done > {ts '" );
			sql.append( sqlbeforedate.toString() );
			sql.append( "'}) " );
			}
		if ( sqlafterdate!=null )
			{
			sql.append( " AND (when_done < {ts '" );
			sql.append( sqlafterdate.toString() );
			sql.append( "'}) " );
			}
		sql.append( " ) " );

		if ( analysis !=null && analysis.length()>0 )
			{
			if ( itemanalysis )
				mcq_analysis = new McqAnalysis( McqAnalysis.SUMMARY_QUESTION_AND_ITEM, true );
			else
				{
				if ( questionanalysis )
					mcq_analysis = new McqAnalysis( McqAnalysis.SUMMARY_AND_QUESTION, true );
				else
					mcq_analysis = new McqAnalysis( McqAnalysis.SUMMARY, true );
				}
			}
			
		results = mcq_session.analyse( sql.toString(), filter3, sidlist, altmarklist, mcq_analysis );
	
		if ( format_print )
			{
			Hashtable users = mcq_session.getMcqUsers();
			User user;
			for ( i=0; i< results.size(); i++ )
				{
				result = (McqResult)results.elementAt( i );
				user = (User)users.get( result.getUserId() );
				if ( i==0 )
					{
					if ( format_table )
						{
						out.print( "<TABLE BORDER><TR><TD>Name</TD>" +
									 "<TD>Bodington ID</TD><TD>Mark</TD>" );
						if ( format_detail )
							out.print( "<TD>Right</TD><TD>Wrong</TD>" +
										 "<TD>Deductions</TD><TD>Date</TD>" );
						out.println( "</TR>" );
						}
					else
						{
						out.println( "<FORM><SPAN CLASS=bs-textarea><TEXTAREA COLS=50 ROWS=10>" );
						out.print( "Name\tBodingtonID\tMark" );
						if ( format_detail )
							out.print( "\tRight\tWrong\tDeductions\tDate" );
						out.print( "\n" );
						}
					}
				resultToHTML( out, result, user, format_table, format_detail );
				}
			if ( i==0 )
				out.println( "<H4>There were no records found.</H4>" );
			else
				out.println( format_table?"</TABLE>":"</TEXTAREA></SPAN></FORM>" );
			}

		if ( mcq_analysis !=null )
			{
			mcq_analysis.dumpAnalysis( new PrintWriter( out ) );
			out.flush();
			
			if ( mcq_analysis.getDepth() >= McqAnalysis.SUMMARY_AND_QUESTION )
				{
				Vector questions = mcq_session.getMcqQuestionsInOrder();
				McqQuestion question;
				McqQuestionAnalysis q_analysis;
				for ( i=0; i<questions.size(); i++ )
					{
					question = (McqQuestion)questions.elementAt( i );
					q_analysis = mcq_analysis.getMcqQuestionAnalysis( question.getMcqQuestionId() );
					out.println( "<TABLE BORDER><TR><TD COLSPAN=3>Question " );
					out.println( (i+1) );
					out.println( "</TD></TR>" );
					this.questionToHTML( question, q_analysis, out, "analysis" );
					out.println( "</TABLE><P></P><P></P>" );
					}
				}
			
			}
		
		return;
		}

	
	private void mcqfield(  Request req, PrintWriter out, McqPaperSession mcq_session, McqPaper mcq, String name )
		throws IOException
		{
		if ( !BuildingContext.getContext().checkPermission( "manage" ) )
			{
			out.println( "<PRE>You don't have permission to manage this mcq test.</PRE>\n" );
			return;
			}

		if ( name.equalsIgnoreCase( "once_only" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=once_only " );
			if ( mcq.isOnceOnly() )
				out.print( "CHECKED" );
			out.println( ">" );
			return;
			}

		if ( name.equalsIgnoreCase( "mark_witheld" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=mark_witheld " );
			if ( mcq.isMarkWitheld() )
				out.print( "CHECKED" );
			out.println( ">" );
			return;
			}

		if ( name.equalsIgnoreCase( "deadline" ) )
			{
			out.print( "<INPUT NAME=deadline VALUE=\"" );
			if ( mcq.getDeadline()!=null )
				Facility.outputDate( out, mcq.getDeadline(), 1 );
			out.println( "\">" );
			return;
			}

		return;
		}
	
	private boolean onceonlydone( Request req, McqPaperSession mcq_session )
		{
		try
			{
			Vector results = mcq_session.getResultsOfUser();
		
			return results.size() > 1;
			}
		catch ( Exception ex )
			{
			return false;
			}
		}

	private void exportXml(  Request req, PrintWriter out, McqPaperSession mcq_session )
		throws IOException
		{
		try
		    {
		    mcq_session.exportXml( true, true );
    		out.println( "<P>The XML file has been created and is available for <A HREF=qtiexp.xml>download</A> now.</P>" );
    		out.println( "<P>(Left click to view in browser, right click to save to disk.) " );
		    }
		catch ( BuildingServerException bsex )
		    {
    		out.println( "<PRE>" );
    		out.println( bsex.getMessage() );
    		out.println( "</PRE>" );
		    }
		
		
		return;
		}

	private void importq(  Request req, PrintWriter out, McqPaperSession mcq_session )
		throws IOException
		{
		String file = req.getParameter( "file" );
		if ( file == null || file.length() == 0 )
		    {
		    out.println( "<P>Import failed: no file was supplied.</P>" );
		    return;
		    }

		try
		    {
		    mcq_session.importXmlQuestions( file );
    		out.println( "DONE" );
		    }
		catch ( BuildingServerException bsex )
		    {
    		out.println( "<PRE>" );
    		out.println( bsex.getMessage() );
    		out.println( "</PRE>" );
		    }
		
		
		return;
		}

	}


