<?php

namespace block_ned_teacher_tools;
use block_ned_teacher_tools\shared_lib as SH;

/**
 * Used to save some global values as one global array
 * @param array $keys - path to the value (example: ['category1', 'category2', 'category3']
 * @param null  $value - saved by $keys if provided
 * @param bool  $set_even_null - set in true, if you need to save $value === null
 *
 * @return array|mixed|null
 */
function ned_global($keys=[], $value=null, $set_even_null=false){
    global $ned_globals_list;
    $ned_globals_list = isset($ned_globals_list) ? $ned_globals_list : [];
    $obj = &$ned_globals_list;
    $set_val = !is_null($value) || $set_even_null;
    foreach ($keys as $key){
        if (!isset($obj[$key])){
            if (!$set_val){
                return null;
            }
            $obj[$key] = [];
        }
        $obj = &$obj[$key];
    }
    if ($set_val){
        $obj = $value;
    }
    return $obj;
}

function _DB(){
    global $DB;
    return $DB;
}

/**
 * @param $courseid - course id ($mod->course)
 * @param $itemmodule - module name (component name without 'mod_') ($mod->itemmodule)
 * @param $iteminstance - instance id ($mod->instance)
 *
 * @return \stdClass|bool Returns a {$itemmodule} instance or false if none found
 * @throws \dml_exception
 */
function get_module_db($courseid, $itemmodule, $iteminstance){
    $G_NAME = 'modules';
    $modules_list = ned_global([$G_NAME, $courseid]);
    if(is_null($modules_list)){
        $modules_list = [];
        $availabletypes = \format_singleactivity::get_supported_activities();
        foreach ($availabletypes as $module => $name){
            $modules_list[$module] = [];
        }
        ned_global([$G_NAME, $courseid], $modules_list);
    }
    if(isset($modules_list[$itemmodule]) and $modules_list[$itemmodule] !== false){
        if(empty($modules_list[$itemmodule])){
            $mods = _DB()->get_records($itemmodule, ['course' => $courseid]);
            if(!empty($mods)){
                foreach ($mods as $mod){
                    $modules_list[$itemmodule][$mod->id] = $mod;
                }
            } else {
                $modules_list[$itemmodule] = false;
            }
            ned_global([$G_NAME, $courseid], $modules_list);
        }
    }

    if(isset($modules_list[$itemmodule][$iteminstance])){
        return $modules_list[$itemmodule][$iteminstance];
    } else {
        return false;
    }
}

/**
 * @param $userid - user id
 * @param $itemid - grade_item id ($grade_item->id)
 *
 * @return \stdClass|bool Returns a grade_grade instance or false if none found
 * @throws \dml_exception
 */
function get_grade_grade($userid, $itemid) {
    $G_NAME = 'grade_grades';
    $grades_list = ned_global([$G_NAME, $userid]);
    if (is_null($grades_list)){
        $grades_list= [];
        $grades = _DB()->get_records('grade_grades', ['userid' => $userid]);
        if (!empty($grades)){
            foreach ($grades as $grade){
                $grades_list[$grade->itemid] = $grade;
            }
        }
        ned_global([$G_NAME, $userid], $grades_list);
    }

    if (isset($grades_list[$itemid])){
        return $grades_list[$itemid];
    } else {
        return false;
    }
}

/**
 * @param $courseid - course id ($mod->course)
 * @param $itemmodule - module name (component name without 'mod_') ($mod->modname)
 * @param $iteminstance - instance id ($mod->instance)
 *
 * @return \grade_item|bool Returns a grade_item instance or false if none found
 */
function get_grade_item($courseid, $itemmodule, $iteminstance){
    $G_NAME = 'grade_items';
    $grades_list = ned_global([$G_NAME, $courseid]);
    if (is_null($grades_list)){
        $grades_list = [];
        $grade_items = \grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $courseid]);
        if (!empty($grade_items)){
            foreach ($grade_items as $grade_item){
                $grades_list[$grade_item->itemmodule][$grade_item->iteminstance] = $grade_item;
            }
        }
        ned_global([$G_NAME, $courseid], $grades_list);
    }

    if (isset($grades_list[$itemmodule][$iteminstance])){
        return $grades_list[$itemmodule][$iteminstance];
    } else {
        return false;
    }
}

/**
 * @param \stdClass $course
 * @param bool      $for_deadline_manager
 *
 * @return array - $allowedgroups (array), $ignore_grouping (bool)
 */
function get_groups($course, $for_deadline_manager=false){
    global $USER;
    $G_NAME = 'groups';
    $userid = $USER->id;
    $courseid = $course->id;
    $for_deadline_manager = (int)$for_deadline_manager;
    $data =  ned_global([$G_NAME, $courseid, $userid, $for_deadline_manager]);

    if (is_null($data)){
        $groupmode = $course->groupmode;
        $context = \context_course::instance($courseid);
        $aag = $for_deadline_manager ?  has_capability('block/ned_teacher_tools:view_all_groups', $context) :
            has_capability('moodle/site:accessallgroups', $context);
        $allowedgroups = [];
        $dm_check = false;

        if (!$groupmode){
            $allowedgroups = [SH::stdClass2(['id' => 0, 'name' => str('allusers')])];
        } elseif ($groupmode == VISIBLEGROUPS or $aag) {
            $all_groups = groups_get_all_groups($courseid, 0, $course->defaultgroupingid);
        } else {
            $all_groups = groups_get_all_groups($courseid, $userid, $course->defaultgroupingid);
            $dm_check = $for_deadline_manager && \block_ned_teacher_tools\deadline_manager::can_view_own_groups($context);
        }

        if (!empty($all_groups)){
            foreach ($all_groups as $key => $group){
                if ($dm_check){
                    if ($group->schedule != \block_ned_teacher_tools\deadline_manager::SCHEDULE_FULL || !groups_is_member($group->id)) continue;
                }

                /**
                 * {@see groups_get_all_groups()} returns cached objects, so if the result is not read-only,
                 *  changing the result objects – affects feature calls of the groups_get_all_groups();
                 * So it's better to clone objects in order to divert subsequent changes from groups_get_all_groups()
                 */
                $allowedgroups[$key] = clone $group;
            }
        }

        $ignore_grouping = !$groupmode || $aag;
        $data = [$allowedgroups, $ignore_grouping];
        ned_global([$G_NAME, $courseid, $userid, $for_deadline_manager], $data);
    }

    // list($allowedgroups, $ignore_grouping) = $data;
    return $data;
}

/**
 * @param \stdClass $course
 * @param int       $teacherid
 * @param int       $groupid
 * @param null      $show_only_active
 *
 * @return array of students ['id' => stdClass] by teacher $userid. If it's student, return array of it's record
 */
function get_students(\stdClass $course, $teacherid=0, $groupid=0, $show_only_active=null){
    global $USER;
    $teacherid = $teacherid ? $teacherid : $USER->id;
    $courseid = $course->id;
    $G_NAME = 'students';
    $student_list = ned_global([$G_NAME, $courseid, $teacherid, $groupid]);

    if (is_null($student_list)){
        $student_list = [];
        $context = \context_course::instance($course->id, MUST_EXIST);
        $isteacher = has_capability('moodle/grade:viewall', $context, $teacherid);
        if (!$isteacher){
            $student_list[$teacherid] = \core_user::get_user($teacherid);
        } else {
            $show_only_active = $show_only_active ?? !get_cache_show_inactive($courseid);
            $student_list = get_course_students_by_role($courseid, $groupid, $show_only_active);
        }
        ned_global([$G_NAME, $courseid, $teacherid, $groupid], $student_list);
    }

    return $student_list;
}

/**
 * Return list of students ['id' => stdClass] by $courseid & $groupid, or false if there no such students.
 *
 * @param int  $courseid
 * @param int  $groupid
 * @param null $show_only_active
 * @param null $load_cm_students
 *
 * @return false|array
 */
function get_course_students_by_role($courseid, $groupid=0, $show_only_active=null, $load_cm_students=null){
    $show_only_active = $show_only_active ?? !get_cache_show_inactive($courseid);
    return SH::get_course_students_by_role($courseid, 0, $groupid, (int)$show_only_active, (int)$load_cm_students);
}

/**
 * @param \stdClass $course
 * @param bool      $for_deadline_manager
 * @param null      $show_only_active
 * @param null      $load_cm_students
 *
 * @return array - $users (array), $allowedgroups(array)
 */
function get_users_and_groups($course, $for_deadline_manager=false, $show_only_active=null, $load_cm_students=null) {
    global $USER;
    $G_NAME = 'users_and_groups';
    $userid = $USER->id;
    $courseid = $course->id;
    $for_deadline_manager = (int)$for_deadline_manager;
    $show_only_active = is_null($show_only_active) ? !get_cache_show_inactive($course->id) : $show_only_active;
    $show_only_active = (int)$show_only_active;
    $load_cm_students = (int)$load_cm_students;
    $data = ned_global([$G_NAME, $courseid, $userid, $for_deadline_manager, $show_only_active, $load_cm_students]);

    if (is_null($data)){
        [$allowedgroups, $ignore_grouping] = get_groups($course, $for_deadline_manager);

        $users = [];
        if ($ignore_grouping) {
            $users = get_course_students_by_role($course->id, 0, $show_only_active, $load_cm_students);
            if (count($allowedgroups) > 1 || !isset($allowedgroups[0])){
                foreach ($allowedgroups as $key => $group){
                    $allowedgroups[$key]->users = [];
                }
                foreach ($users as $user_key => $user){
                    $groupid = $user->groupid ?? 0;
                    if (isset($allowedgroups[$groupid])){
                        $user->group = $allowedgroups[$groupid];
                        $allowedgroups[$groupid]->users[$user_key] = $user;
                    } else {
                        $user->group = null;
                    }
                }
            }
        } else {
            foreach ($allowedgroups as $key => $group){
                $enrolledusers = get_course_students_by_role($course->id, $group->id, $show_only_active, $load_cm_students);

                foreach ($enrolledusers as $user_key => $user){
                    $user->group = $group;
                }

                $allowedgroups[$key]->users = $enrolledusers;
                $users += $enrolledusers;
            }
        }

        $data = [$users, $allowedgroups];
        ned_global([$G_NAME, $courseid, $userid, $for_deadline_manager, $show_only_active, $load_cm_students], $data);
    }

    // list($users, $allowedgroups) = $data;
    return $data;
}

/**
 * @param $modid - mod id
 *
 * @return array Returns array of tags, maybe empty
 */
function get_tags($modid) {
    return SH::cm_get_tags($modid);
}

/**
 * Returns instance of mod (activity)
 * @param $modname
 * @param $instanceid
 *
 * @return array|mixed|null
 * @throws \dml_exception
 */
function get_instance($modname, $instanceid){
    $G_NAME = 'instances';
    $inst = ned_global([$G_NAME, $modname, $instanceid]);
    if (is_null($inst)){
        $inst = _DB()->get_record($modname, ['id' => $instanceid]);
        ned_global([$G_NAME, $modname, $instanceid], $inst);
    }
    return $inst;
}

/**
 * Return result the same to is_graded() from kica class
 *
 * @param $userid
 * @param $kicaitemid
 *
 * @return array|bool|mixed|null
 */
function get_kica_is_graded($userid, $kicaitemid){
    if (!is_kica_exists()){
        return null;
    }

    $G_NAME = 'kica_is_graded';
    $kica_is_graded = ned_global([$G_NAME, $userid, $kicaitemid]);
    if (is_null($kica_is_graded)){
        $kica_is_graded = false;
        if ($grade = SH::kg_get_by_userid_itemid($userid, $kicaitemid)) {
            $kica_is_graded = !empty($grade->id) && $grade->is_graded();
        }
        ned_global([$G_NAME, $userid, $kicaitemid], $kica_is_graded);
    }

    return $kica_is_graded;
}

// Prepare big amounts of data


/**
 * Load data from grade_items & grade_grades for course (and specific user, if set user id)
 *
 * @param      $courseid
 * @param null $userid
 *
 * @throws \dml_exception
 */
function load_mod_grade_items($courseid, $userid=null){
    if (ned_global(['load_mod_grade_items'])){
        return;
    }

    [$gg_l, $gi_l, $sc_l] = array([], [], []);
    $t = ['grade_grades' => 'g_g', 'grade_items' => 'g_i'];
    $t_cols = [];
    $dl = '___';
    $fields = [];
    foreach ($t as $t_name => $alias){
        $cols = _DB()->get_columns($t_name);
        foreach ($cols as $col => $col_info){
            $t_cols[$t_name][$col] = $alias.$dl.$col;
            $fields[] = "$alias.$col AS {$t_cols[$t_name][$col]}";
        }
        $t[$t_name] = $alias.$dl;
    }

    $sql = 'SELECT CONCAT(g_i.id, g_g.id), ' . join(',', $fields) . '
                FROM 
                  {grade_items} g_i
                LEFT JOIN {grade_grades} g_g
                  ON g_g.itemid = g_i.id
                WHERE g_i.courseid = :courseid AND g_i.itemtype = "mod" AND g_g.id
        ';
    $params = ['courseid' => $courseid];
    if (!is_null($userid)){
        $sql .= ' AND g_g.userid = :userid';
        $params['userid'] = $userid;
    }

    $grade_info_records = _DB()->get_records_sql($sql, $params);
    if (empty($grade_info_records)){
        return;
    }

    $gi_list = [];
    $gg_list = [];
    foreach ($grade_info_records as $r){
        $gi = new \stdClass();
        foreach ($t_cols['grade_items'] as $col => $alias_col){
            $gi->$col = $r->$alias_col;
        }
        $gi_list[$gi->itemmodule][$gi->iteminstance] = $gi;

        $gg = new \stdClass();
        foreach ($t_cols['grade_grades'] as $col => $alias_col){
            $gg->$col = $r->$alias_col;
        }
        $gg_list[$gg->userid][$gg->itemid] = $gg;
    }

    ned_global(['grade_items', $courseid], $gi_list);
    ned_global(['grade_grades'], $gg_list);
    ned_global(['load_mod_grade_items'], 1);
}

/**
 * Return saved groupid from block settings
 * @param $courseid
 *
 * @return int|false - return false, if find nothing
 */
function get_cache_group($courseid){
    return SH::get_cached_groupid($courseid) ?? false;
}

/**
 * Saved groupid in cache
 * @param $courseid
 * @param $groupid
 */
function set_cache_group($courseid, $groupid){
    if (is_null($groupid)){
        return;
    }
    $context = \context_course::instance($courseid, MUST_EXIST);
    if (!has_capability('moodle/grade:viewall', $context)){
        return;
    }
    $cache = \cache::make(PLUGIN_NAME, 'block_group');
    $cache->set($courseid, (int)$groupid);
}

/**
 * Check $groupid an replace it with the cache one if necessary
 * @param $groupid
 * @param $courseid
 * @param $groups
 *
 * @return false|int
 */
function check_group_with_cache($groupid, $courseid, $groups){
    if (is_null($groupid)){
        $cache_group = get_cache_group($courseid);
        $groupid = $cache_group;
    }
    if(!in_array($groupid, GROUPS, true) && !isset($groups[$groupid])){
        $groupid = null;
    }
    if ((is_null($groupid) || $groupid == GROUP_NONE) && count($groups) == 1){
        reset($groups);
        $groupid = key($groups);
    }
    return $groupid ?? GROUP_NONE;
}

/**
 * Check, can user see inactive people or not
 * @param $courseid
 *
 * @return bool
 * @throws \coding_exception
 */
function has_show_inactive_capability($courseid){
    $context = \context_course::instance($courseid, MUST_EXIST);
    return has_capability('moodle/course:viewsuspendedusers', $context);
}

/**
 * Return saved $show_inactive from block settings
 * @param $courseid
 *
 * @return false|int
 */
function get_cache_show_inactive($courseid){
    if (!has_show_inactive_capability($courseid)){
        return null;
    }
    $cache = \cache::make(PLUGIN_NAME, 'block_show_inactive');
    return $cache->get($courseid);
}

/**
 * Saved $show_inactive in cache
 * @param $courseid
 * @param $show_inactive
 */
function set_cache_show_inactive($courseid, $show_inactive){
    if (is_null($show_inactive) || !has_show_inactive_capability($courseid)){
        return;
    }
    $cache = \cache::make(PLUGIN_NAME, 'block_show_inactive');
    $cache->set($courseid, (int)$show_inactive);
}

/**
 * Check $show_inactive an replace it with the cache one if necessary
 *  Return 1/0 as $show_inactive value, null if user haven't capability to see inactive people
 * @param $show_inactive
 * @param $courseid
 *
 * @return null|int
 */
function check_show_inactive_with_cache($show_inactive, $courseid){
    if (!has_show_inactive_capability($courseid)){
        return null;
    }
    if (is_null($show_inactive)){
        $show_inactive = get_cache_show_inactive($courseid);
    }
    return (int)$show_inactive;
}
