<?php

namespace local_treestudyplan;
require_once($CFG->libdir.'/externallib.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/course/lib.php');

use core_course\local\repository\caching_content_item_readonly_repository;
use core_course\local\repository\content_item_readonly_repository;
use \grade_item;
use \grade_scale;
use \grade_outcome;

class corecompletioninfo {
    private $course;
    private $completion;
    private $modinfo;
    private static $COMPLETION_HANDLES = null;

    public function id(){
        return $this->course->id;
    }
    public function __construct($course){
        global $DB;
        $this->course = $course;
        $this->completion = new \completion_info($this->course);
        $this->modinfo = get_fast_modinfo($this->course);
    }

    static public function completiontypes(){
        global $COMPLETION_CRITERIA_TYPES; 
        // Just return the keys of the global array COMPLETION_CRITERIA_TYPES, so we don't have to manually
        // add any completion types....
        return \array_keys($COMPLETION_CRITERIA_TYPES);
    }

    /**
     * Translate a numeric completion constant to a text string 
     * @param $completion The completion code as defined in completionlib.php to translate to a text handle
     */
    static public function completion_handle($completion){
        if(empty(self::$COMPLETION_HANDLES)){
            // Cache the translation table, to avoid overhead
            self::$COMPLETION_HANDLES = [
                COMPLETION_INCOMPLETE => "incomplete",
                COMPLETION_COMPLETE => "complete", 
                COMPLETION_COMPLETE_PASS => "complete-pass",
                COMPLETION_COMPLETE_FAIL => "complete-fail",
                COMPLETION_COMPLETE_FAIL_HIDDEN => "complete-fail"]; // the front end won't differentiate between hidden or not
        }
        return self::$COMPLETION_HANDLES[$completion] ?? "undefined";
    }

    public static function completion_item_editor_structure($value=VALUE_REQUIRED){
        return new \external_single_structure([
            "title" => new \external_value(PARAM_TEXT,'name of subitem',VALUE_OPTIONAL),
            "link" => new \external_value(PARAM_TEXT, 'optional link to more details',VALUE_OPTIONAL),
            // ADD BELOW IF NEEDED - try using name, description and link fields first
            /*
            "required_grade" => new \external_value(PARAM_TEXT, 'required_grade',VALUE_OPTIONAL),
            "course_link" => course_info::simple_structure(VALUE_OPTIONAL),
            */            
         ], 'completion type',$value);
    }

    public static function completion_type_editor_structure($value=VALUE_REQUIRED){
        return new \external_single_structure([
            "items" => new \external_multiple_structure(self::completion_item_editor_structure(),'subitems',VALUE_OPTIONAL),
            "title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL),
            "desc" => new \external_value(PARAM_TEXT, 'optional description',VALUE_OPTIONAL),
            "type" => new \external_value(PARAM_TEXT, 'completion type name'),
            "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all","any"]'),
         ], 'completion type',$value);
    }

    public static function editor_structure($value=VALUE_REQUIRED){
        return new \external_single_structure([
           "conditions" => new \external_multiple_structure(self::completion_type_editor_structure(),'completion conditions'),
           "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all","any"]'),
           "enabled" => new \external_value(PARAM_BOOL,"whether completion is enabled here"),
        ], 'course completion info',$value);
    }

    public static function completion_item_user_structure($value=VALUE_REQUIRED){
        return new \external_single_structure([
            "id" => new \external_value(PARAM_INT,'id of subitem',VALUE_OPTIONAL),
            "title" => new \external_value(PARAM_TEXT,'name of subitem',VALUE_OPTIONAL),
            "details" => new \external_single_structure([
                "type" => new \external_value(PARAM_RAW, 'type',VALUE_OPTIONAL),
                "criteria" => new \external_value(PARAM_RAW, 'criteria',VALUE_OPTIONAL),
                "requirement" => new \external_value(PARAM_RAW, 'requirement',VALUE_OPTIONAL),
                "status" => new \external_value(PARAM_RAW, 'status',VALUE_OPTIONAL),
            ]),
            "link" => new \external_value(PARAM_TEXT, 'optional link to more details',VALUE_OPTIONAL),
            // ADD BELOW IF NEEDED - try using name, description and link fields first
            /*
            "required_grade" => new \external_value(PARAM_TEXT, 'required_grade',VALUE_OPTIONAL),
            "course_link" => course_info::simple_structure(VALUE_OPTIONAL),
            */
            "completed" => new \external_value(PARAM_BOOL, 'simple completed or not'),
            "status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete","progress","complete", "complete-pass","complete-fail"]'),
            "pending" => new \external_value(PARAM_BOOL, 'optional pending state, for submitted but not yet reviewed activities',VALUE_OPTIONAL),
            "grade" => new \external_value(PARAM_TEXT, 'optional grade result for this subitem',VALUE_OPTIONAL),
            "feedback" => new \external_value(PARAM_RAW, 'optional feedback for this subitem ',VALUE_OPTIONAL),
         ], 'completion type',$value);
    }

    public static function completion_type_user_structure($value=VALUE_REQUIRED){
        return new \external_single_structure([
            "items" => new \external_multiple_structure(self::completion_item_user_structure(),'subitems',VALUE_OPTIONAL),
            "title" => new \external_value(PARAM_TEXT,'optional title',VALUE_OPTIONAL),
            "desc" => new \external_value(PARAM_TEXT, 'optional description',VALUE_OPTIONAL),
            "type" => new \external_value(PARAM_TEXT, 'completion type name'),
            "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation for this type ["all","any"]'),
            "completed" => new \external_value(PARAM_BOOL, 'current completion value for this type'),
            "status" => new \external_value(PARAM_TEXT, 'extended completion status ["incomplete","progress","complete", "complete-pass","complete-fail"]')
         ], 'completion type',$value);
    }

    public static function user_structure($value=VALUE_REQUIRED){
        return new \external_single_structure([
            "progress" => new \external_value(PARAM_INT, 'completed sub-conditions'),
            "enabled" => new \external_value(PARAM_BOOL,"whether completion is enabled here"),
            "tracked" => new \external_value(PARAM_BOOL,"whether completion is tracked for the user",VALUE_OPTIONAL),
            "count" => new \external_value(PARAM_INT, 'total number of sub-conditions'),
            "conditions" => new \external_multiple_structure(self::completion_type_user_structure(),'completion conditions'),
            "completed" => new \external_value(PARAM_BOOL, 'current completion value'),
            "aggregation" => new \external_value(PARAM_TEXT, 'completion aggregation ["all","any"]'),
            "pending" => new \external_value(PARAM_BOOL,"true if the user has any assignments pending grading",VALUE_OPTIONAL),
        ], 'course completion info',$value);
    }

    private static function aggregation_handle($method){
        return ($method==COMPLETION_AGGREGATION_ALL)?"all":"any";
    }

    public function editor_model() {
        global $DB, $COMPLETION_CRITERIA_TYPES;
        
        $conditions = [];
        $aggregation = "all"; // default
        $info = [
            "conditions" => $conditions,
            "aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
            "enabled" => $this->completion->is_enabled() 
        ];

        // Check if completion tracking is enabled for this course - otherwise, revert to defaults 
        if($this->completion->is_enabled())
        {
            $aggregation = $this->completion->get_aggregation_method();
            // Loop through all condition types to see if they are applicable
            foreach(self::completiontypes() as $type){
                $criterias = $this->completion->get_criteria($type); // Returns array of relevant criteria items
                if(count($criterias) > 0 ) // Only take it into account if the criteria count is > 0
                {
                    $cinfo = [
                        "type" => $COMPLETION_CRITERIA_TYPES[$type],
                        "aggregation" => self::aggregation_handle($this->completion->get_aggregation_method($type)),
                        "title" => reset($criterias)->get_type_title(),
                        "items" => [],
                    ];

                    foreach($criterias as $criteria){
                        $iinfo = [
                            "title" => $criteria->get_title_detailed(),
                        ];

                        //TODO: MAKE SURE THIS DATA IS FILLED

                        if($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){
                            // If it's an activity completion, add the relevant activity
                            //$cm = $this->modinfo->get_cm($criterias->moduleinstance);
                            // retrieve data for this object
                            //$data = $completion->get_data($cm, false, $userid);
                        }
                        else if ($type == COMPLETION_CRITERIA_TYPE_COURSE){
                            // If it's a (sub) course dependency, add the course as a link 

                        }
                        else if ($type == COMPLETION_CRITERIA_TYPE_ROLE){
                            // If it needs approval by a role, it probably already is in the title

                        }
                        // only add the items list if we actually have items...
                        $cinfo["items"][] = $iinfo;
                    }

                    $info['conditions'][] = $cinfo;
                }
            }
        }



        return $info;
    }

    private function aggregate_completions($typeaggregation,$completions){
        $completed = 0;
        $count = count($completions);
        foreach($completions as $c){
            if($c->is_complete()){
                $completed++;
            }
        }
        if($typeaggregation == COMPLETION_AGGREGATION_ALL){
            return $completed >= $count;
        } 
        else { // COMPLETION_AGGREGATION_ANY
            return $completed > 1;
        }

    }

    public function user_model($userid) {
        global $DB, $COMPLETION_CRITERIA_TYPES;

        $progress = $this->get_advanced_progress_percentage($userid);
        $info = [
            'progress' => $progress->completed,
            "count" => $progress->count,
            "conditions" => [],
            "completed" => $this->completion->is_course_complete($userid),
            "aggregation" => self::aggregation_handle($this->completion->get_aggregation_method()),
            "enabled" => $this->completion->is_enabled(), 
            "tracked" => $this->completion->is_tracked_user($userid),
        ];
        
        // Check if completion tracking is enabled for this course - otherwise, revert to defaults 
        if($this->completion->is_enabled() && $this->completion->is_tracked_user($userid))
        {
            $anypending = false;
            // Loop through all conditions to see if they are applicable
            foreach(self::completiontypes() as $type){
                // Get the main completion for this type
                $completions = $this->completion->get_completions($userid,$type);
                if(count($completions) > 0){
                    $typeaggregation = $this->completion->get_aggregation_method($type);
                    $completed = $this->aggregate_completions($typeaggregation,$completions);
                    $cinfo = [
                        "type" => $COMPLETION_CRITERIA_TYPES[$type],
                        "aggregation" => self::aggregation_handle($typeaggregation),
                        "completed" => $completed,
                        "status" => $completed?"completed":"incomplete",
                        "title" => reset($completions)->get_criteria()->get_type_title(),
                        "items" => [],
                    ];

                    foreach($completions as $completion){
                        $criteria = $completion->get_criteria();

                        $iinfo = [
                            "id" => $criteria->id,
                            "title" => $criteria->get_title_detailed(),
                            "details" => $criteria->get_details($completion),
                            "completed" => $completion->is_complete(), // Make sure to override for activi
                            "status" => self::completion_handle($completion->is_complete()?COMPLETION_COMPLETE:COMPLETION_INCOMPLETE),
                        ];

                        if($type == COMPLETION_CRITERIA_TYPE_ACTIVITY){
                            $cm = $this->modinfo->get_cm($criteria->moduleinstance);
                            // If it's an activity completion, add all the relevant activities as sub-items
                            $completion_status = $this->completion->get_grade_completion($cm,$userid);
                            $iinfo['status'] = self::completion_handle($completion_status);
                            // Re-evaluate the completed value, to make sure COMPLETE_FAIL doesn't creep in as completed
                            $iinfo['completed'] = in_array($completion_status,[COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]);
                            // Determine the grade (retrieve from grade item, not from completion)
                            $grade = $this->get_grade($cm,$userid);
                            $iinfo['grade'] = $grade->grade;
                            $iinfo['feedback'] = $grade->feedback;
                            $iinfo['pending'] = $grade->pending;

                            $anypending = $anypending || $grade->pending;
                            // Overwrite the status with progress if something has been graded, or is pending
                            if($completion_status != COMPLETION_INCOMPLETE || $anypending){
                                if($cinfo["status"] == "incomplete"){
                                    $cinfo["status"] = "progress";
                                }
                            }

                        }
                        else if ($type == COMPLETION_CRITERIA_TYPE_GRADE){
                            // Make sure we provide the current course grade
                            $iinfo['grade'] = $this->get_course_grade($userid);
                            if($iinfo["grade"] > 0){
                                $iinfo["status"] = $completion->is_complete()?"complete-pass":"complete-fail";
                                if ($cinfo["status"] == "incomplete"){
                                    $cinfo["status"] = "progress";
                                }
                            }
                        }
                        // finally add the item to the items list
                        $cinfo["items"][] = $iinfo;
                    }
                    $info['conditions'][] = $cinfo;
                    $info['pending'] = $anypending;
                }
            }
        }

        return $info;
    }

    /**
     * Get the grade for a certain course module
     * @return stdClass|null object containing 'grade' and optional 'feedback' attribute
     */
    private function get_grade($cm,$userid){
        // TODO: Display grade in the way described in the course setup (with letters if needed)

        $gi= grade_item::fetch(['itemtype' => 'mod', 
            'itemmodule' => $cm->modname, 
            'iteminstance' => $cm->instance, 
            'courseid' => $this->course->id]); // Make sure we only get results relevant to this course

        if($gi)
        {
            // Only the following types of grade yield a result
            if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
            {
                $scale = $gi->load_scale();

                $grade = $gi->get_final($userid); // Get the grade for the specified user
                $result = new \stdClass;
                // Check if the final grade is available and numeric (safety check)
                if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){
                    // convert scale grades to corresponding scale name
                    if(isset($scale)){
                        // get scale value
                        $result->grade = $scale->get_nearest_item($grade->finalgrade);
                    } 
                    else 
                    {
                        // round final grade to 1 decimal point
                        $result->grade =  round($grade->finalgrade,1);
                    }
                    
                    $result->feedback = trim($grade->feedback);
                    $result->pending = (new gradingscanner($gi))->pending($userid);
                }
                else {
                    $result->grade = "-"; // Activity is gradable, but user did not receive a grade yet
                    $result->feedback = null;
                    $result->pending = false;
                }
                return $result;
            }
        }

        return null; // Activity cannot be graded (Shouldn't be happening, but still....)
    }

    /**
     * Get the grade for a certain course module
     * @return stdClass|null object containing 'grade' and optional 'feedback' attribute
     */
    private function get_course_grade($userid){
        // TODO: Display grade in the way described in the course setup (with letters if needed)
        $gi= grade_item::fetch(['itemtype' => 'course', 
            'iteminstance' => $this->course->id, 
            'courseid' => $this->course->id]);

        if($gi)
        {
            // Only the following types of grade yield a result
            if(($gi->gradetype == GRADE_TYPE_VALUE || $gi->gradetype == GRADE_TYPE_SCALE))
            {
                $scale = $gi->load_scale();
                $grade = $gi->get_final($userid); // Get the grade for the specified user
                // Check if the final grade is available and numeric (safety check)
                if(!empty($grade) && !empty($grade->finalgrade) && is_numeric($grade->finalgrade)){
                    // convert scale grades to corresponding scale name
                    if(isset($scale)){
                        // get scale value
                        return $scale->get_nearest_item($grade->finalgrade);
                    } 
                    else 
                    {
                        // round final grade to 1 decimal point
                        return round($grade->finalgrade,1);
                    }
                }
                else {
                    return "-"; // User did not receive a grade yet for this course
                }
            }
        }

        return null; // Course cannot be graded (Shouldn't be happening, but still....)
    }



    /**
     * Returns the percentage completed by a certain user, returns null if no completion data is available.
     *
     * @param int $userid The id of the user, 0 for the current user
     * @return null|float The percentage, or null if completion is not supported in the course,
     *         or there are no activities that support completion.
     */    
    function get_progress_percentage($userid){

        // First, let's make sure completion is enabled.
        if (!$this->completion->is_enabled()) {
            return null;
        }

        if (!$this->completion->is_tracked_user($userid)) {
            return null;
        }

        $completions = $this->completion->get_completions($userid);
        $count = count($completions);
        $completed = 0;

        // Before we check how many modules have been completed see if the course has completed. 
        if ($this->completion->is_course_complete($userid)) {
            $completed = $count;
        }
        else {
            // count all completions, but treat 
            foreach($completions as $completion){
                $crit = $completion->get_criteria();
                if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
                    // get the cm data object
                    $cm = $this->modinfo->get_cm($crit->moduleinstance);
                    // retrieve data for this object
                    $data = $this->completion->get_data($cm, false, $userid);
                    // Count complete, but failed as incomplete too...
                    if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
                        $completed += 0;
                    } else {
                        $completed += 1;
                    }                
                }
                else {
                    if($completion->is_complete()){
                        $completed += 1;
                    }
                } 
            }
        }
        $result = new \stdClass;
        $result->count = $count;
        $result->completed = $completed;
        $result->percentage = ($completed / $count) * 100;
        return $result; 
    }

    /**
     * Returns the percentage completed by a certain user, returns null if no completion data is available.
     *
     * @param int $userid The id of the user, 0 for the current user
     * @return null|float The percentage, or null if completion is not supported in the course,
     *         or there are no activities that support completion.
     */    
    function get_advanced_progress_percentage($userid){

        // First, let's make sure completion is enabled.
        if (!$this->completion->is_enabled()) {
            return null;
        }

        if (!$this->completion->is_tracked_user($userid)) {
            return null;
        }

        $completions = $this->completion->get_completions($userid);

        $aggregation = $this->completion->get_aggregation_method();
        $critcount = [];

        // Before we check how many modules have been completed see if the course has completed. 
        if ($this->completion->is_course_complete($userid)) {
            $completed = $count;
        }
        else {
            // count all completions, but treat 
            foreach($completions as $completion){
                $crit = $completion->get_criteria();

                // Make a new object for the type if it's not already there
                $type = $crit->criteriatype;
                if(!array_key_exists($type,$critcount)){
                    $critcount[$type] = new \stdClass;
                    $critcount[$type]->count = 0;
                    $critcount[$type]->completed = 0;
                    $critcount[$type]->aggregation = $this->completion->get_aggregation_method($type);
                }
                // Get a reference to the counter object for this type
                $typecount =& $critcount[$type];

                $typecount->count += 1;
                if($crit->criteriatype == COMPLETION_CRITERIA_TYPE_ACTIVITY) {
                    // get the cm data object
                    $cm = $this->modinfo->get_cm($crit->moduleinstance);
                    // retrieve data for this object
                    $data = $this->completion->get_data($cm, false, $userid);
                    // Count complete, but failed as incomplete too...
                    if (($data->completionstate == COMPLETION_INCOMPLETE) || ($data->completionstate == COMPLETION_COMPLETE_FAIL)) {
                        $typecount->completed += 0;
                    } else {
                        $typecount->completed += 1;
                    }                
                }
                else {
                    if($completion->is_complete()){
                        $typecount->completed += 1;
                    }
                } 
            }
        }

        // Now that we have all completions sorted by type, we can be smart about how to do the count
        $count = 0;
        $completed = 0;
        $completion_percentage = 0;
        foreach($critcount as $c){
            // Take only types that are actually present into account
            if($c->count > 0){
                // If the aggregation for the type is ANY, reduce the count to 1 for this type
                // And adjust the progress accordingly (check if any have been completed or not)
                if($c->aggregation == COMPLETION_AGGREGATION_ALL){
                    $ct = $c->count;
                    $cmpl = $c->completed;
                } 
                else {
                    $ct = 1;
                    $cmpl = ($c->completed > 0)?1:0;
                }
                // if ANY completion for the types, count only the criteria type with the highest completion percentage -
                // Overwrite data if current type is more complete
                if($aggregation == COMPLETION_AGGREGATION_ANY) {
                    $pct = $cmpl/$ct;
                    if($pct > $completion_percentage){
                        $count = $ct;
                        $completed = $cmpl;
                        $completion_percentage = $pct;
                    }
                }
                // if ALL completion for the types, add the count for this type to that of the others
                else {
                    $count += $ct;
                    $completed += $cmpl;
                    // Don't really care about recalculating completion percentage every round in this case
                }
            }
        }

        $result = new \stdClass;
        $result->count = $count;
        $result->completed = $completed;
        $result->percentage = ($count > 0)?(($completed / $count) * 100):0;
        return $result; 
    }  



}