<?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/>.

/**
 * Deadline Manager Header
 *
 * @package    block_ned_teacher_tools
 * @copyright  Michael Gardener <mgardener@cissq.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */


namespace block_ned_teacher_tools\mod;

defined('MOODLE_INTERNAL') || die();

use block_ned_teacher_tools as NED;
use block_ned_teacher_tools\shared_lib as SH;

/** @var \stdClass $CFG */
require_once($CFG->dirroot . '/mod/assign/locallib.php');
require_once($CFG->dirroot . '/mod/assign/lib.php');
require_once($CFG->dirroot . '/lib/gradelib.php');
require_once(__DIR__ . '/../../lib.php');


/**
 * Class deadline_manager_assign
 * @package block_ned_teacher_tools\mod
 */
class deadline_manager_assign extends deadline_manager_mod {
    const TABLE = 'assign_overrides';
    const MODNAME = 'assign';
    const FIELD_MOD = 'assignid';
    const FIELD_TIME = 'duedate';

    /**
     * @var object|\assign - object of mod
     */
    protected $_module;

    /**
     * deadline_manager_assign constructor
     *
     * @param \cm_info $cm
     *
     * @throws \dml_exception
     */
    public function __construct(\cm_info $cm){
        parent::__construct($cm);
        $this->_module = new \assign($cm->context, $cm, $this->_courseid);
    }

    /**
     * Return delays for the base deadline values
     *
     * @return array [field => delay]
     */
    static public function get_delay_array(){
        $res = parent::get_delay_array();
        $res['cutoffdate'] = SH::CUTOFFDATE_DELAY;

        return $res;
    }

    /**
     * Return instance object to update by moodle
     *
     * @return object
     */
    public function process_mod_instance_before_update(){
        return parent::process_mod_instance_before_update();
    }


    /**
     * Process $override in mod classes
     * It should also call @see _update_override_object() inside
     *
     * @param object $override
     *
     * @return object - processed override
     */
    protected function _process_override($override){
        // Set the common parameters for one of the events we may be triggering.
        $instanceid = $this->_cm->instance;
        if ($override->id ?? false){
            $override->id = $this->_update_override_object($override);
        } else {
            // new record
            $override->assignid = $instanceid;
            if ($override->groupid ?? false){
                $override->sortorder = 1;
                $overridecountgroup = SH::db()->count_records(static::TABLE, ['userid' => null, 'assignid' => $instanceid]);
                $overridecountall = SH::db()->count_records(static::TABLE, ['assignid' => $instanceid]);
                if ($overridecountgroup || !$overridecountall){ // No group overrides and there are user overrides.
                    $override->sortorder = $overridecountgroup + 1;
                }
            }

            $override->id = $this->_update_override_object($override);
        }

        if (!empty($override->id)){
            assign_update_events($this->_module, $override);
        }

        return $override;
    }

    /**
     * Process override deleting in mod classes
     *
     * @param object $override
     *
     * @return bool - true if $override was deleted
     */
    protected function _process_delete_override($override){
        $assign = $this->_module;
        $res = $assign->delete_override($override->id);
        reorder_group_overrides($this->_cm->instance);

        return $res;
    }


    /**
     * Process all overrides deleting in mod classes
     * Called from the @see delete_all_overrides()
     *
     * @return bool
     */
    protected function _process_delete_all_overrides(){
        $assign = $this->_module;
        $assign->delete_all_overrides();
        reorder_group_overrides($this->_cm->instance);

        return true;
    }

    /**
     * @param $userid
     *
     * @return mixed
     */
    public function get_user_effective_access($userid){
        $usermodule = clone $this->_module;
        $usermodule->update_effective_access($userid);
        return $usermodule->get_instance($userid)->{static::FIELD_TIME};
    }

    /**
     * @return mixed|void
     * @throws \coding_exception
     * @throws \dml_exception
     * @throws \moodle_exception
     */
    public static function cron(){
        global $DB;

        $timeend = time();
        $timestart = $timeend - DAYSECS;
        $sql = "SELECT a.*
                  FROM {assign} a 
                 WHERE (a.cutoffdate > ? AND a.cutoffdate < ?) 
                    OR (SELECT COUNT(1) FROM {assign_overrides} ao WHERE ao.cutoffdate > ? AND ao.cutoffdate < ? AND ao.assignid = a.id) > 0";

        if ($assigns = $DB->get_records_sql($sql, [$timestart, $timeend, $timestart, $timeend])) {
            foreach ($assigns as $instance){
                if (self::is_offline_assignment($instance->id)) {
                    continue;
                }
                $course = get_course($instance->course);
                $cm = get_coursemodule_from_instance('assign', $instance->id, 0, false, MUST_EXIST);
                $cmid = $cm->id;
                $gradeitem = \grade_item::fetch(['itemtype' => 'mod', 'iteminstance' => $instance->id, 'itemmodule' => 'assign']);

                if ($users = NED\get_enrolled_users($course->id, \context_course::instance($instance->course), 'mod/assign:submit')) {
                    foreach ($users as $user){
                        $modinfo = get_fast_modinfo($course, $user->id);
                        $cm = $modinfo->get_cm($cmid);
                        if (!$cm->uservisible){
                            continue;
                        }
                        $assign = new \assign(\context_module::instance($cm->id), $cm, $course);
                        $assign->update_effective_access($user->id);
                        $deadline = $assign->get_instance()->cutoffdate;

                        $submission = $assign->get_user_submission($user->id, false);

                        $submitted = !empty($submission)
                            &&  ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED
                                || $submission->status == ASSIGN_SUBMISSION_STATUS_REOPENED);

                        if (empty($deadline) || $deadline > time() || $submitted){
                            continue;
                        }

                        // If submitted before deadline.
                        $sql = "SELECT a.userid
                                  FROM {assign_submission} a 
                                 WHERE a.assignment = ?
                                   AND a.userid = ?
                                   AND a.status = 'submitted'
                                   AND a.timemodified <= ?";

                        if ($DB->record_exists_sql($sql, [$instance->id, $user->id, ASSIGN_SUBMISSION_STATUS_SUBMITTED, $deadline])) {
                            continue;
                        }

                        if (!$grade = \grade_grade::fetch(['userid' => $user->id, 'itemid' => $gradeitem->id])) {
                            $grade = new \grade_grade();
                            $grade->userid = $user->id;
                            $grade->itemid = $gradeitem->id;
                            $grade->rawgrademax = $gradeitem->grademax;
                            $grade->timemodified = time();
                            $grade->feedbackformat = FORMAT_PLAIN;
                            $grade->feedback = null;
                            $grade->finalgrade = null;
                        }

                        // Skip if graded.
                        if (!is_null($grade->finalgrade)) {
                            continue;
                        }

                        // Give minimum grade.
                        $grade->rawgrade = $gradeitem->grademin;

                        $status = grade_update('mod/assignment', $gradeitem->courseid,
                            $gradeitem->itemtype, $gradeitem->itemmodule,
                            $gradeitem->iteminstance, $gradeitem->itemnumber,
                            $grade);

                        if ($status == GRADE_UPDATE_OK){
                            if ($ag = $assign->get_user_grade($user->id, true)) {
                                $ag->grade = $gradeitem->grademin;
                                $ag->timemodified = time();
                                $DB->update_record('assign_grades', $ag);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * @param null $userid
     * @return string|null
     * @throws \moodle_exception
     */
    public function get_grade_url($userid = null){
        if (!has_capability('mod/assign:grade', $this->_cm->context)) {
            return null;
        }
        $url = new \moodle_url('/mod/assign/view.php', ['id' => $this->_cm->id, 'rownum' => 0, 'action' => 'grader', 'userid' => $userid]);
        return $url->out(false);
    }

    /**
     * @param int $assignmentid Assignment instance ID
     * @return bool
     * @throws \dml_exception
     */
    public static function is_offline_assignment($assignmentid){
        global $DB;

        $sql = "SELECT apc.*
                  FROM {assign_plugin_config} apc 
                 WHERE apc.assignment = ? 
                   AND apc.subtype = 'assignsubmission' 
                   AND apc.name = 'enabled' 
                   AND apc.plugin != 'comments'
                   AND apc.value = '1'";

        return !$DB->record_exists_sql($sql, [$assignmentid]);
    }

    /**
     * @param $userid
     *
     * @return bool
     */
    public function get_mod_grade($userid){
        $grade = $this->_module->get_user_grade($userid, false);
        if ($grade){
            return $grade->grade;
        }
        return false;
    }

    /**
     * @param $userid
     *
     * @return \stdClass
     */
    public function get_user_submission($userid){
        return $this->_module->get_user_submission($userid, false);
    }
}
