/*
 * Copyright 2010 JBoss Inc
 *
 * Licensed 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.optaplanner.examples.examination.solver.solution.initializer;

import org.optaplanner.core.impl.phase.custom.CustomSolverPhaseCommand;
import org.optaplanner.core.impl.score.director.ScoreDirector;
import org.optaplanner.examples.examination.domain.Examination;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExaminationSolutionInitializer implements CustomSolverPhaseCommand {

    protected final transient Logger logger = LoggerFactory.getLogger(getClass());

    public void changeWorkingSolution(ScoreDirector scoreDirector) {
        Examination examination = (Examination) scoreDirector.getWorkingSolution();
//        initializeExamList(scoreDirector, examination);
    }

//    private void initializeExamList(ScoreDirector scoreDirector, Examination examination) {
//        List<Period> periodList = examination.getPeriodList();
//        List<Room> roomList = examination.getRoomList();
//        // TODO the planning entity list from the solution should be used and might already contain initialized entities
//        List<Exam> examList = new ArrayList<Exam>(examination.getTopicList().size()); // TODO this can be returned from createExamAssigningScoreList
//
//        List<ExamInitializationWeight> examInitialWeightList = createExamAssigningScoreList(examination);
//
//        for (ExamInitializationWeight examInitialWeight : examInitialWeightList) {
//            Score unscheduledScore = scoreDirector.calculateScore();
//            Exam leader = examInitialWeight.getExam();
//
//            List<ExamToHandle> examToHandleList = new ArrayList<ExamToHandle>(5);
//            if (leader.getExamCoincidence() == null) {
//                examToHandleList.add(new ExamToHandle(leader));
//            } else {
//                for (Exam coincidenceExam : leader.getExamCoincidence().getCoincidenceExamSet()) {
//                    examToHandleList.add(new ExamToHandle(coincidenceExam));
//                }
//            }
//
//            List<PeriodScoring> periodScoringList = new ArrayList<PeriodScoring>(periodList.size());
//            for (Period period : periodList) {
//                for (ExamToHandle examToHandle : examToHandleList) {
//                    Exam exam = examToHandle.getExam();
//                    if (!examToHandle.isAdded()) {
//                        scoreDirector.beforeEntityAdded(exam);
//                        exam.setPeriod(period);
//                        scoreDirector.afterEntityAdded(exam);
//                        examToHandle.setAdded(true);
//                    } else {
//                        scoreDirector.beforeVariableChanged(exam, "period");
//                        exam.setPeriod(period);
//                        scoreDirector.afterVariableChanged(exam, "period");
//                    }
//                }
//                Score score = scoreDirector.calculateScore();
//                periodScoringList.add(new PeriodScoring(period, score));
//            }
//            Collections.sort(periodScoringList);
//
//            scheduleLeader(periodScoringList, roomList, scoreDirector, unscheduledScore, examToHandleList, leader);
//            examList.add(leader);
//
//            // Schedule the non leaders
//            for (ExamToHandle examToHandle : examToHandleList) {
//                Exam exam = examToHandle.getExam();
//                // Leader already has a room
//                if (!exam.isCoincidenceLeader()) {
//                    scheduleNonLeader(roomList, scoreDirector, exam);
//                    examList.add(exam);
//                }
//            }
//        }
//        Collections.sort(examList, new PersistableIdComparator());
//        examination.setExamList(examList);
//    }
//
//    private void scheduleLeader(List<PeriodScoring> periodScoringList, List<Room> roomList,
//            ScoreDirector scoreDirector, Score unscheduledScore,
//            List<ExamToHandle> examToHandleList, Exam leader) {
//        boolean perfectMatch = false;
//        Score bestScore = HardSoftScore.valueOf(Integer.MIN_VALUE, Integer.MIN_VALUE);
//        Period bestPeriod = null;
//        Room bestRoom = null;
//        for (PeriodScoring periodScoring : periodScoringList) {
//            if (bestScore.compareTo(periodScoring.getScore()) >= 0) {
//                // No need to check the rest
//                break;
//            }
//            for (ExamToHandle examToHandle : examToHandleList) {
//                Exam exam = examToHandle.getExam();
//                scoreDirector.beforeVariableChanged(exam, "period");
//                exam.setPeriod(periodScoring.getPeriod());
//                scoreDirector.afterVariableChanged(exam, "period");
//            }
//            for (Room room : roomList) {
//                scoreDirector.beforeVariableChanged(leader, "room");
//                leader.setRoom(room);
//                scoreDirector.afterVariableChanged(leader, "room");
//                Score score = scoreDirector.calculateScore();
//                if (score.compareTo(unscheduledScore) < 0) {
//                    if (score.compareTo(bestScore) > 0) {
//                        bestScore = score;
//                        bestPeriod = periodScoring.getPeriod();
//                        bestRoom = room;
//                    }
//                } else if (score.equals(unscheduledScore)) {
//                    perfectMatch = true;
//                    break;
//                } else {
//                    throw new IllegalStateException("The score (" + score
//                            + ") cannot be higher than unscheduledScore (" + unscheduledScore + ").");
//                }
//            }
//            if (perfectMatch) {
//                break;
//            }
//        }
//        if (!perfectMatch) {
//            if (bestPeriod == null || bestRoom == null) {
//                throw new IllegalStateException("The bestPeriod (" + bestPeriod + ") or the bestRoom ("
//                        + bestRoom + ") cannot be null.");
//            }
//            scoreDirector.beforeVariableChanged(leader, "room");
//            leader.setRoom(bestRoom);
//            scoreDirector.afterVariableChanged(leader, "room");
//            for (ExamToHandle examToHandle : examToHandleList) {
//                Exam exam = examToHandle.getExam();
//                scoreDirector.beforeVariableChanged(exam, "period");
//                exam.setPeriod(bestPeriod);
//                scoreDirector.afterVariableChanged(exam, "period");
//            }
//        }
//        logger.debug("    Exam ({}) initialized.", leader);
//    }
//
//    private void scheduleNonLeader(List<Room> roomList, ScoreDirector scoreDirector, Exam exam) {
//        if (exam.getRoom() != null) {
//            throw new IllegalStateException("Exam (" + exam + ") already has a room.");
//        }
//        Score unscheduledScore = scoreDirector.calculateScore();
//        boolean perfectMatch = false;
//        Score bestScore = HardSoftScore.valueOf(Integer.MIN_VALUE, Integer.MIN_VALUE);
//        Room bestRoom = null;
//        for (Room room : roomList) {
//            scoreDirector.beforeVariableChanged(exam, "room");
//            exam.setRoom(room);
//            scoreDirector.afterVariableChanged(exam, "room");
//            Score score = scoreDirector.calculateScore();
//            if (score.compareTo(unscheduledScore) < 0) {
//                if (score.compareTo(bestScore) > 0) {
//                    bestScore = score;
//                    bestRoom = room;
//                }
//            } else if (score.equals(unscheduledScore)) {
//                perfectMatch = true;
//                break;
//            } else {
//                throw new IllegalStateException("The score (" + score
//                        + ") cannot be higher than unscheduledScore (" + unscheduledScore + ").");
//            }
//        }
//        if (!perfectMatch) {
//            if (bestRoom == null) {
//                throw new IllegalStateException("The bestRoom ("
//                        + bestRoom + ") cannot be null.");
//            }
//            scoreDirector.beforeVariableChanged(exam, "room");
//            exam.setRoom(bestRoom);
//            scoreDirector.afterVariableChanged(exam, "room");
//        }
//        logger.debug("    Exam ({}) initialized.", exam);
//    }
//
//    public static class ExamToHandle {
//
//        private Exam exam;
//        private boolean added = true;
//
//        public ExamToHandle(Exam exam) {
//            this.exam = exam;
//        }
//
//        public Exam getExam() {
//            return exam;
//        }
//
//        public boolean isAdded() {
//            return added;
//        }
//
//        public void setAdded(boolean added) {
//            this.added = added;
//        }
//    }
//
//    /**
//     * Create and order the exams in the order which we 'll assign them into periods and rooms.
//     * @param examination not null
//     * @return not null
//     */
//    private List<ExamInitializationWeight> createExamAssigningScoreList(Examination examination) {
//        List<Exam> examList = createExamList(examination);
//        List<ExamInitializationWeight> examInitialWeightList = new ArrayList<ExamInitializationWeight>(examList.size());
//        for (Exam exam : examList) {
//            // HACK to avoid resetting initialized entities.
//            // Bug 1 for semi-initialized solutions: if one variable is initialized and other is not
//            // Bug 2 for semi-initialized solutions: if leads is initialized and coincidence is not
//            if (exam.getPeriod() == null || exam.getRoom() == null) {
//                if (exam.isCoincidenceLeader()) {
//                    examInitialWeightList.add(new ExamInitializationWeight(exam));
//                }
//            }
//        }
//        Collections.sort(examInitialWeightList);
//        return examInitialWeightList;
//    }
//
//    public List<Exam> createExamList(Examination examination) {
//        List<Topic> topicList = examination.getTopicList();
//        List<Exam> examList = examination.getExamList();
//        Map<Topic, Exam> topicToExamMap = new HashMap<Topic, Exam>(topicList.size());
//        for (Exam exam : examList) {
//            topicToExamMap.put(exam.getTopic(), exam);
//        }
//        for (PeriodPenalty periodPenalty : examination.getPeriodPenaltyList()) {
//            if (periodPenalty.getPeriodPenaltyType() == PeriodPenaltyType.EXAM_COINCIDENCE) {
//                Exam leftExam = topicToExamMap.get(periodPenalty.getLeftSideTopic());
//                Exam rightExam = topicToExamMap.get(periodPenalty.getRightSideTopic());
//
//                Set<Exam> newCoincidenceExamSet = new LinkedHashSet<Exam>(4);
//                ExamCoincidence leftExamCoincidence = leftExam.getExamCoincidence();
//                if (leftExamCoincidence != null) {
//                    newCoincidenceExamSet.addAll(leftExamCoincidence.getCoincidenceExamSet());
//                } else {
//                    newCoincidenceExamSet.add(leftExam);
//                }
//                ExamCoincidence rightExamCoincidence = rightExam.getExamCoincidence();
//                if (rightExamCoincidence != null) {
//                    newCoincidenceExamSet.addAll(rightExamCoincidence.getCoincidenceExamSet());
//                } else {
//                    newCoincidenceExamSet.add(rightExam);
//                }
//                ExamCoincidence newExamCoincidence = new ExamCoincidence(newCoincidenceExamSet);
//                for (Exam exam : newCoincidenceExamSet) {
//                    exam.setExamCoincidence(newExamCoincidence);
//                }
//            } else if (periodPenalty.getPeriodPenaltyType() == PeriodPenaltyType.AFTER) {
//                Exam afterExam = topicToExamMap.get(periodPenalty.getLeftSideTopic());
//                Exam beforeExam = topicToExamMap.get(periodPenalty.getRightSideTopic());
//                ExamBefore examBefore = beforeExam.getExamBefore();
//                if (examBefore == null) {
//                    examBefore = new ExamBefore(new LinkedHashSet<Exam>(2));
//                    beforeExam.setExamBefore(examBefore);
//                }
//                examBefore.getAfterExamSet().add(afterExam);
//            }
//        }
//        return examList;
//    }
//
//    private static class ExamInitializationWeight implements Comparable<ExamInitializationWeight> {
//
//        private Exam exam;
//        private int totalStudentSize;
//        private int maximumDuration;
//
//        private ExamInitializationWeight(Exam exam) {
//            this.exam = exam;
//            totalStudentSize = calculateTotalStudentSize(exam);
//            maximumDuration = calculateMaximumDuration(exam);
//        }
//
//        private int calculateTotalStudentSize(Exam innerExam) {
//            int innerTotalStudentSize = 0;
//            if (innerExam.getExamCoincidence() == null) {
//                innerTotalStudentSize = innerExam.getTopicStudentSize();
//            } else {
//                for (Exam coincidenceExam : innerExam.getExamCoincidence().getCoincidenceExamSet()) {
//                    innerTotalStudentSize += coincidenceExam.getTopicStudentSize();
//                }
//            }
//            if (innerExam.getExamBefore() != null) {
//                for (Exam afterExam : innerExam.getExamBefore().getAfterExamSet()) {
//                    innerTotalStudentSize += calculateTotalStudentSize(afterExam); // recursive
//                }
//            }
//            return innerTotalStudentSize;
//        }
//
//        private int calculateMaximumDuration(Exam innerExam) {
//            int innerMaximumDuration = innerExam.getTopic().getDuration();
//            if (innerExam.getExamCoincidence() != null) {
//                for (Exam coincidenceExam : innerExam.getExamCoincidence().getCoincidenceExamSet()) {
//                    innerMaximumDuration = Math.max(innerMaximumDuration, coincidenceExam.getTopicStudentSize());
//                }
//            }
//            if (innerExam.getExamBefore() != null) {
//                for (Exam afterExam : innerExam.getExamBefore().getAfterExamSet()) {
//                    innerMaximumDuration = Math.max(innerMaximumDuration, calculateMaximumDuration(afterExam)); // recursive
//                }
//            }
//            return innerMaximumDuration;
//        }
//
//        public Exam getExam() {
//            return exam;
//        }
//
//        public int compareTo(ExamInitializationWeight other) {
//            // TODO calculate a assigningScore based on the properties of a topic and sort on that assigningScore
//            return new CompareToBuilder()
//                    .append(other.totalStudentSize, totalStudentSize) // Descending
//                    .append(other.maximumDuration, maximumDuration) // Descending
//                    .append(exam.getId(), other.exam.getId()) // Ascending
//                    .toComparison();
//        }
//
//    }
//
//    private static class PeriodScoring implements Comparable<PeriodScoring> {
//
//        private Period period;
//        private Score score;
//
//        private PeriodScoring(Period period, Score score) {
//            this.period = period;
//            this.score = score;
//        }
//
//        public Period getPeriod() {
//            return period;
//        }
//
//        public Score getScore() {
//            return score;
//        }
//
//        public int compareTo(PeriodScoring other) {
//            return -new CompareToBuilder().append(score, other.score).toComparison();
//        }
//
//    }

}
