<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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.
//
// Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Class that computes summary of users points
 *
 * @package   mod_attendance
 * @copyright  2016 Antonio Carlos Mariani http://antonio.c.mariani@gmail.com
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/mod/attendance/locallib.php');

/**
 * Class that computes summary of users points
 *
 * @package   mod_attendance
 * @copyright  2016 Antonio Carlos Mariani http://antonio.c.mariani@gmail.com
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class mod_attendance_summary {

    /** @var int attendance instance identifier */
    private $attendanceid;

    /** @var stdclass course course data*/
    private $course;

    /** @var int groupmode*/
    private $groupmode;

    /** @var array userspoints (userid, numtakensessions, points, maxpoints) */
    private $userspoints;

    /** @var array pointsbygroup (groupid, numsessions, maxpoints) */
    private $maxpointsbygroupsessions;

    /** @var array userstakensessionsbyacronym */
    private $userstakensessionsbyacronym;

    /**
     * Initializes the class
     *
     * @param int $attendanceid instance identifier
     * @param array $userids user instances identifier
     * @param int $startdate Attendance sessions startdate
     * @param int $enddate Attendance sessions enddate
     */
    public function __construct($attendanceid, $userids=array(), $startdate = '', $enddate = '') {
        $this->attendanceid = $attendanceid;

        $this->compute_users_points($userids, $startdate, $enddate);
        $this->compute_users_taken_sessions_by_acronym($userids, $startdate, $enddate);
    }

    /**
     * Returns true if the user has some session with points
     *
     * @param int $userid User instance id
     *
     * @return boolean
     */
    public function has_taken_sessions($userid) {
        return isset($this->userspoints[$userid]);
    }

    /**
     * Returns true if the corresponding attendance instance is currently configure to work with grades (points)
     *
     * @return boolean
     */
    public function with_groups() {
        return $this->groupmode > 0;
    }

    /**
     * Returns the groupmode of the corresponding attendance instance
     *
     * @return int
     */
    public function get_groupmode() {
        return $this->groupmode;
    }

    /**
     * Returns the percentages of each user related to the taken sessions
     *
     * @return array
     */
    public function get_user_taken_sessions_percentages() {
        $percentages = array();

        foreach ($this->userspoints as $userid => $userpoints) {
            $percentages[$userid] = attendance_calc_fraction($userpoints->points, $userpoints->maxpoints);
        }

        return $percentages;
    }

    /**
     * Returns a summary of the points assigned to the user related to the taken sessions
     *
     * @param int $userid User instance id
     *
     * @return array
     */
    public function get_taken_sessions_summary_for($userid) {
        $usersummary = new stdClass();
        if ($this->has_taken_sessions($userid)) {
            $usersummary->numtakensessions = $this->userspoints[$userid]->numtakensessions;
            $usersummary->takensessionspoints = $this->userspoints[$userid]->points;
            $usersummary->takensessionsmaxpoints = $this->userspoints[$userid]->maxpoints;
        } else {
            $usersummary->numtakensessions = 0;
            $usersummary->takensessionspoints = 0;
            $usersummary->takensessionsmaxpoints = 0;
        }
        $usersummary->takensessionspercentage = attendance_calc_fraction($usersummary->takensessionspoints,
                                                                         $usersummary->takensessionsmaxpoints);
        if (isset($this->userstakensessionsbyacronym[$userid])) {
            $usersummary->userstakensessionsbyacronym = $this->userstakensessionsbyacronym[$userid];
        } else {
            $usersummary->userstakensessionsbyacronym = array();
        }

        return $usersummary;
    }

    /**
     * Returns a summary of the points assigned to the user, both related to taken sessions and related to all sessions
     *
     * @param int $userid User instance id
     *
     * @return array
     */
    public function get_all_sessions_summary_for($userid) {
        $usersummary = $this->get_taken_sessions_summary_for($userid);

        if (!isset($this->maxpointsbygroupsessions)) {
            $this->compute_maxpoints_by_group_session();
        }

        $usersummary->numallsessions = $this->maxpointsbygroupsessions[0]->numsessions;
        $usersummary->allsessionsmaxpoints = $this->maxpointsbygroupsessions[0]->maxpoints;

        if ($this->with_groups()) {
            $groupids = array_keys(groups_get_all_groups($this->course->id, $userid));
            foreach ($groupids as $gid) {
                if (isset($this->maxpointsbygroupsessions[$gid])) {
                    $usersummary->numallsessions += $this->maxpointsbygroupsessions[$gid]->numsessions;
                    $usersummary->allsessionsmaxpoints += $this->maxpointsbygroupsessions[$gid]->maxpoints;
                }
            }
        }
        $usersummary->allsessionspercentage = attendance_calc_fraction($usersummary->takensessionspoints,
                                                                       $usersummary->allsessionsmaxpoints);

        $deltapoints = $usersummary->allsessionsmaxpoints - $usersummary->takensessionsmaxpoints;
        $usersummary->maxpossiblepoints = $usersummary->takensessionspoints + $deltapoints;
        $usersummary->maxpossiblepercentage = attendance_calc_fraction($usersummary->maxpossiblepoints,
                                                                       $usersummary->allsessionsmaxpoints);

        return $usersummary;
    }

    /**
     * Computes the summary of points for the users that have some taken session
     *
     * @param array $userids user instances identifier
     * @param int $startdate Attendance sessions startdate
     * @param int $enddate Attendance sessions enddate
     * @return  (userid, numtakensessions, points, maxpoints)
     */
    private function compute_users_points($userids=array(), $startdate = '', $enddate = '') {
        global $DB;

        list($this->course, $cm) = get_course_and_cm_from_instance($this->attendanceid, 'attendance');
        $this->groupmode = $cm->effectivegroupmode;

        $params = array(
            'attid'      => $this->attendanceid,
            'attid2'     => $this->attendanceid,
            'cstartdate' => $this->course->startdate,
            );

        $where = '';
        if (!empty($userids)) {
            list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
            $where .= ' AND atl.studentid ' . $insql;
            $params = array_merge($params, $inparams);
        }
        if (!empty($startdate)) {
            $where .= ' AND ats.sessdate >= :startdate';
            $params['startdate'] = $startdate;
        }
        if (!empty($enddate)) {
            $where .= ' AND ats.sessdate < :enddate ';
            $params['enddate'] = $enddate;
        }

        $joingroup = '';
        if ($this->with_groups()) {
            $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
            $where .= ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
        } else {
            $where .= ' AND ats.groupid = 0';
        }

        $sql = " SELECT atl.studentid AS userid, COUNT(DISTINCT ats.id) AS numtakensessions,
                        SUM(stg.grade) AS points, SUM(stm.maxgrade) AS maxpoints
                   FROM {attendance_sessions} ats
                   JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
                   JOIN {attendance_statuses} stg ON (stg.id = atl.statusid AND stg.deleted = 0 AND stg.visible = 1)
                   JOIN (SELECT setnumber, MAX(grade) AS maxgrade
                           FROM {attendance_statuses}
                          WHERE attendanceid = :attid2
                            AND deleted = 0
                            AND visible = 1
                         GROUP BY setnumber) stm
                     ON (stm.setnumber = ats.statusset)
                   {$joingroup}
                  WHERE ats.attendanceid = :attid
                    AND ats.sessdate >= :cstartdate
                    AND ats.lasttaken != 0
                    {$where}
                GROUP BY atl.studentid";
        $this->userspoints = $DB->get_records_sql($sql, $params);
    }

    /**
     * Computes the summary of taken sessions by acronym
     *
     * @param array $userids user instances identifier
     * @param int $startdate Attendance sessions startdate
     * @param int $enddate Attendance sessions enddate
     * @return  null
     */
    private function compute_users_taken_sessions_by_acronym($userids=array(), $startdate = '', $enddate = '') {
        global $DB;

        list($this->course, $cm) = get_course_and_cm_from_instance($this->attendanceid, 'attendance');
        $this->groupmode = $cm->effectivegroupmode;

        $params = array(
            'attid'      => $this->attendanceid,
            'cstartdate' => $this->course->startdate,
            );

        $where = '';
        if (!empty($userids)) {
            list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
            $where .= ' AND atl.studentid ' . $insql;
            $params = array_merge($params, $inparams);
        }
        if (!empty($startdate)) {
            $where .= ' AND ats.sessdate >= :startdate';
            $params['startdate'] = $startdate;
        }
        if (!empty($enddate)) {
            $where .= ' AND ats.sessdate < :enddate ';
            $params['enddate'] = $enddate;
        }

        if ($this->with_groups()) {
            $joingroup = 'LEFT JOIN {groups_members} gm ON (gm.userid = atl.studentid AND gm.groupid = ats.groupid)';
            $where .= ' AND (ats.groupid = 0 or gm.id is NOT NULL)';
        } else {
            $joingroup = '';
            $where .= ' AND ats.groupid = 0';
        }

        $sql = "SELECT atl.studentid AS userid, sts.setnumber, sts.acronym, COUNT(*) AS numtakensessions
                  FROM {attendance_sessions} ats
                  JOIN {attendance_log} atl ON (atl.sessionid = ats.id)
                  JOIN {attendance_statuses} sts
                    ON (sts.attendanceid = ats.attendanceid AND
                        sts.id = atl.statusid AND
                        sts.deleted = 0 AND sts.visible = 1)
                  {$joingroup}
                 WHERE ats.attendanceid = :attid
                   AND ats.sessdate >= :cstartdate
                   AND ats.lasttaken != 0
                   {$where}
              GROUP BY atl.studentid, sts.setnumber, sts.acronym";
        $this->userstakensessionsbyacronym = array();
        $records = $DB->get_recordset_sql($sql, $params);
        foreach ($records as $rec) {
            $this->userstakensessionsbyacronym[$rec->userid][$rec->setnumber][$rec->acronym] = $rec->numtakensessions;
        }
        $records->close();
    }

    /**
     * Computes and store the maximum points possible for each group session
     *
     * @return null
     */
    private function compute_maxpoints_by_group_session() {
        global $DB;

        $params = array(
            'attid'      => $this->attendanceid,
            'attid2'     => $this->attendanceid,
            'cstartdate' => $this->course->startdate,
            );

        $where = '';
        if (!$this->with_groups()) {
            $where = 'AND sess.groupid = 0';
        }

        $sql = "SELECT sess.groupid, COUNT(*) AS numsessions, SUM(stamax.maxgrade) AS maxpoints
                  FROM {attendance_sessions} sess
                  JOIN (SELECT setnumber, MAX(grade) AS maxgrade
                                             FROM {attendance_statuses}
                                            WHERE attendanceid = :attid2
                                              AND deleted = 0
                                              AND visible = 1
                                           GROUP BY setnumber) stamax
                    ON (stamax.setnumber = sess.statusset)
                 WHERE sess.attendanceid = :attid
                   AND sess.sessdate >= :cstartdate
                   {$where}
              GROUP BY sess.groupid";
        $this->maxpointsbygroupsessions = $DB->get_records_sql($sql, $params);

        if (!isset($this->maxpointsbygroupsessions[0])) {
            $gpoints = new stdClass();
            $gpoints->numsessions = 0;
            $gpoints->maxpoints = 0;
            $this->maxpointsbygroupsessions[0] = $gpoints;
        }
    }
}