/**
* A class representing plans.
*
* @Constructor
*
* @example
* var planExample = {
* combination: 1,
* lectures: [{@link Lecture}],
* htmlElement: document.createElement('div'), // só existirá com o design finalizado
* htmlElementCombinations: [
* document.createElement('div'), // só existirá com o design finalizado
* ]
* }
*/
// IMPORTANT: the 'ui' variable must be already set up!
function Plan(jsonObj) {
this.lectures = new Array();
this.combinations = new Array();
if (jsonObj) {
this.activeCombinationIndex = jsonObj.activeCombinationIndex;
for (var i = 0; i < jsonObj.lectures.length; i++) {
this.lectures.push(new Lecture(jsonObj.lectures[i], this));
ui.addLecture(this.lectures[i]);
}
// TODO arrumar isso para o design final
this.htmlElement = document.createElement('div');
this.computeCombinations();
this.setActiveCombination();
} else {
this.combinationIndex = null;
this.htmlElement = null;
}
}
/**
*
*/
Plan.prototype.update = function(classroomUpdated) {
console.log('updating...');
var oldActiveCombination = null;
if (this.activeCombinationIndex != null) {
// There is an active combination.
oldActiveCombination = this.combinations[this.activeCombinationIndex];
this.unsetActiveCombination();
}
this.combinations = new Array();
this.computeCombinations();
this.activeCombinationIndex = this.closestCombination(oldActiveCombination, classroomUpdated);
if (this.activeCombinationIndex != null) {
// There is an active combination.
this.setActiveCombination();
} else {
// If there are no combinations.
document.getElementById('combination-value').value = 0;
document.getElementById('combination-total').innerHTML = 0;
}
if (!classroomUpdated) {
// Plan.update() was called from an insertion or deletion of a lecture.
// Nothing more to be done.
return;
}
// If this was called by an classroom update, classroomUpdated exists.
// At this moment, the mouse pointer (if it's not a touch screen) is over
// the classroomUpdated. If it has a sibling classroom that is active it
// will show up as a consequence of setActiveCombination. So we update the
// highlight status to it (only hides if it was displayed, naturally).
if (hasClass(classroomUpdated.schedules[0].htmlElement, 'schedule-box-highlight')) {
classroomUpdated.setHighlight();
}
};
/**
*
*/
Plan.prototype.closestCombination = function(oldActiveCombination) {
if (this.combinations.length == 0) {
// No combination could be created, probably there isn't any lecture selected.
return null;
}
if (!oldActiveCombination) {
// Now there is only one combination: if there wasn't any and
// update was called from one single event that is the inclusion
// of a single classroom. Return the index of this single
// combination.
return 0;
}
// If there is some combination, there is at least the index 0.
var closestCombinationIndex = 0;
var maximumScoreSoFar = -1;
for (var i = 0; i < this.combinations.length; i++) {
var score = oldActiveCombination.getSimilarityScore(this.combinations[i]);
if (score > maximumScoreSoFar) {
maximumScoreSoFar = score;
closestCombinationIndex = i;
}
}
return closestCombinationIndex;
}
/**
*
*/
Plan.prototype.nextCombination = function() {
this.unsetActiveCombination();
this.activeCombinationIndex = (this.activeCombinationIndex + 1) % this.combinations.length;
this.setActiveCombination();
};
/**
*
*/
Plan.prototype.previousCombination = function() {
this.unsetActiveCombination();
this.activeCombinationIndex = ((this.activeCombinationIndex - 1) + this.combinations.length) % this.combinations.length;
this.setActiveCombination();
};
/**
* Adds a lecture to this plan.
*
* @param {Lecture} lecture
*/
Plan.prototype.addLecture = function(lecture) {
this.lectures.push(lecture);
ui.addLecture(lecture);
this.update();
}
/**
*
*/
Plan.prototype.testCombination = function(potentialCombination) {
// Combinations only exist with all lectures included. So if
// a classroom isn't selected, the combination is invalid.
for(var i = 0; i < potentialCombination.length - 1; i++) {
var classroom1Index = potentialCombination[i];
if (classroom1Index == -1) {
// Lecture isn't selected. Obs.: A combination without any
// selected lecture, is still valid.
continue;
}
var classroom1 = this.lectures[i].classrooms[classroom1Index];
if (!classroom1.selected) {
return false;
}
for (var j = i+1; j < potentialCombination.length; j++) {
var classroom2Index = potentialCombination[j];
if (classroom2Index == -1) {
// Lecture isn't selected.
continue;
}
var classroom2 = this.lectures[j].classrooms[classroom2Index];
if (!classroom2.selected) {
return false;
}
if (classroomsConflict(classroom1, classroom2)) {
return false;
}
}
}
return true;
}
/**
*
*/
Plan.prototype.computeCombinations = function() {
var potentialCombination = Array(this.lectures.length).fill(0);
var leftmostSelectedLectureIndex = -1;
// Initialize to something like this:
// [-1, -1, 0, -1, 0, 0, 0, -1]
// ___^____
// where this guy is the leftmostSelectedLectureIndex
for (var i = 0; i < this.lectures.length; i++) {
if (!this.lectures[i].selected) {
potentialCombination[i] = -1;
} else if (leftmostSelectedLectureIndex == -1) {
leftmostSelectedLectureIndex = i;
}
}
if (leftmostSelectedLectureIndex == -1) {
// No lecture selected. There are no combinations.
return;
}
// TODO tirar daqui
console.log('potentialCombination', potentialCombination);
var loop = 0;
// while condition can be this without affecting the logic:
// potentialCombination[leftmostSelectedLectureIndex] >= this.lectures[leftmostSelectedLectureIndex].classrooms.length
while (loop++ < 100) {
if (this.testCombination(potentialCombination)) {
console.log('combination', potentialCombination);
combination = new Combination(potentialCombination, this);
this.combinations.push(combination);
}
// This process is similar to adding 1 to a number, where each slot is a digit
// we begin at the rightmost digit and whenever a value reaches its maximum
// capacity we turn it in zero and add one to the one to its left.
// The first lecture will be last to change (when traveling in the combinations).
// Also, when there are '-1's (lecture not selected), it jumps. For that matter,
// the loop stops when the leftmost selected lecture excedes its maximum capacity.
for (var slot = potentialCombination.length - 1; slot >= 0; slot--) {
if (potentialCombination[slot] == -1) {
// Lecture not selected.
continue;
}
potentialCombination[slot]++;
if (potentialCombination[slot] >= this.lectures[slot].classrooms.length) {
// Just exceded maximum capacity.
// Obs.: Should never get to 'more than'.
if (slot == leftmostSelectedLectureIndex) {
// All combinations seen. We can return.
return;
}
// There are more combinations to test. At the next potentialCombination,
// 'lectures[slot]' lecture will have its first classroom: classrooms[0].
potentialCombination[slot] = 0;
} else {
// next potentialCombination is already set. Loop to test it.
break;
}
}
}
}
/**
*
*/
Plan.prototype.setActiveCombination = function() {
var activeCombination = this.combinations[this.activeCombinationIndex];
for (var i = 0; i < activeCombination.lecturesClassroom.length; i++) {
var activeClassroom = activeCombination.lecturesClassroom[i];
activeClassroom.showBox();
// TODO refatorar essa linha ou o codigo. Opcao: guardar a active classroom ao inves de guardar seu indice
// possivelmente fazer o mesmo com as combinacoes e so mudar na hora de salvar o json. Isso sera usado,
// por exemplo, no setHighlight em Classroom()
activeClassroom.parent.activeClassroomIndex = activeClassroom.parent.classrooms.indexOf(activeClassroom);
}
document.getElementById('combination-value').value = this.activeCombinationIndex + 1;
document.getElementById('combination-total').innerHTML = this.combinations.length;
};
/**
*
*/
Plan.prototype.unsetActiveCombination = function() {
var activeCombination = this.combinations[this.activeCombinationIndex]
for (var i = 0; i < activeCombination.lecturesClassroom.length; i++) {
var activeClassroom = activeCombination.lecturesClassroom[i];
activeClassroom.hideBox();
activeClassroom.parent.activeClassroomIndex = null;
}
};