/*
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * The Apereo Foundation licenses this file to you under the Apache 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.apache.org/licenses/LICENSE-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.unitime.timetable.model;

import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.ifs.util.CSVFile;
import org.cpsolver.ifs.util.DataProperties;
import org.cpsolver.ifs.util.CSVFile.CSVField;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.unitime.commons.Debug;
import org.unitime.commons.Email;
import org.unitime.localization.impl.Localization;
import org.unitime.timetable.defaults.ApplicationProperty;
import org.unitime.timetable.form.ListSolutionsForm.InfoComparator;
import org.unitime.timetable.gwt.resources.GwtConstants;
import org.unitime.timetable.interfaces.RoomAvailabilityInterface.TimeBlock;
import org.unitime.timetable.model.base.BaseSolution;
import org.unitime.timetable.model.comparators.ClassComparator;
import org.unitime.timetable.model.comparators.DivSecAssignmentComparator;
import org.unitime.timetable.model.dao.Class_DAO;
import org.unitime.timetable.model.dao.InstructionalOfferingDAO;
import org.unitime.timetable.model.dao.SolutionDAO;
import org.unitime.timetable.model.dao.SolutionInfoDAO;
import org.unitime.timetable.solver.ClassAssignmentProxy;
import org.unitime.timetable.solver.ui.AssignmentPreferenceInfo;
import org.unitime.timetable.solver.ui.PropertiesInfo;
import org.unitime.timetable.solver.ui.TimetableInfo;
import org.unitime.timetable.solver.ui.TimetableInfoFileProxy;
import org.unitime.timetable.util.Constants;
import org.unitime.timetable.util.Formats;


/**
 * @author Tomas Muller, Stephanie Schluttenhofer
 */
public class Solution extends BaseSolution implements ClassAssignmentProxy {
	protected static GwtConstants CONSTANTS = Localization.create(GwtConstants.class);

	private static final long serialVersionUID = 1L;
	private static Log sLog = LogFactory.getLog(Solution.class);
	public static DecimalFormat sSufixFormat = new DecimalFormat("000");
	public static boolean DEBUG = false;

/*[CONSTRUCTOR MARKER BEGIN]*/
	public Solution () {
		super();
	}

	/**
	 * Constructor for primary key
	 */
	public Solution (java.lang.Long uniqueId) {
		super(uniqueId);
	}

/*[CONSTRUCTOR MARKER END]*/
	
	public SolutionInfo getSolutionInfo(String name) {
		if ("GlobalInfo".equals(name)) return getGlobalInfo();
		return (SolutionInfo)
			(new SolutionInfoDAO()).
			getSession().
			createQuery("select si from SolutionInfo si where si.definition.name=:name and si.solution.uniqueId=:solutionId").
			setString("name",name).
			setLong("solutionId",getUniqueId()).
			uniqueResult();
		/*
		org.hibernate.Session session = (new SolutionInfoDAO()).getSession();
		SolverInfoDef def = SolverInfoDef.findByName(session,name);
		if (def==null) return null;
		return (SolutionInfo)session.createCriteria(SolutionInfo.class).add(Restrictions.eq("definition",def)).add(Restrictions.eq("solution",this)).setCacheable(true).uniqueResult();
		*/
	}

	public TimetableInfo getInfo(String name) {
		SolutionInfo sinfo = getSolutionInfo(name);
		if (sinfo==null) return null;
		return sinfo.getInfo();
	}
	
	
	public void uncommitSolution(org.hibernate.Session hibSession) {
		uncommitSolution(hibSession, null);
	}
	
	public void uncommitSolution(org.hibernate.Session hibSession, String sendNotificationPuid) {
		if (DEBUG) sLog.debug("uncommit["+getUniqueId()+","+getOwner().getName()+"] -------------------------------------------------------");
		setCommitDate(null);
		setCommited(Boolean.FALSE);

		hibSession.update(this);
		
	    if (ApplicationProperty.ClassAssignmentChangePastMeetings.isTrue()) {
			deleteObjects(hibSession,
		              "ClassEvent",
		              "select e.uniqueId from Solution s inner join s.assignments a, ClassEvent e where e.clazz=a.clazz and s.uniqueId=:solutionId");
	    } else {
			EventContact contact = (sendNotificationPuid == null ? null : EventContact.findByExternalUniqueId(sendNotificationPuid));
		    if (contact == null && sendNotificationPuid != null) {
		        TimetableManager manager = TimetableManager.findByExternalId(sendNotificationPuid);
		        if (manager != null) {
			        contact = new EventContact();
			        contact.setFirstName(manager.getFirstName());
		            contact.setMiddleName(manager.getMiddleName());
			        contact.setLastName(manager.getLastName());
			        contact.setExternalUniqueId(manager.getExternalUniqueId());
			        contact.setEmailAddress(manager.getEmailAddress());
			        hibSession.save(contact);
		        }
		    }
		    
			Calendar cal = Calendar.getInstance(Locale.US);
			cal.set(Calendar.HOUR_OF_DAY, 0);
			cal.set(Calendar.MINUTE, 0);
			cal.set(Calendar.SECOND, 0);
			cal.set(Calendar.MILLISECOND, 0);
			Date today = cal.getTime();
			
			List<ClassEvent> events = (List<ClassEvent>)hibSession.createQuery(
					"select e from Solution s inner join s.assignments a, ClassEvent e where e.clazz=a.clazz and s.uniqueId=:solutionId")
					.setLong("solutionId", getUniqueId()).list();
			for (ClassEvent event: events) {
	        	for (Iterator<Meeting> i = event.getMeetings().iterator(); i.hasNext(); )
	        		if (!i.next().getMeetingDate().before(today)) i.remove();
	        	if (event.getMeetings().isEmpty()) {
	        		hibSession.delete(event);
	        	} else {
	    			if (event.getNotes() == null)
	    				event.setNotes(new HashSet<EventNote>());
					EventNote note = new EventNote();
					note.setEvent(event);
					note.setNoteType(EventNote.sEventNoteTypeDeletion);
					note.setTimeStamp(new Date());
					note.setUser(contact == null ? "System" : contact.getName());
					note.setUserId(sendNotificationPuid);
					note.setTextNote(getOwner().getName() + " uncommitted");
					note.setMeetings("N/A");
					event.getNotes().add(note);
	        		hibSession.saveOrUpdate(event);
	        	}
			}
	    }

//		removeDivSecNumbers(hibSession);
		
		if (sendNotificationPuid!=null) sendNotification(this, null, sendNotificationPuid, true, null);

		// Manually fix the Clazz_.committedAssignment cache.
		for (Assignment a: getAssignments())
			a.getClazz().setCommittedAssignment(null);
	}
	
	public boolean commitSolution(Vector messages, org.hibernate.Session hibSession) {
		return commitSolution(messages, hibSession, null);
	}

    public static boolean shareRooms(Assignment a1, Assignment a2) {
        if (!a1.getPlacement().sameRooms(a2.getPlacement())) return false;
        for (Iterator i=a1.getClazz().getDistributionObjects().iterator();i.hasNext();) {
            DistributionObject distObj = (DistributionObject)i.next();
            DistributionPref dp = distObj.getDistributionPref();
            String ref = dp.getDistributionType().getReference();
            if (!"MEET_WITH".equals(ref) && !"CAN_SHARE_ROOM".equals(ref)) continue;
            if (!PreferenceLevel.sRequired.equals(dp.getPrefLevel().getPrefProlog())) continue;
            for (Iterator j=dp.getDistributionObjects().iterator();j.hasNext();) {
                DistributionObject distObj2 = (DistributionObject)j.next();
                if (distObj2.getPrefGroup().equals(a2.getClazz()) || distObj2.getPrefGroup().equals(a2.getClazz().getSchedulingSubpart()))
                    return true;
            }
        }
        for (Iterator i=a1.getClazz().getSchedulingSubpart().getDistributionObjects().iterator();i.hasNext();) {
            DistributionObject distObj = (DistributionObject)i.next();
            DistributionPref dp = distObj.getDistributionPref();
            String ref = dp.getDistributionType().getReference();
            if (!"MEET_WITH".equals(ref) && !"CAN_SHARE_ROOM".equals(ref)) continue;
            if (!PreferenceLevel.sRequired.equals(dp.getPrefLevel().getPrefProlog())) continue;
            for (Iterator j=dp.getDistributionObjects().iterator();j.hasNext();) {
                DistributionObject distObj2 = (DistributionObject)j.next();
                if (distObj2.getPrefGroup().equals(a2.getClazz()) || distObj2.getPrefGroup().equals(a2.getClazz().getSchedulingSubpart()))
                    return true;
            }
        }
        for (Iterator i=a1.getInstructors().iterator();i.hasNext();) {
            DepartmentalInstructor instr = (DepartmentalInstructor)i.next();
            for (Iterator j=instr.getDistributionPreferences().iterator();j.hasNext();) {
                DistributionPref dp = (DistributionPref)j.next();
                String ref = dp.getDistributionType().getReference();
                if (!"MEET_WITH".equals(ref) && !"CAN_SHARE_ROOM".equals(ref)) continue;
                if (!PreferenceLevel.sRequired.equals(dp.getPrefLevel().getPrefProlog())) continue;
                for (Iterator k=DepartmentalInstructor.getAllForInstructor(instr, instr.getDepartment().getSession().getUniqueId()).iterator();k.hasNext();) {
                    DepartmentalInstructor inst2 = (DepartmentalInstructor)k.next();
                    for (Iterator l=inst2.getClasses().iterator();l.hasNext();) {
                        ClassInstructor ci = (ClassInstructor)l.next();
                        if (ci.getClassInstructing().equals(a2.getClazz())) return true;
                    }
                }
            }
        }
        return false;
    }

	public boolean commitSolution(List<String> messages, org.hibernate.Session hibSession, String sendNotificationPuid) {
		List solutions = hibSession.createCriteria(Solution.class).add(Restrictions.eq("owner",getOwner())).list();
		Solution uncommittedSolution = null;
		for (Iterator i=solutions.iterator();i.hasNext();) {
			Solution s = (Solution)i.next();
			if (s.equals(this)) continue;
			if (s.isCommited().booleanValue()) {
				uncommittedSolution = s;
				s.uncommitSolution(hibSession, null);
			}
		}
		if (DEBUG) sLog.debug("commit["+getUniqueId()+","+getOwner().getName()+"] -------------------------------------------------------");
			
		boolean isOK = true;
		for (Object[] o: (List<Object[]>)hibSession.createQuery(
				"select r, a1, a2 from Location r inner join r.assignments a1 inner join r.assignments a2 "+
				"where a1.solution.uniqueId = :solutionId and a2.solution.commited = true and a2.solution.owner.uniqueId != :ownerId and " +
				"bit_and(a1.days, a2.days) > 0 and (a1.timePattern.type = :exactType or a2.timePattern.type = :exactType or " +
				"(a1.startSlot < a2.startSlot + a2.timePattern.slotsPerMtg and a2.startSlot < a1.startSlot + a1.timePattern.slotsPerMtg))")
				.setLong("ownerId",getOwner().getUniqueId())
				.setLong("solutionId",getUniqueId())
				.setInteger("exactType", TimePattern.sTypeExactTime)
				.list()) {
			Location room = (Location)o[0];
			Assignment a = (Assignment)o[1];
			Assignment b = (Assignment)o[2];
			if (!room.isIgnoreRoomCheck() && a.getTimeLocation().hasIntersection(b.getTimeLocation()) && !shareRooms(a, b)) {
				messages.add("Class "+a.getClassName()+" "+a.getTimeLocation().getName(CONSTANTS.useAmPm())+" overlaps with "+b.getClassName()+" "+b.getTimeLocation().getName(CONSTANTS.useAmPm())+" (room "+room.getLabel()+")");
				isOK=false;
			}
		}
		
		for (Object[] o: (List<Object[]>)hibSession.createQuery(
				"select i1, a1, a2 from ClassInstructor c1 inner join c1.instructor i1 inner join c1.classInstructing.assignments a1, " +
						"ClassInstructor c2 inner join c2.instructor i2 inner join c2.classInstructing.assignments a2 where c1.lead = true and c2.lead = true and " +
						"i1.department.solverGroup.uniqueId = :ownerId and i2.department.solverGroup.uniqueId != :ownerId and i2.department.session = :sessionId and " +
						"i1.externalUniqueId is not null and i1.externalUniqueId = i2.externalUniqueId and " + 
						"a1.solution.uniqueId = :solutionId and a2.solution.commited = true and a2.solution.owner.uniqueId != :ownerId and " +
						"bit_and(a1.days, a2.days) > 0 and (a1.timePattern.type = :exactType or a2.timePattern.type = :exactType or " +
						"(a1.startSlot < a2.startSlot + a2.timePattern.slotsPerMtg and a2.startSlot < a1.startSlot + a1.timePattern.slotsPerMtg))")
						.setLong("ownerId",getOwner().getUniqueId())
						.setLong("solutionId",getUniqueId())
						.setLong("sessionId",getOwner().getSession().getUniqueId())
						.setInteger("exactType", TimePattern.sTypeExactTime)
						.list()) {
			DepartmentalInstructor instructor = (DepartmentalInstructor)o[0];
			Assignment a = (Assignment)o[1];
			Assignment b = (Assignment)o[2];
			if (a.getTimeLocation().hasIntersection(b.getTimeLocation()) && !shareRooms(a,b)) {
				messages.add("Class "+a.getClassName()+" "+a.getTimeLocation().getName(CONSTANTS.useAmPm())+" overlaps with "+b.getClassName()+" "+b.getTimeLocation().getName(CONSTANTS.useAmPm())+" (instructor "+instructor.nameLastNameFirst()+")");
				isOK=false;
			}
		}
		
		if (!isOK) {
			if (sendNotificationPuid!=null) sendNotification(uncommittedSolution, this, sendNotificationPuid, false, messages);
			return false;
		}
		
		// NOTE: In order to decrease the amount of interaction between solutions persistance of committed student conflicts was disabled
		/*
		SolverInfoDef defJenrlInfo = SolverInfoDef.findByName(hibSession,"JenrlInfo");
		Hashtable solverInfos = new Hashtable();
		AssignmentDAO adao = new AssignmentDAO(); 
		q = hibSession.createQuery(
				"select a.uniqueId, oa.uniqueId, count(*) from "+
				"Solution s inner join s.assignments a inner join s.studentEnrollments as e, "+
				"Solution os inner join os.assignments oa inner join os.studentEnrollments as oe "+
				"where "+
				"s.uniqueId=:solutionId and os.owner.session.uniqueId=:sessionId and os.owner.uniqueId!=:ownerId and "+
				"a.clazz=e.clazz and oa.clazz=oe.clazz and a.clazz.schedulingSubpart!=oa.clazz.schedulingSubpart and e.studentId=oe.studentId "+
				"group by a.uniqueId, oa.uniqueId");
		q.setLong("ownerId",getOwner().getUniqueId().longValue());
		q.setLong("solutionId",getUniqueId());
		q.setLong("sessionId",getOwner().getSession().getUniqueId().longValue());
		Iterator otherAssignments = q.iterate();
		while (otherAssignments.hasNext()) {
			Object[] result = (Object[])otherAssignments.next();
			Assignment assignment = adao.get((Integer)result[0],hibSession);
			Assignment otherAssignment = adao.get((Integer)result[1],hibSession);
			int jenrl = ((Number)result[2]).intValue();
			
			if (assignment==null || otherAssignment==null || jenrl==0 || !assignment.isInConflict(otherAssignment)) continue;
			addCommitJenrl(hibSession, assignment, otherAssignment, jenrl, defJenrlInfo, solverInfos);
        }	
		
		for (Iterator i=solverInfos.values().iterator();i.hasNext();) {
			SolverInfo sInfo = (SolverInfo)i.next();
			hibSession.saveOrUpdate(sInfo);
		}
		*/
		
		setCommitDate(new Date());
		setCommited(Boolean.TRUE);
		
//		createDivSecNumbers(hibSession, messages);
		
		EventContact contact = null;
		if (sendNotificationPuid!=null) {
		    contact = EventContact.findByExternalUniqueId(sendNotificationPuid);
		    if (contact==null) {
		        TimetableManager manager = TimetableManager.findByExternalId(sendNotificationPuid);
		        if (manager != null) {
		        	contact = new EventContact();
		        	contact.setFirstName(manager.getFirstName());
		        	contact.setMiddleName(manager.getMiddleName());
		        	contact.setLastName(manager.getLastName());
		        	contact.setExternalUniqueId(manager.getExternalUniqueId());
		        	contact.setEmailAddress(manager.getEmailAddress());
		        	hibSession.save(contact);
		        }
		    }
		}
        Hashtable<Long,ClassEvent> classEvents = new Hashtable();
        for (Iterator i=hibSession.createQuery(
                "select e from Solution s inner join s.assignments a, ClassEvent e where e.clazz=a.clazz and s.uniqueId=:solutionId")
                .setLong("solutionId",getUniqueId())
                .iterate(); i.hasNext();) {
            ClassEvent e = (ClassEvent)i.next();
            classEvents.put(e.getClazz().getUniqueId(),e);
        }
		for (Iterator i=getAssignments().iterator();i.hasNext();) {
		    Assignment a = (Assignment)i.next();
		    ClassEvent event = a.generateCommittedEvent(classEvents.get(a.getClassId()),true);
		    classEvents.remove(a.getClassId());
		    if (event != null && !event.getMeetings().isEmpty()) {
		        event.setMainContact(contact);
    			if (event.getNotes() == null)
    				event.setNotes(new HashSet<EventNote>());
				EventNote note = new EventNote();
				note.setEvent(event);
				note.setNoteType(event.getUniqueId() == null ? EventNote.sEventNoteTypeCreateEvent : EventNote.sEventNoteTypeEditEvent);
				note.setTimeStamp(new Date());
				note.setUser(contact == null ? "System" : contact.getName());
				note.setUserId(sendNotificationPuid);
				note.setTextNote(getOwner().getName() + " committed");
				note.setMeetings(a.getPlacement().getLongName(CONSTANTS.useAmPm()));
				event.getNotes().add(note);
		        hibSession.saveOrUpdate(event);
		    }
		    if (event != null && event.getMeetings().isEmpty() && event.getUniqueId() != null)
		    	hibSession.delete(event);
		}
		
		if (ApplicationProperty.ClassAssignmentChangePastMeetings.isTrue()) {
			for (Enumeration e=classEvents.elements();e.hasMoreElements();) {
			    ClassEvent event = (ClassEvent)e.nextElement();
			    hibSession.delete(event);
			}
		} else {
			Calendar cal = Calendar.getInstance(Locale.US);
			cal.set(Calendar.HOUR_OF_DAY, 0);
			cal.set(Calendar.MINUTE, 0);
			cal.set(Calendar.SECOND, 0);
			cal.set(Calendar.MILLISECOND, 0);
			Date today = cal.getTime();

			for (Enumeration e=classEvents.elements();e.hasMoreElements();) {
			    ClassEvent event = (ClassEvent)e.nextElement();
	        	for (Iterator<Meeting> i = event.getMeetings().iterator(); i.hasNext(); )
	        		if (!i.next().getMeetingDate().before(today)) i.remove();
	        	if (event.getMeetings().isEmpty()) {
	        		hibSession.delete(event);
	        	} else {
	    			if (event.getNotes() == null)
	    				event.setNotes(new HashSet<EventNote>());
					EventNote note = new EventNote();
					note.setEvent(event);
					note.setNoteType(EventNote.sEventNoteTypeDeletion);
					note.setTimeStamp(new Date());
					note.setUser(contact == null ? "System" : contact.getName());
					note.setUserId(sendNotificationPuid);
					note.setTextNote(getOwner().getName() + " committed, class was removed or unassigned");
					note.setMeetings("N/A");
					event.getNotes().add(note);
	        		hibSession.saveOrUpdate(event);
	        	}
			}
		}
		
		if (sendNotificationPuid!=null) sendNotification(uncommittedSolution, this, sendNotificationPuid, true, messages);

		// Manually fix the Clazz_.committedAssignment cache.
		for (Assignment a: getAssignments())
			a.getClazz().setCommittedAssignment(a);

		return true;
	}
	
	public static void sendNotification(Solution uncommittedSolution, Solution committedSolution, String puid, boolean success, List<String> messages) {
		try {
			if (ApplicationProperty.EmailNotificationSolutionCommits.isFalse()) 
				return; //email notification disabled

			Formats.Format<Date> sdf = Formats.getDateFormat(Formats.Pattern.DATE_TIME_STAMP);
			SolverGroup owner = (uncommittedSolution==null?committedSolution:uncommittedSolution).getOwner();
			
			String subject = "Solution "+(committedSolution!=null?uncommittedSolution!=null?"recommitted":"committed":"uncommitted")+" for "+owner.getName();
			
			if (!success) {
				subject = "Failed to "+(committedSolution!=null?uncommittedSolution!=null?"recommit":"commit":"uncommit")+" a solution for "+owner.getName();
			}
			
        	String mail = subject;
        	mail += "\r\n";
        	mail += "\r\n";
        	if (messages!=null && !messages.isEmpty()) {
        		mail += "Message(s): ----------------- \r\n";
        		for (String m: messages) {
        			mail += m + "\r\n";
        		}
            	mail += "\r\n";
            	mail += "\r\n";
        	}
        	if (committedSolution!=null) {
        		mail += "Committed solution info: -------------- \r\n";
        		mail += "Created: "+sdf.format(committedSolution.getCreated())+"\r\n";
        		mail += "Owner: "+committedSolution.getOwner().getName()+"\r\n";
                if (committedSolution.getCommitDate()!=null)
                    mail += "Commited: "+sdf.format(committedSolution.getCommitDate())+"\r\n";
        		if (committedSolution.getNote()!=null && committedSolution.getNote().trim().length()>0) 
        			mail+= "Note: "+committedSolution.getNote()+"\r\n";
        		PropertiesInfo globalInfo = (PropertiesInfo)committedSolution.getInfo("GlobalInfo");
    			Vector infos = new Vector(globalInfo.keySet());
    			Collections.sort(infos, new InfoComparator());
        		for (Enumeration e=infos.elements();e.hasMoreElements();) {
        			String key = (String)e.nextElement();
        			String value = (String)globalInfo.getProperty(key);
        			mail += key+": "+value+"\r\n";
        		}
            	mail += "\r\n";
            	mail += "\r\n";
        	}
        	if (uncommittedSolution!=null) {
        		mail += "Uncommitted solution info: -------------- \r\n";
        		mail += "Created: "+sdf.format(uncommittedSolution.getCreated())+"\r\n";
        		mail += "Owner: "+uncommittedSolution.getOwner().getName()+"\r\n";
        		if (uncommittedSolution.getNote()!=null && uncommittedSolution.getNote().trim().length()>0) 
        			mail+= "Note: "+uncommittedSolution.getNote()+"\r\n";
        		PropertiesInfo globalInfo = (PropertiesInfo)uncommittedSolution.getInfo("GlobalInfo");
    			Vector infos = new Vector(globalInfo.keySet());
    			Collections.sort(infos, new InfoComparator());
        		for (Enumeration e=infos.elements();e.hasMoreElements();) {
        			String key = (String)e.nextElement();
        			String value = (String)globalInfo.getProperty(key);
        			mail += key+": "+value+"\r\n";
        		}
            	mail += "\r\n";
            	mail += "\r\n";
        	}
        	
        	TimetableManager mgr = TimetableManager.findByExternalId(puid);
        	
        	mail += "Manager info -------------- \r\n";
        	mail += "Name: "+mgr.getName()+"\r\n";
        	//mail += "PUID: "+mgr.getPuid()+"\r\n";
        	mail += "Email: "+mgr.getEmailAddress()+"\r\n";
        	mail += "\r\n";
        	mail += "Session info -------------- \r\n";
        	mail += "Session Term: "+owner.getSession().getAcademicYearTerm()+"\r\n";
        	mail += "Session Initiative: "+owner.getSession().getAcademicInitiative()+"\r\n";
        	mail += "Departments (from solver group): \r\n";
        	for (Iterator i=owner.getDepartments().iterator();i.hasNext();) {
        		Department d = (Department)i.next();
        		mail += "  "+d.getLabel()+"\r\n";
        	}
        	mail += "\r\n";
        	mail += "Application info -------------- \r\n";
        	mail += "Version: "+Constants.getVersion()+" ("+Constants.getReleaseDate()+")\r\n";
        	mail += "TimeStamp: "+(new Date());
        	
        	Email email = Email.createEmail();
        	email.addRecipient(mgr.getEmailAddress(), mgr.getName());
        	email.addNotifyCC();
        	email.setSubject("UniTime (Solution Commit): "+subject);
        	email.setText(mail);
        	email.send();
		} catch (Exception e) {
			sLog.error("Unable to send solution commit/uncommit notification, reason: "+e.getMessage(),e);
		}
	}
	
	public void export(CSVFile file, String instructorFormat) {
		file.setSeparator(",");
		file.setQuotationMark("\"");
		if (isCommited().booleanValue()) {
			file.setHeader(new CSVField[] {
					new CSVField("COURSE"),
					new CSVField("ITYPE"),
					new CSVField("SECTION"),
					new CSVField("SUFFIX"),
					new CSVField("EXTERNAL_ID"),
					new CSVField("DATE_PATTERN"),
					new CSVField("DAY"),
					new CSVField("START_TIME"),
					new CSVField("END_TIME"),
					new CSVField("ROOM"),
					new CSVField("INSTRUCTOR"),
					new CSVField("SCHEDULE_NOTE")
				});
		} else {
			file.setHeader(new CSVField[] {
				new CSVField("COURSE"),
				new CSVField("ITYPE"),
				new CSVField("SECTION"),
				new CSVField("SUFFIX"),
				new CSVField("DATE_PATTERN"),
				new CSVField("DAY"),
				new CSVField("START_TIME"),
				new CSVField("END_TIME"),
				new CSVField("ROOM"),
				new CSVField("INSTRUCTOR"),
				new CSVField("SCHEDULE_NOTE")
			});
		}
		
		Vector assignments = new Vector(getAssignments());
		
		assignments.addAll(getOwner().getNotAssignedClasses(this));
		
		Collections.sort(assignments, new ClassOrAssignmentComparator());
		for (Iterator i=assignments.iterator();i.hasNext();) {
			Object o = i.next();
			if (o instanceof Assignment) {
				Assignment assignment = (Assignment)o;
				Class_ clazz = assignment.getClazz();
				List<DepartmentalInstructor> leads = clazz.getLeadInstructors();
				StringBuffer leadsSb = new StringBuffer();
				for (Iterator<DepartmentalInstructor> e=leads.iterator();e.hasNext();) {
					DepartmentalInstructor instructor = (DepartmentalInstructor)e.next();
					leadsSb.append(instructor.getName(instructorFormat));
					if (e.hasNext()) leadsSb.append(";");
				}
				Placement placement = assignment.getPlacement();
				if (isCommited().booleanValue()) {
					file.addLine(new CSVField[] {
							new CSVField(clazz.getCourseName()),
							new CSVField(clazz.getItypeDesc()),
							new CSVField(clazz.getSectionNumber()),
							new CSVField(clazz.getSchedulingSubpart().getSchedulingSubpartSuffix()),
							new CSVField(clazz.getDivSecNumber()),
							new CSVField(clazz.effectiveDatePattern().getName()),
							new CSVField(placement.getTimeLocation().getDayHeader()),
							new CSVField(placement.getTimeLocation().getStartTimeHeader(CONSTANTS.useAmPm())),
							new CSVField(placement.getTimeLocation().getEndTimeHeader(CONSTANTS.useAmPm())),
							new CSVField(placement.getRoomName(",")),
							new CSVField(leadsSb),
							new CSVField(clazz.getSchedulePrintNote()==null?"":clazz.getSchedulePrintNote())
					});
				} else {
					file.addLine(new CSVField[] {
							new CSVField(clazz.getCourseName()),
							new CSVField(clazz.getItypeDesc()),
							new CSVField(clazz.getSectionNumber()),
							new CSVField(clazz.getSchedulingSubpart().getSchedulingSubpartSuffix()),
							new CSVField(clazz.effectiveDatePattern().getName()),
							new CSVField(placement.getTimeLocation().getDayHeader()),
							new CSVField(placement.getTimeLocation().getStartTimeHeader(CONSTANTS.useAmPm())),
							new CSVField(placement.getTimeLocation().getEndTimeHeader(CONSTANTS.useAmPm())),
							new CSVField(placement.getRoomName(",")),
							new CSVField(leadsSb),
							new CSVField(clazz.getSchedulePrintNote()==null?"":clazz.getSchedulePrintNote())
					});
				}
			} else {
				Class_ clazz = (Class_)o;
				List<DepartmentalInstructor> leads = clazz.getLeadInstructors();
				StringBuffer leadsSb = new StringBuffer();
				for (Iterator<DepartmentalInstructor> e=leads.iterator();e.hasNext();) {
					DepartmentalInstructor instructor = (DepartmentalInstructor)e.next();
					leadsSb.append(instructor.getName(instructorFormat));
					if (e.hasNext()) leadsSb.append(";");
				}
				if (isCommited().booleanValue()) {
					file.addLine(new CSVField[] {
							new CSVField(clazz.getCourseName()),
							new CSVField(clazz.getItypeDesc()),
							new CSVField(clazz.getSectionNumber()),
							new CSVField(clazz.getSchedulingSubpart().getSchedulingSubpartSuffix()),
							new CSVField(clazz.getDivSecNumber()),
							new CSVField(clazz.effectiveDatePattern().getName()),
							new CSVField(""),
							new CSVField(""),
							new CSVField(""),
							new CSVField(""),
							new CSVField(leadsSb),
							new CSVField(clazz.getSchedulePrintNote()==null?"":clazz.getSchedulePrintNote())
					});
				} else {
					file.addLine(new CSVField[] {
							new CSVField(clazz.getCourseName()),
							new CSVField(clazz.getItypeDesc()),
							new CSVField(clazz.getSectionNumber()),
							new CSVField(clazz.getSchedulingSubpart().getSchedulingSubpartSuffix()),
							new CSVField(clazz.effectiveDatePattern().getName()),
							new CSVField(""),
							new CSVField(""),
							new CSVField(""),
							new CSVField(""),
							new CSVField(leadsSb),
							new CSVField(clazz.getSchedulePrintNote()==null?"":clazz.getSchedulePrintNote())
					});
				}
			}
		}
	}
	
	private static class ClassOrAssignmentComparator implements Comparator {
		ClassComparator cc = new ClassComparator(ClassComparator.COMPARE_BY_HIERARCHY);
		public int compare(Object o1, Object o2) {
			if (o1==null || o2==null) return 0;
			Class_ c1 = (o1 instanceof Class_ ? (Class_)o1 : ((Assignment)o1).getClazz());
			Class_ c2 = (o2 instanceof Class_ ? (Class_)o2 : ((Assignment)o2).getClazz());
			return cc.compare(c1,c2);
		}
	}
	
	private void deleteObjects(org.hibernate.Session hibSession, String objectName, String idQuery) {
		Iterator idIterator = hibSession.createQuery(idQuery).setLong("solutionId",getUniqueId()).iterate();
		StringBuffer ids = new StringBuffer();
		int idx = 0;
		while (idIterator.hasNext()) {
			ids.append(idIterator.next()); idx++;
			if (idx==100) {
				hibSession.createQuery("delete "+objectName+" as x where x.uniqueId in ("+ids+")").executeUpdate();
				ids = new StringBuffer();
				idx = 0;
			} else if (idIterator.hasNext()) {
				ids.append(",");
			}
		}
		if (idx>0)
			hibSession.createQuery("delete "+objectName+" as x where x.uniqueId in ("+ids+")").executeUpdate();
	}
	
	public void delete(org.hibernate.Session hibSession) {
		// NOTE: In order to decrease the amount of interaction between solutions persistance of committed student conflicts was disabled
		/*
		Iterator i = hibSession.createQuery(
				"select distinct c, oa from "+
				"ConstraintInfo c inner join c.assignments a, Assignment oa "+
				"where "+
				"a.solution.uniqueId=:solutionId and oa.solution.uniqueId!=:solutionId and oa in elements ( c.assignments) ")
				.setLong("solutionId",getUniqueId())
				.iterate();
		while (i.hasNext()) {
			Object[] next = (Object[])i.next();
			ConstraintInfo c = (ConstraintInfo)next[0];
			Assignment oa = (Assignment)next[1];
			oa.getConstraintInfo().remove(c);
			hibSession.saveOrUpdate(oa);
		}
		*/
		
		try {
			SolutionInfo solutionInfo = getSolutionInfo("CBSInfo");
			if (solutionInfo!=null)
				solutionInfo.delete(hibSession);
		} catch (Exception e) {
			Debug.error(e);
		}
		
        hibSession.createQuery(
				"delete StudentEnrollment x where x.solution.uniqueId=:solutionId ")
				.setLong("solutionId", getUniqueId().longValue())
				.executeUpdate();
		
		hibSession.createQuery(
				"delete JointEnrollment x where x.solution.uniqueId=:solutionId ")
				.setLong("solutionId", getUniqueId().longValue())
				.executeUpdate();

		deleteObjects(
				hibSession,
				"SolverInfo",
				"select a.uniqueId from AssignmentInfo a where a.assignment.solution.uniqueId=:solutionId"
				);
		
		deleteObjects(
				hibSession,
				"SolverInfo",
				"select s.uniqueId from SolutionInfo s where s.solution.uniqueId=:solutionId"
				);

		deleteObjects(
				hibSession,
				"SolverInfo",
				"select c.uniqueId from ConstraintInfo c inner join c.assignments a where a.solution.uniqueId=:solutionId"
				);
		
		hibSession.createQuery(
				"delete Assignment x where x.solution.uniqueId=:solutionId ")
				.setLong("solutionId", getUniqueId())
				.executeUpdate();
		
		deleteObjects(
				hibSession,
				"SolverParameter",
				"select p.uniqueId from Solution s inner join s.parameters p where s.uniqueId=:solutionId"
				);
		
		getOwner().getSolutions().remove(this);
		
		hibSession.delete(this);
	}
	
	public void empty(org.hibernate.Session hibSession, TimetableInfoFileProxy proxy) {
		// NOTE: In order to decrease the amount of interaction between solutions persistance of committed student conflicts was disabled
		/*
		Iterator i = hibSession.createQuery(
				"select distinct c, oa from "+
				"ConstraintInfo c inner join c.assignments a, Assignment oa "+
				"where "+
				"a.solution.uniqueId=:solutionId and oa.solution.uniqueId!=:solutionId and oa in elements ( c.assignments) ")
				.setLong("solutionId",getUniqueId())
				.iterate();
		while (i.hasNext()) {
			Object[] next = (Object[])i.next();
			ConstraintInfo c = (ConstraintInfo)next[0];
			Assignment oa = (Assignment)next[1];
			oa.getConstraintInfo().remove(c);
			hibSession.saveOrUpdate(oa);
		}
		*/
		
		try {
			SolutionInfo solutionInfo = getSolutionInfo("CBSInfo");
			if (solutionInfo!=null)
				solutionInfo.delete(hibSession, proxy);
		} catch (Exception e) {
			Debug.error(e);
		}
		
		hibSession.flush(); 

		hibSession.createQuery(
				"delete StudentEnrollment x where x.solution.uniqueId=:solutionId ")
				.setLong("solutionId", getUniqueId())
				.executeUpdate();
		
		hibSession.createQuery(
				"delete JointEnrollment x where x.solution.uniqueId=:solutionId ) ")
				.setLong("solutionId", getUniqueId())
				.executeUpdate();

		deleteObjects(
				hibSession,
				"SolverInfo",
				"select a.uniqueId from AssignmentInfo a where a.assignment.solution.uniqueId=:solutionId"
				);
		
		deleteObjects(
				hibSession,
				"SolverInfo",
				"select s.uniqueId from SolutionInfo s where s.solution.uniqueId=:solutionId"
				);

		deleteObjects(
				hibSession,
				"SolverInfo",
				"select c.uniqueId from ConstraintInfo c inner join c.assignments a where a.solution.uniqueId=:solutionId"
				);
		
		hibSession.createQuery(
				"delete Assignment x where x.solution.uniqueId=:solutionId ) ")
				.setLong("solutionId", getUniqueId())
				.executeUpdate();
		
		deleteObjects(
				hibSession,
				"SolverParameter",
				"select p.uniqueId from Solution s inner join s.parameters p where s.uniqueId=:solutionId"
				);
		
		setAssignments(null);
		setSolutionInfo(null);
		setJointEnrollments(null);
		setStudentEnrollments(null);
		setParameters(null);
		
		hibSession.saveOrUpdate(this);

		hibSession.flush();
	}
	
	public void updateCommittedStudentEnrollmentInfos(org.hibernate.Session hibSession) throws Exception {
		SolutionInfo sInfo = getSolutionInfo("GlobalInfo");
		if (sInfo!=null) {
			PropertiesInfo propInfo = (PropertiesInfo)sInfo.getInfo();
			String conf = propInfo.getProperty("Student conflicts");
			int studentConf = Integer.parseInt(conf.substring(0,conf.indexOf(' ')));
			if (conf.indexOf("committed:")>=0) {
				int commitedStart = conf.indexOf("committed:")+"committed:".length();
				int commitedEnd = conf.indexOf(',',commitedStart);
				int commitedConf = Integer.parseInt(conf.substring(commitedStart,commitedEnd));
			    //String newConf = (studentConf-commitedConf)+" [committed:"+0+conf.substring(commitedEnd);
				String newConf = (studentConf-commitedConf)+" ["+conf.substring(commitedEnd+2);
				propInfo.setProperty("Student conflicts", newConf);
			}
			sInfo.setInfo(propInfo);
			hibSession.saveOrUpdate(sInfo);
		}
		
		//NOTE: In order to decrease the amount of interaction between solutions persistance of committed student conflicts was disabled
		/*
		if (getAssignments()!=null) {
			for (Iterator i=getAssignments().iterator();i.hasNext();) {
				Assignment a = (Assignment)i.next();
				for (Iterator j=a.getAssignmentInfo().iterator();j.hasNext();) {
					AssignmentInfo aInfo = (AssignmentInfo)j.next();
					if (!"AssignmentInfo".equals(aInfo.getDefinition().getName())) continue;
					AssignmentPreferenceInfo assignmentInfo = (AssignmentPreferenceInfo)aInfo.getInfo();
					assignmentInfo.setNrStudentConflicts(assignmentInfo.getNrStudentConflicts()-assignmentInfo.getNrCommitedStudentConflicts());
					assignmentInfo.setNrCommitedStudentConflicts(0);
					aInfo.setInfo(assignmentInfo);
					hibSession.saveOrUpdate(aInfo);
				}
				for (Iterator j=a.getConstraintInfos("JenrlInfo").iterator();j.hasNext();) {
					ConstraintInfo c = (ConstraintInfo)j.next();
					boolean isCommittedInfo = false;
					for (Iterator k=c.getAssignments().iterator();k.hasNext();) {
						Assignment oa = (Assignment)k.next();
						if (!oa.getSolution().equals(this)) {
							oa.getConstraintInfo().remove(c);
							hibSession.saveOrUpdate(oa);
							isCommittedInfo = true;
						}
					}
					if (isCommittedInfo) {
						a.getConstraintInfo().remove(c);
						hibSession.saveOrUpdate(a);
						hibSession.delete(c);
					}
				}
			}
		}
		
		SolverInfoDef defJenrlInfo = SolverInfoDef.findByName(hibSession,"JenrlInfo");
		Hashtable solverInfos = new Hashtable();
		AssignmentDAO adao = new AssignmentDAO(); 
		Query q = hibSession.createQuery(
				"select a.uniqueId, oa.uniqueId, count(*) from "+
				"Solution s inner join s.assignments a inner join s.studentEnrollments as e, "+
				"Solution os inner join os.assignments oa inner join os.studentEnrollments as oe "+
				"where "+
				"s.uniqueId=:solutionId and os.owner.session.uniqueId=:sessionId and os.owner.uniqueId!=:ownerId and os.commited=true and "+
				"a.clazz=e.clazz and oa.clazz=oe.clazz and a.clazz.schedulingSubpart!=oa.clazz.schedulingSubpart and e.studentId=oe.studentId "+
				"group by a.uniqueId, oa.uniqueId");
		q.setLong("ownerId",getOwner().getUniqueId().longValue());
		q.setLong("solutionId",getUniqueId());
		q.setLong("sessionId",getOwner().getSession().getUniqueId().longValue());
		Iterator otherAssignments = q.iterate();
		while (otherAssignments.hasNext()) {
			Object[] result = (Object[])otherAssignments.next();
			Assignment assignment = adao.get((Integer)result[0],hibSession);
			Assignment otherAssignment = adao.get((Integer)result[1],hibSession);
			int jenrl = ((Number)result[2]).intValue();
			
			if (assignment==null || otherAssignment==null || jenrl==0 || !assignment.isInConflict(otherAssignment)) continue;
			addCommitJenrl(hibSession, otherAssignment, assignment, jenrl, defJenrlInfo, solverInfos);
        }
		
		for (Iterator i=solverInfos.values().iterator();i.hasNext();) {
			SolverInfo solverInfo = (SolverInfo)i.next();
			hibSession.saveOrUpdate(solverInfo);
		}
		*/
	}
	
	public Session getSession() {
		return getOwner().getSession();
	}
	
    public static Collection findBySessionId(Long sessionId) {
    	return (new SolutionDAO()).
    			getSession().
    			createQuery("select s from Solution s where s.owner.session.uniqueId=:sessionId").
    			setLong("sessionId", sessionId.longValue()).
    			//setCacheable(true).
    			list();
    }

    private HashSet takenDivisionNumbers(SchedulingSubpart subpart) {
    	HashSet divNums = new HashSet();

    	InstructionalOffering offering = subpart.getInstrOfferingConfig().getInstructionalOffering();
    	ItypeDesc itype = subpart.getItype();
   		for (Iterator i=offering.getInstrOfferingConfigs().iterator();i.hasNext();) {
   			InstrOfferingConfig cfg = (InstrOfferingConfig)i.next();
   			for (Iterator j=cfg.getSchedulingSubparts().iterator();j.hasNext();) {
   				SchedulingSubpart s = (SchedulingSubpart)j.next();
   				if (!s.getItype().equals(itype)) continue;
   		    	for (Iterator k=s.getClasses().iterator();k.hasNext();) {
   		    		Class_ clazz = (Class_)k.next();
   		    		if (clazz.getClassSuffix()==null) continue;
   		    		Integer divNum = Integer.valueOf(clazz.getClassSuffix().substring(0,3));
   		    		divNums.add(divNum);
   		    	}
   			}
   		}

    	return divNums;
    }
    
    public void createDivSecNumbers(org.hibernate.Session hibSession, Vector messages) {
    	Vector assignments = new Vector(getAssignments());
        assignments.addAll(new SolutionDAO().getSession().createQuery(
                "select distinct c from Class_ c, Solution s inner join s.owner.departments d "+
                "where s.uniqueId = :solutionId and c.managingDept=d and "+
                "c.uniqueId not in (select a.clazz.uniqueId from s.assignments a)").
                setLong("solutionId", getUniqueId().longValue()).
                list());
        HashSet relatedOfferings = new HashSet();
        for (Enumeration e=assignments.elements();e.hasMoreElements();) {
            Object o = e.nextElement();
            Assignment assignment = (o instanceof Assignment?(Assignment)o:null);
            Class_ clazz = (assignment==null?(Class_)o:assignment.getClazz());
            relatedOfferings.add(clazz.getSchedulingSubpart().getInstrOfferingConfig().getInstructionalOffering());
        }
        for (Iterator i=relatedOfferings.iterator();i.hasNext();) {
            InstructionalOffering io = (InstructionalOffering)i.next();
            for (Iterator j=io.getInstrOfferingConfigs().iterator();j.hasNext();) {
                InstrOfferingConfig ioc = (InstrOfferingConfig)j.next();
                for (Iterator k=ioc.getSchedulingSubparts().iterator();k.hasNext();) {
                    SchedulingSubpart subpart = (SchedulingSubpart)k.next();
                    for (Iterator l=subpart.getClasses().iterator();l.hasNext();) {
                        Class_ clazz = (Class_)l.next();
                        if (clazz.getClassSuffix()!=null && !getOwner().getDepartments().contains(clazz.getManagingDept())) {
                            Assignment assignment = clazz.getCommittedAssignment();
                            assignments.add(assignment==null?(Object)clazz:(Object)assignment);
                            clazz.setClassSuffix(null);
                        }
                    }
                }
            }
        }
    	DivSecAssignmentComparator cmp = new DivSecAssignmentComparator(this, true, false);
    	Collections.sort(assignments, cmp);
    	
    	Assignment lastAssignment = null;
    	SchedulingSubpart lastSubpart = null;
    	Class_ lastClazz = null;
    	int divNum = 1, secNum = 0;
    	HashSet takenDivNums = null;
    	
    	HashSet recompute = new HashSet();
    	
    	for (Enumeration e=assignments.elements();e.hasMoreElements();) {
            Object o = e.nextElement();
    		Assignment assignment = (o instanceof Assignment?(Assignment)o:null);
    		Class_ clazz = (assignment==null?(Class_)o:assignment.getClazz());
    		
    		if (clazz.getParentClass()!=null && clazz.getSchedulingSubpart().getItype().equals(clazz.getParentClass().getSchedulingSubpart().getItype()))
    			continue;
    		
    		if (lastSubpart==null || !lastSubpart.equals(clazz.getSchedulingSubpart())) {
    			takenDivNums = takenDivisionNumbers(clazz.getSchedulingSubpart());
    			lastAssignment = null; lastSubpart = null; lastClazz = null;
    		}
    		
    		int nrClasses = clazz.getSchedulingSubpart().getInstrOfferingConfig().getInstructionalOffering().getNrClasses(clazz.getSchedulingSubpart().getItype());
    		
    		if (lastAssignment!=null && assignment!=null) {
    			if (nrClasses>=100 && cmp.compareTimeLocations(lastAssignment.getClazz(), assignment.getClazz(), lastAssignment.getTimeLocation(),assignment.getTimeLocation())==0) {
        			if (lastClazz!=null && 
        					clazz.getParentClass()!=null && 
        					!clazz.getParentClass().equals(lastClazz.getParentClass()) && 
        					clazz.getParentClass().getDivSecNumber()!=null && 
        					lastClazz.getParentClass().getDivSecNumber()!=null) {
        				if (cmp.compareTimeLocations(lastAssignment.getClazz(), assignment.getClazz(), lastAssignment.getTimeLocation(),assignment.getTimeLocation())==0 && 
        						clazz.getParentClass().getDivSecNumber().substring(0,3).equals(lastClazz.getParentClass().getDivSecNumber().substring(0,3))) {
        					secNum++;
        				} else {
       						divNum++; secNum = 1;
       						while (takenDivNums.contains(new Integer(divNum))) divNum++;
        				}
        			} else {
        				secNum++;
        			}
    			} else {
    				divNum++; secNum = 1;
    				while (takenDivNums.contains(new Integer(divNum))) divNum++;
    			}
            } else if (lastClazz!=null) {
                divNum++; secNum = 1;
                while (takenDivNums.contains(new Integer(divNum))) divNum++;
    		} else {
    			divNum = 1; secNum = 1;
    			while (takenDivNums.contains(new Integer(divNum))) divNum++;
    		}
    		
    		if (divNum==100 && secNum==1) {
    			sLog.warn("Division number exceeded 99 for scheduling subpart "+clazz.getSchedulingSubpart().getSchedulingSubpartLabel()+".");
    	    	for (Iterator i=clazz.getSchedulingSubpart().getInstrOfferingConfig().getInstructionalOffering().getInstrOfferingConfigs().iterator();i.hasNext();) {
    	    		InstrOfferingConfig cfg = (InstrOfferingConfig)i.next();
    	    		for (Iterator j=cfg.getSchedulingSubparts().iterator();j.hasNext();) {
    	    			SchedulingSubpart subpart = (SchedulingSubpart)j.next();
    	    			if (subpart.getItype().equals(clazz.getSchedulingSubpart().getItype()))
    	    				recompute.add(subpart);
    	    		}
    	    	}
    		}
    		
    		clazz.setClassSuffix(sSufixFormat.format(divNum)+sSufixFormat.format(secNum));
    		hibSession.update(clazz);
    		
    		lastAssignment = assignment;
    		lastSubpart = clazz.getSchedulingSubpart();
    		lastClazz = clazz;
    	}
    	
    	if (!recompute.isEmpty()) {
    		HashSet recompute2 = new HashSet();
        	for (Iterator i=assignments.iterator();i.hasNext();) {
                Object o = i.next();
                Assignment assignment = (o instanceof Assignment?(Assignment)o:null);
                Class_ clazz = (assignment==null?(Class_)o:assignment.getClazz());
        		if (recompute.contains(clazz.getSchedulingSubpart())) {
        			clazz.setClassSuffix(null);
        			hibSession.update(clazz);
        		} else {
        			i.remove();
        		}
        	}
    		cmp = new DivSecAssignmentComparator(this, false, false);
        	Collections.sort(assignments, cmp);
    		lastAssignment = null; lastSubpart = null; lastClazz = null;
        	for (Enumeration e=assignments.elements();e.hasMoreElements();) {
                Object o = e.nextElement();
                Assignment assignment = (o instanceof Assignment?(Assignment)o:null);
                Class_ clazz = (assignment==null?(Class_)o:assignment.getClazz());

        		if (lastSubpart==null || !lastSubpart.equals(clazz.getSchedulingSubpart())) {
        			takenDivNums = takenDivisionNumbers(clazz.getSchedulingSubpart());
        			lastAssignment = null; lastSubpart = null; lastClazz = null;
        		}

        		if (lastAssignment!=null && assignment!=null) {
        			if (cmp.compareTimeLocations(lastAssignment.getClazz(), assignment.getClazz(), lastAssignment.getTimeLocation(),assignment.getTimeLocation())==0) {
        				secNum++;
        			} else {
        				divNum++; secNum = 1;
           				while (takenDivNums.contains(new Integer(divNum))) divNum++;
        			}
                } else if (lastClazz!=null) {
                    divNum++; secNum = 1;
                    while (takenDivNums.contains(new Integer(divNum))) divNum++;
                } else {
        			divNum = 1; secNum = 1;
        			while (takenDivNums.contains(new Integer(divNum))) divNum++;
        		}

        		if (divNum==100 && secNum==1) {
        			sLog.warn("Division number still (fallback) exceeded 99 for scheduling subpart "+clazz.getSchedulingSubpart().getSchedulingSubpartLabel()+".");
        	    	for (Iterator i=clazz.getSchedulingSubpart().getInstrOfferingConfig().getInstructionalOffering().getInstrOfferingConfigs().iterator();i.hasNext();) {
        	    		InstrOfferingConfig cfg = (InstrOfferingConfig)i.next();
        	    		for (Iterator j=cfg.getSchedulingSubparts().iterator();j.hasNext();) {
        	    			SchedulingSubpart subpart = (SchedulingSubpart)j.next();
        	    			if (subpart.getItype().equals(clazz.getSchedulingSubpart().getItype()))
        	    				recompute2.add(subpart);
        	    		}
        	    	}
        		}

        		clazz.setClassSuffix(sSufixFormat.format(divNum)+sSufixFormat.format(secNum));
        		hibSession.update(clazz);
        		
        		lastAssignment = assignment;
        		lastSubpart = clazz.getSchedulingSubpart();
        		lastClazz = clazz;
        	}
        	
        	if (!recompute2.isEmpty()) {
            	for (Iterator i=assignments.iterator();i.hasNext();) {
                    Object o = i.next();
                    Assignment assignment = (o instanceof Assignment?(Assignment)o:null);
                    Class_ clazz = (assignment==null?(Class_)o:assignment.getClazz());
            		if (recompute2.contains(clazz.getSchedulingSubpart())) {
            			clazz.setClassSuffix(null);
            			hibSession.update(clazz);
            		} else {
            			i.remove();
            		}
            	}
        		cmp = new DivSecAssignmentComparator(this, false, true);
            	Collections.sort(assignments, cmp);
        		lastAssignment = null; lastSubpart = null; lastClazz = null;
            	for (Enumeration e=assignments.elements();e.hasMoreElements();) {
                    Object o = e.nextElement();
                    Assignment assignment = (o instanceof Assignment?(Assignment)o:null);
                    Class_ clazz = (assignment==null?(Class_)o:assignment.getClazz());

            		if (lastSubpart==null || cmp.compareSchedulingSubparts(lastSubpart, clazz.getSchedulingSubpart())!=0) {
            			takenDivNums = takenDivisionNumbers(clazz.getSchedulingSubpart());
            			lastAssignment = null; lastSubpart = null; lastClazz = null;
            		}

            		if (lastAssignment!=null && assignment!=null) {
            			if (cmp.compareTimeLocations(lastAssignment.getClazz(), assignment.getClazz(), lastAssignment.getTimeLocation(),assignment.getTimeLocation())==0) {
            				secNum++;
            			} else {
            				divNum++; secNum = 1;
               				while (takenDivNums.contains(new Integer(divNum))) divNum++;
            			}
                    } else if (lastClazz!=null) {
                        divNum++; secNum = 1;
                        while (takenDivNums.contains(new Integer(divNum))) divNum++;
            		} else {
            			divNum = 1; secNum = 1;
            			while (takenDivNums.contains(new Integer(divNum))) divNum++;
            		}

            		if (divNum==100 && secNum==1) {
            			messages.add("Division number exceeded 99 for scheduling subpart "+clazz.getSchedulingSubpart().getSchedulingSubpartLabel()+".");
            			sLog.warn("Division number still (fallback2) exceeded 99 for scheduling subpart "+clazz.getSchedulingSubpart().getSchedulingSubpartLabel()+".");
            		}

            		clazz.setClassSuffix(sSufixFormat.format(divNum)+sSufixFormat.format(secNum));
            		hibSession.update(clazz);
            		
            		lastAssignment = assignment;
            		lastSubpart = clazz.getSchedulingSubpart();
            		lastClazz = clazz;
            	}
        	}
    	}
        
        /*
        lastSubpart = null;
        TreeSet otherClasses = new TreeSet(new ClassComparator(ClassComparator.COMPARE_BY_HIERARCHY));
        otherClasses.addAll(new SolutionDAO().getSession().createQuery(
                "select distinct c from Class_ c, Solution s inner join s.owner.departments d "+
                "where s.uniqueId = :solutionId and c.managingDept=d and "+
                "c.uniqueId not in (select a.clazz.uniqueId from s.assignments a) order by c.schedulingSubpart.uniqueId, c.sectionNumberCache").
                setLong("solutionId", getUniqueId().longValue()).
                list());
        
        for (Iterator i=otherClasses.iterator();i.hasNext();) {
            Class_ clazz = (Class_)i.next();
            
            if (clazz.getParentClass()!=null && clazz.getSchedulingSubpart().getItype().equals(clazz.getParentClass().getSchedulingSubpart().getItype()))
                continue;

            if (clazz.getClassSuffix()!=null) {
                sLog.warn("This is odd, class "+clazz.getClassLabel()+" already has a div-sec number "+clazz.getClassSuffix()+".");
                continue;
            }
            
            if (lastSubpart==null || !lastSubpart.equals(clazz.getSchedulingSubpart())) {
                takenDivNums = takenDivisionNumbers(clazz.getSchedulingSubpart());
            }
            divNum = 1;
            while (takenDivNums.contains(new Integer(divNum)))
                divNum++;
            if (divNum==100) {
                messages.add("Division number exceeded 99 for scheduling subpart "+clazz.getSchedulingSubpart().getSchedulingSubpartLabel()+".");
            }
            clazz.setClassSuffix(sSufixFormat.format(divNum)+sSufixFormat.format(1));
            takenDivNums.add(new Integer(divNum));
            lastSubpart = clazz.getSchedulingSubpart();
            hibSession.update(clazz);
        }
        */
    }
    
    public void removeDivSecNumbers(org.hibernate.Session hibSession) {
    	HashSet classes = new HashSet();
    	for (Iterator i=getAssignments().iterator();i.hasNext();) {
    		Assignment assignment = (Assignment)i.next();
    		Class_ clazz = assignment.getClazz();
    		if (clazz==null || clazz.getClassSuffix()==null) continue;
    		classes.add(clazz);
    	}
        
        HashSet subparts2fix = new HashSet();
    	
    	for (Iterator i=classes.iterator();i.hasNext();) {
    		Class_ clazz = (Class_)i.next();
    		
    		clazz.setClassSuffix(null);
            
            subparts2fix.add(clazz.getSchedulingSubpart());
            
            hibSession.update(clazz);
    	}
    	
        List otherClasses = new SolutionDAO().getSession().createQuery(
                "select distinct c from Class_ c, Solution s inner join s.owner.departments d "+
                "where s.uniqueId = :solutionId and c.managingDept=d and "+
                "c.uniqueId not in (select a.clazz.uniqueId from s.assignments a)").
                setLong("solutionId", getUniqueId().longValue()).
                list();
        for (Iterator i=otherClasses.iterator();i.hasNext();) {
            Class_ clazz = (Class_)i.next();
            if (clazz.getClassSuffix()==null) continue;
            clazz.setClassSuffix(null);
            
            subparts2fix.add(clazz.getSchedulingSubpart());
            
            hibSession.update(clazz);
        }
        
        for (Iterator i=subparts2fix.iterator();i.hasNext();) {
            SchedulingSubpart subpart = (SchedulingSubpart)i.next();
            
            TreeSet takenDivNums = new TreeSet(takenDivisionNumbers(subpart));
            int dec = 0, lastDiv = 0;
            
            for (Iterator j=takenDivNums.iterator();j.hasNext();) {
                int div = ((Integer)j.next()).intValue();
                dec += (div-lastDiv-1); 
                lastDiv = div;
                if (dec>0) {
                    sLog.debug(subpart.getSchedulingSubpartLabel()+": "+div+"->"+(div-dec));
                    InstructionalOffering offering = subpart.getInstrOfferingConfig().getInstructionalOffering();
                    ItypeDesc itype = subpart.getItype();
                    for (Iterator i1=offering.getInstrOfferingConfigs().iterator();i1.hasNext();) {
                        InstrOfferingConfig cfg = (InstrOfferingConfig)i1.next();
                        for (Iterator i2=cfg.getSchedulingSubparts().iterator();i2.hasNext();) {
                            SchedulingSubpart s = (SchedulingSubpart)i2.next();
                            if (!s.getItype().equals(itype)) continue;
                            for (Iterator i3=s.getClasses().iterator();i3.hasNext();) {
                                Class_ clazz = (Class_)i3.next();
                                if (clazz.getClassSuffix()==null || clazz.getClassSuffix().length() != 6) continue;
                                int clazzDivNum = Integer.parseInt(clazz.getClassSuffix().substring(0,3));
                                int clazzSecNum = Integer.parseInt(clazz.getClassSuffix().substring(3,6));
                                if (clazzDivNum==div) {
                                    clazz.setClassSuffix(sSufixFormat.format(clazzDivNum-dec)+sSufixFormat.format(clazzSecNum));
                                    hibSession.update(clazz);
                                }
                            }
                        }
                    }

                }
            }
        }
    }    
    
    private transient Hashtable iAssignmentTable = null; 
    
	public Assignment getAssignment(Long classId) {
		if (iAssignmentTable==null) {
			iAssignmentTable = new Hashtable();
			for (Iterator i=getAssignments().iterator();i.hasNext();) {
				Assignment a = (Assignment)i.next();
				iAssignmentTable.put(a.getClassId(),a);
			}
		}
		return (Assignment)iAssignmentTable.get(classId);
	}
	
	public Assignment getAssignment(Class_ clazz) {
        if (!getOwner().getDepartments().contains(clazz.getManagingDept()))
            return clazz.getCommittedAssignment();
		return getAssignment(clazz.getUniqueId());
	}
	
	public AssignmentPreferenceInfo getAssignmentInfo(Class_ clazz) {
		return getAssignmentInfo(clazz.getUniqueId());
	}
	
	public AssignmentPreferenceInfo getAssignmentInfo(Long classId) {
		Assignment a = getAssignment(classId);
		return (a==null?null:(AssignmentPreferenceInfo)a.getAssignmentInfo("AssignmentInfo"));
	}
	
	public Hashtable getAssignmentTable(Collection classesOrClassIds) {
		Hashtable assignments = new Hashtable();
		for (Iterator i=classesOrClassIds.iterator();i.hasNext();) {
			Object classOrClassId = i.next();
			if (classOrClassId instanceof Object[]) classOrClassId = ((Object[])classOrClassId)[0];
			Assignment assignment = (classOrClassId instanceof Class_ ? getAssignment((Class_)classOrClassId) : getAssignment((Long)classOrClassId));
			if (assignment!=null)
				assignments.put(classOrClassId instanceof Class_ ? ((Class_)classOrClassId).getUniqueId() : (Long)classOrClassId, assignment);
		}
		return assignments;
	}
	
	public Hashtable getAssignmentInfoTable(Collection classesOrClassIds) {
		Hashtable infos = new Hashtable();
		for (Iterator i=classesOrClassIds.iterator();i.hasNext();) {
			Object classOrClassId = i.next();
			if (classOrClassId instanceof Object[]) classOrClassId = ((Object[])classOrClassId)[0];
			AssignmentPreferenceInfo info = (classOrClassId instanceof Class_ ? getAssignmentInfo((Class_)classOrClassId) : getAssignmentInfo((Long)classOrClassId));
			if (info!=null)
				infos.put(classOrClassId instanceof Class_ ? ((Class_)classOrClassId).getUniqueId() : (Long)classOrClassId, info);
		}
		return infos;
	}

	private transient DataProperties iPropertiesCache = null;
	public synchronized DataProperties getProperties() {
		if (iPropertiesCache==null) {
			iPropertiesCache = new DataProperties();
			for (Iterator i=getParameters().iterator();i.hasNext();) {
				SolverParameter p = (SolverParameter)i.next();
				iPropertiesCache.setProperty(p.getDefinition().getName(),p.getValue());
			}
		}
		return iPropertiesCache;
	}

    
    public static void refreshSolution(Long solutionId) {
        SolutionDAO dao = new SolutionDAO();
        org.hibernate.Session hibSession = dao.getSession(); 
        SessionFactory hibSessionFactory = hibSession.getSessionFactory(); 
        hibSessionFactory.getCache().evictEntity(Solution.class, solutionId);
        hibSessionFactory.getCache().evictCollection(Solution.class.getName()+".parameters", solutionId);
        hibSessionFactory.getCache().evictCollection(Solution.class.getName()+".assignments", solutionId);
        for (Iterator i=hibSession.createQuery("select c.uniqueId from "+
                    "Class_ c, Solution s where s.uniqueId=:solutionId and "+
                    "c.managingDept.uniqueId in elements (s.owner.departments)").
                    setLong("solutionId", solutionId.longValue()).iterate(); i.hasNext();) {
            Number classId = (Number)i.next();
            hibSessionFactory.getCache().evictEntity(Class_.class, classId);
            hibSessionFactory.getCache().evictCollection(Class_.class.getName()+".assignments", classId);
        }
        hibSessionFactory.getCache().evictCollection(SolverGroup.class.getName()+".solutions", (Long)hibSession.createQuery("select owner.uniqueId from Solution s where s.uniqueId=:solutionId").setLong("solutionId", solutionId).uniqueResult());
   }
    
    public static boolean hasTimetable(Long sessionId) {
        return ((Number)new SolutionDAO().getSession().
                createQuery("select count(s) from Solution s " +
                        "where s.owner.session.uniqueId=:sessionId and " +
                        "s.commited = true").
                setLong("sessionId",sessionId).setCacheable(true).uniqueResult()).longValue()>0;
    }
    
	@Override
	public boolean hasConflicts(Long offeringId) {
		InstructionalOffering offering = InstructionalOfferingDAO.getInstance().get(offeringId);
		if (offering == null || offering.isNotOffered()) return false;
		
		for (InstrOfferingConfig config: offering.getInstrOfferingConfigs())
			for (SchedulingSubpart subpart: config.getSchedulingSubparts())
				for (Class_ clazz: subpart.getClasses()) {
					if (clazz.isCancelled()) continue;
					Assignment assignment = getAssignment(clazz);
					if (assignment == null) continue;
					if (assignment.getRooms() != null)
						for (Location room : assignment.getRooms()) {
							if (!room.isIgnoreRoomCheck()) {
								for (Assignment a : room.getCommitedAssignments())
									if (!assignment.equals(a) && !a.getClazz().isCancelled() && assignment.overlaps(a) && !clazz.canShareRoom(a.getClazz()))
										return true;
			            	}
			            }
					
					if (clazz.getClassInstructors() != null)
						for (ClassInstructor instructor: clazz.getClassInstructors()) {
							if (!instructor.isLead()) continue;
							for (DepartmentalInstructor di: DepartmentalInstructor.getAllForInstructor(instructor.getInstructor())) {
								for (ClassInstructor ci : di.getClasses()) {
									if (ci.equals(instructor) || ci.getClassInstructing().equals(clazz) || !ci.isLead()) continue;
				            		Assignment a = getAssignment(ci.getClassInstructing());
				            		if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a) && !clazz.canShareInstructor(a.getClazz()))
				            			return true;
				            	}
			            	}
							if (instructor.getInstructor().getExternalUniqueId() != null) {
								for (Class_ c: (List<Class_>)Class_DAO.getInstance().getSession().createQuery(
									"select e.clazz from StudentClassEnrollment e where e.student.externalUniqueId = :externalId and e.student.session.uniqueId = :sessionId")
									.setLong("sessionId", instructor.getInstructor().getDepartment().getSessionId())
									.setString("externalId", instructor.getInstructor().getExternalUniqueId())
									.setCacheable(true).list()) {
									Assignment a = getAssignment(c);
				            		if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a)) return true;
								}
							}
						}
					
			        Class_ parent = clazz.getParentClass();
			        while (parent!=null) {
			        	Assignment a = getAssignment(parent);
			        	if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a))
			        		return true;
			        	parent = parent.getParentClass();
			        }
			        
			        for (Iterator<SchedulingSubpart> i = clazz.getSchedulingSubpart().getInstrOfferingConfig().getSchedulingSubparts().iterator(); i.hasNext();) {
			        	SchedulingSubpart ss = i.next();
			        	if (ss.getClasses().size() == 1) {
			        		Class_ child = ss.getClasses().iterator().next();
			        		if (clazz.equals(child)) continue;
			        		Assignment a = getAssignment(child);
			        		if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a))
			        			return true;
			        	}
			        }
				}
		
		return false;
	}
    
	@Override
	public Set<Assignment> getConflicts(Long classId) {
		if (classId == null) return null;
		Class_ clazz = Class_DAO.getInstance().get(classId);
		if (clazz == null || clazz.isCancelled()) return null;
		Assignment assignment = getAssignment(clazz);
		if (assignment == null) return null;
		Set<Assignment> conflicts = new HashSet<Assignment>();
		if (assignment.getRooms() != null)
			for (Location room : assignment.getRooms()) {
				if (!room.isIgnoreRoomCheck()) {
					for (Assignment a : room.getAssignments(this))
						if (!assignment.equals(a) && !a.getClazz().isCancelled() && assignment.overlaps(a) && !clazz.canShareRoom(a.getClazz()))
							conflicts.add(a);
            	}
            }
		
		if (clazz.getClassInstructors() != null)
			for (ClassInstructor instructor: clazz.getClassInstructors()) {
				if (!instructor.isLead()) continue;
				for (DepartmentalInstructor di: DepartmentalInstructor.getAllForInstructor(instructor.getInstructor())) {
					for (ClassInstructor ci : di.getClasses()) {
						if (ci.equals(instructor) || ci.getClassInstructing().equals(clazz) || !ci.isLead()) continue;
	            		Assignment a = getAssignment(ci.getClassInstructing());
	            		if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a) && !clazz.canShareInstructor(a.getClazz()))
	            			conflicts.add(a);
	            	}
            	}
				if (instructor.getInstructor().getExternalUniqueId() != null) {
					for (Class_ c: (List<Class_>)Class_DAO.getInstance().getSession().createQuery(
						"select e.clazz from StudentClassEnrollment e where e.student.externalUniqueId = :externalId and e.student.session.uniqueId = :sessionId")
						.setLong("sessionId", instructor.getInstructor().getDepartment().getSessionId())
						.setString("externalId", instructor.getInstructor().getExternalUniqueId())
						.setCacheable(true).list()) {
						Assignment a = getAssignment(c);
	            		if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a))
	            			conflicts.add(a);
					}
				}
			}
		
        Class_ parent = clazz.getParentClass();
        while (parent!=null) {
        	Assignment a = getAssignment(parent);
        	if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a))
    			conflicts.add(a);
        	parent = parent.getParentClass();
        }
        
        Queue<Class_> children = new LinkedList(clazz.getChildClasses());
        Class_ child = null;
        while ((child=children.poll())!=null) {
        	Assignment a = getAssignment(child);
        	if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a))
    			conflicts.add(a);
        	if (!child.getChildClasses().isEmpty())
        		children.addAll(child.getChildClasses());
        }
        
        for (Iterator<SchedulingSubpart> i = clazz.getSchedulingSubpart().getInstrOfferingConfig().getSchedulingSubparts().iterator(); i.hasNext();) {
        	SchedulingSubpart ss = i.next();
        	if (ss.getClasses().size() == 1) {
        		child = ss.getClasses().iterator().next();
        		if (clazz.equals(child)) continue;
        		Assignment a = getAssignment(child);
        		if (a != null && !a.getClazz().isCancelled() && assignment.overlaps(a))
        			conflicts.add(a);
        	}
        }
        
        return conflicts;
	}
	
	@Override
	public Set<TimeBlock> getConflictingTimeBlocks(Long classId) {
		return null;
	}
}