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

/**
 * Library of functions and constants for module game
 *
 * @package    mod_game
 * @copyright  2007 Vasilis Daloukas
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

use core_course\local\entity\content_item;

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

// Define CONSTANTS.

/*
 * Options determining how the grades from individual attempts are combined to give
 * the overall grade for a user
 */
define('GAME_GRADEHIGHEST', 1);
define('GAME_GRADEAVERAGE', 2);
define('GAME_ATTEMPTFIRST', 3);
define('GAME_ATTEMPTLAST', 4);

// The different review options are stored in the bits of $game->review.
// These constants help to extract the options.

define('GAME_REVIEW_IMMEDIATELY', 0x3f);    // The first 6 bits refer to the time immediately after the attempt.
define('GAME_REVIEW_OPEN', 0xfc0);          // The next 6 bits refer to the time after the attempt but while the game is open.
define('GAME_REVIEW_CLOSED', 0x3f000);      // The final 6 bits refer to the time after the game closes.

// Within each group of 6 bits we determine what should be shown.
define('GAME_REVIEW_RESPONSES', 1 * 0x1041); // Show responses.
define('GAME_REVIEW_SCORES', 2 * 0x1041); // Show scores.
define('GAME_REVIEW_FEEDBACK', 4 * 0x1041); // Show feedback.
define('GAME_REVIEW_ANSWERS', 8 * 0x1041); // Show correct answers.

// Some handling of worked solutions is already in the code but not yet fully supported.
// and not switched on in the user interface.
define('GAME_REVIEW_SOLUTIONS', 16 * 0x1041);      // Show solutions.
define('GAME_REVIEW_GENERALFEEDBACK', 32 * 0x1041); // Show general feedback.

/**
 * Given an object containing all the necessary data, will create a new instance and return the id number of the new instance.
 *
 * @param object $game An object from the form in mod.html
 *
 * @return int The id of the newly inserted game record
 **/
function game_add_instance($game) {
    global $DB;

    $game->timemodified = time();
    game_before_add_or_update($game);

    // May have to add extra stuff in here.

    $id = $DB->insert_record("game", $game);

    $game = $DB->get_record_select('game', "id=$id");

    // Do the processing required after an add or an update.
    game_grade_item_update($game);

    return $id;
}

/**
 * Given an object containing all the necessary data, this function will update an existing instance with new data.
 *
 * @param object $game An object from the form in mod.html
 * @return boolean Success/Fail
 **/
function game_update_instance($game) {
    global $DB;

    $game->timemodified = time();
    $game->id = $game->instance;

    if (!isset($game->glossarycategoryid)) {
        $game->glossarycategoryid = 0;
    }

    if (!isset($game->glossarycategoryid2)) {
        $game->glossarycategoryid2 = 0;
    }

    if ($game->grade == '') {
        $game->grade = 0;
    }

    if (!isset($game->param1)) {
        $game->param1 = 0;
    }

    if ($game->param1 == 0) {
        $game->param1 = 0;
    }

    if (!isset($game->param2)) {
        $game->param2 = 0;
    }

    if ($game->param2 == 0) {
        $game->param2 = 0;
    }

    if (!isset($game->questioncategoryid)) {
        $game->questioncategoryid = 0;
    }

    game_before_add_or_update($game);

    if (!$DB->update_record("game", $game)) {
        return false;
    }

    // Do the processing required after an add or an update.
    game_grade_item_update($game);

    return true;
}

/**
 * Updates some fields before writing to database.
 *
 * @param stdClass $game
 */
function game_before_add_or_update(&$game) {

    if (isset($game->toptext)) {
        $game->toptext = $game->toptext['text'];
    }

    if (isset($game->bottomtext)) {
        $game->bottomtext = $game->bottomtext['text'];
    }

    if (isset($game->questioncategoryid)) {
        $pos = strpos($game->questioncategoryid, ',');
        if ($pos) {
            $game->questioncategoryid = substr($game->questioncategoryid, 0, $pos);
        }
    }

    if ($game->gamekind == 'millionaire') {
        $pos = strpos('-' . $game->param8, '#');
        if ($pos > 0) {
            $game->param8 = hexdec(substr($game->param8, $pos));
        }
    } else if ($game->gamekind == 'snakes') {
        $s = '';
        if ($game->param3 == 0) {
            // Means user defined.
            $draftitemid = $game->param4;
            if (isset($game->id)) {
                $cmg = get_coursemodule_from_instance('game', $game->id, $game->course);
                $modcontext = game_get_context_module_instance($cmg->id);
                $attachmentoptions = ['subdirs' => 0, 'maxbytes' => 9999999, 'maxfiles' => 1];
                file_save_draft_area_files(
                    $draftitemid,
                    $modcontext->id,
                    'mod_game',
                    'snakes_file',
                    $game->id,
                    ['subdirs' => 0, 'maxbytes' => 9999999, 'maxfiles' => 1]
                );
                $game->param5 = 1;
            }

            if (isset($_POST['snakes_cols'])) {
                $fields = ['snakes_data', 'snakes_cols', 'snakes_rows', 'snakes_headerx', 'snakes_headery',
                    'snakes_footerx', 'snakes_footery', 'snakes_width', 'snakes_height'];
                foreach ($fields as $f) {
                    $s .= '#' . $f . ':' . $_POST[$f];
                }
                $s = substr($s, 1);
            }
        }
        $game->param9 = $s;
    }
}

/**
 * Given an ID of an instance of this module, this function will permanently delete the instance and any data that depends on it.
 *
 * @param int $gameid Id of the module instance
 * @return boolean Success/Failure
 **/
function game_delete_instance($gameid) {
    global $DB;

    // Delete any dependent records here.
    $aids = [];
    if (($recs = $DB->get_records('game_attempts', ['gameid' => $gameid])) != false) {
        $ids = '';

        $count = 0;
        foreach ($recs as $rec) {
            $ids .= ($ids == '' ? $rec->id : ',' . $rec->id);
            if (++$count > 10) {
                $aids[] = $ids;
                $count = 0;
                $ids = '';
            }
        }
        if ($ids != '') {
            $aids[] = $ids;
        }
    }

    foreach ($aids as $ids) {
        $tables = ['game_hangman', 'game_cross', 'game_cryptex', 'game_millionaire',
            'game_bookquiz', 'game_sudoku', 'game_snakes'];

        foreach ($tables as $t) {
            $sql = "DELETE FROM {" . $t . "} WHERE id IN (" . $ids . ')';
            if (!$DB->execute($sql)) {
                return false;
            }
        }
    }

    $tables = ['game_attempts', 'game_grades', 'game_bookquiz_questions', 'game_queries', 'game_repetitions'];
    foreach ($tables as $t) {
        if (!$DB->delete_records($t, ['gameid' => $gameid])) {
            return false;
        }
    }

    $tables = ['game_export_javame', 'game_export_html', 'game'];
    foreach ($tables as $table) {
        if (!$DB->delete_records($table, ['id' => $gameid])) {
            return false;
        }
    }

    return true;
}

/**
 * Return a small object with summary information about what a user has done
 *
 * @param stdClass $course
 * @param stdClass $user
 * @param string $mod
 * @param stdClass $game
 *
 * $return->time = the time they did it
 * $return->info = a short text description
 **/
function game_user_outline($course, $user, $mod, $game) {
    global $DB;

    if ($grade = $DB->get_record_select('game_grades', "userid=$user->id AND gameid = $game->id", null, 'id,score,timemodified')) {
        $result = new stdClass();
        if ((float)$grade->score) {
            $result->info = get_string('gradenoun') . ':&nbsp;' . round($grade->score * $game->grade, $game->decimalpoints) . ' ' .
                            get_string('percent', 'game') . ':&nbsp;' . round(100 * $grade->score, $game->decimalpoints) . ' %';
        }
        $result->time = $grade->timemodified;
        return $result;
    }

    return null;
}

/**
 * Print a detailed representation of what a user has done with a given particular game,(user activity reports).

 * @param stdClass $course
 * @param stdClass $user
 * @param string $mod
 * @param stdClass $game
 */
function game_user_complete($course, $user, $mod, $game) {
    global $DB;

    if ($attempts = $DB->get_records_select('game_attempts', "userid='$user->id' AND gameid='$game->id'", null, 'attempt ASC')) {
        if ($game->grade && $grade = $DB->get_record('game_grades', ['userid' => $user->id, 'gameid' => $game->id])) {
            echo get_string('gradenoun') . ': ' . game_format_score($game, $grade->score) . '/' . $game->grade . '<br />';
        }
        foreach ($attempts as $attempt) {
            echo get_string('attempt', 'game') . ' ' . $attempt->attempt . ': ';
            if ($attempt->timefinish == 0) {
                print_string('unfinished');
            } else {
                echo game_format_score($game, $attempt->score) . '/' . $game->grade;
            }
            echo ' - ' . userdate($attempt->timelastattempt) . '<br />';
        }
    } else {
        print_string('noattempts', 'game');
    }

    return true;
}

/**
 * Given a course and a time, this module should find recent activity that has occurred in game activities and print it out.
 *
 * @param stdClass $course
 * @param int $isteacher
 * @param int $timestart
 *
 * @return boolean
 *
 * @return bool: True if anything was printed, otherwise false.
 */
function game_print_recent_activity(stdClass $course, int $isteacher, int $timestart) {
    return false;
}

/**
 * Function to be run periodically according to the moodle cron
 *
 * @uses $CFG
 * @return boolean
 **/
function game_cron() {
    return true;
}

/**
 * Must return an array of grades for a given instance of this module, indexed by user.
 *
 * Example:
 *    $return->grades = array of grades;
 *    $return->maxgrade = maximum allowed grade;
 *
 *    return $return;
 *
 * @param int $gameid ID of an instance of this module
 * @return mixed Null or object with an array of grades and with the maximum grade
 * @throws dml_exception
 */
function game_grades($gameid) {
    // Must return an array of grades, indexed by user, and a max grade.

    global $DB;

    $game = $DB->get_record('game', ['id' => intval($gameid)]);
    if (empty($game) || empty($game->grade)) {
        return null;
    }

    $return = new stdClass();
    $return->grades = $DB->get_records_menu('game_grades', 'gameid', $game->id, '', "userid, score * {$game->grade}");
    $return->maxgrade = $game->grade;

    return $return;
}

/**
 * Return grade for given user or all users.
 *
 * @param stdClass $game
 * @param int $userid optional user id, 0 means all users
 * @return array array of grades, false if none
 * @throws dml_exception
 */
function game_get_user_grades($game, $userid = 0) {
    global $DB;

    $user = $userid ? "AND u.id = $userid" : "";

    if (!isset($game->grade)) {
        $game->grade = 1;
    }
    $sql = 'SELECT u.id, u.id AS userid, ' . $game->grade .
            ' * g.score AS rawgrade, g.timemodified AS dategraded, MAX(a.timefinish) AS datesubmitted
            FROM {user} u, {game_grades} g, {game_attempts} a
            WHERE u.id = g.userid AND g.gameid = ' . $game->id . ' AND a.gameid = g.gameid AND u.id = a.userid';
    if ($userid != 0) {
        $sql .= ' AND u.id=' . $userid;
    }
    $sql .= ' GROUP BY u.id, g.score, g.timemodified';

    return $DB->get_records_sql($sql);
}

/**
 * Must return an array of user records (all data) who are participants for a given instance of game.
 *
 * @param int $gameid ID of an instance of this module
 * @return mixed boolean/array of students
 **/
function game_get_participants(int $gameid) {
    return false;
}

/**
 * This function returns if a scale is being used by one game it it has support for grading and scales.
 *
 * @param int $gameid ID of an instance of this module
 * @param int $scaleid
 * @return bool
 **/
function game_scale_used($gameid, $scaleid): bool {
    return false;
}

/**
 * Update grades in central gradebook
 *
 * @param object $game null means all games
 * @param int $userid specific user only, 0 mean all
 * @param boolean $nullifnone
 */
function game_update_grades($game = null, $userid = 0, $nullifnone = true) {
    global $CFG, $DB;

    if (!function_exists('grade_update')) { // Workaround for buggy PHP versions.
        if (file_exists($CFG->libdir . '/gradelib.php')) {
            require_once($CFG->libdir . '/gradelib.php');
        } else {
            return;
        }
    }
    if ($game != null) {
        $grades = game_get_user_grades($game, $userid);
        if ($grades != null) {
            game_grade_item_update($game, $grades);
        } else if ($userid && $nullifnone) {
            $grade = new stdClass();
            $grade->userid = $userid;
            $grade->rawgrade = null;
            game_grade_item_update($game, $grade);
        } else {
            game_grade_item_update($game);
        }
    } else {
        $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
                  FROM {game} a, {course_modules} cm, {modules} m
                 WHERE m.name='game' AND m.id=cm.module AND cm.instance=a.id";
        if ($rs = $DB->get_recordset_sql($sql)) {
            while ($game = $DB->rs_fetch_next_record($rs)) {
                if ($game->grade != 0) {
                    game_update_grades($game, 0, false);
                } else {
                    game_grade_item_update($game);
                }
            }
            $DB->rs_close($rs);
        }
    }
}

/**
 * Create grade item for given game
 * Updates table grade_grades
 *
 * @param object $game object with extra cmidnumber
 * @param stdClass $grades
 * @return int 0 if ok, error code otherwise
 */
function game_grade_item_update($game, $grades = null) {
    global $CFG;

    if (!function_exists('grade_update')) { // Workaround for buggy PHP versions.
        if (file_exists($CFG->libdir . '/gradelib.php')) {
            require_once($CFG->libdir . '/gradelib.php');
        } else {
            return;
        }
    }

    if (isset($game->cmidnumber)) { // Tt may not be always present.
        $params = ['itemname' => $game->name, 'idnumber' => $game->cmidnumber];
    } else {
        $params = ['itemname' => $game->name];
    }

    if ($game->grade > 0) {
        $params['gradetype'] = GRADE_TYPE_VALUE;
        $params['grademax'] = $game->grade;
        $params['grademin'] = 0;
    } else {
        $params['gradetype'] = GRADE_TYPE_NONE;
    }

    if ($grades === 'reset') {
        $params['reset'] = true;
        $grades = null;
    }

    return grade_update('mod/game', $game->course, 'mod', 'game', $game->id, 0, $grades, $params);
}


/**
 * Delete grade item for given game
 *
 * @param object $game object
 * @return object game
 */
function game_grade_item_delete($game) {
    global $CFG;

    if (file_exists($CFG->libdir . '/gradelib.php')) {
        require_once($CFG->libdir . '/gradelib.php');
    } else {
        return;
    }

    return grade_update('mod/game', $game->course, 'mod', 'game', $game->id, 0, null, ['deleted' => 1]);
}

/**
 * Returns all game graded users since a given time for specified game
 *
 * @param stdClass $activities
 * @param int $index
 * @param int $timestart
 * @param int $courseid
 * @param int $cmid
 * @param int $userid
 * @param int $groupid
 */
function game_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) {
    global $DB, $COURSE, $USER;

    if ($COURSE->id == $courseid) {
        $course = $COURSE;
    } else {
        $course = $DB->get_record('course', ['id' => $courseid]);
    }

    $modinfo = get_fast_modinfo($course);

    $cm = $modinfo->cms[$cmid];

    if ($userid) {
        $userselect = "AND u.id = $userid";
    } else {
        $userselect = "";
    }

    if ($groupid) {
        $groupselect = "AND gm.groupid = $groupid";
        $groupjoin = "JOIN {groups_members} gm ON  gm.userid=u.id";
    } else {
        $groupselect = "";
        $groupjoin = "";
    }

    $sql = "SELECT qa.*, qa.gameid, q.grade, u.lastname,u.firstname,u.picture " .
    "FROM {game_attempts} qa JOIN {game} q ON q.id = qa.gameid JOIN {user} u ON u.id = qa.userid $groupjoin " .
    "WHERE qa.timefinish > $timestart AND q.id = $cm->instance $userselect $groupselect " .
    "ORDER BY qa.timefinish ASC";
    if (!$attempts = $DB->get_records_sql($sql)) {
         return;
    }

    $cmcontext = game_get_context_module_instance($cm->id);
    $grader = has_capability('moodle/grade:viewall', $cmcontext);
    $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
    $viewfullnames = has_capability('moodle/site:viewfullnames', $cmcontext);
    $groupmode = groups_get_activity_groupmode($cm, $course);

    if (is_null($modinfo->groups)) {
        $modinfo->groups = groups_get_user_groups($course->id); // Load all my groups and cache it in modinfo.
    }

    $aname = format_string($cm->name, true);
    foreach ($attempts as $attempt) {
        if ($attempt->userid != $USER->id) {
            if (!$grader) {
                // Grade permission required.
                continue;
            }

            if ($groupmode == SEPARATEGROUPS && !$accessallgroups) {
                $usersgroups = groups_get_all_groups($course->id, $attempt->userid, $cm->groupingid);
                if (!is_array($usersgroups)) {
                    continue;
                }
                $usersgroups = array_keys($usersgroups);
                $interset = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
                if (empty($intersect)) {
                    continue;
                }
            }
        }

        $tmpactivity = new stdClass();

        $tmpactivity->type = 'game';
        $tmpactivity->gameid = $attempt->gameid;
        $tmpactivity->cmid = $cm->id;
        $tmpactivity->name = $aname;
        $tmpactivity->sectionnum = $cm->sectionnum;
        $tmpactivity->timestamp = $attempt->timefinish;

        $tmpactivity->content = new stdClass();
        $tmpactivity->content->attemptid = $attempt->id;
        $tmpactivity->content->sumgrades = $attempt->score * $attempt->grade;
        $tmpactivity->content->maxgrade = $attempt->grade;
        $tmpactivity->content->attempt = $attempt->attempt;

        $tmpactivity->user = new stdClass();
        $tmpactivity->user->userid = $tmpactivity->user->id = $attempt->userid;
        $tmpactivity->user->fullname = fullname($attempt, $viewfullnames);
        $tmpactivity->user->firstname = $attempt->firstname;
        $tmpactivity->user->lastname = $attempt->lastname;
        $tmpactivity->user->picture = $attempt->picture;
        $tmpactivity->user->imagealt = $attempt->imagealt;
        $tmpactivity->user->email = $attempt->email;

        $activities[$index++] = $tmpactivity;
    }
}

/**
 * Prints recent activity.
 *
 * @param stdClass $activity
 * @param int $courseid
 * @param stdClass $detail
 * @param array $modnames
 */
function game_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
    global $CFG, $OUTPUT;

    echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';

    echo "<tr><td class=\"userpicture\" valign=\"top\">";
    echo $OUTPUT->user_picture($activity->user, ['courseid' => $courseid]);
    echo "</td><td>";

    if ($detail) {
        $modname = $modnames[$activity->type];
        echo '<div class="title">';
        echo "<img src=\"$CFG->modpixpath/{$activity->type}/icon.gif\" " .
             "class=\"icon\" alt=\"$modname\" />";
        echo "<a href=\"{$CFG->wwwroot}/mod/game/view.php?id={$activity->cmid}\">{$activity->name}</a>";
        echo '</div>';
    }

    echo '<div class="grade">';
    echo  get_string("attempt", "game") . " {$activity->content->attempt}: ";
    $grades = "({$activity->content->sumgrades} / {$activity->content->maxgrade})";

    echo "<a href=\"{$CFG->wwwroot}/mod/game/review.php" .
        "?attempt={$activity->content->attemptid}&q={$activity->gameid}\">$grades</a>";
    echo '</div>';

    echo '<div class="user">';
    echo "<a href=\"{$CFG->wwwroot}/user/view.php?id={$activity->user->userid}&amp;course=$courseid\">"
         . "{$activity->user->fullname}</a> - " . userdate($activity->timestamp);
    echo '</div>';

    echo "</td></tr></table>";
}


/**
 * Removes all grades from gradebook
 *
 * @param int $courseid
 * @param string $type
 **/
function game_reset_gradebook($courseid, $type = '') {
    global $DB;

    $sql = "SELECT q.*, cm.idnumber as cmidnumber, q.course as courseid
              FROM {game} q, {course_modules} cm, {modules} m
             WHERE m.name='game' AND m.id=cm.module AND cm.instance=q.id AND q.course=$courseid";

    if ($games = $DB->get_records_sql($sql)) {
        foreach ($games as $game) {
            game_grade_item_update($game, 'reset');
        }
    }
}

/**
 * What supports.
 *
 * @uses FEATURE_GRADE_HAS_GRADE
 * @param string $feature
 * @return bool True if quiz supports feature
 */
function game_supports(string $feature) {
    global $CFG;
    if ($CFG->branch >= 400) {
        if ($feature == FEATURE_MOD_PURPOSE) {
            return MOD_PURPOSE_ASSESSMENT;
        }
    }
    switch ($feature) {
        case FEATURE_GRADE_HAS_GRADE:
            return true;
        case FEATURE_GROUPS:
            return true;
        case FEATURE_GROUPINGS:
            return true;
        case FEATURE_GROUPMEMBERSONLY:
            return true;
        case FEATURE_MOD_INTRO:
            return true;
        case FEATURE_COMPLETION_TRACKS_VIEWS:
            return true;
        case FEATURE_COMPLETION_HAS_RULES:
            return true;
        case FEATURE_GRADE_OUTCOMES:
            return true;
        case FEATURE_RATE:
            return false;
        case FEATURE_BACKUP_MOODLE2:
            return true;
        case FEATURE_SHOW_DESCRIPTION:
            return true;
        default:
            return null;
    }
}

/**
 * get extra capabilities
 *
 * @return array all other caps used in module
 * @throws dml_exception
 */
function game_get_extra_capabilities(): array {
    global $DB, $CFG;

    require_once($CFG->libdir . '/questionlib.php');
    $caps = question_get_all_capabilities();
    $reportcaps = $DB->get_records_select_menu('capabilities', 'name LIKE ?', ['quizreport/%'], 'id,name');
    $caps = array_merge($caps, $reportcaps);
    $caps[] = 'moodle/site:accessallgroups';

    return $caps;
}

/**
 * Return a summary of the number of attempts made for a game.
 *
 * @param stdClass $game The game object. Only $game->id is used.
 * @param stdClass $cm The course module object. Only $cm->course, $cm->groupmode and $cm->groupingid are used.
 * @param bool $returnzero If false, when no attempts have been made an empty string is returned.
 * @param int $currentgroup The current group id. Default 0 means no current group.
 * @return string A summary such as "Attempts: 123".
 */
function game_num_attempt_summary(stdClass $game, stdClass $cm, bool $returnzero = false, int $currentgroup = 0) {
    global $CFG, $USER, $DB;

    $numattempts = $DB->count_records('game_attempts', ['gameid' => $game->id, 'preview' => 0]);

    if ($numattempts || $returnzero) {
        if (groups_get_activity_groupmode($cm)) {
            $a = new stdClass();
            $a->total = $numattempts;
            if ($currentgroup) {
                $a->group = $DB->count_records_sql(
                    'SELECT count(1) FROM ' .
                    '{game_attempts} qa JOIN ' .
                    '{groups_members} gm ON qa.userid = gm.userid ' .
                    'WHERE gameid = ? AND preview = 0 AND groupid = ?',
                    [$game->id, $currentgroup]
                );
                return get_string('attemptsnumthisgroup', 'quiz', $a);
            } else if ($groups = groups_get_all_groups($cm->course, $USER->id, $cm->groupingid)) {
                [$usql, $params] = $DB->get_in_or_equal(array_keys($groups));
                $a->group = $DB->count_records_sql(
                    'SELECT count(1) FROM ' .
                    '{game_attempts} qa JOIN ' .
                    '{groups_members} gm ON qa.userid = gm.userid ' .
                    'WHERE gameid = ? AND preview = 0 AND ' .
                    "groupid $usql",
                    array_merge([$game->id], $params)
                );
                return get_string('attemptsnumyourgroups', 'quiz', $a);
            }
        }
        return get_string('attemptsnum', 'quiz', $numattempts);
    }
    return '';
}

/**
 * Converts score of game to grade.
 *
 * @param stdClass $game
 * @param float $score
 *
 * @return float  the score
 */
function game_format_score(stdClass $game, float $score): float {
    return format_float($game->grade * $score / 100, $game->decimalpoints);
}

/**
 * Converts grade to score.
 *
 * @param stdClass $game
 * @param float $grade
 *
 * @return float score
 */
function game_format_grade(stdClass $game, float $grade): float {
    return format_float($grade, $game->decimalpoints == null ? 2 : $game->decimalpoints);
}

/**
 * get grading options
 *
 * @return array: the options for calculating the quiz grade from the individual attempt grades.
 */
function game_get_grading_options(): array {
    return [
            GAME_GRADEHIGHEST => get_string('gradehighest', 'quiz'),
            GAME_GRADEAVERAGE => get_string('gradeaverage', 'quiz'),
            GAME_ATTEMPTFIRST => get_string('attemptfirst', 'quiz'),
            GAME_ATTEMPTLAST => get_string('attemptlast', 'quiz')];
}

/**
 * This function extends the settings navigation block for the site.
 *
 * It is safe to rely on PAGE here as we will only ever be within the module
 * context when this is called
 *
 * @param settings_navigation $settings
 * @param navigation_node $gamenode
 * @return void
 */
function game_extend_settings_navigation(settings_navigation $settings, navigation_node $gamenode) {
    global $PAGE, $CFG, $DB;

    $context = $PAGE->cm->context;

    if (!has_capability('mod/game:viewreports', $context)) {
        return;
    }

    if (has_capability('mod/game:view', $context)) {
        $url = new moodle_url('/mod/game/view.php', ['id' => $PAGE->cm->id]);
        $gamenode->add(get_string('info', 'game'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/info', ''));
    }

    if (has_capability('mod/game:manage', $context)) {
        $url = new moodle_url('/course/modedit.php', ['update' => $PAGE->cm->id, 'return' => true, 'sesskey' => sesskey()]);
        $gamenode->add(
            get_string('edit', 'moodle', ''),
            $url,
            navigation_node::TYPE_SETTING,
            null,
            null,
            new pix_icon('t/edit', '')
        );
    }

    if (has_capability('mod/game:manage', $context)) {
        $gameid = $PAGE->cm->instance;
        $sql = "SELECT id,gamekind,sourcemodule,bookid,course,glossaryid,quizid,questioncategoryid " .
            "FROM {$CFG->prefix}game WHERE id=$gameid";
        $game = $DB->get_record_sql($sql);
        if (($game->gamekind == 'bookquiz') && ($game->bookid != 0)) {
            $book = $DB->get_record_sql("SELECT id,name FROM {$CFG->prefix}book WHERE id={$game->bookid}");
            $cmd = get_coursemodule_from_instance('book', $game->bookid, $game->course);
            $url = new moodle_url('/mod/book/view.php', ['id' => $cmd->id]);
            $gamenode->add(
                get_string('viewbook', 'game', $book->name),
                $url,
                navigation_node::TYPE_SETTING,
                null,
                null,
                new pix_icon('t/edit', '')
            );
        }
        if (($game->sourcemodule == 'glossary') && ($game->glossaryid != 0)) {
            $glossary = $DB->get_record_sql("SELECT id,name FROM {$CFG->prefix}glossary WHERE id={$game->glossaryid}");
            $cmd = get_coursemodule_from_instance('glossary', $game->glossaryid, $game->course);
            $url = new moodle_url('/mod/glossary/view.php', ['id' => $cmd->id]);
            $gamenode->add(
                get_string('viewglossary', 'game', '&nbsp;' . $glossary->name),
                $url,
                navigation_node::TYPE_SETTING,
                null,
                null,
                new pix_icon('t/edit', '')
            );
        }
        if (($game->sourcemodule == 'quiz') && ($game->quizid != 0)) {
            $quiz = $DB->get_record_sql("SELECT id,name FROM {$CFG->prefix}quiz WHERE id={$game->quizid}");
            $cmd = get_coursemodule_from_instance('quiz', $game->quizid, $game->course);
            $url = new moodle_url('/mod/quiz/view.php', ['id' => $cmd->id]);
            $gamenode->add(
                get_string('viewquiz', 'game', $quiz->name),
                $url,
                navigation_node::TYPE_SETTING,
                null,
                null,
                new pix_icon('t/edit', '')
            );
        }
        if ($game->sourcemodule == 'question') {
            $url = new moodle_url('/question/edit.php', ['courseid' => $game->course]);
            $gamenode->add(
                get_string('viewquestions', 'game'),
                $url,
                navigation_node::TYPE_SETTING,
                null,
                null,
                new pix_icon('t/edit', '')
            );
        }
    }

    if (has_capability('mod/game:viewreports', $context)) {
        $url = new moodle_url('/mod/game/showanswers.php', ['q' => $PAGE->cm->instance]);
        $reportnode = $gamenode->add(
            get_string('showanswers', 'game'),
            $url,
            navigation_node::TYPE_SETTING,
            null,
            null,
            new pix_icon('i/item', '')
        );
    }

    if (has_capability('mod/game:viewreports', $context)) {
        $url = new moodle_url('/mod/game/showattempts.php', ['q' => $PAGE->cm->instance]);
        $reportnode = $gamenode->add(
            get_string('showattempts', 'game'),
            $url,
            navigation_node::TYPE_SETTING,
            null,
            null,
            new pix_icon('f/explore', '')
        );
    }

    if (has_capability('mod/game:viewreports', $context)) {
        $game = $DB->get_record('game', ["id" => $PAGE->cm->instance]);
        $courseid = $game->course;

        switch ($game->gamekind) {
            case 'bookquiz':
                $url = new moodle_url('/mod/game/bookquiz/questions.php', ['q' => $PAGE->cm->instance]);
                $exportnode = $gamenode->add(
                    get_string('bookquiz_questions', 'game'),
                    $url,
                    navigation_node::TYPE_SETTING,
                    null,
                    ull,
                    new pix_icon('i/item', '')
                );
                break;
            case 'hangman':
                $url = new moodle_url(
                    '/mod/game/export.php',
                    ['id' => $PAGE->cm->id, 'courseid' => $courseid, 'target' => 'html']
                );
                $gamenode->add(
                    get_string('export_to_html', 'game'),
                    $url,
                    navigation_node::TYPE_SETTING,
                    null,
                    null,
                    new pix_icon('i/item', '')
                );
                break;
            case 'snakes':
            case 'cross':
            case 'millionaire':
                $url = new moodle_url('/mod/game/export.php', ['q' => $game->id,
                    'courseid' => $courseid, 'target' => 'html']);
                $gamenode->add(
                    get_string('export_to_html', 'game'),
                    $url,
                    navigation_node::TYPE_SETTING,
                    null,
                    null,
                    new pix_icon('i/item', '')
                );
                break;
        }
    }

    $gamenode->make_active();
}

/* Returns an array of game type objects to construct menu list when adding new game  */
require($CFG->dirroot . '/version.php');
if ($branch >= '31' && $branch < '401') {
    define('USE_GET_SHORTCUTS', '1');
}

if ($branch >= '401') {
    define('GAME_MOODLE_401', 1);
}

if (!defined('USE_GET_SHORTCUTS')) {
    /**
     * Shows kind of games
     */
    function game_get_types() {
        global $DB;

        $config = get_config('game');

        $types = [];

        $type = new stdClass();
        $type->modclass = MOD_CLASS_ACTIVITY;
        $type->type = "game_group_start";
        $type->typestr = '--' . get_string('modulenameplural', 'game');
        $types[] = $type;

        $hide = (isset($config->hidehangman) ? ($config->hidehangman != 0) : false);

        if ($hide == false) {
            $type = new stdClass();
            $type->modclass = MOD_CLASS_ACTIVITY;
            $type->type = "game&amp;type=hangman";
            $type->typestr = get_string('game_hangman', 'game');
            $types[] = $type;
        }

        if (isset($config->hidecross)) {
            $hide = ($config->hidecross != 0);
        } else {
            $hide = false;
        }

        if ($hide == false) {
            $type = new stdClass();
            $type->modclass = MOD_CLASS_ACTIVITY;
            $type->type = "game&amp;type=cross";
            $type->typestr = get_string('game_cross', 'game');
            $types[] = $type;
        }

        if (isset($config->hidecryptex)) {
            $hide = ($config->hidecryptex != 0);
        } else {
            $hide = false;
        }

        if ($hide == false) {
            $type = new stdClass();
            $type->modclass = MOD_CLASS_ACTIVITY;
            $type->type = "game&amp;type=cryptex";
            $type->typestr = get_string('game_cryptex', 'game');
            $types[] = $type;
        }

        $hide = (isset($config->hidemillionaire) ? ($config->hidemillionaire != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->modclass = MOD_CLASS_ACTIVITY;
            $type->type = "game&amp;type=millionaire";
            $type->typestr = get_string('game_millionaire', 'game');
            $types[] = $type;
        }

        $hide = (isset($config->hidesudoku) ? ($config->hidesudoku != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->modclass = MOD_CLASS_ACTIVITY;
            $type->type = "game&amp;type=sudoku";
            $type->typestr = get_string('game_sudoku', 'game');
            $types[] = $type;
        }

        $hide = (isset($config->hidesnakes) ? ($config->hidesnakes != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->modclass = MOD_CLASS_ACTIVITY;
            $type->type = "game&amp;type=snakes";
            $type->typestr = get_string('game_snakes', 'game');
            $types[] = $type;
        }

        $hide = (isset($config->hidehiddenpicture) ? ($config->hidehiddenpicture != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->modclass = MOD_CLASS_ACTIVITY;
            $type->type = "game&amp;type=hiddenpicture";
            $type->typestr = get_string('game_hiddenpicture', 'game');
            $types[] = $type;
        }

        $hide = (isset($config->hidebookquiz) ? ($config->hidebookquiz != 0) : false);
        if ($hide == false) {
            if ($DB->get_record('modules', ['name' => 'book'], 'id,id')) {
                $type = new stdClass();
                $type->modclass = MOD_CLASS_ACTIVITY;
                $type->type = "game&amp;type=bookquiz";
                $type->typestr = get_string('game_bookquiz', 'game');
                $types[] = $type;
            }
        }

        $type = new stdClass();
        $type->modclass = MOD_CLASS_ACTIVITY;
        $type->type = "game_group_end";
        $type->typestr = '--';
        $types[] = $type;

        return $types;
    }
}

if (defined('USE_GET_SHORTCUTS')) {
    /**
     * Returns an array of game type objects to construct menu list when adding new game
     *
     * @param stdClass $defaultitem
     */
    function game_get_shortcuts($defaultitem) {
        global $DB, $CFG;
        $config = get_config('game');
        $types = [];
        $hide = (isset($config->hidehangman) ? ($config->hidehangman != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->archetype = MOD_CLASS_ACTIVITY;
            $type->type = "game&type=hangman";
            $type->name = preg_replace('/.*type=/', '', $type->type);
            $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_hangman', 'game');
            $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
            if (
                empty($type->help) && !empty($type->name) &&
                get_string_manager()->string_exists('help' . $type->name, 'game')
            ) {
                    $type->help = get_string('help' . $type->name, 'game');
            }
            $types[] = $type;
        }
        if (isset($config->hidecross)) {
            $hide = ($config->hidecross != 0);
        } else {
            $hide = false;
        }
        if ($hide == false) {
            $type = new stdClass();
            $type->archetype = MOD_CLASS_ACTIVITY;
            $type->type = "game&type=cross";
            $type->name = preg_replace('/.*type=/', '', $type->type);
            $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_cross', 'game');
            $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
            if (
                empty($type->help) && !empty($type->name) &&
                get_string_manager()->string_exists('help' . $type->name, 'game')
            ) {
                    $type->help = get_string('help' . $type->name, 'game');
            }
            $types[] = $type;
        }
        if (isset($config->hidecryptex)) {
            $hide = ($config->hidecryptex != 0);
        } else {
            $hide = false;
        }
        if ($hide == false) {
            $type = new stdClass();
            $type->archetype = MOD_CLASS_ACTIVITY;
            $type->type = "game&type=cryptex";
            $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_cryptex', 'game');
            $type->name = preg_replace('/.*type=/', '', $type->type);
            $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
            if (
                empty($type->help) && !empty($type->name) &&
                get_string_manager()->string_exists('help' . $type->name, 'game')
            ) {
                    $type->help = get_string('help' . $type->name, 'game');
            }
            $types[] = $type;
        }
        $hide = (isset($config->hidemillionaire) ? ($config->hidemillionaire != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->archetype = MOD_CLASS_ACTIVITY;
            $type->type = "game&type=millionaire";
            $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_millionaire', 'game');
            $type->name = preg_replace('/.*type=/', '', $type->type);
            $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
            if (
                empty($type->help) && !empty($type->name) &&
                get_string_manager()->string_exists('help' . $type->name, 'game')
            ) {
                    $type->help = get_string('help' . $type->name, 'game');
            }
            $types[] = $type;
        }
        $hide = (isset($config->hidesudoku) ? ($config->hidesudoku != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->archetype = MOD_CLASS_ACTIVITY;
            $type->type = "game&type=sudoku";
            $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_sudoku', 'game');
            $type->name = preg_replace('/.*type=/', '', $type->type);
            $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
            if (
                empty($type->help) && !empty($type->name) &&
                get_string_manager()->string_exists('help' . $type->name, 'game')
            ) {
                    $type->help = get_string('help' . $type->name, 'game');
            }
            $types[] = $type;
        }
        $hide = (isset($config->hidesnakes) ? ($config->hidesnakes != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->archetype = MOD_CLASS_ACTIVITY;
            $type->type = "game&type=snakes";
            $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_snakes', 'game');
            $type->name = preg_replace('/.*type=/', '', $type->type);
            $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
            if (empty($type->help) && !empty($type->name) && get_string_manager()->string_exists('help' . $type->name, 'game')) {
                    $type->help = get_string('help' . $type->name, 'game');
            }
            $types[] = $type;
        }
        $hide = (isset($config->hidehiddenpicture) ? ($config->hidehiddenpicture != 0) : false);
        if ($hide == false) {
            $type = new stdClass();
            $type->archetype = MOD_CLASS_ACTIVITY;
            $type->type = "game&type=hiddenpicture";
            $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_hiddenpicture', 'game');
            $type->name = preg_replace('/.*type=/', '', $type->type);
            $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
            if (
                empty($type->help) && !empty($type->name) &&
                get_string_manager()->string_exists('help' . $type->name, 'game')
            ) {
                    $type->help = get_string('help' . $type->name, 'game');
            }
            $types[] = $type;
        }
        $hide = (isset($config->hidebookquiz) ? ($config->hidebookquiz != 0) : false);
        if ($hide == false) {
            if ($DB->get_record('modules', ['name' => 'book'], 'id,id')) {
                $type = new stdClass();
                $type->archetype = MOD_CLASS_ACTIVITY;
                $type->type = "game&type=bookquiz";
                $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_bookquiz', 'game');
                $type->name = preg_replace('/.*type=/', '', $type->type);
                $type->link = new moodle_url($defaultitem->link, ['type' => $type->name]);
                if (
                    empty($type->help) && !empty($type->name) && get_string_manager()->string_exists('help' . $type->name, 'game')
                ) {
                        $type->help = get_string('help' . $type->name, 'game');
                }
                $types[] = $type;
            }
        }
        return $types;
    }
}

if (defined('GAME_MOODLE_401')) {
    /**
     * Return the preconfigured tools which are configured for inclusion in the activity picker.
     *
     * @param content_item $defaultmodulecontentitem reference to the content item for the LTI module.
     * @param stdClass $user the user object, to use for cap checks if desired.
     * @param stdClass $course the course to scope items to.
     * @return array the array of content items.
     */
    function mod_game_get_course_content_items(
        content_item $defaultmodulecontentitem,
        stdClass $user,
        stdClass $course
    ) {

        $types = [];
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'hangman');
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'cryptex');
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'cross');
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'hiddenpicture');
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'millionaire');
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'sudoku');
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'bookquiz');
        mod_game_get_course_content_items_type($defaultmodulecontentitem, $user, $course, $types, 'snakes');

        return $types;
    }

    /**
     * Helper function for mod_game_get_course_content_items.
     * It is called for every game of module Game.
     *
     * @param \core_course\local\entity\content_item $defaultmodulecontentitem Reference to the content item for the LTI module.
     * @param \stdClass $user The user object, to use for cap checks if desired.
     * @param \stdClass $course The course to scope items to.
     * @param array $types
     * @param string $kind The kind of each game.
     * @throws \core\exception\moodle_exception
     * @throws coding_exception
     * @throws dml_exception
     */
    function mod_game_get_course_content_items_type(
        content_item $defaultmodulecontentitem,
        stdClass $user,
        stdClass $course,
        array &$types,
        string $kind
    ) {
        global $OUTPUT;

        $name = 'hide' . $kind;
        $config = get_config('game');
        $hide = (isset($config->$name) ? ($config->$name != 0) : false);
        if ($hide) {
            return;
        }
        $type = new stdClass();
        $type->type = "game&type=" . $kind;
        $type->name = preg_replace('/.*type=/', '', $type->type);
        $type->title = get_string('pluginname', 'game') . ' - ' . get_string('game_' . $kind, 'game');
        $type->link = new moodle_url(
            '/course/modedit.php',
            ['add' => 'game', 'return' => 0, 'type' => $kind, 'course' => $course->id, 'id' => $course->id]
        );
        $type->help = '';
        if (
            empty($type->help) && !empty($type->name) &&
            get_string_manager()->string_exists('help' . $type->name, 'game')
        ) {
                $type->help = get_string('help' . $type->name, 'game');
        }

        if (empty($type->icon)) {
            $type->icon = $OUTPUT->pix_icon('monologo', '', 'game', ['class' => 'icon']);
        } else {
            $type->icon = html_writer::empty_tag('img', ['src' => $type->icon, 'alt' => $type->name, 'class' => 'icon']);
        }

        $types[] = new content_item(
            2,
            $type->name,
            new \core_course\local\entity\string_title($type->title),
            $type->link,
            $type->icon,
            $type->help,
            $defaultmodulecontentitem->get_archetype(),
            $defaultmodulecontentitem->get_component_name(),
            $defaultmodulecontentitem->get_purpose()
        );
    }
}

/**
 * Serves the game attachents.
 *
 * @param stdClass $course
 * @param stdClass $cm
 * @param stdClass $context
 * @param string $filearea
 * @param array $args
 * @param boolean $forcedownload
 *
 * @return boolean false if not exists file
 */
function mod_game_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
    global $CFG, $DB;

    if ($context->contextlevel != CONTEXT_MODULE) {
        return false;
    }

    require_course_login($course, true, $cm);

    if ($filearea == 'questiontext') {
        $questionid = $args[0];
        $file = $args[1];
        $a = explode('/', $context->path);
        if (!$contextcourse = game_get_context_course_instance($course->id)) {
            throw new moodle_exception('game_error', 'game', 'nocontext');
        }
        $a = ['component' => 'question', 'filearea' => 'questiontext',
            'itemid' => $questionid, 'filename' => $file, 'contextid' => $contextcourse->id];
        $rec = $DB->get_record('files', $a);

        $fs = get_file_storage();
        $file = $fs->get_file_by_hash($rec->pathnamehash);
        if (!$file || $file->is_directory()) {
            return false;
        }

        // Finally send the file.
        send_stored_file($file, 0, 0, true); // Download MUST be forced - security!
    } else if ($filearea == 'answer') {
        $answerid = $args[0];
        $file = $args[1];

        if (!$contextcourse = game_get_context_course_instance($course->id)) {
            throw new moodle_exception('game_error', 'game', 'nocontext');
        }
        $rec = $DB->get_record('files', ['component' => 'question', 'filearea' => 'answer',
            'itemid' => $answerid, 'filename' => $file, 'contextid' => $contextcourse->id]);

        $fs = get_file_storage();
        if (!$file = $fs->get_file_by_hash($rec->pathnamehash) || $file->is_directory()) {
            return false;
        }

        // Finally send the file.
        send_stored_file($file, 0, 0, true); // Download MUST be forced - security!
    }

    $filearea = $args[0];
    $filename = $args[1];

    $fs = get_file_storage();
    $relativepath = implode('/', $args);
    $fullpath = "/$context->id/mod_game/$filearea/$cm->instance/$filename";
    if (!$file = $fs->get_file_by_hash(sha1($fullpath)) || $file->is_directory()) {
        return false;
    }

    // Finally send the file.
    send_stored_file($file, 0, 0, true); // Download MUST be forced - security!
}

/**
 * Add reset buttons to form.
 *
 * @param object $mform form passed by reference
 */
function game_reset_course_form_definition(&$mform) {
    $mform->addElement('header', 'gameheader', get_string('modulenameplural', 'game'));
    $mform->addElement('checkbox', 'reset_game_all', get_string('reset_game_all', 'game'));
    $mform->addElement('checkbox', 'reset_game_deleted_course', get_string('reset_game_deleted_course', 'game'));
}

/**
 * Course reset form defaults.
 *
 * @param stdClass $course
 *
 * @return array
 */
function game_reset_course_form_defaults($course) {
    return ['reset_game_all' => 0, 'reset_game_deleted_course' => 0];
}

/**
 * Actual implementation of the reset course functionality, delete all the Game responses for course $data->courseid.
 *
 * @param stdClass $data the data submitted from the reset course.
 *
 * @return array status array
 */
function game_reset_userdata($data) {
    global $DB;

    $componentstr = get_string('modulenameplural', 'game');
    $status = [];
    $fs = get_file_storage();

    for ($i = 1; $i <= 2; $i++) {
        if ($i == 1) {
            if (empty($data->reset_game_all)) {
                continue;
            }
            $allgamessql = 'SELECT g.id FROM {game} g WHERE g.course = ' . $data->courseid;
            $allattemptssql = 'SELECT ga.id FROM {game} g LEFT JOIN {game_attempts} ga ON g.id = ga.gameid WHERE g.course = ' .
                $data->courseid;
            $newstatus = ['component' => $componentstr, 'item' => get_string('reset_game_all', 'game'), 'error' => false];
        } else if ($i == 2) {
            if (empty($data->reset_game_deleted_course)) {
                continue;
            }

            $allgamessql = 'SELECT g.id FROM {game} g WHERE NOT EXISTS(SELECT * FROM {course} c WHERE c.id = g.course)';
            $allattemptssql = 'SELECT ga.id FROM {game_attempts} ga ' .
                'WHERE NOT EXISTS(SELECT * FROM {game} g WHERE ga.gameid = g.id)';
            $newstatus = ['component' => $componentstr, 'item' => get_string('reset_game_deleted_course', 'game'),
                'error' => false];
        }

        $recs = $DB->get_recordset_sql($allgamessql);
        if ($recs->valid()) {
            foreach ($recs as $rec) {
                if (!$cm = get_coursemodule_from_instance('game', $rec->id)) {
                    continue;
                }
                $context = game_get_context_module_instance($cm->id);
                $fs->delete_area_files($context->id, 'mod_game', 'gnakes_file');
                $fs->delete_area_files($context->id, 'mod_game', 'gnakes_board');

                // Reset grades.
                $game = $DB->get_record_select('game', 'id=' . $rec->id, null, 'id,name,course ');
                $grades = null;
                $params = ['itemname' => $game->name, 'idnumber' => 0];
                $params['reset'] = true;
                grade_update('mod/game', $game->course, 'mod', 'game', $game->id, 0, $grades, $params);
            }
        }

        $DB->delete_records_select('game_bookquiz', "id IN ($allgamessql)");
        $DB->delete_records_select('game_bookquiz_chapters', "attemptid IN ($allattemptssql)");
        $DB->delete_records_select('game_bookquiz_questions', "gameid IN ($allgamessql)");
        $DB->delete_records_select('game_cross', "id IN ($allgamessql)");
        $DB->delete_records_select('game_cryptex', "id IN ($allgamessql)");
        $DB->delete_records_select('game_export_html', "id IN ($allgamessql)");
        $DB->delete_records_select('game_export_javame', "id IN ($allgamessql)");
        $DB->delete_records_select('game_grades', "gameid IN ($allgamessql)");
        $DB->delete_records_select('game_hangman', "id IN ($allgamessql)");
        $DB->delete_records_select('game_hiddenpicture', "id IN ($allgamessql)");
        $DB->delete_records_select('game_millionaire', "id IN ($allgamessql)");
        $DB->delete_records_select('game_queries', "gameid IN ($allgamessql)");
        $DB->delete_records_select('game_repetitions', "gameid IN ($allgamessql)");
        $DB->delete_records_select('game_snakes', "id IN ($allgamessql)");
        $DB->delete_records_select('game_sudoku', "id IN ($allgamessql)");

        if ($i == 2) {
            $DB->delete_records_select('game_attempts', "NOT EXISTS (SELECT * FROM {game} g WHERE {game_attempts}.gameid=g.id)");
        } else {
            $DB->delete_records_select('game_attempts', "gameid IN ($allgamessql)");
        }

        $status[] = $newstatus;
    }

    if (empty($data->reset_game_deleted_course)) {
        return $status;
    }

    // Delete data from deleted games.
    $a = ['bookquiz', 'cross', 'cryptex', 'grades', 'bookquiz_questions', 'export_html', 'export_javame', 'hangman',
            'hiddenpicture', 'millionaire', 'snakes', 'sudoku'];
    foreach ($a as $table) {
        $DB->delete_records_select('game_' . $table, "NOT EXISTS(SELECT * FROM {game} g WHERE {game_$table}.id=g.id)");
    }

    $a = ['grades', 'queries', 'repetitions'];
    foreach ($a as $table) {
        $DB->delete_records_select('game_' . $table, "NOT EXISTS(SELECT * FROM {game} g WHERE {game_$table}.gameid=g.id)");
    }

    $a = ['bookquiz_chapters'];
    foreach ($a as $table) {
        $DB->delete_records_select(
            'game_' . $table,
            "NOT EXISTS(SELECT * FROM {game_attempts} ga WHERE {game_$table}.attemptid=ga.id)"
        );
    }

    return $status;
}

/**
 * Obtains the automatic completion state for this module based on any conditions in game settings.
 *
 * @param object $course Course
 * @param object $cm Course-module
 * @param int $userid User ID
 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
 *
 * @return bool True if completed, false if not, $type if conditions not set.
 */
function game_get_completion_state($course, $cm, $userid, $type) {
    global $CFG, $DB;

    if (($cm->completion == 0) || ($cm->completion == 1)) {
        // Completion option is not enabled so just return $type.
        return $type;
    }

    if ($cm->completionview) {
        // Just want to view it. Not needed it.
        return true;
    }

    if (! $game = $DB->get_record('game', ['id' => $cm->instance])) {
        throw new moodle_exception('game_error', 'game', 'invalidcoursemodule');
    }

    // Check for passing grade.
    if ($game->completionpass) {
        require_once($CFG->libdir . '/gradelib.php');
        $item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod',
                'itemmodule' => 'game', 'iteminstance' => $cm->instance, 'outcomeid' => null]);
        if ($item) {
            $grades = grade_grade::fetch_users_grades($item, [$userid], false);
            if (!empty($grades[$userid])) {
                return $grades[$userid]->is_passed($item);
            }
        }
    } else if (!is_null($cm->completiongradeitemnumber)) {
        require_once($CFG->libdir . '/gradelib.php');
        $item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod',
                'itemmodule' => 'game', 'iteminstance' => $cm->instance, 'outcomeid' => null]);
        if ($item) {
            $grades = grade_grade::fetch_users_grades($item, [$userid], false);
            return !empty($grades[$userid]);
        }
    }

    return false;
}

/**
 * Checks if scale is being used by any instance of Game
 *
 * This is used to find out if scale used anywhere
 *
 * @param int $scaleid
 * @return boolean True if the scale is used by any Game
 */
function game_scale_used_anywhere($scaleid) {
    global $DB;

    if ($scaleid && $DB->record_exists('game', ['grade' => -$scaleid])) {
        return true;
    } else {
        return false;
    }
}

/**
 * Returns the context instance of a Module. Is the same for all version of Moodle.
 *
 * This is used to find out if scale used anywhere
 *
 * @param int $moduleid
 * @return stdClass context
 */
function game_get_context_module_instance($moduleid) {
    if (class_exists('context_module')) {
        return context_module::instance($moduleid);
    }

    return get_context_instance(CONTEXT_MODULE, $moduleid);
}

/**
 * Returns the context instance of a course. Is the same for all version of Moodle.
 *
 * This is used to find out if scale used anywhere
 *
 * @param int $courseid
 *
 * @return stdClass context
 */
function game_get_context_course_instance($courseid) {
    if (class_exists('context_course')) {
        return context_course::instance($courseid);
    }

    return get_context_instance(CONTEXT_COURSE, $courseid);
}

/**
 * Returns the url of a pix file. Is the same for all versions of Moodle.
 *
 * @param string $filename
 * @param string $module
 *
 * @return stdClass url
 */
function game_pix_url($filename, $module = '') {
    global $OUTPUT;

    if (game_get_moodle_version() >= '04.00') {
        global $CFG;
        $ret = $CFG->wwwroot . (substr($CFG->wwwroot, -1) == '/' ? '' : '/') . 'mod/game/pix' . '/' . $filename . '.';

        $file = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'pix' . DIRECTORY_SEPARATOR . $filename . '.';
        $exts = ['svg', 'png', 'jpg'];
        foreach ($exts as $ext) {
            if (file_exists($file . $ext)) {
                return $ret . $ext;
            }
        }
        return $OUTPUT->image_url($filename);
    } else if (game_get_moodle_version() >= '03.03') {
        return $OUTPUT->image_url($filename, $module);
    } else {
        return $OUTPUT->pix_url($filename, $module);
    }
}

/**
 * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
 *
 * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
 * @return array $descriptions the array of descriptions for the custom rules.
 */
function mod_game_get_completion_active_rule_descriptions($cm) {
    // Values will be present in cm_info, and we assume these are up to date.
    if (empty($cm->customdata['customcompletionrules']) || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
        return [];
    }

    $descriptions = [];
    foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
        switch ($key) {
            case 'completionattemptsexhausted':
                if (!empty($val)) {
                    $descriptions[] = get_string('completionattemptsexhausteddesc', 'quiz');
                }
                break;
            case 'completionpass':
                if (!empty($val)) {
                    $descriptions[] = get_string('completionpassdesc', 'quiz', format_time($val));
                }
                break;
        }
    }
    return $descriptions;
}

/**
 * Delete all the attempts belonging to a user in a particular game.
 *
 * @param int $gameid The id of game.
 * @param object $user The user object.
 */
function game_delete_user_attempts($gameid, $user) {
    global $CFG, $DB;
    require_once($CFG->dirroot . '/mod/game/locallib.php');

    $sql = "SELECT id FROM {$CFG->prefix}game_attempts WHERE gameid=$gameid AND userid={$user->id}";
    $recs = $DB->get_records_sql($sql);
    foreach ($recs as $rec) {
        $params = ['id' => $rec->id];
        $DB->delete_records('game_bookquiz', $params);
        $DB->delete_records('game_cross', $params);
        $DB->delete_records('game_cryptex', $params);
        $DB->delete_records('game_hangman', $params);
        $DB->delete_records('game_hiddenpicture', $params);
        $DB->delete_records('game_millionaire', $params);
        $DB->delete_records('game_snakes', $params);
        $DB->delete_records('game_sudoku', $params);
    }

    $params = ['gameid' => $gameid, 'userid' => $user->id];
    $DB->delete_records('game_grades', $params);

    $params = ['gameid' => $gameid, 'userid' => $user->id];
    $DB->delete_records('game_attempts', $params);
    $DB->delete_records('game_repetitions', $params);
    $DB->delete_records('game_queries', $params);
}

/**
 * Add a get_coursemodule_info function in case any game type wants to add 'extra' information
 * for the course (see resource).
 *
 * Given a course_module object, this function returns any "extra" information that may be needed
 * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
 *
 * @param stdClass $coursemodule The coursemodule object (record).
 * @return cached_cm_info An object on information that the courses
 *                        will know about (most noticeably, an icon).
 */
function game_get_coursemodule_info($coursemodule) {
    global $DB;

    $dbparams = ['id' => $coursemodule->instance];
    $fields = 'id, name, intro, introformat';
    if (!$game = $DB->get_record('game', $dbparams, $fields)) {
        return false;
    }

    $result = new cached_cm_info();
    $result->name = $game->name;

    if ($coursemodule->showdescription) {
        // Convert intro to html. Do not filter cached version, filters run at display time.
        $result->content = format_module_intro('game', $game, $coursemodule->id, false);
    }

    return $result;
}
