<commit_before>/**********************************************************************************
 * $URL$
 * $Id$
 ***********************************************************************************
 *
 * Copyright (c) 2004, 2005, 2006, 2007, 2008, 2009 The Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.osedu.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **********************************************************************************/

package org.sakaiproject.tool.assessment.ui.listener.delivery;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedItemData;
import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedSectionData;
import org.sakaiproject.tool.assessment.data.dao.grading.AssessmentGradingData;
import org.sakaiproject.tool.assessment.data.dao.grading.ItemGradingData;
import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.PublishedAssessmentIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.SectionDataIfc;
import org.sakaiproject.tool.assessment.data.ifc.grading.AssessmentGradingIfc;
import org.sakaiproject.tool.assessment.data.ifc.grading.ItemGradingIfc;
import org.sakaiproject.tool.assessment.facade.AgentFacade;
import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade;
import org.sakaiproject.tool.assessment.services.FinFormatException;
import org.sakaiproject.tool.assessment.services.GradebookServiceException;
import org.sakaiproject.tool.assessment.services.GradingService;
import org.sakaiproject.tool.assessment.services.ItemService;
import org.sakaiproject.tool.assessment.services.SaLengthException;
import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService;
import org.sakaiproject.tool.assessment.ui.bean.delivery.DeliveryBean;
import org.sakaiproject.tool.assessment.ui.bean.delivery.FinBean;
import org.sakaiproject.tool.assessment.ui.bean.delivery.ItemContentsBean;
import org.sakaiproject.tool.assessment.ui.bean.delivery.SectionContentsBean;
import org.sakaiproject.tool.assessment.ui.bean.shared.PersonBean;
import org.sakaiproject.tool.assessment.ui.listener.util.ContextUtil;
import org.sakaiproject.tool.assessment.util.TextFormat;

/**
 * <p>
 * Title: Samigo
 * </p>
 * <p>
 * Purpose: This listener is called in delivery when the user clicks on submit,
 * save, or previous, next, toc.... It calculates and saves scores in DB
 * 
 * @version $Id: SubmitToGradingActionListener.java 11634 2006-07-06 17:35:54Z
 *          <EMAIL> $
 */

public class SubmitToGradingActionListener implements ActionListener {
	private static Log log = LogFactory
			.getLog(SubmitToGradingActionListener.class);

	/**
	 * ACTION.
	 * 
	 * @param ae
	 * @throws AbortProcessingException
	 */
	public void processAction(ActionEvent ae) throws AbortProcessingException, FinFormatException, SaLengthException {
		try {
			log.debug("SubmitToGradingActionListener.processAction() ");
			
			// get managed bean
			DeliveryBean delivery = (DeliveryBean) ContextUtil
					.lookupBean("delivery");

			if ((ContextUtil.lookupParam("showfeedbacknow") != null
					&& "true"
							.equals(ContextUtil.lookupParam("showfeedbacknow")) || delivery
					.getActionMode() == DeliveryBean.PREVIEW_ASSESSMENT))
				delivery.setForGrade(false);

			// get service
			PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService();

			// get assessment
			PublishedAssessmentFacade publishedAssessment = null;
			if (delivery.getPublishedAssessment() != null)
				publishedAssessment = delivery.getPublishedAssessment();
			else {
				publishedAssessment = publishedAssessmentService
						.getPublishedAssessment(delivery.getAssessmentId());
				delivery.setPublishedAssessment(publishedAssessment);
			}
			
			HashMap invalidFINMap = new HashMap();
			ArrayList invalidSALengthList = new ArrayList();
			AssessmentGradingData adata = submitToGradingService(ae, publishedAssessment, delivery, invalidFINMap, invalidSALengthList);
			// set AssessmentGrading in delivery
			delivery.setAssessmentGrading(adata);

			// set url & confirmation after saving the record for grade
			if (adata != null && delivery.getForGrade())
				setConfirmation(adata, publishedAssessment, delivery);

			if (isForGrade(adata) && !isUnlimited(publishedAssessment)) {
				delivery.setSubmissionsRemaining(delivery
						.getSubmissionsRemaining() - 1);
			}

			if (invalidFINMap.size() != 0) {
				delivery.setIsAnyInvalidFinInput(true);
				throw new FinFormatException ("Not a valid FIN input");
			}
			
			if (invalidSALengthList.size() != 0) {
				delivery.setIsAnyInvalidFinInput(true);
				throw new SaLengthException ("Short Answer input is too long");
			}
			
			delivery.setIsAnyInvalidFinInput(false);

		} catch (GradebookServiceException ge) {
			ge.printStackTrace();
			FacesContext context = FacesContext.getCurrentInstance();
			String err = (String) ContextUtil.getLocalizedString(
					"org.sakaiproject.tool.assessment.bundle.AuthorMessages",
					"gradebook_exception_error");
			context.addMessage(null, new FacesMessage(err));
			return;

		}
	}

	private boolean isForGrade(AssessmentGradingData aData) {
		if (aData != null)
			return (Boolean.TRUE).equals(aData.getForGrade());
		else
			return false;
	}

	private boolean isUnlimited(PublishedAssessmentFacade publishedAssessment) {
		return (Boolean.TRUE).equals(publishedAssessment
				.getAssessmentAccessControl().getUnlimitedSubmissions());
	}

	/**
	 * This method set the url & confirmation string for submitted.jsp. The
	 * confirmation string =
	 * assessmentGradingId-publishedAssessmentId-agentId-submitteddate
	 * 
	 * @param adata
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setConfirmation(AssessmentGradingData adata,
			PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery) {
		if (publishedAssessment.getAssessmentAccessControl() != null) {
			setFinalPage(publishedAssessment, delivery);
			setSubmissionMessage(publishedAssessment, delivery);
		}
		setConfirmationId(adata, publishedAssessment, delivery);
	}

	/**
	 * Set confirmationId which is AssessmentGradingId-TimeStamp.
	 * 
	 * @param adata
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setConfirmationId(AssessmentGradingData adata,
			PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery) {
		delivery.setConfirmation(adata.getAssessmentGradingId() + "-"
				+ publishedAssessment.getPublishedAssessmentId() + "-"
				+ adata.getAgentId() + "-"
				+ adata.getSubmittedDate().toString());
	}

	/**
	 * Set the submission message.
	 * 
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setSubmissionMessage(
			PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery) {
		String submissionMessage = publishedAssessment
				.getAssessmentAccessControl().getSubmissionMessage();
		if (submissionMessage != null)
			delivery.setSubmissionMessage(submissionMessage);
	}

	/**
	 * Set finalPage url in delivery bean.
	 * 
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setFinalPage(PublishedAssessmentFacade publishedAssessment,
			DeliveryBean delivery) {
		String url = publishedAssessment.getAssessmentAccessControl()
				.getFinalPageUrl();
		if (url != null)
			url = url.trim();
		delivery.setUrl(url);
	}

	/**
	 * Invoke submission and return the grading data
	 * 
	 * @param publishedAssessment
	 * @param delivery
	 * @return
	 */
	private synchronized AssessmentGradingData submitToGradingService(
			ActionEvent ae, PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery, HashMap invalidFINMap, ArrayList invalidSALengthList) throws FinFormatException {
		log.debug("****1a. inside submitToGradingService ");
		String submissionId = "";
		HashSet itemGradingHash = new HashSet();
		// daisyf decoding: get page contents contains SectionContentsBean, a
		// wrapper for SectionDataIfc
		Iterator iter = delivery.getPageContents().getPartsContents()
				.iterator();
		log.debug("****1b. inside submitToGradingService, iter= " + iter);
		HashSet adds = new HashSet();
		HashSet removes = new HashSet();
		
		// we go through all the answer collected from JSF form per each
		// publsihedItem and
		// work out which answer is an new addition and in cases like
		// MC/MCMR/Survey, we will
		// discard any existing one and just save teh new one. For other
		// question type, we
		// simply modify the publishedText or publishedAnswer of teh existing
		// ones.
		while (iter.hasNext()) {
			SectionContentsBean part = (SectionContentsBean) iter.next();
			log.debug("****1c. inside submitToGradingService, part " + part);
			Iterator iter2 = part.getItemContents().iterator();
			while (iter2.hasNext()) { // go through each item from form
				ItemContentsBean item = (ItemContentsBean) iter2.next();
				log.debug("****** before prepareItemGradingPerItem");
				prepareItemGradingPerItem(ae, delivery, item, adds, removes);
				log.debug("****** after prepareItemGradingPerItem");
			}
		}
		
		AssessmentGradingData adata = persistAssessmentGrading(ae, delivery,
				itemGradingHash, publishedAssessment, adds, removes, invalidFINMap, invalidSALengthList);

		
		StringBuffer redrawAnchorName = new StringBuffer("p");
		String tmpAnchorName = "";

		Iterator iterPart = delivery.getPageContents().getPartsContents().iterator();
		while (iterPart.hasNext()) {
			SectionContentsBean part = (SectionContentsBean) iterPart.next();
			String partSeq = part.getNumber();
			Iterator iterItem = part.getItemContents().iterator();
			while (iterItem.hasNext()) { // go through each item from form
				ItemContentsBean item = (ItemContentsBean) iterItem.next();
				String itemSeq = item.getSequence();
				Long itemId = item.getItemData().getItemId();
				if (item.getItemData().getTypeId() == 5) {
					if (invalidSALengthList.contains(itemId)) {
						item.setIsInvalidSALengthInput(true);
						redrawAnchorName.append(partSeq);
						redrawAnchorName.append("q");
						redrawAnchorName.append(itemSeq);
						if (tmpAnchorName.equals("") || tmpAnchorName.compareToIgnoreCase(redrawAnchorName.toString()) > 0) {
							tmpAnchorName = redrawAnchorName.toString();
						}
					}
					else {
						item.setIsInvalidSALengthInput(false);
					}
				}
				else if (item.getItemData().getTypeId() == 11) {
					if (invalidFINMap.containsKey(itemId)) {
						item.setIsInvalidFinInput(true);
						redrawAnchorName.append(partSeq);
						redrawAnchorName.append("q");
						redrawAnchorName.append(itemSeq);
						if (tmpAnchorName.equals("") || tmpAnchorName.compareToIgnoreCase(redrawAnchorName.toString()) > 0) {
							tmpAnchorName = redrawAnchorName.toString();
						}
						ArrayList list = (ArrayList) invalidFINMap.get(itemId);
						ArrayList finArray = item.getFinArray();
						Iterator iterFin = finArray.iterator();
						while (iterFin.hasNext()) {
							FinBean finBean = (FinBean) iterFin.next();
							if (finBean.getItemGradingData() != null) {
								Long itemGradingId = finBean.getItemGradingData().getItemGradingId();
								if (list.contains(itemGradingId)) {
									finBean.setIsCorrect(false);
								}
							}
						}
					}
					else {
						item.setIsInvalidFinInput(false);
					}
				}
			}
		}
		
		
		if (tmpAnchorName != null && !tmpAnchorName.equals("")) {
			delivery.setRedrawAnchorName(tmpAnchorName.toString());
		}
		else {
			delivery.setRedrawAnchorName("");
		}
		
		delivery.setSubmissionId(submissionId);
		delivery.setSubmissionTicket(submissionId);// is this the same thing?
		// hmmmm
		delivery.setSubmissionDate(new Date());
		delivery.setSubmitted(true);
		return adata;
	}

	private AssessmentGradingData persistAssessmentGrading(ActionEvent ae, 
			DeliveryBean delivery, HashSet itemGradingHash,
			PublishedAssessmentFacade publishedAssessment, HashSet adds,
			HashSet removes, HashMap invalidFINMap, ArrayList invalidSALengthList) throws FinFormatException {
		AssessmentGradingData adata = null;
		if (delivery.getAssessmentGrading() != null) {
			adata = delivery.getAssessmentGrading();
		}

		GradingService service = new GradingService();
		log.debug("**adata=" + adata);
		if (adata == null) { // <--- this shouldn't happened 'cos it should
			// have been created by BeginDelivery
			adata = makeNewAssessmentGrading(publishedAssessment, delivery,
					itemGradingHash);
			delivery.setAssessmentGrading(adata);
		} else {
			// 1. add all the new itemgrading for MC/Survey and discard any
			// itemgrading for MC/Survey
			// 2. add any modified SAQ/TF/FIB/Matching/MCMR/FIN
			// 3. save any modified Mark for Review in FileUplaod/Audio

			HashMap fibMap = getFIBMap(publishedAssessment);
			HashMap finMap = getFINMap(publishedAssessment);
			HashMap mcmrMap = getMCMRMap(publishedAssessment);
			Set itemGradingSet = adata.getItemGradingSet();
			log.debug("*** 2a. before removal & addition " + (new Date()));
			if (itemGradingSet != null) {
				log.debug("*** 2aa. removing old itemGrading " + (new Date()));
				itemGradingSet.removeAll(removes);
				service.deleteAll(removes);
				// refresh itemGradingSet & assessmentGrading after removal
				log.debug("*** 2ab. reload itemGradingSet " + (new Date()));
				itemGradingSet = service.getItemGradingSet(adata
						.getAssessmentGradingId().toString());
				log.debug("*** 2ac. load assessmentGarding " + (new Date()));
				adata = service.load(adata.getAssessmentGradingId().toString());

				Iterator iter = adds.iterator();
				while (iter.hasNext()) {
					((ItemGradingIfc) iter.next()).setAssessmentGradingId(adata
							.getAssessmentGradingId());
				}
				// make update to old item and insert new item
				// and we will only update item that has been changed
				log
						.debug("*** 2ad. set assessmentGrading with new/updated itemGrading "
								+ (new Date()));
				log
						.debug("Submitforgrading: before calling .....................oldItemGradingSet.size = "
								+ itemGradingSet.size());
				log.debug("Submitforgrading: newItemGradingSet.size = "
						+ adds.size());

				HashSet updateItemGradingSet = getUpdateItemGradingSet(
						itemGradingSet, adds, fibMap, finMap, mcmrMap, adata);
				adata.setItemGradingSet(updateItemGradingSet);
			}
		}

		adata.setIsLate(isLate(publishedAssessment));
		adata.setForGrade(Boolean.valueOf(delivery.getForGrade()));
		
		// If this assessment grading data has been updated (comments or adj. score) by grader and then republic and allow student to resubmit
		// when the student submit his answers, we update the status back to 0 and remove the grading entry/info.
		if (AssessmentGradingIfc.ASSESSMENT_UPDATED_NEED_RESUBMIT.equals(adata.getStatus()) || AssessmentGradingIfc.ASSESSMENT_UPDATED.equals(adata.getStatus())) {
			adata.setStatus(Integer.valueOf(0));
			adata.setGradedBy(null);
			adata.setGradedDate(null);
			adata.setComments(null);
			adata.setTotalOverrideScore(Float.valueOf(0f));
		}
	
		log.debug("*** 2b. before storingGrades, did all the removes and adds "
				+ (new Date()));
		
		if (delivery.getNavigation().equals("1") && ae != null && "showFeedback".equals(ae.getComponent().getId())) {
			log.debug("Do not persist to db if it is linear access and the action is show feedback");
			// 3. let's build three HashMap with (publishedItemId, publishedItem),
			// (publishedItemTextId, publishedItem), (publishedAnswerId,
			// publishedItem) to help with storing grades to adata only, not db
			HashMap publishedItemHash = delivery.getPublishedItemHash();
			HashMap publishedItemTextHash = delivery.getPublishedItemTextHash();
			HashMap publishedAnswerHash = delivery.getPublishedAnswerHash();
			service.storeGrades(adata, publishedAssessment, publishedItemHash, publishedItemTextHash, publishedAnswerHash, false, invalidFINMap, invalidSALengthList);
		}
		else {
			log.debug("Persist to db otherwise");
			// The following line seems redundant. I cannot see a reason why we need to save the adata here
			// and then again in following service.storeGrades(). Comment it out.
			//service.saveOrUpdateAssessmentGrading(adata);
			log.debug("*** 3. before storingGrades, did all the removes and adds " + (new Date()));
			// 3. let's build three HashMap with (publishedItemId, publishedItem),
			// (publishedItemTextId, publishedItem), (publishedAnswerId,
			// publishedItem) to help with storing grades to adata and then persist to DB
			HashMap publishedItemHash = delivery.getPublishedItemHash();
			HashMap publishedItemTextHash = delivery.getPublishedItemTextHash();
			HashMap publishedAnswerHash = delivery.getPublishedAnswerHash();
			service.storeGrades(adata, publishedAssessment, publishedItemHash, publishedItemTextHash, publishedAnswerHash, invalidFINMap, invalidSALengthList);
		}
		return adata;
	}

	private HashMap getFIBMap(PublishedAssessmentIfc publishedAssessment) {
		PublishedAssessmentService s = new PublishedAssessmentService();
		return s.prepareFIBItemHash(publishedAssessment);
	}

  
  	private HashMap getFINMap(PublishedAssessmentIfc publishedAssessment){
	    PublishedAssessmentService s = new PublishedAssessmentService();
	    return s.prepareFINItemHash(publishedAssessment);
	}
  

	private HashMap getMCMRMap(PublishedAssessmentIfc publishedAssessment) {
		PublishedAssessmentService s = new PublishedAssessmentService();
		return s.prepareMCMRItemHash(publishedAssessment);
	}

	private HashSet getUpdateItemGradingSet(Set oldItemGradingSet,
			Set newItemGradingSet, HashMap fibMap, HashMap finMap, HashMap mcmrMap,
			AssessmentGradingData adata) {
		log.debug("Submitforgrading: oldItemGradingSet.size = "
				+ oldItemGradingSet.size());
		log.debug("Submitforgrading: newItemGradingSet.size = "
				+ newItemGradingSet.size());
		HashSet updateItemGradingSet = new HashSet();
		Iterator iter = oldItemGradingSet.iterator();
		HashMap map = new HashMap();
		while (iter.hasNext()) { // create a map with old itemGrading
			ItemGradingIfc item = (ItemGradingIfc) iter.next();
			map.put(item.getItemGradingId(), item);
		}

		// go through new itemGrading
		Iterator iter1 = newItemGradingSet.iterator();
		while (iter1.hasNext()) {
			ItemGradingIfc newItem = (ItemGradingIfc) iter1.next();
			ItemGradingIfc oldItem = (ItemGradingIfc) map.get(newItem
					.getItemGradingId());
			if (oldItem != null) {
				// itemGrading exists and value has been change, then need
				// update
				Boolean oldReview = oldItem.getReview();
				Boolean newReview = newItem.getReview();
				Long oldAnswerId = oldItem.getPublishedAnswerId();
				Long newAnswerId = newItem.getPublishedAnswerId();
				String oldRationale = oldItem.getRationale();
				String newRationale = TextFormat.convertPlaintextToFormattedTextNoHighUnicode(log, newItem.getRationale());
				String oldAnswerText = oldItem.getAnswerText();
				// Change to allow student submissions in rich-text [SAK-17021]
				String newAnswerText = ContextUtil.stringWYSIWYG(newItem.getAnswerText());
				if ((oldReview != null && !oldReview.equals(newReview))
				    || (newReview!=null && !newReview.equals(oldReview))
						|| (oldAnswerId != null && !oldAnswerId
								.equals(newAnswerId))
						|| (newAnswerId != null && !newAnswerId
								.equals(oldAnswerId))
						|| (oldRationale != null && !oldRationale
								.equals(newRationale))
						|| (newRationale != null && !newRationale
								.equals(oldRationale))
						|| (oldAnswerText != null && !oldAnswerText
								.equals(newAnswerText))
						|| (newAnswerText != null && !newAnswerText
								.equals(oldAnswerText))
						|| fibMap.get(oldItem.getPublishedItemId()) != null
						|| finMap.get(oldItem.getPublishedItemId())!=null
						|| mcmrMap.get(oldItem.getPublishedItemId()) != null) {
					oldItem.setReview(newItem.getReview());
					oldItem.setPublishedAnswerId(newItem.getPublishedAnswerId());
					oldItem.setRationale(newRationale);
							
					oldItem.setAnswerText(newAnswerText);
					oldItem.setSubmittedDate(new Date());
					oldItem.setAutoScore(newItem.getAutoScore());
					oldItem.setOverrideScore(newItem.getOverrideScore());
					updateItemGradingSet.add(oldItem);
					// log.debug("**** SubmitToGrading: need update
					// "+oldItem.getItemGradingId());
				}
			} else { // itemGrading from new set doesn't exist, add to set in
				// this case
				// log.debug("**** SubmitToGrading: need add new item");
				newItem.setAgentId(adata.getAgentId());
				updateItemGradingSet.add(newItem);
			}
		}
		return updateItemGradingSet;
	}

	/**
	 * Make a new AssessmentGradingData object for delivery
	 * 
	 * @param publishedAssessment
	 *            the PublishedAssessmentFacade
	 * @param delivery
	 *            the DeliveryBean
	 * @param itemGradingHash
	 *            the item data
	 * @return
	 */
	private AssessmentGradingData makeNewAssessmentGrading(
			PublishedAssessmentFacade publishedAssessment,
			DeliveryBean delivery, HashSet itemGradingHash) {
		PersonBean person = (PersonBean) ContextUtil.lookupBean("person");
		AssessmentGradingData adata = new AssessmentGradingData();
		adata.setAgentId(person.getId());
		adata.setPublishedAssessmentId(publishedAssessment
				.getPublishedAssessmentId());
		adata.setForGrade(Boolean.valueOf(delivery.getForGrade()));
		adata.setItemGradingSet(itemGradingHash);
		adata.setAttemptDate(new Date());
		adata.setIsLate(Boolean.FALSE);
		adata.setStatus(Integer.valueOf(0));
		adata.setTotalOverrideScore(Float.valueOf(0));
		adata.setTimeElapsed(Integer.valueOf("0"));
		return adata;
	}

	/*
	 * This is specific to JSF - question for each type is layout differently in
	 * JSF and the answers submitted are being collected differently too. e.g.
	 * for each MC/Survey/MCMR, an itemgrading is associated with each choice.
	 * whereas there is only one itemgrading per each question for SAQ/TF/Audio,
	 * and one for ecah blank in FIB. To understand the logic in this method, it
	 * is best to study jsf/delivery/item/deliver*.jsp
	 */
	private void prepareItemGradingPerItem(ActionEvent ae, DeliveryBean delivery,
			ItemContentsBean item, HashSet adds, HashSet removes) {
		ArrayList grading = item.getItemGradingDataArray();
		int typeId = item.getItemData().getTypeId().intValue();
		
		//no matter what kinds of type questions, if it marks as review, add it in.
		for (int m = 0; m < grading.size(); m++) {
			ItemGradingData itemgrading = (ItemGradingData) grading.get(m);
			if (itemgrading.getItemGradingId() == null && (itemgrading.getReview() != null && itemgrading.getReview().booleanValue())  == true) {
				adds.addAll(grading);
			} 
		}
		
		// 1. add all the new itemgrading for MC/Survey and discard any
		// itemgrading for MC/Survey
		// 2. add any modified SAQ/TF/FIB/Matching/MCMR/Audio/FIN
		switch (typeId) {
		case 1: // MC
		case 12: // MC Single Selection
		case 3: // Survey
			boolean answerModified = false;
			for (int m = 0; m < grading.size(); m++) {
				ItemGradingData itemgrading = (ItemGradingData) grading.get(m);

				if (itemgrading.getItemGradingId() == null
						|| itemgrading.getItemGradingId().intValue() <= 0) { // =>
					// new answer
					if (itemgrading.getPublishedAnswerId() != null || (itemgrading.getRationale() != null && !itemgrading.getRationale().trim().equals(""))) { 
						answerModified = true;
						break;
					}
				}
			}
			
			// Click the Reset Selection link<commit_msg>SAM-1301: Samigo / mark for review<commit_after>/**********************************************************************************
 * $URL$
 * $Id$
 ***********************************************************************************
 *
 * Copyright (c) 2004, 2005, 2006, 2007, 2008, 2009 The Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.osedu.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **********************************************************************************/

package org.sakaiproject.tool.assessment.ui.listener.delivery;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedItemData;
import org.sakaiproject.tool.assessment.data.dao.assessment.PublishedSectionData;
import org.sakaiproject.tool.assessment.data.dao.grading.AssessmentGradingData;
import org.sakaiproject.tool.assessment.data.dao.grading.ItemGradingData;
import org.sakaiproject.tool.assessment.data.ifc.assessment.AssessmentAccessControlIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.PublishedAssessmentIfc;
import org.sakaiproject.tool.assessment.data.ifc.assessment.SectionDataIfc;
import org.sakaiproject.tool.assessment.data.ifc.grading.AssessmentGradingIfc;
import org.sakaiproject.tool.assessment.data.ifc.grading.ItemGradingIfc;
import org.sakaiproject.tool.assessment.facade.AgentFacade;
import org.sakaiproject.tool.assessment.facade.PublishedAssessmentFacade;
import org.sakaiproject.tool.assessment.services.FinFormatException;
import org.sakaiproject.tool.assessment.services.GradebookServiceException;
import org.sakaiproject.tool.assessment.services.GradingService;
import org.sakaiproject.tool.assessment.services.ItemService;
import org.sakaiproject.tool.assessment.services.SaLengthException;
import org.sakaiproject.tool.assessment.services.assessment.PublishedAssessmentService;
import org.sakaiproject.tool.assessment.ui.bean.delivery.DeliveryBean;
import org.sakaiproject.tool.assessment.ui.bean.delivery.FinBean;
import org.sakaiproject.tool.assessment.ui.bean.delivery.ItemContentsBean;
import org.sakaiproject.tool.assessment.ui.bean.delivery.SectionContentsBean;
import org.sakaiproject.tool.assessment.ui.bean.shared.PersonBean;
import org.sakaiproject.tool.assessment.ui.listener.util.ContextUtil;
import org.sakaiproject.tool.assessment.util.TextFormat;

/**
 * <p>
 * Title: Samigo
 * </p>
 * <p>
 * Purpose: This listener is called in delivery when the user clicks on submit,
 * save, or previous, next, toc.... It calculates and saves scores in DB
 * 
 * @version $Id: SubmitToGradingActionListener.java 11634 2006-07-06 17:35:54Z
 *          <EMAIL> $
 */

public class SubmitToGradingActionListener implements ActionListener {
	private static Log log = LogFactory
			.getLog(SubmitToGradingActionListener.class);

	/**
	 * ACTION.
	 * 
	 * @param ae
	 * @throws AbortProcessingException
	 */
	public void processAction(ActionEvent ae) throws AbortProcessingException, FinFormatException, SaLengthException {
		try {
			log.debug("SubmitToGradingActionListener.processAction() ");
			
			// get managed bean
			DeliveryBean delivery = (DeliveryBean) ContextUtil
					.lookupBean("delivery");

			if ((ContextUtil.lookupParam("showfeedbacknow") != null
					&& "true"
							.equals(ContextUtil.lookupParam("showfeedbacknow")) || delivery
					.getActionMode() == DeliveryBean.PREVIEW_ASSESSMENT))
				delivery.setForGrade(false);

			// get service
			PublishedAssessmentService publishedAssessmentService = new PublishedAssessmentService();

			// get assessment
			PublishedAssessmentFacade publishedAssessment = null;
			if (delivery.getPublishedAssessment() != null)
				publishedAssessment = delivery.getPublishedAssessment();
			else {
				publishedAssessment = publishedAssessmentService
						.getPublishedAssessment(delivery.getAssessmentId());
				delivery.setPublishedAssessment(publishedAssessment);
			}
			
			HashMap invalidFINMap = new HashMap();
			ArrayList invalidSALengthList = new ArrayList();
			AssessmentGradingData adata = submitToGradingService(ae, publishedAssessment, delivery, invalidFINMap, invalidSALengthList);
			// set AssessmentGrading in delivery
			delivery.setAssessmentGrading(adata);

			// set url & confirmation after saving the record for grade
			if (adata != null && delivery.getForGrade())
				setConfirmation(adata, publishedAssessment, delivery);

			if (isForGrade(adata) && !isUnlimited(publishedAssessment)) {
				delivery.setSubmissionsRemaining(delivery
						.getSubmissionsRemaining() - 1);
			}

			if (invalidFINMap.size() != 0) {
				delivery.setIsAnyInvalidFinInput(true);
				throw new FinFormatException ("Not a valid FIN input");
			}
			
			if (invalidSALengthList.size() != 0) {
				delivery.setIsAnyInvalidFinInput(true);
				throw new SaLengthException ("Short Answer input is too long");
			}
			
			delivery.setIsAnyInvalidFinInput(false);

		} catch (GradebookServiceException ge) {
			ge.printStackTrace();
			FacesContext context = FacesContext.getCurrentInstance();
			String err = (String) ContextUtil.getLocalizedString(
					"org.sakaiproject.tool.assessment.bundle.AuthorMessages",
					"gradebook_exception_error");
			context.addMessage(null, new FacesMessage(err));
			return;

		}
	}

	private boolean isForGrade(AssessmentGradingData aData) {
		if (aData != null)
			return (Boolean.TRUE).equals(aData.getForGrade());
		else
			return false;
	}

	private boolean isUnlimited(PublishedAssessmentFacade publishedAssessment) {
		return (Boolean.TRUE).equals(publishedAssessment
				.getAssessmentAccessControl().getUnlimitedSubmissions());
	}

	/**
	 * This method set the url & confirmation string for submitted.jsp. The
	 * confirmation string =
	 * assessmentGradingId-publishedAssessmentId-agentId-submitteddate
	 * 
	 * @param adata
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setConfirmation(AssessmentGradingData adata,
			PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery) {
		if (publishedAssessment.getAssessmentAccessControl() != null) {
			setFinalPage(publishedAssessment, delivery);
			setSubmissionMessage(publishedAssessment, delivery);
		}
		setConfirmationId(adata, publishedAssessment, delivery);
	}

	/**
	 * Set confirmationId which is AssessmentGradingId-TimeStamp.
	 * 
	 * @param adata
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setConfirmationId(AssessmentGradingData adata,
			PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery) {
		delivery.setConfirmation(adata.getAssessmentGradingId() + "-"
				+ publishedAssessment.getPublishedAssessmentId() + "-"
				+ adata.getAgentId() + "-"
				+ adata.getSubmittedDate().toString());
	}

	/**
	 * Set the submission message.
	 * 
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setSubmissionMessage(
			PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery) {
		String submissionMessage = publishedAssessment
				.getAssessmentAccessControl().getSubmissionMessage();
		if (submissionMessage != null)
			delivery.setSubmissionMessage(submissionMessage);
	}

	/**
	 * Set finalPage url in delivery bean.
	 * 
	 * @param publishedAssessment
	 * @param delivery
	 */
	private void setFinalPage(PublishedAssessmentFacade publishedAssessment,
			DeliveryBean delivery) {
		String url = publishedAssessment.getAssessmentAccessControl()
				.getFinalPageUrl();
		if (url != null)
			url = url.trim();
		delivery.setUrl(url);
	}

	/**
	 * Invoke submission and return the grading data
	 * 
	 * @param publishedAssessment
	 * @param delivery
	 * @return
	 */
	private synchronized AssessmentGradingData submitToGradingService(
			ActionEvent ae, PublishedAssessmentFacade publishedAssessment, DeliveryBean delivery, HashMap invalidFINMap, ArrayList invalidSALengthList) throws FinFormatException {
		log.debug("****1a. inside submitToGradingService ");
		String submissionId = "";
		HashSet itemGradingHash = new HashSet();
		// daisyf decoding: get page contents contains SectionContentsBean, a
		// wrapper for SectionDataIfc
		Iterator iter = delivery.getPageContents().getPartsContents()
				.iterator();
		log.debug("****1b. inside submitToGradingService, iter= " + iter);
		HashSet adds = new HashSet();
		HashSet removes = new HashSet();
		
		// we go through all the answer collected from JSF form per each
		// publsihedItem and
		// work out which answer is an new addition and in cases like
		// MC/MCMR/Survey, we will
		// discard any existing one and just save teh new one. For other
		// question type, we
		// simply modify the publishedText or publishedAnswer of teh existing
		// ones.
		while (iter.hasNext()) {
			SectionContentsBean part = (SectionContentsBean) iter.next();
			log.debug("****1c. inside submitToGradingService, part " + part);
			Iterator iter2 = part.getItemContents().iterator();
			while (iter2.hasNext()) { // go through each item from form
				ItemContentsBean item = (ItemContentsBean) iter2.next();
				log.debug("****** before prepareItemGradingPerItem");
				prepareItemGradingPerItem(ae, delivery, item, adds, removes);
				log.debug("****** after prepareItemGradingPerItem");
			}
		}
		
		AssessmentGradingData adata = persistAssessmentGrading(ae, delivery,
				itemGradingHash, publishedAssessment, adds, removes, invalidFINMap, invalidSALengthList);

		
		StringBuffer redrawAnchorName = new StringBuffer("p");
		String tmpAnchorName = "";

		Iterator iterPart = delivery.getPageContents().getPartsContents().iterator();
		while (iterPart.hasNext()) {
			SectionContentsBean part = (SectionContentsBean) iterPart.next();
			String partSeq = part.getNumber();
			Iterator iterItem = part.getItemContents().iterator();
			while (iterItem.hasNext()) { // go through each item from form
				ItemContentsBean item = (ItemContentsBean) iterItem.next();
				String itemSeq = item.getSequence();
				Long itemId = item.getItemData().getItemId();
				if (item.getItemData().getTypeId() == 5) {
					if (invalidSALengthList.contains(itemId)) {
						item.setIsInvalidSALengthInput(true);
						redrawAnchorName.append(partSeq);
						redrawAnchorName.append("q");
						redrawAnchorName.append(itemSeq);
						if (tmpAnchorName.equals("") || tmpAnchorName.compareToIgnoreCase(redrawAnchorName.toString()) > 0) {
							tmpAnchorName = redrawAnchorName.toString();
						}
					}
					else {
						item.setIsInvalidSALengthInput(false);
					}
				}
				else if (item.getItemData().getTypeId() == 11) {
					if (invalidFINMap.containsKey(itemId)) {
						item.setIsInvalidFinInput(true);
						redrawAnchorName.append(partSeq);
						redrawAnchorName.append("q");
						redrawAnchorName.append(itemSeq);
						if (tmpAnchorName.equals("") || tmpAnchorName.compareToIgnoreCase(redrawAnchorName.toString()) > 0) {
							tmpAnchorName = redrawAnchorName.toString();
						}
						ArrayList list = (ArrayList) invalidFINMap.get(itemId);
						ArrayList finArray = item.getFinArray();
						Iterator iterFin = finArray.iterator();
						while (iterFin.hasNext()) {
							FinBean finBean = (FinBean) iterFin.next();
							if (finBean.getItemGradingData() != null) {
								Long itemGradingId = finBean.getItemGradingData().getItemGradingId();
								if (list.contains(itemGradingId)) {
									finBean.setIsCorrect(false);
								}
							}
						}
					}
					else {
						item.setIsInvalidFinInput(false);
					}
				}
			}
		}
		
		
		if (tmpAnchorName != null && !tmpAnchorName.equals("")) {
			delivery.setRedrawAnchorName(tmpAnchorName.toString());
		}
		else {
			delivery.setRedrawAnchorName("");
		}
		
		delivery.setSubmissionId(submissionId);
		delivery.setSubmissionTicket(submissionId);// is this the same thing?
		// hmmmm
		delivery.setSubmissionDate(new Date());
		delivery.setSubmitted(true);
		return adata;
	}

	private AssessmentGradingData persistAssessmentGrading(ActionEvent ae, 
			DeliveryBean delivery, HashSet itemGradingHash,
			PublishedAssessmentFacade publishedAssessment, HashSet adds,
			HashSet removes, HashMap invalidFINMap, ArrayList invalidSALengthList) throws FinFormatException {
		AssessmentGradingData adata = null;
		if (delivery.getAssessmentGrading() != null) {
			adata = delivery.getAssessmentGrading();
		}

		GradingService service = new GradingService();
		log.debug("**adata=" + adata);
		if (adata == null) { // <--- this shouldn't happened 'cos it should
			// have been created by BeginDelivery
			adata = makeNewAssessmentGrading(publishedAssessment, delivery,
					itemGradingHash);
			delivery.setAssessmentGrading(adata);
		} else {
			// 1. add all the new itemgrading for MC/Survey and discard any
			// itemgrading for MC/Survey
			// 2. add any modified SAQ/TF/FIB/Matching/MCMR/FIN
			// 3. save any modified Mark for Review in FileUplaod/Audio

			HashMap fibMap = getFIBMap(publishedAssessment);
			HashMap finMap = getFINMap(publishedAssessment);
			HashMap mcmrMap = getMCMRMap(publishedAssessment);
			Set itemGradingSet = adata.getItemGradingSet();
			log.debug("*** 2a. before removal & addition " + (new Date()));
			if (itemGradingSet != null) {
				log.debug("*** 2aa. removing old itemGrading " + (new Date()));
				itemGradingSet.removeAll(removes);
				service.deleteAll(removes);
				// refresh itemGradingSet & assessmentGrading after removal
				log.debug("*** 2ab. reload itemGradingSet " + (new Date()));
				itemGradingSet = service.getItemGradingSet(adata
						.getAssessmentGradingId().toString());
				log.debug("*** 2ac. load assessmentGarding " + (new Date()));
				adata = service.load(adata.getAssessmentGradingId().toString());

				Iterator iter = adds.iterator();
				while (iter.hasNext()) {
					((ItemGradingIfc) iter.next()).setAssessmentGradingId(adata
							.getAssessmentGradingId());
				}
				// make update to old item and insert new item
				// and we will only update item that has been changed
				log
						.debug("*** 2ad. set assessmentGrading with new/updated itemGrading "
								+ (new Date()));
				log
						.debug("Submitforgrading: before calling .....................oldItemGradingSet.size = "
								+ itemGradingSet.size());
				log.debug("Submitforgrading: newItemGradingSet.size = "
						+ adds.size());

				HashSet updateItemGradingSet = getUpdateItemGradingSet(
						itemGradingSet, adds, fibMap, finMap, mcmrMap, adata);
				adata.setItemGradingSet(updateItemGradingSet);
			}
		}

		adata.setIsLate(isLate(publishedAssessment));
		adata.setForGrade(Boolean.valueOf(delivery.getForGrade()));
		
		// If this assessment grading data has been updated (comments or adj. score) by grader and then republic and allow student to resubmit
		// when the student submit his answers, we update the status back to 0 and remove the grading entry/info.
		if (AssessmentGradingIfc.ASSESSMENT_UPDATED_NEED_RESUBMIT.equals(adata.getStatus()) || AssessmentGradingIfc.ASSESSMENT_UPDATED.equals(adata.getStatus())) {
			adata.setStatus(Integer.valueOf(0));
			adata.setGradedBy(null);
			adata.setGradedDate(null);
			adata.setComments(null);
			adata.setTotalOverrideScore(Float.valueOf(0f));
		}
	
		log.debug("*** 2b. before storingGrades, did all the removes and adds "
				+ (new Date()));
		
		if (delivery.getNavigation().equals("1") && ae != null && "showFeedback".equals(ae.getComponent().getId())) {
			log.debug("Do not persist to db if it is linear access and the action is show feedback");
			// 3. let's build three HashMap with (publishedItemId, publishedItem),
			// (publishedItemTextId, publishedItem), (publishedAnswerId,
			// publishedItem) to help with storing grades to adata only, not db
			HashMap publishedItemHash = delivery.getPublishedItemHash();
			HashMap publishedItemTextHash = delivery.getPublishedItemTextHash();
			HashMap publishedAnswerHash = delivery.getPublishedAnswerHash();
			service.storeGrades(adata, publishedAssessment, publishedItemHash, publishedItemTextHash, publishedAnswerHash, false, invalidFINMap, invalidSALengthList);
		}
		else {
			log.debug("Persist to db otherwise");
			// The following line seems redundant. I cannot see a reason why we need to save the adata here
			// and then again in following service.storeGrades(). Comment it out.
			//service.saveOrUpdateAssessmentGrading(adata);
			log.debug("*** 3. before storingGrades, did all the removes and adds " + (new Date()));
			// 3. let's build three HashMap with (publishedItemId, publishedItem),
			// (publishedItemTextId, publishedItem), (publishedAnswerId,
			// publishedItem) to help with storing grades to adata and then persist to DB
			HashMap publishedItemHash = delivery.getPublishedItemHash();
			HashMap publishedItemTextHash = delivery.getPublishedItemTextHash();
			HashMap publishedAnswerHash = delivery.getPublishedAnswerHash();
			service.storeGrades(adata, publishedAssessment, publishedItemHash, publishedItemTextHash, publishedAnswerHash, invalidFINMap, invalidSALengthList);
		}
		return adata;
	}

	private HashMap getFIBMap(PublishedAssessmentIfc publishedAssessment) {
		PublishedAssessmentService s = new PublishedAssessmentService();
		return s.prepareFIBItemHash(publishedAssessment);
	}

  
  	private HashMap getFINMap(PublishedAssessmentIfc publishedAssessment){
	    PublishedAssessmentService s = new PublishedAssessmentService();
	    return s.prepareFINItemHash(publishedAssessment);
	}
  

	private HashMap getMCMRMap(PublishedAssessmentIfc publishedAssessment) {
		PublishedAssessmentService s = new PublishedAssessmentService();
		return s.prepareMCMRItemHash(publishedAssessment);
	}

	private HashSet getUpdateItemGradingSet(Set oldItemGradingSet,
			Set newItemGradingSet, HashMap fibMap, HashMap finMap, HashMap mcmrMap,
			AssessmentGradingData adata) {
		log.debug("Submitforgrading: oldItemGradingSet.size = "
				+ oldItemGradingSet.size());
		log.debug("Submitforgrading: newItemGradingSet.size = "
				+ newItemGradingSet.size());
		HashSet updateItemGradingSet = new HashSet();
		Iterator iter = oldItemGradingSet.iterator();
		HashMap map = new HashMap();
		while (iter.hasNext()) { // create a map with old itemGrading
			ItemGradingIfc item = (ItemGradingIfc) iter.next();
			map.put(item.getItemGradingId(), item);
		}

		// go through new itemGrading
		Iterator iter1 = newItemGradingSet.iterator();
		while (iter1.hasNext()) {
			ItemGradingIfc newItem = (ItemGradingIfc) iter1.next();
			ItemGradingIfc oldItem = (ItemGradingIfc) map.get(newItem
					.getItemGradingId());
			if (oldItem != null) {
				// itemGrading exists and value has been change, then need
				// update
				Boolean oldReview = oldItem.getReview();
				Boolean newReview = newItem.getReview();
				Long oldAnswerId = oldItem.getPublishedAnswerId();
				Long newAnswerId = newItem.getPublishedAnswerId();
				String oldRationale = oldItem.getRationale();
				String newRationale = TextFormat.convertPlaintextToFormattedTextNoHighUnicode(log, newItem.getRationale());
				String oldAnswerText = oldItem.getAnswerText();
				// Change to allow student submissions in rich-text [SAK-17021]
				String newAnswerText = ContextUtil.stringWYSIWYG(newItem.getAnswerText());
				if ((oldReview != null && !oldReview.equals(newReview))
				    || (newReview!=null && !newReview.equals(oldReview))
						|| (oldAnswerId != null && !oldAnswerId
								.equals(newAnswerId))
						|| (newAnswerId != null && !newAnswerId
								.equals(oldAnswerId))
						|| (oldRationale != null && !oldRationale
								.equals(newRationale))
						|| (newRationale != null && !newRationale
								.equals(oldRationale))
						|| (oldAnswerText != null && !oldAnswerText
								.equals(newAnswerText))
						|| (newAnswerText != null && !newAnswerText
								.equals(oldAnswerText))
						|| fibMap.get(oldItem.getPublishedItemId()) != null
						|| finMap.get(oldItem.getPublishedItemId())!=null
						|| mcmrMap.get(oldItem.getPublishedItemId()) != null) {
					oldItem.setReview(newItem.getReview());
					oldItem.setPublishedAnswerId(newItem.getPublishedAnswerId());
					oldItem.setRationale(newRationale);
							
					oldItem.setAnswerText(newAnswerText);
					oldItem.setSubmittedDate(new Date());
					oldItem.setAutoScore(newItem.getAutoScore());
					oldItem.setOverrideScore(newItem.getOverrideScore());
					updateItemGradingSet.add(oldItem);
					// log.debug("**** SubmitToGrading: need update
					// "+oldItem.getItemGradingId());
				}
			} else { // itemGrading from new set doesn't exist, add to set in
				// this case
				// log.debug("**** SubmitToGrading: need add new item");
				newItem.setAgentId(adata.getAgentId());
				updateItemGradingSet.add(newItem);
			}
		}
		return updateItemGradingSet;
	}

	/**
	 * Make a new AssessmentGradingData object for delivery
	 * 
	 * @param publishedAssessment
	 *            the PublishedAssessmentFacade
	 * @param delivery
	 *            the DeliveryBean
	 * @param itemGradingHash
	 *            the item data
	 * @return
	 */
	private AssessmentGradingData makeNewAssessmentGrading(
			PublishedAssessmentFacade publishedAssessment,
			DeliveryBean delivery, HashSet itemGradingHash) {
		PersonBean person = (PersonBean) ContextUtil.lookupBean("person");
		AssessmentGradingData adata = new AssessmentGradingData();
		adata.setAgentId(person.getId());
		adata.setPublishedAssessmentId(publishedAssessment
				.getPublishedAssessmentId());
		adata.setForGrade(Boolean.valueOf(delivery.getForGrade()));
		adata.setItemGradingSet(itemGradingHash);
		adata.setAttemptDate(new Date());
		adata.setIsLate(Boolean.FALSE);
		adata.setStatus(Integer.valueOf(0));
		adata.setTotalOverrideScore(Float.valueOf(0));
		adata.setTimeElapsed(Integer.valueOf("0"));
		return adata;
	}

	/*
	 * This is specific to JSF - question for each type is layout differently in
	 * JSF and the answers submitted are being collected differently too. e.g.
	 * for each MC/Survey/MCMR, an itemgrading is associated with each choice.
	 * whereas there is only one itemgrading per each question for SAQ/TF/Audio,
	 * and one for ecah blank in FIB. To understand the logic in this method, it
	 * is best to study jsf/delivery/item/deliver*.jsp
	 */
	private void prepareItemGradingPerItem(ActionEvent ae, DeliveryBean delivery,
			ItemContentsBean item, HashSet adds, HashSet removes) {
		ArrayList grading = item.getItemGradingDataArray();
		int typeId = item.getItemData().getTypeId().intValue();
		
		//no matter what kinds of type questions, if it marks as review, add it in.
		for (int m = 0; m < grading.size(); m++) {
			ItemGradingData itemgrading = (ItemGradingData) grading.get(m);
			if (itemgrading.getItemGradingId() == null && (itemgrading.getReview() != null && itemgrading.getReview().booleanValue())  == true) {
				adds.addAll(grading);
				break;
			} 
		}
		
		// 1. add all the new itemgrading for MC/Survey and discard any
		// itemgrading for MC/Survey
		// 2. add any modified SAQ/TF/FIB/Matching/MCMR/Audio/FIN
		switch (typeId) {
		case 1: // MC
		case 12: // MC Single Selection
		case 3: // Survey
			boolean answerModified = false;
			for (int m = 0; m < grading.size(); m++) {
				ItemGradingData itemgrading = (ItemGradingData) grading.get(m);

				if (itemgrading.getItemGradingId() == null
						|| itemgrading.getItemGradingId().intValue() <= 0) { // =>
					// new answer
					if (itemgrading.getPublishedAnswerId() != null || (itemgrading.getRationale() != null && !itemgrading.getRationale().trim().equals(""))) { 
						answerModified = true;
						break;
					}
				}
			}
			
			// Click the Reset Selection link
{'fraction_non_alphanumeric': 0.11766627199608051, 'fraction_numerical': 0.0040419711754378805, 'mean_word_length': 4.357869408290496, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5018179', 'n_tokens_mistral': 16811, 'n_tokens_neox': 14117, 'n_words': 3246}