/* ======================================================================
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.List;
import java.util.Vector;

import org.bodington.database.*;
import org.bodington.server.*;
import org.bodington.server.resources.*;
import org.bodington.text.BigString;

/**
 * Class that encapsulates a questionnaire. This class represents a
 * questionnaire as a whole. An instance is an aggregation of individual
 * questions (see {@link QuestionnaireQuestion}).
 * @see QuestionnaireQuestion
 * @author Jon Maber
 * @author Alexis O'Connor
 */
public class Questionnaire extends org.bodington.server.resources.Resource
	{
    private static final byte Q_FLAG_IS_RECORD_ONCE = 1;
	private byte questionnaire_flags;
	public static final int DEFAULT_ORDINAL_INCREMENT = 100;
	private Vector questions;
	
	
    public Class sessionClass()
    	{
        // This method is derived from class org.bodington.server.resources.Resource
        // to do: code goes here
        return org.bodington.assessment.QuestionnaireSessionImpl.class;
    	}
    	

   	/**
     * Find the specified questionnaire.
   	 * @param key the ID of the questionnaire you want to find.
     * @see #getQuestionnaireId()
   	 */
   	public static Questionnaire findQuestionnaire( PrimaryKey key )
		throws BuildingServerException
		{
		return (Questionnaire)findPersistentObject( key, "org.bodington.assessment.Questionnaire" );
		}
	/**
     * Find the specified questionnaires. This method returns an enumeration
     * whose elements are instances of {@link Questionnaire}.
     * @param whereSQL a string representing the <code>WHERE</code> clause of
     *        some SQL representing the criteria upon which to select the
     *        questionnaires.
     * @return an enumeration of questionnaires.
     * @see PersistentObject#findPersistentObjects(String, String)
     */
	public static Enumeration findQuestionnaires( String whereSQL )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( whereSQL, "org.bodington.assessment.Questionnaire" );
	    }
    /**
     * Find the specified questionnaires. This method returns an enumeration
     * whose elements are instances of {@link Questionnaire}.
     * @param whereSQL a string representing the <code>WHERE</code> clause of
     *        some SQL representing the criteria upon which to select the
     *        questionnaires.
     * @param orderSQL a string representing the <code>ORDER BY</code> clause
     *        of some SQL representing the criteria upon which to the returned
     *        questionnaire results.
     * @return an enumeration of questionnaires.
     * @see PersistentObject#findPersistentObjects(String, String)
     */
	public static Enumeration findQuestionnaires( String whereSQL, String orderSQL )
	    throws BuildingServerException
	    {
	    return findPersistentObjects( whereSQL, orderSQL, "org.bodington.assessment.Questionnaire" );
	    }
	    
    public PrimaryKey getQuestionnaireId()
		{
        return getResourceId();
        }

    public void setQuestionnaireId(PrimaryKey key)
    	{
    	setResourceId( key );
    	}
    	
	public byte getQuestionnaireFlags()
		{
		return questionnaire_flags;
		}
	public void setQuestionnaireFlags( byte f )
		{
		if ( f==questionnaire_flags ) return;
		questionnaire_flags=f;
    	setUnsaved();
		}
		

	/**
     * Update the list of questions. This method is used internally to synch
     * the list of questions with persistent storage and to ensure that the value
     * of the <code>maxOrdinal</code> property is up to date.
     * @see #getMaxOrdinal()
	 */
	private void updateQuestionList()
		throws BuildingServerException
		{
		Enumeration qlist;

		questions = new Vector();
        // TODO: looks wrong, but see QuestionnaireQuestion.getResourceId().
		qlist = QuestionnaireQuestion.findQuestionnaireQuestions( 
            "resource_id = " + getQuestionnaireId(), "ordinal" );
		while ( qlist.hasMoreElements() )
			questions.addElement( qlist.nextElement() );
        getMaxOrdinal();
		}
		
	/**
     * Get a list of the questions. The items in this list are instances of
     * {@link QuestionnaireQuestion}. The list returned is a shallow copy of the
     * underlying list used by this object.
	 * @return a list of the questions.
	 * @throws BuildingServerException
	 */
	public List getQuestions()
		throws BuildingServerException
		{
        initialize();
		return (List) questions.clone();
		}

	public QuestionnaireQuestion getQuestionAt( int n )
		throws BuildingServerException
		{
        initialize();
		if ( n<0 || n>= questions.size() )
			return null;
			
		return (QuestionnaireQuestion)questions.elementAt( n );
		}
		
	public QuestionnaireQuestion createQuestion()
        throws BuildingServerException
        {
        initialize();
        QuestionnaireQuestion question = new QuestionnaireQuestion();
        question.setQuestionnaireId( getResourceId() );
        question.setIntroduction( "The text of the question goes here." );
        for ( int i = 0; i < QuestionnaireQuestion.MAX_STATEMENT_COUNT; i++ )
            {
            question.setStatement( i, "" );
            }
        question.setFlags( (byte)0 );
        question.setOrdinal( nextOrdinal() );
        question.save();
        updateQuestionList();
        return question;
        }

	/**
     * Remove the specified question. The specified parameter corresponds to the
     * value of {@link QuestionnaireQuestion#getQuestionnaireQuestionId()}.
     * @param questionID the ID of the question to be removed.
	 * @throws BuildingServerException
	 */
	public void removeQuestion( PrimaryKey questionID ) throws BuildingServerException
		{
        initialize();
        QuestionnaireQuestion question 
            = QuestionnaireQuestion.findQuestionnaireQuestion( questionID );
        if ( question == null )
            throw new BuildingServerException( "Question not found." );

        if ( !question.getQuestionnaireId().equals( getQuestionnaireId() ) )
            throw new BuildingServerException( "Question doesn't belong in this Questionnaire." );

        question.delete();
        updateQuestionList();
		}


    public int getResourceType()
    	{
    	return RESOURCE_QUESTIONNAIRE;
    	}
    

    private int ordinalIncrement = DEFAULT_ORDINAL_INCREMENT;
    
    /**
     * Set the ordinal increment value. This is the value by which the ordinal
     * should be incremented by when, for example, creating a new question.
     * @param ordinalIncrement the ordinal increment value to set.
     */
    public void setOrdinalIncrement( int ordinalIncrement )
        {
        this.ordinalIncrement = ordinalIncrement;
        }


    /**
     * Get the ordinal increment value. 
     * @return the ordinal increment value.
     * @see #setOrdinalIncrement(int)
     */
    public int getOrdinalIncrement()
        {
        return ordinalIncrement;
        }
    
    private int maxOrdinal = 0;
    
    /**
     * Get the maximum ordinal value. This is the maximum ordinal of any
     * question within this questionnaire.
     * @return the maximum ordinal value.
     */
    public int getMaxOrdinal() throws BuildingServerException
        {
        initialize();
        Vector v = this.questions;
        Object q = !v.isEmpty() ? v.lastElement() : null;
        maxOrdinal = (q != null) ? ((QuestionnaireQuestion)q).getOrdinal()
                        : maxOrdinal;
        return maxOrdinal;
        }
    
    /**
     * Obtain the next available ordinal value. This method implictly increments
     * the <code>maxOrdinal</code> property by the value returned by
     * {@link #getOrdinalIncrement()}. By default, when a new question is
     * created it is appended to the end of the list. Therefore the value if its
     * <code>ordinal</code> property is that returned by this method.
     * @return the next available ordinal value.
     * @see #getOrdinalIncrement()
     * @see #getMaxOrdinal()
     * @see QuestionnaireQuestion#getOrdinal()
     */
    private int nextOrdinal()
        {
        return maxOrdinal += getOrdinalIncrement();
        }

    /**
     * Initialize this object. This method is used internally to lazy load the 
     * questions of this questionnaire from persistent storage.
     */
    private void initialize() throws BuildingServerException
        {
        if (questions == null)
            updateQuestionList();
        }
    
    /**
     * Change the specified question. The <code>questionID</code> parameter
     * corresponds to the value of
     * {@link QuestionnaireQuestion#getQuestionnaireQuestionId()}.
     * @param questionID the ID of the question to be changed.
     * @param newQuestion an object containing the new values for the specified
     *        question.
     * @throws BuildingServerException
     */
    public void changeQuestion( PrimaryKey questionID, QuestionnaireQuestion newQuestion )
        throws BuildingServerException
        {
        initialize();
        QuestionnaireQuestion question 
            = QuestionnaireQuestion.findQuestionnaireQuestion( questionID );
        if ( question == null )
            throw new BuildingServerException( "Question not found." );

        if ( !question.getQuestionnaireId().equals( getResourceId() ) )
            throw new BuildingServerException(
                "Question does not belong in this Questionnaire." );

        question.setOrdinal( newQuestion.getOrdinal() );
        question.setFlags( newQuestion.getFlags() );
        question.save();
        updateQuestionList();
        }

    /**
     * Change the specified question. The <code>questionID</code> parameter
     * corresponds to the value of
     * {@link QuestionnaireQuestion#getQuestionnaireQuestionId()}. The text
     * that can be changed includes the introduction and the text of the
     * statements / responses.
     * @param questionID the ID of the question whose text is to be changed.
     * @param textID the ID of the text to be changed.
     * @param newText the value of the new text.
     * @throws BuildingServerException
     */
    public void changeQuestionText( PrimaryKey questionID, PrimaryKey textID,
        String newText ) throws BuildingServerException
        {
        initialize();
        QuestionnaireQuestion question 
            = QuestionnaireQuestion.findQuestionnaireQuestion( questionID );
        if ( question == null )
            throw new BuildingServerException( "Question not found." );

        if ( !question.getQuestionnaireId().equals( getResourceId() ) )
            throw new BuildingServerException(
                "Question does not belong in this Questionnaire." );

        boolean found = false;
        found = found || textID.equals( question.getIntroductionBigStringId() );
        found = found || textID.equals( question.getStatementABigStringId() );
        found = found || textID.equals( question.getStatementBBigStringId() );
        found = found || textID.equals( question.getStatementCBigStringId() );
        found = found || textID.equals( question.getStatementDBigStringId() );
        found = found || textID.equals( question.getStatementEBigStringId() );
        found = found || textID.equals( question.getStatementFBigStringId() );
        found = found || textID.equals( question.getStatementGBigStringId() );
        found = found || textID.equals( question.getStatementHBigStringId() );
        found = found || textID.equals( question.getStatementIBigStringId() );
        found = found || textID.equals( question.getStatementJBigStringId() );

        if ( !found )
            throw new BuildingServerException(
                "Can not identify which part of question to edit." );

        BigString big = BigString.findBigString( textID );
        big.setString( newText );
        big.save();
        question.setUnsaved();
        updateQuestionList();
        }
    
    /**
     * Indicates whether this questionnaire is recorded once. If a questionnaire
     * is recorded once, it must be completed within a single session, i.e. it
     * can only be submitted once.
     * @return <code>true</code> if this questionnaire is recorded once,
     *         otherwise <code>false</code>.
     * @see QuestionnaireResult#isRecorded()
     */
    public boolean isRecordOnce()
        {
        return (questionnaire_flags & Q_FLAG_IS_RECORD_ONCE) != 0;
        }
    
    /**
     * Set whether or not this questionnaire is recorded once.
     * @param b the value to be set to.
     * @see #isRecordOnce()
     */
    public void setRecordOnce( boolean b )
        {
        if ( isRecordOnce() == b )
            return;
        setQuestionnaireFlags( 
            (byte)(questionnaire_flags ^ Q_FLAG_IS_RECORD_ONCE) );
        }
	}


