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

/**
 * Aggregate course results based on failed, completed, excellent states for grades
 * @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\gradeinfo;
use local_treestudyplan\studyitem;
use local_treestudyplan\completion;

/**
 * Aggregate course results based on failed, achieved, completed states for grades
 * @deprecated This aggregation style is no longer used, but included to support old study plans
 */
class tristate_aggregator extends \local_treestudyplan\aggregator {
    /** @var bool */
    public const DEPRECATED = true;
    /** @var string */
    private const DEFAULT_CONDITION = "50";

    /**
     * Determine if aggregation method wants to select gradables
     * @return bool True if aggregation method needs gradables to be selected
     */
    public function select_gradables() {
        return true;
    }
    /**
     * Determine if aggregation method is deprecated
     * @return bool True if aggregation method is deprecated
     */
    public function deprecated() {
        return self::DEPRECATED;
    }

    /**
     * Determine if the aggregation method uses manual activity selection,
     * @return bool True if the aggregation method uses manual activity selection
     */
    public function use_manualactivityselection() {
        return true;
    }

    /**
     * Aggregate completions into final result
     * @param int[] $a List of completion inputs
     * @param string $condition Condition description [ALL, 67, 50, ANY]
     * @return int Aggregated completion as completion class constant
     */
    protected function aggregate_completion(array $a, $condition = "50") {
        if (in_array(strtoupper($condition), ['ALL', '67', '50', 'ANY'])) {
            // Condition is one of the valid conditions.
            $ccompleted = 0;
            $cexcellent = 0;
            $cprogress = 0;
            $cpending = 0;
            $count = count($a);

            if ($count > 0) {
                foreach ($a as $c) {
                    $cprogress += ($c >= completion::PROGRESS) ? 1 : 0;
                    $ccompleted += ($c >= completion::COMPLETED) ? 1 : 0;
                    $cexcellent += ($c >= completion::EXCELLENT) ? 1 : 0;
                    $cpending += ($c >= completion::PENDING) ? 1 : 0;
                }

                $required = [
                    'ALL' => 1.00 * $count,
                    '67'  => 0.67 * $count,
                    '50'  => 0.50 * $count,
                    'ANY' => 1,
                    ][$condition];

                if ($cexcellent >= $required) {
                    return completion::EXCELLENT;
                } else if ($ccompleted >= $required) {
                    return completion::COMPLETED;
                } else {
                    /* Return PROGRESS if one or more completions are COMPLETED or EXCELLENT,
                       but the aggregation margin is not met.
                       State PROGRESS will not carry on if aggregations are chained.
                    */
                    if ($cprogress > 0) {
                        return completion::PROGRESS;
                    } else if ($cpending > 0) {
                        return completion::PENDING;
                    } else {
                        return completion::INCOMPLETE;
                    }
                }
            } else {
                return completion::INCOMPLETE;
            }
        } else {
            // Indeterminable.
            return completion::INCOMPLETE;
        }
    }

    /**
     * Aggregate all completions in a course into one final course completion
     * 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 courseinfo $courseinfo Courseinfo object for the course to check
     * @param studyitem $studyitem Studyitem object for the course to check
     * @param int $userid Id of user to check this course for
     * @return int Aggregated completion as completion class constant
     */
    public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid) {
        $condition = self::DEFAULT_CONDITION;

        $list = [];
        foreach (gradeinfo::list_studyitem_gradables($studyitem) as $gi) {
            $list[] = $this->grade_completion($gi, $userid);
        }
        $completion = self::aggregate_completion($list, $condition);
        return $completion;
    }

    /**
     * Aggregate juncton/filter inputs into one final junction outcome
     * @param int[] $completion List of completion inputs
     * @param studyitem $studyitem Studyitem object for the junction
     * @param int $userid Id of user to check completion for
     * @return int Aggregated completion as completion class constant
     */
    public function aggregate_junction(array $completion, studyitem $studyitem, $userid) {
        $completed = self::aggregate_completion($completion, $studyitem->conditions());
        // If null result (conditions are unknown/null) - default to ALL.
        return isset($completed) ? $completed : (self::aggregate_completion($completion, 'ALL'));
    }

    /**
     * Determine completion for a single grade and user
     * @param gradeinfo $gradeinfo Gradeinfo object for grade to check
     * @param mixed $userid Id of user to check completion for
     * @return int Aggregated completion as completion class constant
     */
    public function grade_completion(gradeinfo $gradeinfo, $userid) {
        $gradeitem = $gradeinfo->get_gradeitem();
        $grade = $gradeitem->get_final($userid);

        if (!is_object($grade) || 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->get_gradingscanner()->pending($userid)) {
                return completion::PENDING;
            } else {
                return completion::INCOMPLETE;
            }
        } else {
            $finalgrade = $grade->finalgrade;

            if ($gradeitem->gradepass > 0) {
                // Base completion off of gradepass (if set).
                if ($gradeitem->grademax > $gradeitem->gradepass && $finalgrade >= $gradeitem->grademax) {
                    // If gradepass is configured .
                    return completion::EXCELLENT;
                } else if ($finalgrade >= $gradeitem->gradepass) {
                    return completion::COMPLETED;
                } else {
                    return completion::PROGRESS;
                }
            } else {
                // Blind assumptions:.
                // Over 55% of range is completed.
                // Over 85% of range is excellent.
                $g = floatval($finalgrade - $gradeitem->grademin);
                $range = floatval($gradeitem->grademax - $gradeitem->grademin);
                $score = $g / $range;

                if ($score > 0.85) {
                    return completion::EXCELLENT;
                } else if ($score > 0.55) {
                    return completion::COMPLETED;
                } else {
                    return completion::PROGRESS;
                }
            }
        }
    }
}
