/*
 * 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.solver.ui;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.cpsolver.coursett.constraint.InstructorConstraint;
import org.cpsolver.coursett.model.Lecture;
import org.cpsolver.coursett.model.Placement;
import org.cpsolver.coursett.model.TimetableModel;
import org.cpsolver.ifs.assignment.Assignment;
import org.cpsolver.ifs.solver.Solver;
import org.cpsolver.ifs.util.DistanceMetric;
import org.unitime.timetable.model.PreferenceLevel;
import org.unitime.timetable.solver.interactive.ClassAssignmentDetails;


/**
 * @author Tomas Muller
 */
public class PerturbationReport implements Serializable {
	private static final long serialVersionUID = 1L;
	private HashSet iGroups = new HashSet();
	
	public PerturbationReport(Solver solver) {
		Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();
		TimetableModel model = (TimetableModel)solver.currentSolution().getModel();
		for (Lecture lecture: model.perturbVariables(assignment)) {
			Placement placement = assignment.getValue(lecture);
			Placement initial = lecture.getInitialAssignment();
			if (placement==null || initial==null || placement.equals(initial)) continue;
			iGroups.add(new PerturbationGroup(solver,lecture));
		}
		
	}
	
	public Set getGroups() {
		return iGroups;
	}
	
	public class PerturbationGroup implements Serializable {
		private static final long serialVersionUID = 1L;
		private ClassAssignmentDetails iDetail = null;
        public long affectedStudents=0;
        public int affectedInstructors=0;
        public long affectedStudentsByTime=0;
        public int affectedInstructorsByTime=0;
        public int differentRoom=0;
        public int affectedInstructorsByRoom=0;
        public long affectedStudentsByRoom=0;
        public int differentBuilding=0;
        public int affectedInstructorsByBldg=0;
        public long affectedStudentsByBldg=0;
        public int deltaRoomPreferences=0;
        public int differentTime=0;
        public int differentDay=0;
        public int differentHour=0;
        public int tooFarForInstructors=0;
        public int tooFarForStudents=0;
        public int deltaStudentConflicts=0;
        public int newStudentConflicts=0;
        public double deltaTimePreferences=0;
        public int deltaInstructorDistancePreferences=0;
        public double distance = 0;
        
		public PerturbationGroup(Solver solver, Lecture lecture) {
			Assignment<Lecture, Placement> assignment = solver.currentSolution().getAssignment();
			Placement assignedPlacement = (Placement)assignment.getValue(lecture);
			Placement initialPlacement = (Placement)lecture.getInitialAssignment();
			iDetail = new ClassAssignmentDetails(solver, lecture, initialPlacement, false);
			iDetail.setAssigned(new AssignmentPreferenceInfo(solver, assignedPlacement, false), assignedPlacement.getRoomIds(),assignedPlacement.getTimeLocation().getDayCode(), assignedPlacement.getTimeLocation().getStartSlot(), assignedPlacement.getTimeLocation().getTimePatternId(), assignedPlacement.getTimeLocation().getDatePatternId());
			
	        affectedStudents = lecture.classLimit(assignment);
	        affectedInstructors= lecture.getInstructorConstraints().size();
	        affectedStudentsByTime=(initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())?0:lecture.classLimit(assignment));
	        affectedInstructorsByTime=(initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())?0:lecture.getInstructorConstraints().size());
	        
	    	differentRoom = initialPlacement.nrDifferentRooms(assignedPlacement);
	        affectedInstructorsByRoom = differentRoom*lecture.getInstructorConstraints().size();
	        affectedStudentsByRoom = differentRoom*lecture.classLimit(assignment);
	        
	        differentBuilding = initialPlacement.nrDifferentBuildings(assignedPlacement);
	        affectedInstructorsByBldg = differentBuilding*lecture.getInstructorConstraints().size();
	        affectedStudentsByBldg = differentBuilding*lecture.classLimit(assignment);
	        
	        deltaRoomPreferences = assignedPlacement.sumRoomPreference() - initialPlacement.sumRoomPreference();

	        differentTime=(initialPlacement.getTimeLocation().equals(assignedPlacement.getTimeLocation())?0:1);
	        differentDay=(initialPlacement.getTimeLocation().getDayCode()!=assignedPlacement.getTimeLocation().getDayCode()?1:0);
	        differentHour=(initialPlacement.getTimeLocation().getStartSlot()!=assignedPlacement.getTimeLocation().getStartSlot()?1:0);
	        deltaStudentConflicts=lecture.countStudentConflicts(assignment, assignedPlacement)-lecture.countInitialStudentConflicts();
	        deltaTimePreferences=(assignedPlacement.getTimeLocation().getNormalizedPreference() - initialPlacement.getTimeLocation().getNormalizedPreference());
	        
	        DistanceMetric m = ((TimetableModel)lecture.getModel()).getDistanceMetric();
	        distance = Placement.getDistanceInMeters(m,initialPlacement, assignedPlacement);
	        if (!lecture.getInstructorConstraints().isEmpty()) {
	            if (distance>m.getInstructorNoPreferenceLimit() && distance<=m.getInstructorDiscouragedLimit()) {
	                tooFarForInstructors+=PreferenceLevel.sIntLevelDiscouraged;
	            } else if (distance>m.getInstructorDiscouragedLimit() && distance<=m.getInstructorProhibitedLimit()) {
	                tooFarForInstructors+=PreferenceLevel.sIntLevelStronglyDiscouraged;
	            } else if (distance>m.getInstructorProhibitedLimit()) {
	                tooFarForInstructors+=PreferenceLevel.sIntLevelProhibited;
	            }
	        }
	        if (distance > m.minutes2meters(10))
	        	tooFarForStudents = (int)lecture.classLimit(assignment);
	        
	        Set newStudentConflictsVect = lecture.conflictStudents(assignment, assignedPlacement);
	        Set initialStudentConflicts = lecture.initialStudentConflicts();
	        for (Iterator e=newStudentConflictsVect.iterator();e.hasNext();)
	            if (!initialStudentConflicts.contains(e.next())) newStudentConflicts++;


	       	for (InstructorConstraint ic: lecture.getInstructorConstraints()) {
	       		for (Lecture lect: ic.variables()) {
	       			if (lect.equals(lecture)) continue;
	       			int initialPreference = (lect.getInitialAssignment()==null?PreferenceLevel.sIntLevelNeutral:ic.getDistancePreference(initialPlacement,(Placement)lect.getInitialAssignment()));
	       			int assignedPreference = (assignment.getValue(lect)==null?PreferenceLevel.sIntLevelNeutral:ic.getDistancePreference(assignedPlacement,(Placement)assignment.getValue(lect)));
	       			deltaInstructorDistancePreferences += (assignedPreference - initialPreference);
	       		}
	        }
		}
		
		public ClassAssignmentDetails getClazz() { return iDetail; }
		
	}
	
}
