<?php
namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php');

use \local_treestudyplan\courseinfo;
use \local_treestudyplan\associationservice;
use \local_treestudyplan\local\helpers\webservicehelper;
use \local_treestudyplan\completionscanner;
use \local_treestudyplan\gradingscanner;

class courseservice extends \external_api 
{
    const CAP_EDIT = "local/treestudyplan:editstudyplan";
    const CAP_VIEW = "local/treestudyplan:viewuserreports";

    /************************
     *                      *
     * list_courses         *
     *                      *
     ************************/

    public static function map_categories_parameters() 
	{
        return new \external_function_parameters( [
            "root_id" => new \external_value(PARAM_INT, 'root category to use as base', VALUE_DEFAULT),
         ] );
    }

	public static function map_categories_returns() 
	{
        return new \external_multiple_structure(static::map_category_structure(false));
    }

    protected static function map_category_structure($lazy=false,$value=VALUE_REQUIRED)
    {
        $s = [
            "id" => new \external_value(PARAM_INT, 'course category id'),
            "context_id" => new \external_value(PARAM_INT, 'course category context id'),
            "category" =>  contextinfo::structure(VALUE_OPTIONAL),
            "haschildren" => new \external_value(PARAM_BOOL, 'True if the category has child categories'),
            "hascourses" => new \external_value(PARAM_BOOL, 'True if the category contains courses'),
            "studyplancount" =>  new \external_value(PARAM_INT, 'number of linked studyplans',VALUE_OPTIONAL),
        ];

        if(!$lazy > 0) {
            $s["courses"] = new \external_multiple_structure( courseinfo::editor_structure() );
            $s["children"] = new \external_multiple_structure( static::map_category_structure(true));
        }
        return  new \external_single_structure($s,"CourseCat info",$value);
    }

    public static function map_categories($root_id = 0){
        global $CFG, $DB;

        $root = \core_course_category::get($root_id);
        $context = $root->get_context();
        // Make sure the user has access to the context for editing purposes
        webservicehelper::require_capabilities(self::CAP_EDIT,$context);

        // Determine top categories from provided context

        if($root->id == 0){
            // on the system level, determine the user's topmost allowed catecories
            $user_top = \core_course_category::user_top();
            if($user_top->id == 0){ // top category..
                $children = $root->get_children(); // returns a list of çore_course_category, let it overwrite $children
            } else {
                $children = [$user_top];
            }
        } else if ($root->is_uservisible()){
            $children = [$root];
        } 

        foreach($children as $cat){
            $list[] = static::map_category($cat,false);
        }
        return $list;
    }    

    public static function get_category_parameters() 
	{
        return new \external_function_parameters( [
            "id" => new \external_value(PARAM_INT, 'id of category'),
        ] );
    }

	public static function get_category_returns() 
	{
        return static::map_category_structure(false);
    }

    public static function get_category($id){
        $cat = \core_course_category::get($id);
        return static::map_category($cat);
    }   

    protected static function map_category(\core_course_category $cat,$lazy=false){
        global $DB;
        $catcontext = $cat->get_context();
        $ctx_info = new contextinfo($catcontext);
        $children = $cat->get_children(); // only shows children visible to the current user
        $courses = $cat->get_courses();
        $model = [
            "id" => $cat->id,
            "context_id" => $catcontext->id,
            "category" => $ctx_info->model(),
            "haschildren" => !empty($children),
            "hascourses" => !empty($courses),
        ];

        if(!$lazy)
        {
            $model["courses"] = [];
            foreach($courses as $course){
                $courseinfo = new courseinfo($course->id);
                $model["courses"][] = $courseinfo->editor_model();
            }

            $model["children"] = [];
            foreach($children as $child){
                $model["children"][] = static::map_category($child,true);
            }
        }

        return $model;
    }

    public static function list_accessible_categories_parameters() 
	{
        return new \external_function_parameters( [
            "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]',VALUE_DEFAULT),] 
        );
    }

	public static function list_accessible_categories_returns() 
	{
        return new \external_multiple_structure(static::map_category_structure(true));
    }

    public static function list_accessible_categories($operation="edit") 
	{
        if($operation == "edit"){
            $capability = self::CAP_EDIT;
        } else { // $operation == "view" || default
            $capability = self::CAP_VIEW;
        }

        $cats = static::categories_by_capability($capability);

        $list = [];
        /* @var $cat \core_course_category */
        foreach($cats as $cat){
            $list[] = static::map_category($cat,true);
        }
        return $list;

    }

    public static function categories_by_capability($capability,\core_course_category $parent=null){
        // List the categories in which the user has a specific capability
        $list = [];
        // initialize parent if needed
        if($parent == null){
            $parent = \core_course_category::user_top();
            if(has_capability($capability,$parent->get_context())){
                $list[] = $parent;
            }
        }
        
        $children = $parent->get_children();
        foreach($children as $child){
            // Check if we should add this category
            if(has_capability($capability,$child->get_context())){
                $list[] = $child;
                // For optimization purposes, we include all its children now, since they will have inherited the permission
                // #PREMATURE_OPTIMIZATION  ???
                $list = array_merge($list,self::recursive_child_categories($child));
            } else {
                if($child->get_children_count() > 0){
                    $list = array_merge($list,self::categories_by_capability($capability,$child));
                }
            }
        }

        return $list;
    }

    protected static function recursive_child_categories(\core_course_category $parent){
        $list = [];
        $children = $parent->get_children();
        foreach($children as $child){
            $list[] = $child;
            if($child->get_children_count() > 0){
                $list = array_merge($list,self::recursive_child_categories($child));
            }
        }
        return $list;
    }

    public static function list_used_categories_parameters() 
	{
        return new \external_function_parameters( [
            "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]',VALUE_DEFAULT),
        ]);
    }

	public static function list_used_categories_returns() 
	{
        return new \external_multiple_structure(static::map_category_structure(true));
    }

    public static function list_used_categories($operation='edit') 
	{
        global $DB;
        if($operation == "edit"){
            $capability = self::CAP_EDIT;
        } else { // $operation == "view" || default
            $capability = self::CAP_VIEW;
        }    
        $context_ids = [];
        $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan} 
                                         GROUP BY context_id");
        foreach($rs as $r){
            $context_ids[$r->context_id] = $r->num;
        }
        $rs->close();
        

        // Now filter the categories that the user has acces to by the used context id's
        // (That should filter out irrelevant stuff)
        $cats = static::categories_by_capability($capability);

        $list = [];
        foreach($cats as $cat){
            $count = 0;
            $ctxid = $cat->get_context()->id;
            if(array_key_exists($ctxid,$context_ids)){
                $count = $context_ids[$ctxid];
            }

            $o = static::map_category($cat,true);
            $o["studyplancount"] = $count;
            $list[] = $o;
        }
        return $list;
    }

    public static function list_accessible_categories_with_usage($operation='edit'){
        global $DB;
        if($operation == "edit"){
            $capability = self::CAP_EDIT;
        } else { // $operation == "view" || default
            $capability = self::CAP_VIEW;
        }    
        // retrieve context ids used
        $context_ids = [];
        $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan} 
                                         GROUP BY context_id");
        foreach($rs as $r){
            $context_ids[$r->context_id] = $r->num;
        }
        $rs->close();

        // Now filter the categories that the user has acces to by the used context id's
        // (That should filter out irrelevant stuff)
        $cats = static::categories_by_capability($capability);

        $list = [];
        foreach($cats as $cat){
            $count = 0;
            $ctxid = $cat->get_context()->id;
            if(array_key_exists($ctxid,$context_ids)){
                $count = $context_ids[$ctxid];
            }
            $o = new \stdClass();
            $o->cat = $cat;
            $o->ctxid = $ctxid;
            $o->count = $count;
            $list[] = $o;
        }
        return $list;
    }


    /**************************************
     * 
     * Progress scanners for teacherview
     * 
     **************************************/

    public static function scan_grade_progress_parameters() 
    {
        return new \external_function_parameters( [
            "gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for',VALUE_DEFAULT),
            "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in',VALUE_DEFAULT),

        ]);
    }

    public static function scan_grade_progress_returns() 
    {
    return gradingscanner::structure(VALUE_REQUIRED);
    }
 
    public static function scan_grade_progress($gradeitemid, $studyplanid) 
    {
        global $DB;
        // Verify access to the study plan
        $o = studyplan::findById($studyplanid);
        webservicehelper::require_capabilities(self::CAP_VIEW,$o->context());
        
        // Retrieve grade item
        $gi = \grade_item::fetch(["id" => $gradeitemid]);

        // Validate course is linked to studyplan
        $courseid = $gi->courseid;
        if(!$o->course_linked($courseid)){
            throw new \webservice_access_exception("Course {$courseid} linked to grade item {$gradeitemid} is not linked to studyplan {$o->id()}");
        }

        $scanner = new gradingscanner($gi);
        return $scanner->model();
    }


    public static function scan_completion_progress_parameters() 
	{
        return new \external_function_parameters( [
            "criteriaid" => new \external_value(PARAM_INT, 'CriteriaID to scan progress for',VALUE_DEFAULT),
            "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in',VALUE_DEFAULT),
            "courseid" => new \external_value(PARAM_INT, 'Course id of criteria',VALUE_DEFAULT),
        ]);
    }

	public static function scan_completion_progress_returns() 
	{
        return completionscanner::structure(VALUE_REQUIRED);
    }

    public static function scan_completion_progress($criteriaid, $studyplanid,$courseid) 
	{
        global $DB;
        // Verify access to the study plan
        $o = studyplan::findById($studyplanid);
        webservicehelper::require_capabilities(self::CAP_VIEW,$o->context());

        $crit = \completion_criteria::fetch(["id" => $criteriaid]);
        if(!$o->course_linked($courseid)){
            throw new \webservice_access_exception("Course {$courseid} linked to criteria {$criteriaid} is not linked to studyplan {$o->id()}");
        }
        $scanner = new completionscanner($crit,\get_course($courseid));
        return $scanner->model();
    }

    public static function scan_badge_progress_parameters() 
	{
        return new \external_function_parameters( [
            "badgeid" => new \external_value(PARAM_INT, 'Badge to scan progress for',VALUE_DEFAULT),
            "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to limit progress search to (to determine which students to scan)',VALUE_DEFAULT),
        ]);
    }

	public static function scan_badge_progress_returns() 
	{
        return new \external_single_structure([
            "total" => new \external_value(PARAM_INT, 'Total number of students scanned'),
            "issued" => new \external_value(PARAM_INT, 'Number of issued badges'),
        ]);
    }

    public static function scan_badge_progress($badgeid,$studyplanid) 
	{
        global $DB;
        // Check access to the study plan
        $o = studyplan::findById($studyplanid);
        webservicehelper::require_capabilities(self::CAP_VIEW,$o->context());

        // Validate that badge is linked to studyplan
        if(!$o->badge_linked($badgeid)){
            throw new \webservice_access_exception("Badge {$badgeid} is not linked to studyplan {$o->id()}");
        }

        // Get badge info
        $badge = new \core_badges\badge($badgeid);
        $badgeinfo = new badgeinfo($badge);

        // get the connected users
        $students = associationservice::all_associated($studyplanid);
        // Just get the user ids
        $studentids = array_map(function ($a){ return $a["id"];},$students);

        return [
            "total" => count($studentids),
            "issued" => $badgeinfo->count_issued($studentids),
        ];

    }


}