<?php

namespace local_treestudyplan\local\aggregators;

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

class bistate_aggregator extends \local_treestudyplan\aggregator {
    public const DEPRECATED = false;
    private const DEFAULT_CONDITION = "50";

    private $thresh_excellent = 1.0;            // Minimum fraction that must be completed to aggregate as excellent (usually 1.0)
    private $thresh_good = 0.8;                 // Minimum fraction that must be completed to aggregate as good
    private $thresh_completed = 0.66;           // Minimum fraction that must be completed to aggregate as completed
    private $use_failed = True;                 // Support failed completion yes/no
    private $thresh_progress = 0.33;              // Minimum fraction that must be failed to aggregate as failed instead of progress
    private $accept_pending_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(["thresh_excellent", "thresh_good", "thresh_completed","thresh_progress",] as $key){
            $val = intval(get_config('local_treestudyplan', "bistate_{$key}"));
            if($val >= 0 && $val <= 100)
            {
                $this->$key = floatval($val)/100;
            }
        }
        foreach(["use_failed", "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(["thresh_excellent", "thresh_good", "thresh_completed","thresh_progress",] as $key){
                if(array_key_exists($key,$config)){
                    $val = $config[$key];
                    if($val >= 0 && $val <= 100)
                    {
                        $this->$key = floatval($val)/100;
                    }
                } 
            }
            foreach(["use_failed", "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([
            "thresh_excellent" => 100*$this->thresh_excellent,
            "thresh_good" => 100*$this->thresh_good,
            "thresh_completed" => 100*$this->thresh_completed,
            "thresh_progress" => 100*$this->thresh_progress,
            "use_failed" => $this->use_failed,
            "accept_pending_as_submitted" => $this->accept_pending_as_submitted,
        ]);
    }

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


    public function aggregate_binary_goals(array $completions, array $required = []){
        // function is public to allow access for the testing code

        // return te following conditions
        // Possible states:
        // - completion::EXCELLENT  - At least $thresh_excellent fraction of goals are complete and all required goals are met
        // - completion::GOOD       - At least $thresh_good fraction of goals are complete and all required goals are met
        // - completion::COMPLETED  - At least $thresh_complete fraction of goals are completed and all required goals are met
        // - completion::FAILED     - At least $thresh_progress fraction of goals is not failed
        // - completion::INCOMPLETE - No goals have been started
        // - completion::PROGRESS   - All other states


        $total = count($completions);
        $completed = 0;
        $progress = 0;
        $failed = 0;
        $started = 0;

        $total_required = 0;
        $required_met = 0;

        $MIN_PROGRESS = ($this->accept_pending_as_submitted)?completion::PENDING:completion::PROGRESS;

        foreach($completions as $index => $c) {

            $completed += ($c >= completion::COMPLETED)?1:0;
            $progress  += ($c >= $MIN_PROGRESS)?1:0;
            $failed    += ($c <= completion::FAILED)?1:0;
        }
        $started = $progress + $failed;
        $allrequiredmet = ($required_met >= $total_required);

        $fraction_completed = ($total >0)?(floatval($completed)/floatval($total)):0.0;
        $fraction_progress  = ($total >0)?(floatval($progress)/floatval($total)):0.0;
        $fraction_failed    = ($total >0)?(floatval($failed)/floatval($total)):0.0;
        $fraction_started   = ($total >0)?(floatval($started)/floatval($total)):0.0;

        if($total == 0){
            return completion::INCOMPLETE;
        }
        if($fraction_completed >= $this->thresh_excellent && $allrequiredmet){
            return completion::EXCELLENT;
        }
        else if($fraction_completed >= $this->thresh_good && $allrequiredmet){
            return completion::GOOD;
        }
        else if($fraction_completed >= $this->thresh_completed && $allrequiredmet){
            return completion::COMPLETED;
        }
        else if($started == 0){
            return completion::INCOMPLETE;
        }
        else if($this->use_failed && ($fraction_failed >= $this->thresh_progress)){
            return completion::FAILED;
        }
        else {
            return completion::PROGRESS;
        }
    }

    public function aggregate_course(courseinfo $courseinfo, studyitem $studyitem, $userid){
        // Note: studyitem condition config is not used in this aggregator.
        // loop through all associated gradables and count the totals, completed, etc..
        $completions = [];
        $required = [];
        foreach(gradeinfo::list_studyitem_gradables($studyitem) as $gi){
            $completions[] = $this->grade_completion($gi,$userid);
            if($gi->is_required()){
                // if it's a required grade 
                // also add it's index in the completion list to the list of required grades 
                $required[] = count($completions) - 1;
            }
        }

        // Combine the aquired completions into one
        return self::aggregate_binary_goals($completions,$required);

    }

    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;
        }
    }

    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;
                }
            }
        }
    }


}