import {ValueAddedMonitoring} from './ValueAddedMonitoring.class.js';

/**
 * Monitoring handler class
 * Aggregates ValueAddedMonitoring instances
 * Handles course level charting/dropdowns [new class?]
 * TODO Rename to CourseMonitor
 */
export class Monitoring {
	// jshint ignore:start
	/**
	 *
	 * @type monitors {ValueAddedMonitoring[]}
	 */
	monitors = {};
	dropdown = null;
	dataReady = false;
	chartReady = false;
	classCount = 0;
	completedAjaxes = 0;
	modalIndex = 0;
	timers = {};
	vaBoxHeight = 60;
	totalChartsLookup = [];
	chartsReadyTimer = null;
	chartsReadyLookup = [];

	// jshint ignore:end

	constructor(classCount) {
		this.classCount = classCount;
		this.dropdown = $('.overall-assignment-select').first();
		if ($('.course-view').length > 0) {
			this.modalIndex = initiateModal($('.course-view'), 'lock', null, 'ops_page_loading');
		}
	}

	addAjaxComplete(classCode) {
		this.completedAjaxes++;
		if (this.completedAjaxes === this.classCount) {
			// Clear ready charts
			//this.chartsReadyLookup = [];
			let me = this;
			$('.course-table-wide .results').each((idx, elem) => tippy(elem, {content: '---'}));

			this.dataReady = true;
			this.chartAttendanceAndRetention();
			this.chartsReadyTimer = this.chartsReadyTimer = setTimeout(function () {
				me.chartCheck();
			}, 500);
			this.updateFinalGradeAverage();
			this.filterChange();
		}
	}

	addReadyChart(chartName) {
		// Check name is not already counted
		if (this.chartsReadyLookup.indexOf(chartName) === -1) {
			this.chartsReadyLookup.push(chartName);
			if (this.chartsReadyTimer == null) {
				let me = this;
				this.chartsReadyTimer = setTimeout(function () {
					me.chartCheck();
				}, 500);
			}
		}
	}

	chartCheck() {
		if (this.chartsReadyLookup.length == this.totalChartsLookup.length) {
			this.chartsAllReady = true;
			closeModal(this.modalIndex);
		} else {
			var me = this;
			this.chartsReadyTimer = setTimeout(function () {
				me.chartCheck();
			}, 500);
		}
	}

	/**
	 * @param reference
	 * @returns {null|ValueAddedMonitoring}
	 */
	getMonitor(reference) {
		// Any monitor will do
		if (reference === 'any') {
			for (let property in this.monitors) {
				if (this.monitors.hasOwnProperty(property)) {
					var monitor = this.monitors[property];
					return monitor;
				}
			}
			return null;
			// Element given, use parent monitor
		} else if (typeof reference == 'object') {
			reference = this.getMonitorCodeForElement(reference);
		}

		if (!this.monitors.hasOwnProperty(reference)) {
			throw "Monitoring ref " + reference + " was not found";
		}
		return this.monitors[reference];
	}

	/**
	 * @param {string} reference
	 * @param {ValueAddedMonitoring} monitor
	 * @returns {null|ValueAddedMonitoring}
	 */
	addMonitor(reference, monitor) {
		if (reference == undefined || monitor == undefined) {
			throw "cannot add monitor, a value was undefined: ref:" + reference + "|obj:" + monitor;
		}
		this.monitors[reference] = monitor;
		return monitor;
	}

	hasMonitor(reference) {
		return this.monitors.hasOwnProperty(reference);
	}

	/**
	 * @param element
	 * @returns {ValueAddedMonitoring}
	 */
	getMonitorForElement(element) {
		var code = this.getMonitorCodeForElement(element);
		const monitor = this.getMonitor(code);
		return monitor;
	}

	/**
	 * @param element
	 * @returns {string}
	 */
	getMonitorCodeForElement(element) {
		element = $(element);
		var parent = element.closest('.value-added');
		var code = parent.data('name');
		return code;
	}

	getSelectedAssignment(monitor) {
		monitor = typeof monitor !== 'undefined' ? monitor : null;

		if (!this.dropdown || this.dropdown.length == 0) {
			if (monitor) {
				// Defer to local monitor (class view etc)
				return monitor.getSelectedAssignment();
			}
			return null;
		}
		return parseInt(this.dropdown.val());
	}

	setupAssignmentDropdown(assignmentLookup, assignmentOrder) {
		for (var assignmentIndex in assignmentOrder) {
			var assignmentId = assignmentOrder[assignmentIndex];
			var assignment = assignmentLookup[assignmentId];
			this.dropdown.prepend($('<option>', {value: assignmentId}).text(
				assignment.name + ' ' + assignment.date
			));
		}
		var me = this;
		this.dropdown.change(function (e) {
			MonitoringObjects.filterChange();
			me.updateCourseAssignmentAverage();
			me.updateCourseOverallAverage();

			//MonitoringObjects.chartVAPerClass();
		});
	}

	chartAttendanceAndRetention() {
		if (!this.chartReady || !this.dataReady || !this.chart) {
			return;
		}
		this.chart.clearChartRows('Attendance');
		this.chart.clearChartRows('Retention');
		for (var monitorIndex in this.monitors) {
			var monitor = this.getMonitor(monitorIndex);
			var attendanceRow = this.chart.addNewDatapoint('Attendance');
			this.chart.setCell(attendanceRow, 0, monitor.classReference); // Class
			this.chart.setCell(attendanceRow, this.chart.getColumnIndexFromName('Retention'), Math.round(monitor.stats.attendance_current), 0);
			this.chart.setCell(attendanceRow, this.chart.getColumnIndexFromName('Retention') + 1, Math.round(monitor.stats.attendance_current) + '%', 0);
			this.chart.setCell(attendanceRow, this.chart.getColumnIndexFromName('Attendance'), Math.round(monitor.stats.retention_current), 0);
			this.chart.setCell(attendanceRow, this.chart.getColumnIndexFromName('Attendance') + 1, Math.round(monitor.stats.retention_current) + '%', 0);
		}

		this.chart.sort([0]);
		var options = this.chart.getOptions();
		options.vAxis.viewWindow = {max: 100};
		options.vAxis.ticks = [60, 70, 80, 90, 92.5, 95, 97.5, 100];
		this.chart.draw(null, options);
	}

	hidePercentile(targetClass) {
		var noDataElement = $('.' + targetClass + ' .no-data', this.context).first();
		var percentileStripElement = $('.' + targetClass + ' .percentile-strip', this.context).first();
		percentileStripElement.hide();
		noDataElement.show();
	}

	updateCourseOverallAverage() {
		var targetClass = 'course-overall-average';
		clearTimeout(this.timers[targetClass]);
		this.hidePercentile(targetClass);
		if (this.checkCalculationsFinished()) {
			var totalActualPoints = 0;
			var totalExpectedPoints = 0;
			var totalStudents = 0;
			/**
			 * @property monitor {ValueAddedMonitoring}
			 */
			let monitor;

			for (var monitorIndex in this.monitors) {
				monitor = this.monitors[monitorIndex];
				var vaData = monitor.getClassOverallAverageVaData();
				if (vaData.students > 0) {
					totalStudents += parseInt(vaData.students);
					totalExpectedPoints += parseFloat(vaData.expectedPoints);
					totalActualPoints += parseFloat(vaData.actualPoints);
				}
			}
			if (totalStudents == 0) {
				this.hidePercentile(targetClass);
				return;
			}

			var vaScore = monitor.calculateValueAdded(
				totalStudents,
				totalActualPoints,
				totalExpectedPoints
			);
			var percentile = monitor.calculatePercentile(vaScore, monitor.percentiles);
			this.displayPercentile(targetClass, percentile, this.vaBoxHeight);
			this.setVATooltipValues(
				targetClass,
				vaScore,
				percentile,
				totalActualPoints,
				totalExpectedPoints,
				totalStudents,
				'Total Marks:'
			);
		} else {
			var me = this;
			this.timers[targetClass] = setTimeout(function () {
				me.updateCourseOverallAverage();
			}, 400);
		}
	}

	updateFinalGradeAverage() {
		var targetClass = 'course-final-average';
		// This has it's own timer as it uses results from all ajax-fed entries
		clearTimeout(this.timers[targetClass]);
		this.hidePercentile(targetClass);
		if (this.checkCalculationsFinished()) {
			var totalScore = 0;
			var totalExpected = 0;
			var totalStudents = 0;
			var finalResultsFound = false;
			let monitor;
			for (var monitorIndex in this.monitors) {
				monitor = this.monitors[monitorIndex];
				if (monitor.hasFinalResults) {
					var addScore = monitor.calculateFinalGradeValueAdded();
					totalScore += addScore.points;
					totalExpected += addScore.expected;
					totalStudents += addScore.students;
					finalResultsFound = true;
				}
			}
			if (!finalResultsFound || totalScore == 0 || totalStudents == 0) {
				this.hidePercentile(targetClass);
			} else {
				var vaScore = monitor.calculateValueAdded(totalStudents, totalScore, totalExpected);
				var percentile = monitor.calculatePercentile(vaScore, monitor.percentiles);
				var pixelHeight = $('.' + targetClass + ' .percentile-window').outerHeight();
				this.displayPercentile(targetClass, percentile, pixelHeight);
				this.setVATooltipValues(
					targetClass,
					vaScore,
					percentile,
					totalScore,
					totalExpected,
					totalStudents,
					'Total Marks:'
				);
				this.updateTooltips();
			}
		} else {
			var me = this;
			this.timers[targetClass] = setTimeout(function () {
				me.updateFinalGradeAverage();
			}, 400);
		}
	}

	updateCourseAssignmentAverage() {
		var targetClass = 'course-assignment-average';
		clearTimeout(this.timers[targetClass]);
		this.hidePercentile(targetClass);
		if (this.checkCalculationsFinished()) {
			var totalStudents = 0;
			var totalExpectedPoints = 0;
			var totalActualPoints = 0;
			let monitor;

			// Loop over classes
			for (var monitorIndex in this.monitors) {
				monitor = this.monitors[monitorIndex];
				monitor.recalculateLiveValueAddedScore();
				// If they have results include their last percentile score in calculation
				if (monitor.totalStudents > 0) {
					totalStudents += parseInt(monitor.totalStudents);
					totalExpectedPoints += parseFloat(monitor.expectedPoints);
					totalActualPoints += parseFloat(monitor.totalPoints);
				}
			}

			if (totalStudents == 0) {
				this.hidePercentile(targetClass);
				return;
			}

			var vaScore = monitor.calculateValueAdded(
				totalStudents,
				totalActualPoints,
				totalExpectedPoints
			);
			var percentile = monitor.calculatePercentile(vaScore, monitor.percentiles);
			this.displayPercentile(targetClass, percentile, this.vaBoxHeight);
			this.setVATooltipValues(
				targetClass,
				vaScore,
				percentile,
				totalActualPoints,
				totalExpectedPoints,
				totalStudents,
				'Total Marks:'
			);
		} else {
			var me = this;
			this.timers[targetClass] = setTimeout(function () {
				me.updateCourseAssignmentAverage();
			}, 400);
		}
	} // End of assignment average

// TODO abstract this as it is (exactly?) same as version in Class-level JS class
// TODO use value object instead of generic for percentile contextual info?
	displayPercentile(targetClass, percentileData, vaBoxHeight) {
		if (!(percentileData instanceof Object)) {
			throw "displayPercentile() second argument requires percentile data Object was given " + percentileData;
		}
		clearTimeout(this.timers[targetClass]);
		if (this.checkCalculationsFinished()) {
			vaBoxHeight = parseInt(vaBoxHeight);
			var noDataElement = $('.' + targetClass + ' .no-data', this.context).first();
			var percentileStripElement = $('.' + targetClass + ' .percentile-strip', this.context).first();
			percentileStripElement.stop();
			percentileStripElement.show();
			noDataElement.hide();
			// If you change the height of animated strip also change the CSS and vaboxheight number
			var pixelStart = vaBoxHeight * -1;
			var pixelTarget = (parseInt(percentileData.percentileIndex) * vaBoxHeight) - pixelStart;
			if (pixelTarget > 0) {
				pixelTarget = pixelTarget * -1;
			}

			$('.' + targetClass + ' .percentile-strip', this.context).first().animate(
				{'top': pixelTarget + 'px'},
				1500,
				'easeInOutBack'
			);
		} else {
			var me = this;
			this.hidePercentile(targetClass);
			this.timers[targetClass] = setTimeout(function () {
				me.displayPercentile(targetClass, percentileData, vaBoxHeight);
			}, 400);
		}
	}

	checkCalculationsFinished() {
		for (var monitorIndex in this.monitors) {
			var monitor = this.monitors[monitorIndex];
			// Finished or no results count as a pass
			if (monitor.finishedCalculating == false && monitor.noResults == false) {
				//console.log(monitor.code+" not finished", monitor);
				return false;
			}
		}

		return true;
	}

	filterChange(sourceMonitor, chart) {
		for (var classCode in this.monitors) {
			var monitor = this.getMonitor(classCode);
			if (monitor != sourceMonitor) {
				monitor.filterChange();
			}
		}
		//   this.resetPercentileToMiddle('course-overall-average');
		//   this.resetPercentileToMiddle('course-assignment-average');

		this.updateCourseAssignmentAverage();
		this.updateCourseOverallAverage();
	}

	setChart(chart) {
		this.chart = chart;
		this.chart.addNewChartColumn('number', 'Attendance', 'attendance', null, {
			courseAttendanceAnnotation: {role: 'annotation', type: 'string'}
		});
		this.chart.addNewChartColumn('number', 'Retention', 'retention', null, {
			courseRetentionAnnotation: {role: 'annotation', type: 'string'}
		});
		/*
		this.chart.addNewChartColumn('number', 'Value Added', 'va', null, {
			vaTooltip: {type:'string', role:'tooltip', 'p': {'html': true}},
			vaColouration: {type:'string', role:'style'},
			vaAnnotation: {type:'string', role:'annotation'}
		});*/
		/*
		this.chart.addNewChartColumn('number', 'Grades', 'gradeGroupsAStar', null);
		this.chart.addNewChartColumn('number', 'Grades', 'gradeGroupsAtoC', null);
		this.chart.addNewChartColumn('number', 'Grades', 'gradeGroupsDBelow', null);
		*/
		// Use closure and referenced "this" to allow method callback
		var obj = this;
		this.chart.listen('select', function (e) {
			obj.chartClickAction(e);
		});
	}

	chartClickAction() {
		var selected = this.chart.getSelection();
		var rowId = selected[0].row;
		var code = this.chart.getCell(rowId, 0);
		var classMonitor = this.getClassMonitorByCode(code);
		// For now just link to class view page
		var url = String(this.classViewUrl).replace('undefined', classMonitor.classId);
		// Open in new tab hack
		$("<a>").attr("href", url).attr("target", "_blank")[0].click();
	}

	getClassMonitorByCode(code) {
		for (var index in this.monitors) {
			var classMonitor = this.monitors[index];
			if (classMonitor.code == code) {
				return classMonitor;
			}
		}
		throw "Class monitor not found for code " + code;
	}

// TODO move to superclass (partially)
	updateTooltips() {
		// Update qtips on va results boxes
		var vaBoxes = ['course-assignment-average', 'course-overall-average', 'course-final-average'];
		for (var targetClassIndex in vaBoxes) {
			var targetClass = vaBoxes[targetClassIndex];
			var data = $('.' + targetClass + 'Tooltip.results-pop-out', this.context).first().html();
			$('.results.' + targetClass, this.context).each((idx, target) => {
				if (target.hasOwnProperty('_tippy')) {
					tippy(target, {content: data, allowHTML: true});
				}
			});
		}
	}

// TODO move to superclass
	setVATooltipValues(
		target,
		va,
		percentileData,
		points,
		expPoints,
		students,
		studentLabel
	) {
		studentLabel = typeof studentLabel !== 'undefined' ? studentLabel : null;

		var context = $('.course-table-wide .frozen-col');
		var tooltipSelector = '.' + target + 'Tooltip';
		$(tooltipSelector + ' .percentile-next-boundary span', context).html(percentileData.last);
		$(tooltipSelector + ' .percentile-last-boundary span', context).html(percentileData.next);
		$(tooltipSelector + ' .class-points', context).html(points.toFixed(0));
		$(tooltipSelector + ' .class-value-added-score', context).html(va);
		$(tooltipSelector + ' .expected-points', context).html(expPoints.toFixed(0));
		$(tooltipSelector + ' .students-included', context).html(students);
		if (studentLabel) {
			$(tooltipSelector + ' .students-label', context).html(studentLabel);
		}
		this.updateTooltips();
	}

// TODO move to superclass
	addChartName(chartName) {
		if (this.totalChartsLookup.indexOf(chartName) === -1) {
			this.totalChartsLookup.push(chartName);
		}
	}

// TODO move to superclass
	resetPercentileToMiddle(targetClass) {
		var percentileStripElement = $('.' + targetClass + ' .percentile-strip', this.context).first();
		var percentileIndex = Math.floor(Object.keys(this.getMonitor('any').percentiles).length / 2);
		// TODO guess this top is wrong
		percentileStripElement.css('top', this.calculatePercentilePixelTarget(percentileIndex, this.vaBoxHeight));
	}

// TODO move to superclass
	calculatePercentilePixelTarget(percentileIndex, vaBoxHeight) {
		// If you change the height of animated strip also change the CSS and vaboxheight number
		var pixelStart = vaBoxHeight * -1;
		// Uses percentileIndex not actual Number
		var pixelTarget = (percentileIndex * vaBoxHeight) - pixelStart;
		if (pixelTarget > 0) {
			pixelTarget = pixelTarget * -1;
		}
		return pixelTarget;
	}

}
