# Source: analytics.js

    /*
    Copyright (C) 2016
    Developed at University of Toronto

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    */

    const users = require('./users.js');
    const questions = require('./questions.js');
    const logger = require('./log.js');
    const common = require('./common.js');
    const db = require('./db.js');

    const classId = 'class';
    const analyticsTimeInterval = 86400000; // 24 * 60 * 60 * 1000

    var totalStudentsCount = 0;

    /**
     * start the analytics daily process
     *
     * @param {function} callback
     */
    exports.initialize = function(callback) {
        if (process.env.DEBUG) {
            getAnalytics(callback);
        } else {
            var d = new Date();
            var h = d.getHours();
            var m = d.getMinutes();
            var s = d.getSeconds();
            var secondsTilMidNight = ((24 * 60 * 60) - (h * 60 * 60) - (m * 60) - s) * 1000;

            setTimeout(function () {
                getAnalytics(callback);
                setInterval(function () {
                    getAnalytics(callback);
                }, analyticsTimeInterval);
            }, secondsTilMidNight);

            users.getStudentsList(function (err, studentsList) {
                if (err) {
                    logger.error(JSON.stringify(err));
                }
                totalStudentsCount = studentsList.length;
            });
        }
    }

    /**
     * get the charts based on type
     *
     * @param {object} query
     * @param {function} callback
     */
    exports.getChart = function(query, callback) {
        switch(query.type) {
            case 'QuestionsAnsweredVsClass':
                return getQuestionsAnsweredVsClass(query, callback);
            case 'overallVsClass':
                return getOverallVsClass(query, callback);
            case 'pointsPerAttemptVsClass':
                return getPointsPerAttemptVsClass(query, callback);
            case 'AccuracyVsClass':
                return getAccuracyVsClass(query, callback);
            case 'PointsVsClass':
                return getPointsVsClass(query, callback);
            case 'RatingVsClass':
                return getRatingVsClass(query, callback);
            case 'classAnswered':
                return getClassAnswered(query, callback);
            case 'classPointsPerAttempt':
                return getClassPointsPerAttempt(query, callback);
            case 'classOverall':
                return getClassOverall(query, callback);
            case 'classAnsweredOverTime':
                return getClassAnsweredOverTime(query, callback);
            case 'classPointsPerAttemptOverTime':
                return getClassPointsPerAttemptOverTime(query, callback);
            case 'classOverallOverTime':
                return getClassOverallOverTime(query, callback);
            case 'classAccuracyOverTime':
                return getClassAccuracyOverTime(query, callback);
            case 'classPointsOverTime':
                return getClassPointsOverTime(query, callback);
            case 'classAccuracy':
                return getClassAccuracy(query, callback);
            case 'classPoints':
                return getClassPoints(query, callback);
            case 'classRating':
                return getClassRating(query, callback);
            case 'correctAttemptsOverTime':
                return getCorrectAttemptsOverTime(query, callback);
            case 'accuracyOverTime':
                return getAccuracyOverTime(query, callback);
            case 'pointsOverTime':
                return getPointsOverTime(query, callback);
            case 'overallRankOverTime':
                return getOverallRankOverTime(query, callback);
            case 'pointsRankOverTime':
                return getPointsRankOverTime(query, callback);
            case 'attemptRankOverTime':
                return getAttemptRankOverTime(query, callback);
            case 'correctAttemptRankOverTime':
                return getCorrectAttemptRankOverTime(query, callback);
            case 'accuracyRankOverTime':
                return getAccuracyRankOverTime(query, callback);
            case 'pointsPerAttemptsOverTime':
                return getPointsPerAttemptsOverTime(query, callback);
            case 'overallOverTime':
                return getOverallOverTime(query, callback);
            case 'pointsPerTopicVsClass':
                return getPointsPerTopicVsClass(query, callback);
            case 'accuracyPerTopicVsClass':
                return getAccuracyPerTopicVsClass(query, callback);
            case 'pointsPerTypeVsClass':
                return getPointsPerTypeVsClass(query, callback);
            case 'accuracyPerTypeVsClass':
                return getAccuracyPerTypeVsClass(query, callback);
            case 'classPointsPerTopicVsClass':
                return getClassPointsPerTopicVsClass(query, callback);
            case 'classAccuracyPerTopicVsClass':
                return getClassAccuracyPerTopicVsClass(query, callback);
            case 'classPointsPerTypeVsClass':
                return getClassPointsPerTypeVsClass(query, callback);
            case 'classAccuracyPerTypeVsClass':
                return getClassAccuracyPerTypeVsClass(query, callback);
            case 'classRatingPerTopicVsClass':
                return getClassRatingPerTopicVsClass(query, callback);
            case 'classRatingPerTypeVsClass':
                return getClassRatingPerTypeVsClass(query, callback);
            default:
                return callback('notFound', null);
        }
    }

    /**
     * get analytics for a admins
     *
     * @param {function} callback
     */
    var getAnalytics = function(callback) {
        var studentsCount = 0;
        var classObject = {
            correctAttemptsCount: 0,
            wrongAttemptsCount: 0,
            totalAttemptsCount: 0,
            points: 0,
            accuracy: 0
        };
        var currentDate = common.getDateFormatedMinutesAgo('YYYY-MM-DD', 1);

        users.getFullLeaderboard(function (err, leaderboardList) {
            if (err) {
                logger.error(JSON.stringify(err));
                return callback(err, null);
            }

            if (!leaderboardList || leaderboardList.length === 0) {
                logger.log('Finished updating analytics. Status: warning. (No students available/active)');
                return callback(null, 'ok');
            }

            var classCorrectAttemptsCount = 0;
            var classWrongAttemptsCount = 0;
            var classTotalAttemptsCount = 0;
            var classPoints = 0;

            var getRanks = function (query) {
                var objList = {};
                var list = sortLeaderBoard(leaderboardList, query);
                var rank = 0;
                var score = -1;
                var repeated = 1;
                for (var i = 0; i < list.length; i++) {
                    if (score !== list[i][query]) {
                        score = list[i][query];
                        rank += repeated;
                        repeated = 1;
                    } else {
                        repeated ++;
                    }
                    objList[list[i]._id] = rank;
                }
                return objList;
            }

            var overallList = getRanks('overall');
            var attemptList = getRanks('attempt');
            var accuracyList = getRanks('accuracy');
            var pointsList = getRanks('points');
            var correctAttemptsList = getRanks('correctAttemptsCount');

            var overallSum = common.sumListOfNumbers(common.getIdsListFromJSONList(sortLeaderBoard(leaderboardList, 'overall'), 'overall'));
            var attemptSum = common.sumListOfNumbers(common.getIdsListFromJSONList(sortLeaderBoard(leaderboardList, 'attempt'), 'attempt'));
            var accuracySum = common.sumListOfNumbers(common.getIdsListFromJSONList(sortLeaderBoard(leaderboardList, 'accuracy'), 'accuracy'));
            var pointsSum = common.sumListOfNumbers(common.getIdsListFromJSONList(sortLeaderBoard(leaderboardList, 'points'), 'points'));
            var correctAttemptsSum = common.sumListOfNumbers(common.getIdsListFromJSONList(sortLeaderBoard(leaderboardList, 'correctAttemptsCount'), 'correctAttemptsCount'));

            users.getStudentsList(function (err, studentsList) {
                if (err) {
                    logger.error(JSON.stringify(err));
                    return callback(err, null);
                }

                totalStudentsCount = studentsList.length;
                for (var i in studentsList) {
                    var student = studentsList[i];
                    var row = {};

                    row.correctAttemptsCount = student.correctAttemptsCount;
                    row.wrongAttemptsCount = student.wrongAttemptsCount;
                    row.totalAttemptsCount = student.totalAttemptsCount;
                    row.points = student.points;
                    row.accuracy = student.totalAttemptsCount === 0 ? 0 : ((student.correctAttemptsCount / student.totalAttemptsCount) * 100).toFixed(2);

                    row.overallValue = leaderboardList.find((element) => {return element._id === student._id}).overall;
                    row.pointsPerAttemptValue = leaderboardList.find((element) => {return element._id === student._id}).attempt;
                    row.overallRank = overallList[student._id];
                    row.pointsPerAttemptRank = attemptList[student._id];
                    row.accuracyRank = accuracyList[student._id];
                    row.pointsRank = pointsList[student._id];
                    row.correctAttemptsRank = correctAttemptsList[student._id];

                    classCorrectAttemptsCount += student.correctAttemptsCount;
                    classWrongAttemptsCount += student.wrongAttemptsCount;
                    classTotalAttemptsCount += student.totalAttemptsCount;
                    classPoints += student.points;

                    addStudentAnalyticsWithDate(
                        student._id,
                        currentDate,
                        row,
                        function (err, result) {
                            if (err) {
                                logger.error(JSON.stringify(err));
                                return callback(err, null);
                            }

                            studentsCount++;
                            if (studentsCount === studentsList.length) {
                                classObject.points = studentsCount === 0 ? 0 :  (classPoints / studentsCount).toFixed(0);
                                classObject.accuracy = classCorrectAttemptsCount === 0 ? 0 :  ((classCorrectAttemptsCount / classTotalAttemptsCount) * 100).toFixed(2);

                                classObject.correctAttemptsCount = studentsCount === 0 ? 0 :  (classCorrectAttemptsCount / studentsCount).toFixed(0);
                                classObject.wrongAttemptsCount = studentsCount === 0 ? 0 :  (classWrongAttemptsCount / studentsCount).toFixed(0);
                                classObject.totalAttemptsCount = studentsCount === 0 ? 0 :  (classTotalAttemptsCount / studentsCount).toFixed(0);

                                classObject.overallAverage = studentsCount === 0 ? 0 :  (overallSum / studentsCount).toFixed(2);
                                classObject.pointsPerAttemptAverage = studentsCount === 0 ? 0 :  (attemptSum / studentsCount).toFixed(2);
                                classObject.accuracyAverage = studentsCount === 0 ? 0 :  (accuracySum / studentsCount).toFixed(2);
                                classObject.pointsAverage = studentsCount === 0 ? 0 :  (pointsSum / studentsCount).toFixed(2);
                                classObject.correctAttemptsAverage = studentsCount === 0 ? 0 :  (correctAttemptsSum / studentsCount).toFixed(2);

                                addStudentAnalyticsWithDate(
                                    classId,
                                    currentDate,
                                    classObject,
                                    function (err, result) {
                                        if (err) {
                                            logger.error(JSON.stringify(err));
                                            return callback(err, null);
                                        }

                                        logger.log('Finished updating analytics. Status: ok.');
                                        return callback(null, 'ok');
                                    }
                                );
                            }
                        }
                    );
                }
            });
        });
    }

    /**
     * sort leaderboard based on criteria
     *
     * @param {list} list
     * @param {string} criteria
     */
    var sortLeaderBoard = function(list, criteria) {
        return list.sort(function(student1,student2){
            return student2[criteria] - student1[criteria];
        });
    }

    /**
     * clean all previous analytics data
     *
     * @param {function} callback
     */
    exports.removeAnalytics = function (callback) {
        db.removeAnalytics(callback);
    }

    /**
     * add student analytics
     * if there are no records of the student, create a new record
     * if there are recards of the student, get the last recard and compute the deltas
     *
     * @param {string} studentId
     * @param {string} date
     * @param {object} info
     * @param {function} callback
     */
    var addStudentAnalyticsWithDate = function (studentId, date, info, callback) {
        db.addStudentAnalyticsWithDate(studentId, date, info, callback);
    }

    /**
     * get the list of data on the user by date
     *
     * @param {object} findQuery
     * @param {function} callback
     */
    var getTimeBasedAnalytics = function (findQuery, callback) {
        db.getTimeBasedAnalytics(findQuery, callback);
    }

    /**
     * get questions answered vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getQuestionsAnsweredVsClass = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var studentId = query.userId;
            var studentAnswered = 0;
            var classAnswered = 0;
            var classCount = 0;
            var classAnsweredAverage = 0;

            for (i in students) {
                if (students[i]._id === studentId) {
                    studentAnswered = students[i].correctAttemptsCount;
                } else {
                    classAnswered += students[i].correctAttemptsCount;
                    classCount++;
                }
            }

            classAnsweredAverage = classCount === 0 ? 0 : Math.ceil(classAnswered / classCount);
            return callback(null, [studentAnswered, classAnsweredAverage]);
        });
    }

    /**
     * get questions answered vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getOverallVsClass = function(query, callback) {
        users.getFullLeaderboard(function(err, leaderboardList) {
            if (err) {
                logger.error(JSON.stringify(err));
                return callback(err, null);
            }

            var studentId = query.userId;
            var studentOverall = 0;
            var classOverall = 0;
            var classCount = 0;

            for (i in leaderboardList) {
                if (leaderboardList[i]._id === studentId) {
                    studentOverall = parseFloat(leaderboardList[i].overall);
                } else {
                    classOverall += parseFloat(leaderboardList[i].overall);
                    classCount++;
                }
            }

            var classOverallAverage = classCount === 0 ? 0 : Math.ceil(classOverall / classCount);
            return callback(null, [studentOverall, classOverallAverage]);
        });
    }

    /**
     * get questions answered vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getPointsPerAttemptVsClass = function(query, callback) {
        users.getFullLeaderboard(function(err, leaderboardList) {
            if (err) {
                logger.error(JSON.stringify(err));
                return callback(err, null);
            }

            var studentId = query.userId;
            var studentPointsPerAttempt = 0;
            var classPointsPerAttempt = 0;
            var classCount = 0;

            for (i in leaderboardList) {
                if (leaderboardList[i]._id === studentId) {
                    studentPointsPerAttempt = parseFloat(leaderboardList[i].attempt);
                } else {
                    classPointsPerAttempt += parseFloat(leaderboardList[i].attempt);
                    classCount++;
                }
            }

            var classAttemptsAverage = classCount === 0 ? 0 : Math.ceil(classPointsPerAttempt / classCount);
            return callback(null, [studentPointsPerAttempt, classAttemptsAverage]);
        });
    }

    /**
     * get questions answered vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getAccuracyVsClass = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var studentId = query.userId;
            var studentAccuracy = 0;
            var classAccuracy = 0;
            var classCount = 0;
            var classAverageAccuracy = 0;

            for (i in students) {
                if (students[i]._id === studentId) {
                    studentAccuracy = parseFloat(students[i].totalAttemptsCount ? ((students[i].correctAttemptsCount / students[i].totalAttemptsCount)*100).toFixed(2) : 0);
                } else {
                    classAccuracy += parseFloat(students[i].totalAttemptsCount ? ((students[i].correctAttemptsCount / students[i].totalAttemptsCount)*100).toFixed(2) : 0);
                    classCount++;
                }
            }

            classAverageAccuracy = classCount === 0 ? 0 : parseFloat(((classAccuracy / classCount)).toFixed(2));
            return callback(null, [studentAccuracy, classAverageAccuracy]);
        });
    }

    /**
     * get points vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getPointsVsClass = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var studentId = query.userId;
            var studentPoints = 0;
            var classPoints = 0;
            var classCount = 0;
            var classPointsAverage = 0;

            for (i in students) {
                if (students[i]._id === studentId) {
                    studentPoints = students[i].points;
                } else {
                    classPoints += students[i].points;
                    classCount++;
                }
            }

            classPointsAverage = classCount === 0 ? 0 : Math.ceil(classPoints / classCount);
            return callback(null, [studentPoints, classPointsAverage]);
        });
    }

    /**
     * get rating vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getRatingVsClass = function(query, callback) {
        users.getUsersList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var studentId = query.userId;
            var studentRating = 0;
            var classRating = 0;
            var classCount = 0;
            var classRatingAverage = 0;

            for (i in students) {
                if (students[i].ratings.length !== 0) {
                    if (students[i]._id === studentId) {
                        studentRating = getAverageRating(students[i].ratings);
                    } else {
                        classRating += getAverageRating(students[i].ratings);
                        classCount++;
                    }
                }
            }

            classRatingAverage = classRating ? Math.ceil(classRating / classCount) : 0;
            return callback(null, [studentRating, classRatingAverage]);
        });
    }

    /**
     * get average rating
     *
     * @param {list} ratingsList
     */
    var getAverageRating = function(ratingsList) {
        var totalRate = 0;

        for (i in ratingsList) {
            totalRate += ratingsList[i].rating;
        }

        return Math.ceil(totalRate / ratingsList.length);
    }

    /**
     * get questions answered of the class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassAnswered = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var classAnswered = 0;
            var classCount = 0;
            var classAnsweredAverage = 0;

            for (i in students) {
                classAnswered += students[i].correctAttemptsCount;
                classCount++;
            }

            classAnsweredAverage =  classCount === 0 ? 0 :Math.ceil(classAnswered / classCount);
            return callback(null, [classAnsweredAverage]);
        });
    }

    /**
     * get class points per attempt of the class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassPointsPerAttempt = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var classPointsPerAttempt = 0;
            var classCount = 0;
            var classPointsPerAttemptAverage = 0;

            for (i in students) {
                classPointsPerAttempt += students[i].totalAttemptsCount === 0 ? 0 : parseFloat((students[i].points / students[i].totalAttemptsCount).toFixed(2));
                classCount++;
            }

            classPointsPerAttemptAverage =  classCount === 0 ? 0 : (classPointsPerAttempt / classCount).toFixed(2);
            return callback(null, [classPointsPerAttemptAverage]);
        });
    }

    /**
     * get overall value of the class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassOverall = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var classOverall = 0;
            var classCount = 0;
            var classOverallAverage = 0;

            for (i in students) {
                classOverall += (students[i].totalAttemptsCount === 0)
                ? 0
                : parseFloat((students[i].points *
                ((students[i].correctAttemptsCount/students[i].totalAttemptsCount) +
                (students[i].points/students[i].totalAttemptsCount))).toFixed(2));
                classCount++;
            }

            classOverallAverage =  classCount === 0 ? 0 : (classOverall / classCount).toFixed(2);
            return callback(null, [classOverallAverage]);
        });
    }

    /**
     * get questions answered of the class over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassAnsweredOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var classData = [];

            for (var i = 0; i < classObject.dates.length; i++) {
                dates.push(classObject.dates[i].date);
                classData.push(classObject.dates[i].info.correctAttemptsCount);
            }

            return callback(null, {
                dates: dates,
                classData: classData
            });
        });
    }

    /**
     * get overall average of the class over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassOverallOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var classData = [];

            for (var i = 0; i < classObject.dates.length; i++) {
                dates.push(classObject.dates[i].date);
                classData.push(classObject.dates[i].info.overallAverage);
            }

            return callback(null, {
                dates: dates,
                classData: classData
            });
        });
    }

    /**
     * get points per attempt average of the class over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassPointsPerAttemptOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var classData = [];

            for (var i = 0; i < classObject.dates.length; i++) {
                dates.push(classObject.dates[i].date);
                classData.push(classObject.dates[i].info.pointsPerAttemptAverage);
            }

            return callback(null, {
                dates: dates,
                classData: classData
            });
        });
    }

    /**
     * get accuracy of the class over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassAccuracyOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var classData = [];

            for (var i = 0; i < classObject.dates.length; i++) {
                dates.push(classObject.dates[i].date);
                classData.push(classObject.dates[i].info.accuracyAverage);
            }

            return callback(null, {
                dates: dates,
                classData: classData
            });
        });
    }

    /**
     * get points of the class over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassPointsOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var classData = [];

            for (var i = 0; i < classObject.dates.length; i++) {
                dates.push(classObject.dates[i].date);
                classData.push(classObject.dates[i].info.pointsAverage);
            }

            return callback(null, {
                dates: dates,
                classData: classData
            });
        });
    }

    /**
     * get accuracy of the class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassAccuracy = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var classAccuracy = 0;
            var classCount = 0;
            var classAverageAccuracy = 0;

            for (i in students) {
                classAccuracy += parseFloat(students[i].totalAttemptsCount ? ((students[i].correctAttemptsCount / students[i].totalAttemptsCount)*100).toFixed(2) : 0);
                classCount++;
            }

            classAverageAccuracy = classCount === 0 ? 0 : parseFloat(((classAccuracy / classCount)).toFixed(2));
            return callback(null, [classAverageAccuracy]);
        });
    }

    /**
     * get points of the class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassPoints = function(query, callback) {
        users.getStudentsList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var classPoints = 0;
            var classCount = 0;
            var classPointsAverage = 0;

            for (i in students) {
                classPoints += students[i].points;
                classCount++;
            }

            classPointsAverage = classCount === 0 ? 0 : Math.ceil(classPoints / classCount);
            return callback(null, [classPointsAverage]);
        });
    }

    /**
     * get rating of the class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassRating = function(query, callback) {
        users.getUsersList(function(err, students) {
            if (err) {
                return callback(err, null);
            }

            if (!students) {
                return  callback('no results', null);
            }

            var classRating = 0;
            var classCount = 0;
            var classRatingAverage = 0;

            for (i in students) {
                if (students[i].ratings.length !== 0) {
                    classRating += getAverageRating(students[i].ratings);
                    classCount++;
                }
            }

            classRatingAverage = classRating ? Math.ceil(classRating / classCount) : 0;
            return callback(null, [classRatingAverage]);
        });
    }

    /**
     * get number correct attempts of user over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getCorrectAttemptsOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }
            getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
                if (err) {
                    return callback(err, null);
                }

                var dates = [];
                var studentData = [];
                var classData = [];

                for (var i = 0; i < classObject.dates.length - studentObject.dates.length; i++) {
                    studentData.push(0);
                }

                for (var i = 0; i < studentObject.dates.length; i++) {
                    studentData.push(studentObject.dates[i].info.correctAttemptsCount);
                }

                for (var i = 0; i < classObject.dates.length; i++) {
                    dates.push(classObject.dates[i].date);
                    classData.push(classObject.dates[i].info.correctAttemptsCount);
                }

                return callback(null, {
                    dates: dates,
                    studentData: studentData,
                    classData: classData
                });
            });
        });
    }

    /**
     * get points per attempts of user and class over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getPointsPerAttemptsOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }
            getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
                if (err) {
                    return callback(err, null);
                }

                var dates = [];
                var studentData = [];
                var classData = [];

                for (var i = 0; i < classObject.dates.length - studentObject.dates.length; i++) {
                    studentData.push(0);
                }

                for (var i = 0; i < studentObject.dates.length; i++) {
                    studentData.push(studentObject.dates[i].info.pointsPerAttemptValue);
                }

                for (var i = 0; i < classObject.dates.length; i++) {
                    dates.push(classObject.dates[i].date);
                    classData.push(classObject.dates[i].info.pointsPerAttemptAverage);
                }

                return callback(null, {
                    dates: dates,
                    studentData: studentData,
                    classData: classData
                });
            });
        });
    }

    /**
     * get points per attempts of user and class over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getOverallOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }
            getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
                if (err) {
                    return callback(err, null);
                }

                var dates = [];
                var studentData = [];
                var classData = [];

                for (var i = 0; i < classObject.dates.length - studentObject.dates.length; i++) {
                    studentData.push(0);
                }

                for (var i = 0; i < studentObject.dates.length; i++) {
                    studentData.push(studentObject.dates[i].info.overallValue);
                }

                for (var i = 0; i < classObject.dates.length; i++) {
                    dates.push(classObject.dates[i].date);
                    classData.push(classObject.dates[i].info.overallAverage);
                }

                return callback(null, {
                    dates: dates,
                    studentData: studentData,
                    classData: classData
                });
            });
        });
    }

    /**
     * get accuracy of user over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getAccuracyOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }
            getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
                if (err) {
                    return callback(err, null);
                }

                var dates = [];
                var studentData = [];
                var classData = [];

                for (var i = 0; i < classObject.dates.length - studentObject.dates.length; i++) {
                    studentData.push(0);
                }

                for (var i = 0; i < studentObject.dates.length; i++) {
                    studentData.push(studentObject.dates[i].info.accuracy);
                }

                for (var i = 0; i < classObject.dates.length; i++) {
                    dates.push(classObject.dates[i].date);
                    classData.push(classObject.dates[i].info.accuracy);
                }

                return callback(null, {
                    dates: dates,
                    studentData: studentData,
                    classData: classData
                });
            });
        });
    }

    /**
     * get points of user over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getPointsOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }
            getTimeBasedAnalytics({_id:classId}, function(err, classObject) {
                if (err) {
                    return callback(err, null);
                }

                var dates = [];
                var studentData = [];
                var classData = [];

                for (var i = 0; i < classObject.dates.length - studentObject.dates.length; i++) {
                    studentData.push(0);
                }

                for (var i = 0; i < studentObject.dates.length; i++) {
                    studentData.push(studentObject.dates[i].info.points);
                }

                for (var i = 0; i < classObject.dates.length; i++) {
                    dates.push(classObject.dates[i].date);
                    classData.push(classObject.dates[i].info.points);
                }

                return callback(null, {
                    dates: dates,
                    studentData: studentData,
                    classData: classData
                });
            });
        });
    }

    /**
     * get overall rank over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getOverallRankOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var studentData = [];

            for (var i = 0; i < studentObject.dates.length; i++) {
                dates.push(studentObject.dates[i].date);
                studentData.push(studentObject.dates[i].info.overallRank);
            }

            return callback(null, {
                dates: dates,
                studentData: studentData,
                totalStudentsCount: totalStudentsCount
            });
        });
    }

    /**
     * get points rank over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getPointsRankOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var studentData = [];

            for (var i = 0; i < studentObject.dates.length; i++) {
                dates.push(studentObject.dates[i].date);
                studentData.push(studentObject.dates[i].info.pointsRank);
            }

            return callback(null, {
                dates: dates,
                studentData: studentData,
                totalStudentsCount: totalStudentsCount
            });
        });
    }

    /**
     * get attempt rank over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getAttemptRankOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var studentData = [];

            for (var i = 0; i < studentObject.dates.length; i++) {
                dates.push(studentObject.dates[i].date);
                studentData.push(studentObject.dates[i].info.pointsPerAttemptRank);
            }

            return callback(null, {
                dates: dates,
                studentData: studentData,
                totalStudentsCount: totalStudentsCount
            });
        });
    }

    /**
     * get correct attempt rank over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getCorrectAttemptRankOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var studentData = [];

            for (var i = 0; i < studentObject.dates.length; i++) {
                dates.push(studentObject.dates[i].date);
                studentData.push(studentObject.dates[i].info.correctAttemptsRank);
            }

            return callback(null, {
                dates: dates,
                studentData: studentData,
                totalStudentsCount: totalStudentsCount
            });
        });
    }

    /**
     * get accuracy rank over time
     *
     * @param {object} query
     * @param {function} callback
     */
    var getAccuracyRankOverTime = function(query, callback) {
        getTimeBasedAnalytics({_id:query.userId}, function(err, studentObject) {
            if (err) {
                return callback(err, null);
            }

            var dates = [];
            var studentData = [];

            for (var i = 0; i < studentObject.dates.length; i++) {
                dates.push(studentObject.dates[i].date);
                studentData.push(studentObject.dates[i].info.accuracyRank);
            }

            return callback(null, {
                dates: dates,
                studentData: studentData,
                totalStudentsCount: totalStudentsCount
            });
        });
    }

    /**
     * get points per question topic
     *
     * @param {object} query
     * @param {function} callback
     */
    var getPointsPerTopicVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {topic: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var studentId = query.userId;
            var studentData = [];
            var classData = [];
            var labels = [];
            var currentTopic = questionsList[0].topic;
            var studentPoints = 0;
            var classPoints = 0;
            var classCount = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.topic !== currentTopic) {
                    labels.push(currentTopic);
                    studentData.push(studentPoints);
                    classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
                    studentPoints = 0;
                    classPoints = 0;
                    classCount = 0;
                    currentTopic = question.topic;
                }

                for (var j = 0; j < question.correctAttempts.length; j++) {
                    var attempt = question.correctAttempts[j];
                    if (attempt.userId === studentId) {
                        studentPoints = attempt.points;
                    } else {
                        classPoints += attempt.points;
                        classCount ++;
                    }
                }
            }

            if (questionsList.length > 0) {
                currentTopic = questionsList[questionsList.length-1].topic;
                labels.push(currentTopic);
                studentData.push(studentPoints);
                classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
            }

            return callback(null, {
                studentData: studentData,
                classData: classData,
                labels: labels
            });
        });
    }

    /**
     * get accuracy per question topic
     *
     * @param {object} query
     * @param {function} callback
     */
    var getAccuracyPerTopicVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {topic: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var studentId = query.userId;
            var studentData = [];
            var classData = [];
            var labels = [];
            var currentTopic = questionsList[0].topic;
            var studentCorrect = 0;
            var studentTotal = 0;
            var classCorrect = 0;
            var classTotal = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.topic !== currentTopic) {
                    labels.push(currentTopic);
                    studentData.push((studentTotal === 0) ? 0 : ((studentCorrect / studentTotal) * 100).toFixed(2));
                    classData.push((classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
                    studentCorrect = 0;
                    studentTotal = 0;
                    classCorrect = 0;
                    classTotal = 0;
                    currentTopic = question.topic;
                }

                for (var j = 0; j < question.correctAttempts.length; j++) {
                    var attempt = question.correctAttempts[j];
                    if (attempt.userId === studentId) {
                        studentCorrect ++;
                    } else {
                        classCorrect ++;
                    }
                }

                for (var j = 0; j < question.totalAttempts.length; j++) {
                    var attempt = question.totalAttempts[j];
                    if (attempt.userId === studentId) {
                        studentTotal ++;
                    } else {
                        classTotal ++;
                    }
                }
            }

            if (questionsList.length > 0) {
                currentTopic = questionsList[questionsList.length-1].topic;
                labels.push(currentTopic);
                studentData.push((studentTotal === 0) ? 0 : ((studentCorrect / studentTotal) * 100).toFixed(2));
                classData.push((classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
            }

            return callback(null, {
                studentData: studentData,
                classData: classData,
                labels: labels
            });
        });
    }

    /**
     * get points per question type
     *
     * @param {object} query
     * @param {function} callback
     */
    var getPointsPerTypeVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {type: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var studentId = query.userId;
            var studentData = [];
            var classData = [];
            var labels = [];
            var currentType = questionsList[0].type;
            var studentPoints = 0;
            var classPoints = 0;
            var classCount = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.type !== currentType) {
                    labels.push(currentType);
                    studentData.push(studentPoints);
                    classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
                    studentPoints = 0;
                    classPoints = 0;
                    classCount = 0;
                    currentType = question.type;
                }

                for (var j = 0; j < question.correctAttempts.length; j++) {
                    var attempt = question.correctAttempts[j];
                    if (attempt.userId === studentId) {
                        studentPoints = attempt.points;
                    } else {
                        classPoints += attempt.points;
                        classCount ++;
                    }
                }
            }

            if (questionsList.length > 0) {
                currentType = questionsList[questionsList.length-1].type;
                labels.push(currentType);
                studentData.push(studentPoints);
                classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
            }

            return callback(null, {
                studentData: studentData,
                classData: classData,
                labels: labels
            });
        });
    }

    /**
     * get accuracy per question type
     *
     * @param {object} query
     * @param {function} callback
     */
    var getAccuracyPerTypeVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {type: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var studentId = query.userId;
            var studentData = [];
            var classData = [];
            var labels = [];
            var currentType = questionsList[0].type;
            var studentCorrect = 0;
            var studentTotal = 0;
            var classCorrect = 0;
            var classTotal = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.type !== currentType) {
                    labels.push(currentType);
                    studentData.push((studentTotal === 0) ? 0 : ((studentCorrect / studentTotal) * 100).toFixed(2));
                    classData.push((classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
                    studentCorrect = 0;
                    studentTotal = 0;
                    classCorrect = 0;
                    classTotal = 0;
                    currentType = question.type;
                }

                for (var j = 0; j < question.correctAttempts.length; j++) {
                    var attempt = question.correctAttempts[j];
                    if (attempt.userId === studentId) {
                        studentCorrect ++;
                    } else {
                        classCorrect ++;
                    }
                }

                for (var j = 0; j < question.totalAttempts.length; j++) {
                    var attempt = question.totalAttempts[j];
                    if (attempt.userId === studentId) {
                        studentTotal ++;
                    } else {
                        classTotal ++;
                    }
                }
            }

            if (questionsList.length > 0) {
                currentType = questionsList[questionsList.length-1].type;
                labels.push(currentType);
                studentData.push((studentTotal === 0) ? 0 : ((studentCorrect / studentTotal) * 100).toFixed(2));
                classData.push( (classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
            }

            return callback(null, {
                studentData: studentData,
                classData: classData,
                labels: labels
            });
        });
    }

    /**
     * get class points per question topic
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassPointsPerTopicVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {topic: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var classData = [];
            var labels = [];
            var currentTopic = questionsList[0].topic;
            var classPoints = 0;
            var classCount = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.topic !== currentTopic) {
                    labels.push(currentTopic);
                    classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
                    classPoints = 0;
                    classCount = 0;
                    currentTopic = question.topic;
                }

                for (var j = 0; j < question.correctAttempts.length; j++) {
                    classPoints += question.correctAttempts[j].points;
                    classCount ++;
                }
            }

            if (questionsList.length > 0) {
                currentTopic = questionsList[questionsList.length-1].topic;
                labels.push(currentTopic);
                classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
            }

            return callback(null, {
                classData: classData,
                labels: labels
            });
        });
    }

    /**
     * get class accuracy per question topic
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassAccuracyPerTopicVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {topic: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var classData = [];
            var labels = [];
            var currentTopic = questionsList[0].topic;
            var classCorrect = 0;
            var classTotal = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.topic !== currentTopic) {
                    labels.push(currentTopic);
                    classData.push((classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
                    classCorrect = 0;
                    classTotal = 0;
                    currentTopic = question.topic;
                }

                classCorrect += question.correctAttemptsCount;
                classTotal += question.totalAttemptsCount;
            }

            if (questionsList.length > 0) {
                currentTopic = questionsList[questionsList.length-1].topic;
                labels.push(currentTopic);
                classData.push((classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
            }

            return callback(null, {
                classData: classData,
                labels: labels
            });
        });
    }

    /**
     * get class rating per topic vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassRatingPerTopicVsClass = function (query, callback) {
        users.getUsersList(function (err, studentsList) {
            if (err) {
                return callback(err, null);
            }

            var studentsObject = {};
            for (var j = 0; j < studentsList.length; j++) {
                studentsObject[studentsList[j]._id] = studentsList[j].type;
            }

            questions.getAllQuestionsByQuery({}, {topic: 1}, function(err, questionsList) {
                if (err) {
                    return callback(err, null);
                }

                if (!questionsList || !questionsList[0]) {
                    return callback('no questions available', null);
                }

                var adminsData = [];
                var studentsData = [];
                var labels = [];
                var currentTopic = questionsList[0].topic;
                var adminsRating = 0;
                var studentsRating = 0;
                var adminsCount = 0;
                var studentsCount = 0;

                for (var i = 0; i < questionsList.length; i++) {
                    var question = questionsList[i];

                    if (question.topic !== currentTopic) {
                        labels.push(currentTopic);
                        adminsData.push((adminsCount === 0) ? 0 : (adminsRating / adminsCount).toFixed(2));
                        studentsData.push((studentsCount === 0) ? 0 : (studentsRating / studentsCount).toFixed(2));
                        adminsCount = 0;
                        studentsCount = 0;
                        adminsRating = 0;
                        studentsRating = 0;
                        currentTopic = question.topic;
                    }

                    for (var j = 0; j < question.ratings.length; j++) {
                        var userObject = question.ratings[j];

                        if (studentsObject[userObject.userId] === common.userTypes.ADMIN) {
                            adminsRating += userObject.rating;
                            adminsCount ++;
                        } else {
                            studentsRating += userObject.rating;
                            studentsCount ++;
                        }
                    }
                }

                if (questionsList.length > 0) {
                    currentTopic = questionsList[questionsList.length-1].topic;
                    labels.push(currentTopic);
                    adminsData.push((adminsCount === 0) ? 0 : (adminsRating / adminsCount).toFixed(2));
                    studentsData.push((studentsCount === 0) ? 0 : (studentsRating / studentsCount).toFixed(2));
                }

                return callback(null, {
                    adminsData: adminsData,
                    studentsData: studentsData,
                    labels: labels
                });
            });
        });
    }

    /**
     * get class rating per type vs class
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassRatingPerTypeVsClass = function (query, callback) {
        users.getUsersList(function (err, studentsList) {
            if (err) {
                return callback(err, null);
            }

            var studentsObject = {};
            for (var j = 0; j < studentsList.length; j++) {
                studentsObject[studentsList[j]._id] = studentsList[j].type;
            }

            questions.getAllQuestionsByQuery({}, {type: 1}, function(err, questionsList) {
                if (err) {
                    return callback(err, null);
                }

                if (!questionsList || !questionsList[0]) {
                    return callback('no questions available', null);
                }

                var adminsData = [];
                var studentsData = [];
                var labels = [];
                var currentType = questionsList[0].type;
                var adminsRating = 0;
                var studentsRating = 0;
                var adminsCount = 0;
                var studentsCount = 0;

                for (var i = 0; i < questionsList.length; i++) {
                    var question = questionsList[i];

                    if (question.type !== currentType) {
                        labels.push(currentType);
                        adminsData.push((adminsCount === 0) ? 0 : (adminsRating / adminsCount).toFixed(2));
                        studentsData.push((studentsCount === 0) ? 0 : (studentsRating / studentsCount).toFixed(2));
                        adminsCount = 0;
                        studentsCount = 0;
                        adminsRating = 0;
                        studentsRating = 0;
                        currentType = question.type;
                    }

                    for (var j = 0; j < question.ratings.length; j++) {
                        var userObject = question.ratings[j];

                        if (studentsObject[userObject.userId] === common.userTypes.ADMIN) {
                            adminsRating += userObject.rating;
                            adminsCount ++;
                        } else {
                            studentsRating += userObject.rating;
                            studentsCount ++;
                        }
                    }
                }

                if (questionsList.length > 0) {
                    currentType = questionsList[questionsList.length-1].type;
                    labels.push(currentType);
                    adminsData.push((adminsCount === 0) ? 0 : (adminsRating / adminsCount).toFixed(2));
                    studentsData.push((studentsCount === 0) ? 0 : (studentsRating / studentsCount).toFixed(2));
                }

                return callback(null, {
                    adminsData: adminsData,
                    studentsData: studentsData,
                    labels: labels
                });
            });
        });
    }

    /**
     * get class points per question type
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassPointsPerTypeVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {type: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var classData = [];
            var labels = [];
            var currentType = questionsList[0].type;
            var classPoints = 0;
            var classCount = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.type !== currentType) {
                    labels.push(currentType);
                    classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
                    classPoints = 0;
                    classCount = 0;
                    currentType = question.type;
                }

                for (var j = 0; j < question.correctAttempts.length; j++) {
                    classPoints = question.correctAttempts[j].points;
                    classCount ++;
                }
            }

            if (questionsList.length > 0) {
                currentType = questionsList[questionsList.length-1].type;
                labels.push(currentType);
                classData.push((classCount === 0) ? 0 : (classPoints / classCount).toFixed(0));
            }

            return callback(null, {
                classData: classData,
                labels: labels
            });
        });
    }

    /**
     * get class accuracy per question type
     *
     * @param {object} query
     * @param {function} callback
     */
    var getClassAccuracyPerTypeVsClass = function(query, callback) {
        questions.getAllQuestionsByQuery({}, {type: 1}, function(err, questionsList) {
            if (err) {
                return callback(err, null);
            }

            if (!questionsList || !questionsList[0]) {
                return callback('no questions available', null);
            }

            var classData = [];
            var labels = [];
            var currentType = questionsList[0].type;
            var classCorrect = 0;
            var classTotal = 0;

            for (var i = 0; i < questionsList.length; i++) {
                var question = questionsList[i];

                if (question.type !== currentType) {
                    labels.push(currentType);
                    classData.push((classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
                    classCorrect = 0;
                    classTotal = 0;
                    currentType = question.type;
                }

                classCorrect += question.correctAttemptsCount;
                classTotal += question.totalAttemptsCount;
            }

            if (questionsList.length > 0) {
                currentType = questionsList[questionsList.length-1].type;
                labels.push(currentType);
                classData.push( (classTotal === 0) ? 0 : ((classCorrect / classTotal) * 100).toFixed(2));
            }

            return callback(null, {
                classData: classData,
                labels: labels
            });
        });
    }