/* ======================================================================
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.assessment;

import java.rmi.RemoteException;
import java.sql.Timestamp;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.Iterator;

import org.bodington.sqldatabase.*;
import org.bodington.database.*;
import org.bodington.server.BuildingServerException;

/**
 * Class that encapsulates a users response to a questionnaire. This class
 * pertains to the questionnaire as a whole. It includes information such as 
 * who the corresponding user is and when the questionnaire was started / saved
 * to the database.
 * @see org.bodington.assessment.Questionnaire
 */
public class QuestionnaireResult extends org.bodington.sqldatabase.SqlPersistentObject
	{
    private PrimaryKey primary_key;
    private PrimaryKey questionnaire_id;
	private PrimaryKey user_id;
	private java.sql.Timestamp when_started;
	private java.sql.Timestamp when_saved;
    private boolean isRecorded = false;
    
    /**
     * Creates a blank instance of this class. This is assumed to represent
     * a recorded result.
     * @see #isRecorded()
     */
    public QuestionnaireResult()
        {
        this(true);
        }
    
    /**
     * Creates a blank instance of this class. The value of the
     * <code>isRecorded</code> parameter indicates whether or not this
     * instances represents a recorded result.
     * @param isRecorded indicates whether or not this instance represents a
     *        recorded result.
     * @see #isRecorded()
     */
    public QuestionnaireResult(boolean isRecorded)
        {
        this.isRecorded = isRecorded;
        }
	
	public static QuestionnaireResult findQuestionnaireResult( PrimaryKey key )
	    throws BuildingServerException
	    {
	    QuestionnaireResult result = (QuestionnaireResult)findPersistentObject( 
            key, "org.bodington.server.assessment.QuestionnaireResult" );
        return (result != null) ? result : new QuestionnaireResult(false);
	    }
	
	public static QuestionnaireResult findQuestionnaireResult( String where )
	    throws BuildingServerException
	    {
        QuestionnaireResult result = (QuestionnaireResult)findPersistentObject( 
            where, "org.bodington.assessment.QuestionnaireResult" );
        return (result != null) ? result : new QuestionnaireResult(false);
	    }
	
	public static Enumeration findQuestionnaireResults( String where )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, "org.bodington.assessment.QuestionnaireResult" );
	    }
	
	public static Enumeration findQuestionnaireResults( String where, String order )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( where, order, "org.bodington.assessment.QuestionnaireResult" );
	    }
	
    public PrimaryKey getPrimaryKey()
    	{
        return primary_key;
    	}

    public void setPrimaryKey(PrimaryKey key)
    	{
        primary_key = key;
        setUnsaved();
    	}

	public PrimaryKey getQuestionnaireResultId()
		{
		return getPrimaryKey();
		}
		
	public void setQuestionnaireResultId( PrimaryKey id )
		{
		setPrimaryKey(id);
		}

    /**
     * Kept for backwards compatibility. This class is <em>not</em> an
     * instance of {@link org.bodington.server.resources.Resource} therefore it
     * should not have this method. Unfortunately, it's use is embedded in the
     * persistence layer so it is maintained for the time being. In practice,
     * the value of this property is synonymous with
     * {@link #getQuestionnaireId()}, which is why you should call that method
     * directly instead.
     * @return the ID.
     * @deprecated Use {@link #getQuestionnaireId()} instead.
     */
    public PrimaryKey getResourceId()
        {
        return getQuestionnaireId();
        }
		
    /**
     * Kept for backwards compatibility. This class is <em>not</em> an
     * instance of {@link org.bodington.server.resources.Resource} therefore it
     * should not have this method. Unfortunately, it's use is embedded in the
     * persistence layer so it is maintained for the time being.
     * @param id the ID to set.
     * @deprecated Use {@link #setQuestionnaireId(PrimaryKey)} instead.
     * @see #getResourceId()
     */
    public void setResourceId( PrimaryKey id )
        {
        setQuestionnaireId(id);
        }
    
	/**
     * Get the ID of the user to which this response refers.
	 * @return the ID of the user to which this response refers.
     * @see org.bodington.server.realm.User
	 */
	public PrimaryKey getUserId()
		{
		return user_id;
		}
		
	/**
     * Set the ID of the user to which this response refers.
	 * @param id the ID of the user to which this response refers.
     * @see #getUserId()
	 */
	public void setUserId( PrimaryKey id )
		{
		user_id = id;
		setUnsaved();
		}

	/**
     * Get the time stamp of when the user started recording their response.
	 * @return the time stamp of when the user started recording their response.
	 */
	public Timestamp getWhenStarted()
		{
		return when_started;
		}
		
	/**
     * Set the time stamp of when the user started recording their response. 
	 * @param t the time stamp of when the user started recording their response.
     * @see #getWhenStarted()
	 */
	public void setWhenStarted( Timestamp t )
		{
		when_started = t;
		setUnsaved();
		}
		
	/**
     * Get the time stamp of when this object was saved to the database.
	 * @return the time stamp of when this object was saved to the database.
	 */
	public Timestamp getWhenSaved()
		{
		return when_saved;
		}
		
	/**
     * Set the time stamp of when this object was saved to the database.
	 * @param t the time stamp of when this object was saved to the database.
     * @see #getWhenSaved()
	 */
	public void setWhenSaved( Timestamp t )
		{
		when_saved = t;
		setUnsaved();
		}
    
    /**
     * Get a map of the responses associated with this instance. The form of the
     * returned map is as follows:
     * 
     * <pre>
     * <code>
     *  Map &lt;{@link QuestionnaireResponse#getQuestionnaireQuestionId()}, 
     *    {@link QuestionnaireResponse}&gt;
     * </code>
     * </pre>
     * 
     * For each question in the questionnaire, there will be a corresponding
     * response within the map. If there is no response recorded in the
     * database, then the {@link QuestionnaireResponse#isRecorded()} method for
     * that response will return <code>false</code>.
     * @return a map of the responses associated with this instance.
     * @see QuestionnaireResponse
     * @see QuestionnaireResponse#isRecorded()
     */
    public Map getQuestionnaireResponses()
        throws RemoteException, BuildingServerException
        {
        Hashtable map = new Hashtable();
        if (!isRecorded())
            return map;
  
        // First of all, populate the map with 'blank' responses.
        Iterator questions = getQuestionnaire().getQuestions().iterator();
        while (questions.hasNext())
            {
            QuestionnaireQuestion question 
                = (QuestionnaireQuestion) questions.next();
            map.put(question.getQuestionnaireQuestionId(), 
                new QuestionnaireResponse(this, false));
            }
        // Query what the recorded responses are.
        Enumeration responses 
            = QuestionnaireResponse.findQuestionnaireResponses( 
                "questionnaire_result_id = " + getPrimaryKey() );
        // Overwrite the blank responses with any recorded responses.
        while ( responses.hasMoreElements() )
            {
            QuestionnaireResponse response 
                = (QuestionnaireResponse)responses.nextElement();
            map.put( response.getQuestionnaireQuestionId(), response );
            }

        return map;
        }
    
    /**
     * Indicates whether this instance represents a recorded result. A result
     * of <code>true</code> means that there is a persistent record of the
     * associated users result in the database. A result of <code>false</code>
     * indicates that no result has yet been recorded. This latter case is
     * especially relevant in the case of single-session questionnaires.
     * @return <code>true</code> if this instance represents a submitted result,
     * otherwise <code>false</code>.
     * @see Questionnaire#isRecordOnce()
     * @see #QuestionnaireResult(boolean)
     */
    public boolean isRecorded()
        {
        return isRecorded;
        }

    /**
     * Get the ID of the associated questionnaire. This is the questionnaire to
     * which this instance is a result.
     * <p>
     * NOTE: for legacy reasons, the corresponding name for this field in the
     * persistence layer is currently <code>resource_id</code>.
     * @return the ID of the associated questionnaire.
     */
    public PrimaryKey getQuestionnaireId()
        {
        return questionnaire_id;
        }
    
    /**
     * Set the ID of the associated questionnaire.
     * @param id the ID of the associated questionnaire.
     * @see #getQuestionnaireId()
     */
    public void setQuestionnaireId(PrimaryKey id)
        {
        questionnaire_id = id;
        setUnsaved();
        }

    /**
     * Get the associated questionnaire. This is the questionnaire
     * to which this object is a users questionnaire result. 
     * @return the associated questionnaire.
     * @see #getQuestionnaireId()
     */
    public Questionnaire getQuestionnaire()
        {
        // TODO: we could cache this as an instance variable:
        try
            {
            return Questionnaire.findQuestionnaire(getQuestionnaireId());
            }
        catch (BuildingServerException e)
            {
            return null;
            }
        }
	}
