<?php
require_once('lti.php');
require_once('service_base.php');
require_once('lineitem.php');
require_once('lineitems.php');
require_once('results.php');
require_once('scores.php');
class gradebookservices extends service_base
{
    /** Read-only access to Gradebook services */
    const GRADEBOOKSERVICES_READ = 1;
    /** Full access to Gradebook services */
    const GRADEBOOKSERVICES_FULL = 2;
    /** Scope for full access to Lineitem service */
    const SCOPE_GRADEBOOKSERVICES_LINEITEM = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem';
    /** Scope for full access to Lineitem service */
    const SCOPE_GRADEBOOKSERVICES_LINEITEM_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly';
    /** Scope for access to Result service */
    const SCOPE_GRADEBOOKSERVICES_RESULT_READ = 'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly';
    /** Scope for access to Score service */
    const SCOPE_GRADEBOOKSERVICES_SCORE = 'https://purl.imsglobal.org/spec/lti-ags/scope/score';


    /**
     * Class constructor.
     */
    public function __construct()
    {
        parent::__construct();
        $this->id = 'gradebookservices';
        $this->name = 'ltiservice_gradebookservices';

    }

    /**
     * Get the resources for this service.
     *
     * @return resource_base[]
     */
    public function get_resources()
    {
        // The containers should be ordered in the array after their elements.
        // Lineitems should be after lineitem.
        if (empty($this->resources)) {
            $this->resources = array();
            $this->resources[] = new lineitem($this);
            $this->resources[] = new lineitems($this);
            $this->resources[] = new results($this);
            $this->resources[] = new scores($this);
        }
        return $this->resources;
    }

    /**
     * Fetch a lineitem instance.
     *
     * Returns the lineitem instance if found, otherwise false.
     *
     * @param string $courseid ID of course
     * @param string $itemid ID of lineitem
     * @param string $typeid
     *
     */
    public function get_lineitem($offering_id, $contentdtl_id, $grade_item_id, $typeid)
    {
        $lti = new LTI();

        $result = array();
        $grade_items = new stdClass();
        //Fetch grade_item
        $grade_items_record = $lti->get_grade_item($grade_item_id);
        if ($result = $grade_items_record->fetch(PDO::FETCH_ASSOC)) {
            $grade_items->grade_items_id = $result['grade_items_id'];
            $grade_items->name = $result['name'];
            $grade_items->lti_id = $result['lti_id'];
            $grade_items->grade_type = $result['grade_type'];
            $grade_items->grade_max = $result['grade_max'];
            $grade_items->grade_min = $result['grade_min'];
            $grade_items->grade_pass = $result['grade_pass'];
        }

        if ($grade_items) {
            // We will need to check if the activity related belongs to our tool proxy.
            $ltiactivity = new stdClass();
            $lti_class_record = $lti->get_lti_by_grade_item($grade_items->lti_id);
            if ($result = $lti_class_record->fetch(PDO::FETCH_ASSOC)) {
                $ltiactivity->lti_id = $result['lti_id'];
                $ltiactivity->name = $result['name'];
                $ltiactivity->lti_types_id = $result['lti_types_id'];
                $ltiactivity->tool_url = $result['tool_url'];
                $ltiactivity->text = $result['text'];
                $ltiactivity->launch_container = $result['launch_container'];
                $ltiactivity->custom_id = $result['custom_id'];
                $ltiactivity->grade_max = $result['grade_max'];
            }

            if (($ltiactivity) && (isset($ltiactivity->lti_types_id))) {
                if ($ltiactivity->lti_types_id != 0) {
                    //Fetch lti_types
                    $tool = new stdClass();
                    $stmt = $lti->get_lti_types($ltiactivity->lti_types_id);
                    if ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
                        $tool->lti_typename = $result['tool_name'];
                        $tool->typeid = $result['lti_types_id'];
                        $tool->lti_toolurl = $result['tool_url'];
                        $tool->lti_ltiversion = $result['lti_version'];
                        $tool->lti_clientid = $result['client_id'];
                        $tool->toolproxyid = $result['tool_proxy_id'];
                        $tool->lti_parameters = $result['parameter'];
                    }
                }
                if (is_null($typeid)) {
                    if (!(($tool) && ($this->get_tool_proxy()->id == $tool->toolproxyid))) {
                        return false;
                    }
                } else {
                    if (!(($tool) && ($tool->typeid == $typeid))) {
                        return false;
                    }
                }
            } else {
                return false;
            }
        }
        return $grade_items;
    }

    /**
     * Saves a score received from the LTI tool.
     *
     * @param object $gradeitem Grade Item record
     * @param object $score Result object
     * @param int $userid User ID
     *
     * @throws \Exception
     */
    public function save_grade_item($gradeitem, $score, $userid, $offering_id, $contentdtl_id, $date_time)
    {
        $lti = new LTI();
        $user_exists = $lti->check_user($userid);
        if (!$user_exists) {
            throw new \Exception(null, 400);
        }

        $finalgrade = null;
        $last_updated_ts = null;
        $file_name = "LTIService_log.txt";
        $fp = fopen($file_name, "a");
        if (isset($score->scoreGiven)) {
            $scr = ($score->scoreGiven * 100) /  $score->scoreMaximum;
            $finalgrade = $this->grade_floatval($scr);
            $finalgrade_actual = $this->grade_floatval($score->scoreGiven);
            // $max = 1;
            // if (isset($score->scoreMaximum)) {
            //     $max = $score->scoreMaximum;
            // }
            // Tip: Implement in next version
            // if (!is_null($max) && $this->grade_floats_different($max, $gradeitem->grademax) && $this->grade_floats_different($max, 0.0)) {
            //     // Rescale to match the grade item maximum.
            //    $finalgrade = $this->grade_floatval($finalgrade * $gradeitem->grademax / $max);
            // }
            if (isset($score->timestamp)) {
                $last_updated_ts = $score->timestamp;
            } else {
                $last_updated_ts = $date_time;
            }
        }

        $grade = new \stdClass();
        $grade->user_id = $userid;
        $grade->grade_items_id = $gradeitem->grade_items_id;
        $grade->raw_grade_max = $score->scoreMaximum;
        $grade->timemodified = $last_updated_ts;
        $grade->raw_grade = $finalgrade;
        $grade->raw_grade_actual = $finalgrade_actual;
        $grade->offering_id = $offering_id;
        $grade->raw_grade_min = 0;

        $final_result = true;
        $grade_grades_data = $lti->get_grade_grades($gradeitem->grade_items_id, $offering_id, $userid);
        if ($grade_grades_data->rowCount() > 0) {
            $final_result = $lti->update_user_grade($grade, $contentdtl_id, $gradeitem->grade_pass);
        } else {
            $final_result = $lti->insert_user_grade($grade, $contentdtl_id, $gradeitem->grade_pass);
        }

        if (!$final_result) {
            fwrite($fp, "Error: For user: " . $userid . ", offering: " . $offering_id . ", score: " . $score->scoreMaximum . " not updated.\n");
            throw new \Exception(null, 500);
        } else {
            //Result criteria check
            $results = array();
            $tmp_score = 0;
            $batch_user_id = 0;
            //Code similar to cal_result.php      
            //First - Assessment
            $result1 = $lti->get_assessment_criteria($offering_id);
            if ($result1->rowCount() > 0) {
                if ($row1 = $result1->fetch(PDO::FETCH_ASSOC)) {
                    $f_id = $row1['final_assmt_id'];
                    $wtg = $row1['weightage'];
                    $result2 = $lti->get_assessment_attempt($offering_id, $userid, $f_id);
                    if ($result2->rowCount() > 0) {
                        if ($row2 = $result2->fetch(PDO::FETCH_ASSOC)) {
                            $last_attm = $row2['attempt_no'];
                            $tmp_score = $wtg * $row2['score'];
                            $result3 = $lti->get_offering_passing_score($offering_id);
                            if ($row3 = $result3->fetch(PDO::FETCH_ASSOC)) {
                                $p_scr = $row3['passing_score'];
                            }
                            if ($row2['score'] >= $p_scr) {
                                array_push($results, "passed");
                            } else {
                                array_push($results, "failed");
                                $result4 = $lti->get_user_offering_max_attempt($offering_id);
                                if ($row4 = $result4->fetch(PDO::FETCH_ASSOC)) {
                                    if ($last_attm >= $row4['max_attempts']) {
                                        $incomplete = 1;
                                    }
                                }
                            }
                        }
                    } else {
                        array_push($results, "not attempted");
                    }
                }
            }
            //Second - Scorm
            $result5 = $lti->get_scorm_criteria($offering_id);
            if ($result5->rowCount() > 0) {
                while ($row5 = $result5->fetch(PDO::FETCH_ASSOC)) {
                    $result6 = $lti->get_user_scorm_status($offering_id, $userid, $row5['scorm_id']);
                    if ($result6->rowCount() > 0) {
                        if ($row6 = $result6->fetch(PDO::FETCH_ASSOC)) {
                            array_push($results, $row6['value']);
                        }
                    } else {
                        array_push($results, "not attempted");
                    }

                    $result7 = $lti->get_user_scorm_score($offering_id, $userid, $row5['scorm_id']);
                    if ($result7->rowCount() > 0) {
                        if ($row7 = $result7->fetch(PDO::FETCH_ASSOC)) {
                            $tmp_score = $tmp_score + ($row5['weightage'] * $row7['value']);
                        }
                    }
                }
            }
            //Third - xapi
            $result_xapi = $lti->get_xapi_criteria($offering_id);
            if ($result_xapi->rowCount() > 0) {
                while ($row_xapi = $result_xapi->fetch(PDO::FETCH_ASSOC)) {
                    $result6 = $lti->get_user_xapi_status($offering_id, $userid, $row_xapi['xapi_id']);
                    if ($result6->rowCount() > 0) {
                        if ($row6 = $result6->fetch(PDO::FETCH_ASSOC)) {
                            if ($row6['status'] == 'passed' || $row6['status'] == 'failed' || $row6['status'] == 'completed' || $row6['status'] == 'incomplete') {
                                array_push($results, $row6['status']);
                            } else {
                                array_push($results, "not attempted");
                            }
                        }
                    } else {
                        array_push($results, "not attempted");
                    }

                    $result7 = $lti->get_user_xapi_score($offering_id, $userid, $row_xapi['xapi_id']);
                    if ($result7->rowCount() > 0) {
                        if ($row7 = $result7->fetch(PDO::FETCH_ASSOC)) {
                            $tmp_score = $tmp_score + ($row_xapi['weightage'] * $row7['actual_score']);
                        }
                    }
                }
            }
            //Fourth - LTI
            $result_lti = $lti->get_lti_criteria($offering_id);
            if ($result_lti->rowCount() > 0) {
                while ($row_lti = $result_lti->fetch(PDO::FETCH_ASSOC)) {
                    $result6 = $lti->get_user_lti_score($offering_id, $userid, $row_lti['lti_id']);
                    if ($result6->rowCount() > 0) {
                        if ($row6 = $result6->fetch(PDO::FETCH_ASSOC)) {
                            if ($row6['raw_grade'] >= $row6['grade_pass']) {
                                array_push($results, "completed");
                            } else {
                                array_push($results, "incomplete");
                            }
                            $tmp_score = $tmp_score + ($row_lti['weightage'] * $row6['raw_grade']);
                        }
                    } else {
                        array_push($results, "not attempted");
                    }
                }
            }
            //Fifth update result
            $final_score = $tmp_score / 100;

            $course_assignemnt = new stdClass();
            $course_assignemnt->offering_id = $offering_id;
            $course_assignemnt->user_id = $userid;
            $course_assignemnt->score = $final_score;
            $course_assignemnt->date = $date_time;
            $course_assignemnt->last_updated_user = $batch_user_id;
            if (in_array('not attempted', $results) || in_array('failed', $results) || in_array('incomplete', $results)) {
                if ($incomplete == 1) {
                    $overall_result = "incomplete";
                    $course_assignemnt->result = $overall_result;
                } else {
                    $course_assignemnt->result = null;
                }
                $lti->update_user_offering_incomplete_or_null($course_assignemnt);
            } else if (in_array('passed', $results) || in_array('completed', $results)) {
                //Sixth - Calculate Completion Points
                $completion_points = null;
                $result9 = $lti->check_completion_point($offering_id);
                if ($result9->rowCount() > 0) {
                    $row9 = $result9->fetch(PDO::FETCH_ASSOC);
                    if ($row9['points'] != null) {
                        $result10 = $lti->get_completion_point($offering_id, $final_score);
                        if ($result10->rowCount() > 0) {
                            if ($row10 = $result10->fetch(PDO::FETCH_ASSOC)) {
                                $completion_points = $row10['completion_points'];
                            }
                        } else {
                            $result13 = $lti->get_completion_point_cutoff_null($offering_id);
                            if ($result13->rowCount() > 0) {
                                if ($row13 = $result13->fetch(PDO::FETCH_ASSOC)) {
                                    $completion_points = $row13['completion_points'];
                                }
                            }
                        }
                    } else {
                        $result11 = $lti->get_completion_point_offering($offering_id);
                        if ($result11->rowCount() > 0) {
                            if ($row11 = $result11->fetch(PDO::FETCH_ASSOC)) {
                                $completion_points = $row11['completion_points'];
                            }
                        }
                    }
                } else {
                    $result11 = $lti->get_completion_point_offering($offering_id);
                    if ($result11->rowCount() > 0) {
                        if ($row11 = $result11->fetch(PDO::FETCH_ASSOC)) {
                            $completion_points = $row11['completion_points'];
                        }
                    }
                }
                if ($completion_points == "") {
                    $completion_points = null;
                }

                $overall_result = "complete";
                $course_assignemnt->result = $overall_result;
                $course_assignemnt->completion_points = $completion_points;
                $result12 = $lti->update_user_offering_complete($course_assignemnt);
                if ($result12) {
                    $result14 = $lti->get_sum_of_completion_point($userid);
                    if ($result14->rowCount() > 0) {
                        $row14 = $result14->fetch(PDO::FETCH_ASSOC);                        
                        $lti->update_user_completion_point($userid, $row14['count'], $date_time, $batch_user_id);
                    }
                }
            }
            fwrite($fp, "For user: " . $userid . ", offering: " . $offering_id . ", score: " . $score->scoreMaximum . " updated.\n");
        }
        fclose($fp);
    }

    /**
     * Convert a number to 5 decimal point float, null db compatible format
     * (we need this to decide if db value changed)
     *
     * @param float|null $number The number to convert
     * @return float|null float or null
     */
    public function grade_floatval(?float $number)
    {
        if (is_null($number)) {
            return null;
        }
        // we must round to 2 digits to get the same precision as in 10,2 db fields
        // note: db rounding for 10,2 is different from php round() function
        return round($number, 2);
    }

    /**
     * Compare two float numbers safely. Uses 5 decimals php precision using {@link grade_floatval()}. Nulls accepted too.
     * Used for determining if a database update is required
     *
     * @param float|null $f1 Float one to compare
     * @param float|null $f2 Float two to compare
     * @return bool True if the supplied values are different
     */
    function grade_floats_different(?float $f1, ?float $f2): bool
    {
        // note: db rounding for 10,2 is different from php round() function
        return ($this->grade_floatval($f1) !== $this->grade_floatval($f2));
    }

    /**
     * Check if an LTI id is valid when we are in a LTI 1.x case
     *
     * @param string $linkid             The lti id
     * @param string  $course            The course
     * @param string  $typeid            The lti type id
     *
     * @return boolean
     */
    public static function check_lti_1x_id($linkid, $contentdtl_id, $typeid)
    {
        $lti = new LTI();
        // Check if lti type is zero or not (comes from a backup).
        $result = $lti->get_lti_by_contentdtl_id($linkid, $contentdtl_id);
        if ($row = $result->fetch(PDO::FETCH_ASSOC)) {
            $result2 = $lti->check_lti_and_lti_types($linkid, $contentdtl_id, $row['lti_types_id']);
            if ($result2->rowCount() > 0) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}