<?php
// This file is part of the Studyplan plugin for Moodle
//
// 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 <https://www.gnu.org/licenses/>.
/**
 *
 * @package    local_treestudyplan
 * @copyright  2023 P.M. Kuipers
 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */


namespace local_treestudyplan\local\aggregators;

use \local_treestudyplan\courseinfo;
use \local_treestudyplan\corecompletioninfo;
use \local_treestudyplan\gradeinfo;
use \local_treestudyplan\studyitem;
use \local_treestudyplan\completion;
use \local_treestudyplan\debug;

class core_aggregator extends \local_treestudyplan\aggregator {
    public const DEPRECATED = false;
    private $acceptpending_as_submitted = False;  // Also count ungraded but submitted .

    public function __construct($configstr) {
        // Allow public constructor for testing purposes.
        $this->initialize($configstr);
    }

    protected function initialize($configstr) {
        // First initialize with the defaults.
        foreach (["accept_pending_as_submitted"] as $key) {
            $this->$key = boolval(get_config('local_treestudyplan', "bistate_{$key}"));
        }

        // Next, decode json.
        $config = \json_decode($configstr, true);

        if (is_array($config)) {
            // Copy all valid config settings to this item.
            foreach (["accept_pending_as_submitted"] as $key) {
                if (array_key_exists($key, $config)) {
                    $this->$key = boolval($config[$key]);
                }
            }
        } else {

        }
    }

    // Return active configuration model.
    public function config_string() {
        return json_encode([
            "accept_pending_as_submitted" => $this->accept_pending_as_submitted,
        ]);
    }

    public function needSelectGradables() { return False;}
    public function isDeprecated() { return self::DEPRECATED;}
    public function useRequiredGrades() { return True;}
    public function useItemConditions() { return False;}
    public function usecorecompletioninfo() { return True; }

    /**
     * Return course completion information based on the core completion infromation
     * Possible states:
     *   completion::EXCELLENT  - Completed with excellent results
     *   completion::GOOD       - Completed with good results
     *   completion::COMPLETED  - Completed
     *   completion::PROGRESS   - Started, but not completed yey
     *   completion::FAILED     - Failed
     *   completion::INCOMPLETE - Not yet started
     *
     * @param  mixed $courseinfo
     * @param  mixed $studyitem
     * @param  mixed $userid
     * @return void
     */

    public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
         // Retrieve the core completion info from the core.
         $course = $courseinfo->course();
         $completion = new \completion_info($course);

         if ($completion->is_enabled() && $completion->is_tracked_user($userid)) {
            if ($completion->is_course_complete($userid)) {
                // Now, the trick is to determine what constitutes excellent and good completion....
                // TODO: Determine excellent and maybe good completion.
                // Option: Use course end grade to determine that...
                // Probably needs a config value in the aggregator....


                return completion::COMPLETED;
            } else {
                // Check if the course is over or not, if it is over, display failed.
                // Else, return PROGRESS.

                // Retrieve timing through courseinfo .
                $timing = courseinfo::coursetiming($course);

                // Not met and time is passed, means FAILED.
                if ($timing == "past") {
                    return completion::FAILED;
                } else {
                    // Check if any of the requirements are being met?.
                    $completions = $completion->get_completions($userid);
                    foreach ($completions as $c) {
                        if ($c->is_complete()) {
                            // If so, return progress.
                            return completion::PROGRESS;
                        }
                    }
                    return completion::INCOMPLETE;
                }
            }
        } else{
            return completion::INCOMPLETE;
        }
    }

    public function aggregate_junction(array $completion, studyitem $studyitem = null, $userid = 0) {
        // Aggregate multiple incoming states into one junction or finish.
        // Possible states:.
        // - completion::EXCELLENT  - All incoming states are excellent.
        // - completion::GOOD       - All incoming states are at least good.
        // - completion::COMPLETED  - All incoming states are at least completed.
        // - completion::FAILED     - All incoming states are failed.
        // - completion::INCOMPLETE - All incoming states are incomplete.
        // - completion::PROGRESS   - All other states.

        // First count all states.
        $statecount = completion::count_states($completion);
        $total = count($completion);

        if ( $total == $statecount[completion::EXCELLENT]) {
            return completion::EXCELLENT;
        } else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD]) {
            return completion::GOOD;
        } else if ( $total == $statecount[completion::EXCELLENT] + $statecount[completion::GOOD] + $statecount[completion::COMPLETED]) {
            return completion::COMPLETED;
        } else if (    $statecount[completion::FAILED]) {
            return completion::FAILED;
        } else if ( $total == $statecount[completion::INCOMPLETE]) {
            return completion::INCOMPLETE;
        } else {
            return completion::PROGRESS;
        }
    }


    // CORE COMPLETION DOESN'T REALLY USE THE FUNCTIONS BELOW.
    // AGGREGATORS ARE GOING TO BE DEPRECATED ANYWAY... but used in legacy parts of this plugin.

    public function grade_completion(gradeinfo $gradeinfo, $userid) {
        global $DB;
        $table = "local_treestudyplan_gradecfg";
        $gradeitem = $gradeinfo->getGradeitem();
        $grade = $gradeitem->get_final($userid);

        if (empty($grade)) {
            return completion::INCOMPLETE;
        } else if ($grade->finalgrade === NULL) {
            // On assignments, grade NULL means a submission has not yet been graded,.
            // But on quizes this can also mean a quiz might have been started.
            // Therefor, we treat a NULL result as a reason to check the relevant gradingscanner for presence of pending items.

            // Since we want old results to be visible until a pending item was graded, we only use this state here.
            // Pending items are otherwise expressly indicated by the "pendingsubmission" field in the user model.
            if ($gradeinfo->getGradingscanner()->pending($userid)) {
                return completion::PENDING;
            } else {
                return completion::INCOMPLETE;
            }
        } else {
            $grade = $gradeitem->get_final($userid);
            // First determine if we have a grade_config for this scale or this maximum grade.
            $finalgrade = $grade->finalgrade;
            $scale = $gradeinfo->getScale();
            if ( isset($scale)) {
                $gradecfg = $DB->get_record($table, ["scale_id"=>$scale->id]);
            } else if ($gradeitem->grademin == 0) {
                $gradecfg = $DB->get_record($table, ["grade_points"=>$gradeitem->grademax]);
            } else {
                $gradecfg = null;
            }

            // For point grades, a provided grade pass overrides the defaults in the gradeconfig.
            // For scales, the configuration in the gradeconfig is leading.

            if ($gradecfg && (isset($scale) || $gradeitem->gradepass == 0)) {
                // If so, we need to know if the grade is .
                if ($finalgrade >= $gradecfg->min_completed) {
                    // Return completed if completed.
                    return completion::COMPLETED;
                } else if ($this->use_failed && $finalgrade < $gradecfg->min_progress) {
                    // Return failed if failed is enabled and the grade is less than the minimum grade for progress.
                    return completion::FAILED;
                } else {
                    return completion::PROGRESS;
                }
            } else if ($gradeitem->gradepass > 0) {
                $range = floatval($gradeitem->grademax - $gradeitem->grademin);
                // If no gradeconfig and gradepass is set, use that one to determine config.
                if ($finalgrade >= $gradeitem->gradepass) {
                    return completion::COMPLETED;
                } else if ($this->use_failed && $gradeitem->gradepass >= 3  && $range >= 3 && $finalgrade == 1) {
                    // Return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
                    return completion::FAILED;
                } else {
                    return completion::PROGRESS;
                }
            } else {
                // Blind assumptions if nothing is provided.
                // Over 55% of range is completed.
                // If range >= 3 and failed is enabled, assume that this means failed.
                $g = floatval($finalgrade - $gradeitem->grademin);
                $range = floatval($gradeitem->grademax - $gradeitem->grademin);
                $score = $g / $range;

                if ($score > 0.55) {
                    return completion::COMPLETED;
                } else if ($this->use_failed && $range >= 3 && $finalgrade == 1) {
                    // Return failed if failed is enabled and the grade is 1, while there are at leas 3 states.
                    return completion::FAILED;
                } else {
                    return completion::PROGRESS;
                }
            }
        }
    }

}