/* ======================================================================
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 org.apache.log4j.Logger;

import org.bodington.servlet.*;

import java.io.*;
import java.sql.*;
import java.util.*;
import java.rmi.RemoteException;
import javax.servlet.*;
import javax.servlet.http.*;

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

/**
 * Class that encapsulates the web-based user interface to a questionnaire. The
 * session type associated with this class is
 * {@link org.bodington.assessment.QuestionnaireSession}. The type of resource
 * associated with this class is {@link org.bodington.assessment.Questionnaire}.
 * @see org.bodington.assessment.QuestionnaireSession
 * @see org.bodington.assessment.Questionnaire
 * @author Jon Maber
 * @author Alexis O'Connor
 */
public class QuestionnaireFacility extends org.bodington.servlet.facilities.Facility
	{
    
    private static Logger log = Logger.getLogger(QuestionnaireFacility.class);
	
	static final int EVENT_START        =0;
	static final int EVENT_RECORD  =1;


	/**
     * Facility subclasses can refuse copying of resources.
     * @return True if resources that use this facility can be copied.
     */
    public boolean canCopy( Resource resource )
    {
        return true;
    }
    
	public Resource newResource()
		{
		return new Questionnaire();
		}
	
	
	public boolean initResource( Request breq, Resource new_resource )
		throws Exception
		{
		Questionnaire questionnaire;

		if ( !(new_resource instanceof Questionnaire) )
			throw new Exception( "Technical problem: An incorrect type of resource was created." );
		
		questionnaire = (Questionnaire)new_resource;
		questionnaire.setFlags( 0 );
		questionnaire.setRecordOnce( 
            breq.getParameter( "record_once" ) != null );
		return true;
		}


    /**
     * Use the properties of an existing Resource to initialise a newly created Resource. <p>
     * <i>(WebLearn modification (method added): 02/12/2003 Colin Tatham)</i>
     * @param original_resource The resource with properties to copy.
     * @param new_resource The resource to initialise.
     * @return True if initialisation was OK.
     * @exception BuildingServerException Thrown if there is any problem initialising the resource.
     *
     */
	public boolean initResource( Resource original_resource, Resource new_resource )
		throws BuildingServerException
		{
		 if ( !(new_resource instanceof Questionnaire) )
		   throw new BuildingServerException( "Error: The wrong type of resource was created." );

		 Questionnaire original = (Questionnaire)original_resource;
		 Questionnaire new_q = (Questionnaire)new_resource;

		 new_q.setQuestionnaireFlags(original.getQuestionnaireFlags());

		 return true;
	}


    /**
     * Copy the content of an existing Resource to another Resource. 
     * Content may be authored content or user data, and varies according to the resource type:<br />
     * Questionnaire<br />
     * authored : questions, statements<br />
     * userdata : answers (Not implemented.) <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.
     * @param breq The building HTTP request, which is used to determine which type of content to copy.
     * @exception BuildingServerException Thrown if there is any problem copying the resource content.
     *
     */
	public void copyContent( Resource original_resource, Resource new_resource, Request breq )
		 throws BuildingServerException
	        {
//	  authored : questions, statements
//	  userdata : answers? /** @todo  */

	         BuildingSession session = BuildingSessionManagerImpl.getSession( original_resource );
		 if ( !(session instanceof QuestionnaireSession) )
		   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.
     * @exception RemoteException Thrown if there is any problem copying the resource content.
     *
     */
	private void copyAuthoredContent( Resource original_resource, Resource new_resource )
		throws BuildingServerException, RemoteException
		{
		 QuestionnaireSession session;
		 QuestionnaireQuestion orig_question, new_question;

		 session = (QuestionnaireSession)BuildingSessionManagerImpl.getSession( original_resource );
		 List questions = ((QuestionnaireSession)session).getQuestionnaireQuestionsInOrder();

		 for (int i=0; i<questions.size(); i++ )
		 {
		   orig_question = (QuestionnaireQuestion)questions.get( i );
		   new_question = new QuestionnaireQuestion();
		   new_question.setQuestionnaireId( new_resource.getResourceId() );
		   /** @todo report possible bug: */
		   new_question.setStandardQuestion( true ); // sets all statements to empty string
		   new_question.setStandardQuestion( orig_question.isStandardQuestion() );
		   new_question.setCanComment( orig_question.canComment() );
		   new_question.setIntroduction( orig_question.getIntroduction() );
		   new_question.setOrdinal( orig_question.getOrdinal() );
		   new_question.setFlags( orig_question.getFlags() );
		   for ( int n=0; n<orig_question.getStatementCount(); n++ )
		     new_question.setStatement( n, orig_question.getStatement(n) );

		   new_question.save();
		 }
		}


	public boolean createCheck( Request breq, PrintWriter out )
		{
		return true;
		}
	

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

		try
			{
			BuildingSession session;
			QuestionnaireSession q_session;

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

		

			if ( command.equalsIgnoreCase( "qpaper" ) ||
		    	command.equalsIgnoreCase( "qsummary" ) ||
		    	command.equalsIgnoreCase( "qcomments" ) ||
		    	command.equalsIgnoreCase( "qrecord" ) ||
		    	command.equalsIgnoreCase( "qitemedit" ) ||
		    	command.equalsIgnoreCase( "qeditqconfirm" ) ||
		    	command.equalsIgnoreCase( "qdeleteq" ) ||
		    	command.equalsIgnoreCase( "qrunbutton" ) ||
		    	command.equalsIgnoreCase( "qnewq" ) ||
		    	command.equalsIgnoreCase( "export" ) ||
		    	command.equalsIgnoreCase( "import" )	)
				{

				if ( out==null )
					return;

				if ( command.equalsIgnoreCase( "qpaper" ) )
					qprintpaper( req, out, q_session, insertname );
				if ( command.equalsIgnoreCase( "qsummary" ) )
					qprintsummary( req, out, q_session );
				if ( command.equalsIgnoreCase( "qcomments" ) )
					qprintqcomments( req, out, q_session );

				if ( command.equalsIgnoreCase( "qrecord" ) )
					qrecord( req, out, q_session, insertname );

				if ( command.equalsIgnoreCase( "qitemedit" ) )
					qitemedit( req, out, q_session, insertname );

				if ( command.equalsIgnoreCase( "qrunbutton" ) )
					qrunbutton( req, out, q_session );
				
				if ( command.equalsIgnoreCase( "export" ) )
					export( req, out, q_session );
				
				if ( command.equalsIgnoreCase( "import" ) )
					importq( req, out, q_session );
				
				return;
				}
			}		
		catch ( BuildingServerException bsex )
			{
			logException( out, "QuestionnaireFacility", "insert", 
			    "Technical error processing page.",
			    bsex );
			return;
			}
		
		
		super.insert( req, out, command, insertname );
		}



	
	private void questionToHTML( QuestionnaireQuestion question, QuestionnaireResponse response, QuestionnaireAnalysis q_analysis, PrintWriter out, String style )
		throws IOException, BuildingServerException
		{
		char letter;
		int i, j, n, total, max;
		int[] counts;
		double scale;
		boolean answer[]=new boolean[10];
		String statement[]=new String[10];
		Vector comments;
		
		out.println( "<TR><TD COLSPAN=3>" );
		if ( style.equalsIgnoreCase( "run" ) )
			{
			out.println( "<INPUT TYPE=HIDDEN VALUE=TRUE NAME=Q" + question.getQuestionnaireQuestionId() + ">" );
			}
		out.println( question.getIntroduction() );
		out.println( "</TD></TR>" );

        int[] indices = question.getValidStatementIndices();
		n = indices.length;
		counts = new int[n];
		total=0;
		max=0;
		for ( i=0; i<indices.length; 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>" );
				out.print( "<INPUT TYPE=RADIO " );
				out.print( "NAME=QR" );
				out.print( question.getQuestionnaireQuestionId().toString() );
				out.print( " VALUE=" + letter );
				if ( response != null && response.getItem() == i )
					out.print( " CHECKED>" );
				else
					out.print( ">" );
				out.println( "</TD><TD VALIGN=TOP WIDTH=1000>" );
				out.println( question.getStatement( indices[i] ) );
				out.println( "</TD></TR>" );
				}
			else 
				{
				out.println( "<TR><TD VALIGN=TOP WIDTH=20><B>" );
				out.println( letter );
				out.println( "</B></TD>" );
				if ( q_analysis != null )
					{
					counts[i] = q_analysis.getItemCounts( question.getQuestionnaireQuestionId(), i );
					out.print( "<TD WIDTH=20>" );
					out.print( counts[i] );
					out.print( "</TD>" );
					total+=counts[i];
					if ( counts[i]>max ) max=counts[i];
					}
				out.println( "<TD VALIGN=TOP WIDTH=1000>" );
                out.println( question.getStatement( indices[i] ) );
				out.println( "</TD></TR>" );
				if ( q_analysis != null )
					{
					comments = q_analysis.getComments( question.getQuestionnaireQuestionId(), i );
					for ( j=0; j< comments.size(); j++ )
						{
						out.print( "<TR><TD COLSPAN=2></TD><TD><BLOCKQUOTE>" );
						out.print( (String)comments.elementAt( j ) );
						out.println( "</BLOCKQUOTE></TD></TR>" );
						}
					}
				}
			}
        
        // Indicate whether the question allows for a comment:
        if ( question.canComment() && 
            (style.equalsIgnoreCase( "edit" ) ||
            (style.equalsIgnoreCase( "view" ) && 
                !response.getQuestionnaireResult().isRecorded())))                
            {
            out.println( "<TR><TD COLSPAN=3 ALIGN=CENTER><I>"
                + "~ This question allows for a free text comment ~"
                + "</I></TD></TR>" );
            }

		if ( q_analysis != null )
			{
			if ( question.isStandardQuestion() )
				{
				if ( (counts[0]+counts[1])>max ) max=counts[0]+counts[1];
				if ( (counts[3]+counts[4])>max ) max=counts[3]+counts[4];
				}
				
			if ( max<10 ) scale=25.0;
			else scale=250.0/max;
			out.println( "<TR><TD COLSPAN=3>" );
			if ( question.isStandardQuestion() )
				{
				out.println( "<TABLE BORDER><TR><TD WIDTH=75>Agree</TD>" );
				if ( (int)(scale*counts[1]) > 0 )
					out.println( "<TD BGCOLOR=GREEN WIDTH=" + (int)(scale*counts[1]) + ">.</TD>" );
				if ( (int)(scale*counts[0]) > 0 )
					out.println( "<TD BGCOLOR=RED WIDTH=" + (int)(scale*counts[0]) + ">.</TD>" );
				out.println( "</TR></TABLE>" );
				out.println( "<TABLE BORDER><TR><TD WIDTH=75>Neutral</TD>" );
				if ( (int)(scale*counts[2]) > 0 )
					out.println( "<TD BGCOLOR=GREEN WIDTH=" + (int)(scale*counts[2]) + ">.</TD>" );
				out.println( "</TR></TABLE>" );
				out.println( "<TABLE BORDER><TR><TD WIDTH=75>Disagree</TD>" );
				if ( (int)(scale*counts[3]) > 0 )
					out.println( "<TD BGCOLOR=GREEN WIDTH=" + (int)(scale*counts[3]) + ">.</TD>" );
				if ( (int)(scale*counts[4]) > 0 )
					out.println( "<TD BGCOLOR=RED WIDTH=" + (int)(scale*counts[4]) + ">.</TD>" );
				out.println( "</TR></TABLE>" );
				}
			else
				{
				for ( i=0; i<n; i++ )
					{
					letter=(char)('A'+i);
					out.println( "<TABLE BORDER><TR><TD WIDTH=30><B>" + letter + "</B></TD>" );
					if ( (int)(scale*counts[i]) > 0 )
						out.println( "<TD BGCOLOR=GREEN WIDTH=" + (int)(scale*counts[i]) + ">.</TD>" );
					out.println( "</TR></TABLE>" );
					}
				}
			out.println( "</TD></TR>" );
			
			comments = q_analysis.getComments( question.getQuestionnaireQuestionId(), 10 );
			for ( j=0; j< comments.size(); j++ )
				{
				if ( n>0 && j==0 )
					out.print( "<TR><TD COLSPAN=3>Comments from people who didn't select an option.</TD></TR>" );

				out.print( "<TR><TD COLSPAN=2></TD><TD><BLOCKQUOTE>" );
				out.print( (String)comments.elementAt( j ) );
				out.println( "</BLOCKQUOTE></TD></TR>" );
				}
			}

		if ( style.equalsIgnoreCase( "run" ) )
			{
			if ( n>0 )
				{
				out.println( "<TR><TD VALIGN=TOP WIDTH=20></TD><TD VALIGN=TOP WIDTH=20>" );
				out.print( "<INPUT TYPE=RADIO " );
				out.println( "NAME=QR" + question.getQuestionnaireQuestionId() + " VALUE=X>" );
				out.println( "</TD><TD VALIGN=TOP WIDTH=1000>Cancel selection.</TD></TR>" );
				}
			if ( question.canComment() )
				{
				out.print( "<TR>" );
				out.print( "<TD COLSPAN=3><SPAN CLASS=bs-textarea><TEXTAREA ROWS=6 COLS=40 NAME=QC" );
				out.print( question.getQuestionnaireQuestionId().toString() );
				out.print( ">" );
				if ( response != null && response.getComment() != null )
					out.print( response.getComment() );
				out.println( "</TEXTAREA></SPAN></TD></TR>" );
				}
			}
		
		if ( style.equalsIgnoreCase( "view" ) && response!=null )
			{
			out.print( "<TR><TD COLSPAN=3>" );
			if ( n>0 )
				{
				if ( response.getItem()<10 )
					out.println( "You selected " + (char)('A' + response.getItem()) + "." );
				else
					out.println( "You haven't selected any item for this question." );
				}
			if ( question.canComment() && response.getComment() != null 
                && response.getComment().length()>0 )
                {
				out.println( "You entered this comment;<BLOCKQUOTE>" );
				out.print( response.getComment() );
				out.println( "</BLOCKQUOTE>" );
				}
			}

		}


	/**
     * Print out the questionnaire paper. This ouputs the contents of the
     * questionnaire to the <code>out</code> parameter. The rendering can be
     * altered via the <code>style</code> parameter, which corresponds to the
     * activity that the user is currently doing. The <code>style</code>
     * parameter understands the following values:
     * <ul>
     * <li><code>run</code> - a user is recording his or her responses.
     * <li><code>view</code> - a user is viewing the questions.
     * <li><code>edit</code> - a user is editing quesions within the
     * questionnaire.
     * <li><code>analysis</code> - a user is analysing recorded responses to
     * the questionnaire.
     * </ul>
     * @param req the request object.
     * @param out the writer object.
     * @param q_session the questionnaire session object.
     * @param style the style of the presentation required (see above).
     * @throws IOException
     * @throws BuildingServerException
     */
	private void qprintpaper( Request req, PrintWriter out, QuestionnaireSession q_session, String style )
		throws IOException, BuildingServerException
		{
		int i;
		
		List questions_in_order;
        Questionnaire questionnaire = null;
		QuestionnaireQuestion question;
		QuestionnaireResult result=null;
		QuestionnaireResponse response=null;
		QuestionnaireAnalysis analysis=null;
		Map responses = null;
		User user;

        questionnaire = q_session.getQuestionnaire();
		user = (User)BuildingContext.getContext().getUser();
		questions_in_order = q_session.getQuestionnaireQuestionsInOrder();
			
		if ( style.equalsIgnoreCase( "analysis" ) )
			{
			analysis =  new QuestionnaireAnalysis();
			q_session.analyse( null, analysis );
			}
        
        if ( style.equalsIgnoreCase( "run" ) || style.equalsIgnoreCase( "view" ) )
            {
            result = q_session.getQuestionnaireResult();
            responses=result.getQuestionnaireResponses();
            }
        
        // Special handling for single session questionnaires:
        if ( questionnaire.isRecordOnce() ) 
            {
            // 'Flip' the style if a user has submitted a single session paper.
            if ( style.equalsIgnoreCase( "run" ) && result.isRecorded() )
                {
                style = "view";
                }
            // Render some useful notices depending on style.  
            if (style.equalsIgnoreCase( "edit" ))
                {
                out.println( "<h4><i>NOTE: this questionnaire must be completed "
                    + "by respondants within a single session.</i></h4>" );
                }
            else if (style.equalsIgnoreCase( "run" ))
                {
                out.println( "<h4><i>NOTE: this questionnaire must be completed "
                    + "within a single session. In other words, you can only "
                    + "click the 'Record Answers Now' button once.</i></h4>" );
                }
            else if (style.equalsIgnoreCase( "view" ))
                {
                out.println( "<h4><i>NOTE: this questionnaire must be completed "
                    + "within a single session and you have already submitted " 
                    + "your response. However, you are able to review the "
                    + "answers that you gave.</i></h4>" );
                }
            }
			
        if ( style.equalsIgnoreCase( "run" ) )
            {
            out.println( "<FORM METHOD=POST ACTION=bs_template_qrecord.html>" );
            }
        
		for ( i=0; i<questions_in_order.size(); i++ )
			{
			question=(QuestionnaireQuestion)questions_in_order.get( i );
			response = (responses != null) 
                ? (QuestionnaireResponse)responses.get( 
                    question.getQuestionnaireQuestionId() ) : null;
				
            String identifier = "Q" + question.getQuestionnaireQuestionId();
			if ( style.equalsIgnoreCase( "edit" ) )
				{
				out.println( "<P></P><TABLE BORDER id=" + identifier + "><TR><TD COLSPAN=2><H4>Question " + 
					(i+1) + " (" + question.getOrdinal() + ")</H4></TD>" );
				out.print( "<TD ALIGN=CENTER><FORM METHOD=POST ACTION=\"" );
				out.print( req.getContextPath() );
				out.print( req.getServletPath() );
				out.print( questionnaire.getFullName()  );
				out.print( question.getQuestionnaireQuestionId() + "/bs_template_qeditq.html\">" );
				out.println( "<INPUT TYPE=SUBMIT VALUE=\"Edit This Question\"></FORM></TD></TR>" );
				}
			else
				{
				out.println( "<HR><TABLE id=" + identifier + "><TR><TD COLSPAN=3><H4>Question " + (i+1) + "</H4></TD></TR>" );
				}
				
			questionToHTML( question, response, analysis, out, style );
				
			out.println( "</TABLE>" );
			}

		if ( i==0 )
			out.println( "<P>There are currently no questions in this questionnaire.</P>" );
		else
			{
			if ( style.equalsIgnoreCase( "run" ) )
				{
				out.println( "<P><INPUT TYPE=SUBMIT VALUE=\"Record Answers Now\"></FORM></P>" );
        		Event event = new OpinionEvent( 
        						OpinionEvent.EVENT_START,
        						req.getResource().getResourceId(), 
        						user.getUserId(), 
    	    					null );
	        	event.save();
				}
			}
		}

	private void qitemedit(  Request req, PrintWriter out, QuestionnaireSession q_session, String insertname )
		throws IOException, BuildingServerException
		{
		String id, field, statement;
		PrimaryKey qid;
		Map questions;
		QuestionnaireQuestion 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 = q_session.getQuestionnaireQuestions();
		q=(QuestionnaireQuestion)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( "statements" ) )
			{
			out.println( "<INPUT NAME=statements VALUE=\"" +q.getStatementCount() + "\">" );
			return;
			}
		if ( insertname.equalsIgnoreCase( "introduction" ) )
			{
			out.print( "<SPAN CLASS=bs-textarea><TEXTAREA NAME=introduction COLS=45 ROWS=8>" );
			out.print( q.getIntroduction() );
			out.println( "</TEXTAREA></SPAN>" );
			return;
			}

		for ( int i=0; i<10; 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( "is_standard" ) )
			{
			out.print( "<INPUT TYPE=CHECKBOX NAME=is_standard VALUE=true ONCLICK=standardise() " );
			out.println( q.isStandardQuestion()?"CHECKED>":">" );
			return;
			}

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

	
	/**
     * Add a new question. Upon success, this method returns a number which is
     * the string form of the ID of the question that has just been added (see
     * {@link QuestionnaireQuestion#getQuestionnaireQuestionId()}). Upon
     * failure, the string returned is in fact an error message.
     * <p>
     * <i>NOTE: This method is intended to be called from templates.</i>
     * @param request the request object.
     * @return the ID of the newly created question, or an error message.
     * @see QuestionnaireQuestion#getQuestionnaireQuestionId()
     * @see #isNumber(String)
     */
    public String addQuestion(Request request)
        {
        // TODO: this clearly needs better factoring out!
        try
            {
            QuestionnaireSession session 
                = (QuestionnaireSession)BuildingSessionManagerImpl.getSession( 
                    request.getResource() );
            QuestionnaireQuestion q = session.createQuestion();
            return q.getQuestionnaireQuestionId().toString();
            }
        catch ( BuildingServerException e )
            {
            return e.getMessage();
            }
        catch ( RemoteException e )
            {
            return e.getMessage();
            }
        }
    
    /**
     * Edit a question. Upon success, this method returns a number which is the
     * string form of the ID of the question that has just been edited (see
     * {@link QuestionnaireQuestion#getQuestionnaireQuestionId()}). Upon
     * failure, the string returned is in fact an error message.
     * <p>
     * <i>NOTE: This method is intended to be called from templates.</i>
     * @param request the request object.
     * @return the ID of the newly created question, or an error message.
     * @see QuestionnaireQuestion#getQuestionnaireQuestionId()
     * @see #isNumber(String)
     */
    public String editQuestion( Request request )
        {
        int n;
        String id, param;
        PrimaryKey questionID;
        Map questions;
        QuestionnaireQuestion q;
        QuestionnaireSession session;

        try
            {
            session 
                = (QuestionnaireSession)BuildingSessionManagerImpl.getSession( 
                    request.getResource() );
            }
        catch ( BuildingServerException e )
            {
            return e.getMessage();
            }

        if ( !BuildingContext.getContext().checkPermission( "edit" ) )
            return "You do not have permission to edit questions in this "
                + "location of the building.";

        // must have an id in the parameter part of the URL
        if ( request.getTemplateParameterCount() < 1 )
            return "Can not display edit field";

        id = (String)request.getTemplateParameter( 0 );
        questionID = new PrimaryKey( Integer.parseInt( id ) );
        try
            {
            questions = session.getQuestionnaireQuestions();
            q = (QuestionnaireQuestion)questions.get( questionID );

            if ( q == null ) 
                return "Can not display edit field";

            param = request.getParameter( "ordinal" );
            try
                {
                q.setOrdinal( Integer.parseInt( param ) );
                }
            catch ( NumberFormatException ex )
                {
                return "Invalid number in ordinal field.";
                }

            param = request.getParameter( "introduction" );
            if ( param == null ) param = "";
            session.changeQuestionText( q.getQuestionnaireQuestionId(), 
                q.getIntroductionBigStringId(), param );
            for ( int i = 0; i < 10; i++ )
                {
                param = request.getParameter( "statement_" + (char)('a' + i) );
                if ( param == null ) param = "";
                session.changeQuestionText( q.getQuestionnaireQuestionId(), 
                    q.getStatementBigStringId( i ), param );
                }

            param = request.getParameter( "is_standard" );
            q.setStandardQuestion( param != null && param.length() > 0 );

            param = request.getParameter( "can_comment" );
            q.setCanComment( param != null && param.length() > 0 );
            session.changeQuestion( q.getQuestionnaireQuestionId(), q );
            }
        catch ( RemoteException e )
            {
            return "There was a problem with saving the edited question.";
            }
        catch ( BuildingServerException e )
            {
            return "There was a problem with saving the edited question.";
            }

        return q.getQuestionnaireQuestionId().toString();
        }
    
    /**
     * Delete a question. Upon success, this method attempts to return a number
     * which is the string form of the ID of a question that is adjacent to the
     * one that has just been deleted (see
     * {@link QuestionnaireQuestion#getQuestionnaireQuestionId()}). If there is
     * a previous question, that is returned. If there is no previous question,
     * but there is a following question, that is the one returned. If no other
     * question is found, i.e. the deleted question was the only one left, the
     * value <code>0</code> is returned. Upon failure, the string returned is
     * in fact an error message.
     * <p>
     * <i>NOTE: This method is intended to be called from templates.</i>
     * @param request the request object.
     * @return the ID of the deleted question, or an error message.
     * @see QuestionnaireQuestion#getQuestionnaireQuestionId()
     * @see #isNumber(String)
     */
    public String deleteQuestion( Request request )
        {
        int n;
        String id, param, notparam;
        PrimaryKey qid;
        QuestionnaireSession session;

        if ( !BuildingContext.getContext().checkPermission( "edit" ) )
            return "You do not have permission to delete (edit) questions in "
                + "this location of the building.";

        try
            {
            session 
                = (QuestionnaireSession)BuildingSessionManagerImpl.getSession( 
                    request.getResource() );
            }
        catch ( BuildingServerException e )
            {
            return e.getMessage();
            }

        // must have an id in the parameter part of the URL
        if ( request.getTemplateParameterCount() < 1 )
            return "Can not delete question.";

        id = (String)request.getTemplateParameter( 0 );
        qid = new PrimaryKey( Integer.parseInt( id ) );

        param = request.getParameter( "confirm" );
        if ( param == null ) param = "";
        notparam = request.getParameter( "notconfirm" );
        if ( notparam == null ) notparam = "";
        if ( param.length() < 1 || notparam.length() > 0 )
            return "Deleting was not confirmed - question not deleted.";
        
        // Try and find the ID of an adjacent question to the deletee:
        String questionID = String.valueOf( 0 );
        try
            {
            // NOTE: this is a UI convenience and *not* mission-critical!
            List questions = session.getQuestionnaireQuestionIds();
            int index = questions.indexOf( qid );
            if ( (index - 1) > 0 ) // does 'previous' exist?
                questionID = 
                    ((PrimaryKey)questions.get( index - 1 )).toString();
            else if ( (index + 1) < questions.size() ) // does 'next' exist?
                questionID = 
                    ((PrimaryKey)questions.get( index - 1 )).toString();
            }
        catch ( RemoteException e )
            {
            }
        catch ( BuildingServerException e )
            {
            }

        // Actually remove the intended deletee:
        try
            {
            session.removeQuestion( qid );
            }
        catch ( RemoteException e )
            {
            return e.getMessage();
            }
        catch ( BuildingServerException e )
            {
            return e.getMessage();
            }
        
        return questionID;
        }
    
    /**
     * Indicates whether the string is a number. Specifically, this method
     * indicates whether the string is parsable as a <code>double</code>.
     * @param s the string to be parsed.
     * @return <code>true</code> if the string is a parsable number, otherwise
     *         <code>false</code>.
     */
    public boolean isNumber(String s)
        {
            try
                {
                    Double.parseDouble(s);
                    return true;
                }
            catch (NumberFormatException e)
                {
                return false;
                }
        }

	private void qprintsummary( Request req, PrintWriter out, QuestionnaireSession q_session )
		throws IOException
		{
		/*
		int i, lastqid;
		int tally[]=null;
		String sql;
		ResultSet results;
		Enumeration enumeration=questionnaire.getQuestions();
		QuestionnaireQuestion q;
		QuestionnaireSummary summary = new QuestionnaireSummary();
		Hashtable summarytable=new Hashtable();
		//java.util.Date d=new java.util.Date();

		Logger.getLogger("org.bodington").fine( "Questionnaire print paper A" );

		try
			{
			sql="SELECT question_id, item, COUNT(*) 'count' FROM " + DBObject.schema + "questionnaire_responses "+
				"WHERE result_id IN "+
 				"(SELECT result_id FROM " + DBObject.schema + "questionnaire_results WHERE location = " +
 				req.getResource().getResourceId().intValue() + ") " +
				"GROUP BY question_id, item "+
				"ORDER BY question_id, item	";
			
			results=db.select( sql );
			if ( results==null )
				{
				out.println( "<HR>Problem fetching results<HR>" );
				return;
				}
			
			lastqid=-1;
			//out.println( "<PRE>" );
			while ( results.next() )
				{
				//out.println( "Next record." );
				summary.load( results );
				//out.println( "Loaded." );
				//out.println( "qid: " + 
				//			 summary.question_id +
				//			 " item: " +
				//			 summary.item +
				//			 " count: " +
				//			 summary.count );
				
				if ( summary.question_id.intValue()!=lastqid )
					{
					tally=new int[10];
					for ( i=0; i<10; i++ )
						tally[i]=0;
					lastqid=summary.question_id.intValue();
					summarytable.put( summary.question_id, tally );
					}
				if ( summary.item!=null && summary.count!=null )
					if ( summary.item.intValue() >= 0 &&
						 summary.item.intValue() < 10 &&
						 summary.count.intValue() >= 0 )
						tally[summary.item.intValue()]=summary.count.intValue();
				}
			//out.println( "</PRE>" );
			}
		catch ( Exception ex )
			{
			out.println( "<HR>Problem fetching results<HR>" + ex );
			return;
			}

		Logger.getLogger("org.bodington").fine( "Questionnaire print paper B" );
		
		for ( i=0; enumeration.hasMoreElements(); i++ )
			{
			q=(QuestionnaireQuestion)enumeration.nextElement();
			tally=(int[])summarytable.get( q.question_id );
			out.println( "<HR><H4>Question " + (i+1) + "</H4>" );
			q.printSummary( "/icons/", out, tally );
			
			if ( BuildingContext.getContext().checkPermission( "manage" ) && q.can_comment )
				out.println( "<P><I><A HREF=\"" + 
							req.absoluteURL() + 
							q.question_id + 
							"/bs_template_qcomments.html\">Comments</A> were allowed " +
							"on this question.</I></P>" );
			
			}

		if ( i==0 )
			out.println( "<P>There are currently no questions in this questionnaire.</P>" );

		Logger.getLogger("org.bodington").fine( "Questionnaire print paper C" );
		*/
		}

	private void qprintqcomments( Request req, PrintWriter out, QuestionnaireSession q_session )
		throws IOException
		{
		/*
		int i, lastitem;
		String sql, id;
		Integer qid;
		ResultSet results;

		if ( !BuildingContext.getContext().checkPermission( "manage" ) )
			{
			out.println( "Sorry, you don't have sufficient access rights to see this information." );
			return;
			}

		//must have an id in the parameter part of the URL
		if ( req.getTemplateParameterCount() < 1 )
			{
			out.println( "<I>Invalid URL - no question ID included.</I>" );
			return;
			}
		id=(String)req.getTemplateParameter( 0 );

		qid= new Integer( id );
		QuestionnaireQuestion q=questionnaire.getQuestion( qid );
		if ( q==null )
			{
			out.println( "Unknown question ID." );
			return;
			}
		QuestionnaireResponse response;
		Vector responselist=new Vector();
		//java.util.Date d=new java.util.Date();

		Logger.getLogger("org.bodington").fine( "Questionnaire print paper A" );

		try
			{
			sql="SELECT * FROM " + DBObject.schema + "questionnaire_responses "+
				"WHERE (question_id = " + id + ") " +
				"ORDER BY item	";
			
			results=db.select( sql );
			if ( results==null )
				{
				out.println( "<HR>Problem fetching comments<HR>" );
				return;
				}
			
			while ( results.next() )
				{
				response=new QuestionnaireResponse();
				response.load( results );
				responselist.addElement( response );
				}
			//out.println( "</PRE>" );
			}
		catch ( Exception ex )
			{
			out.println( "<HR>Problem fetching comments<HR>" + ex );
			return;
			}

		Logger.getLogger("org.bodington").fine( "Questionnaire print paper B" );
		
		q.printComments( "/icons/", out, responselist );

		Logger.getLogger("org.bodington").fine( "Questionnaire print paper C" );
		*/
		}


	
	private void qrecord(  Request req, PrintWriter out, QuestionnaireSession q_session, String insertname )
		throws IOException, BuildingServerException
		{
		char letter;
		int i, j;
		String param;
		List questions_in_order;
		PrimaryKey[] list;
		QuestionnaireQuestion q;
		int items[];
		String comments[];
		User user = (User)BuildingContext.getContext().getUser();
		questions_in_order = q_session.getQuestionnaireQuestionsInOrder();
			
		items = new int[questions_in_order.size()];
		comments = new String[questions_in_order.size()];
		list = new PrimaryKey[questions_in_order.size()];
		
		for ( i=0; i<questions_in_order.size(); i++ )
			{
			q=(QuestionnaireQuestion)questions_in_order.get( i );
			list[i] = q.getQuestionnaireQuestionId();
		
			//if no selection this is indicated with item = 100
			items[i]=100;
			comments[i]=null;
			
			// is the question present?
			param=req.getParameter( "Q" + list[i] );
			if ( param==null || !param.equalsIgnoreCase( "true" ) )
				continue;
				

			param=req.getParameter( "QR" + list[i] );
			if ( param!=null && param.length()>0 )
				{
				for ( j=0; j<10; j++ )
					{
					letter=(char)('A'+j);
					if ( param.charAt( 0 )==letter )
						{
						items[i]=j;
						break;
						}
					}
				}

			comments[i]=req.getParameter( "QC" + list[i] );
			if ( comments[i]!=null )
				comments[i]=comments[i].trim();
			
			}
			
			
		PrimaryKey result_id =  q_session.record( list, items, comments );
		
		out.println( "<P>Your responses have been recorded .</P>" );
			
        Event event = new OpinionEvent( 
        				OpinionEvent.EVENT_RECORD,
        				req.getResource().getResourceId(), 
        				user.getUserId(), 
        				new Integer( result_id.intValue() ) );
        event.save();
		}



	private void qrunbutton(  Request req, PrintWriter out, QuestionnaireSession q_session )
		throws IOException
		{
		out.println( "<INPUT NAME=run TYPE=BUTTON ONCLICK=\"launchtest()\" VALUE=\"Show Questions Now\">" );
		
		return;
		}

	private void export(  Request req, PrintWriter out, QuestionnaireSession q_session )
		throws IOException
		{
		try
		    {
		    q_session.exportXml( true, true );
    		out.println( "<P>The XML file has been created and is available for <A HREF=qti.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, QuestionnaireSession q_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
		    {
		    q_session.importXmlQuestions( file );
    		out.println( "DONE" );
		    }
		catch ( BuildingServerException bsex )
		    {
    		out.println( "<PRE>" );
    		out.println( bsex.getMessage() );
    		out.println( "</PRE>" );
		    }
		
		
		return;
		}

	}


