<?php
// This file is part of mod_offlinequiz for Moodle - http://moodle.org/
//
// 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 <http://www.gnu.org/licenses/>.

/**
 * The results import report for offlinequizzes
 *
 * @package       offlinequiz_rimport
 * @subpackage    offlinequiz
 * @author        Thomas Wedekind
 * @copyright     2017 Academic Moodle Cooperation {@link http://www.academic-moodle-cooperation.org}
 * @since         Moodle 3.4
 * @license       http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 *
 */
namespace offlinequiz_result_import;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->dirroot . '/mod/offlinequiz/report/rimport/page.php');
require_once($CFG->dirroot . '/mod/offlinequiz/locallib.php');
define('RESULT_STATUS_ERROR', 'error');
define('RESULT_STATUS_RESULT_ALREADY_EXISTS_FOR_OTHER_GRUOP', 'resultfordifferentgroups');
define('RESULT_STATUS_RESULT_ALREADY_EXISTS_WITH_SAME_CROSSES', 'sameresultexists');
define('RESULT_STATUS_RESULT_ALREADY_EXISTS_WITH_OTHER_CROSSES', 'otherresultexists');
/**
 * saves results in the database
 *
 * @package       offlinequiz_rimport
 * @subpackage    offlinequiz
 * @author        Thomas Wedekind
 * @copyright     2019 Academic Moodle Cooperation {@link http://www.academic-moodle-cooperation.org}
 * @since         Moodle 3.7
 * @license       http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 *
 **/
class offlinequiz_resultsaver {
    /**
     * result with the same scannedpageid
     * @var bool
     */
    private $resultwithsame;

    /**
     * inserts a result in the offlinequiz db
     * @param mixed $scannedpageid
     * @param mixed $teacherid
     * @throws \coding_exception
     * @return void
     */
    public function create_or_update_result_in_db($scannedpageid, $teacherid) {

        global $DB;
        $sql = "SELECT p2.* FROM {offlinequiz_scanned_pages} p1, {offlinequiz_scanned_pages} p2
                WHERE p1.id = :scannedpageid
                AND   p1.offlinequizid = p2.offlinequizid
                AND   p1.userkey = p2.userkey
                AND   (p1.status = 'ok' OR p1.status = 'submitted') ";
        $scannedpages = $DB->get_records_sql($sql, ['scannedpageid' => $scannedpageid]);
        // TO DO check, if page with same page and userky, but other scannedpageid exists.

        if (!$scannedpages || !$scannedpages[$scannedpageid]) {
            throw new \coding_exception('A scannedpage can not be updated if it has errors');
        }
        $resulterror = $this->get_result_exists_errors($scannedpageid);
        if ($resulterror) {
            self::save_page_status($scannedpageid, 'error', $resulterror);
        }
        if ($this->resultwithsame) {
            $groupnumber = 0;
        }
        foreach ($scannedpages as $scannedpage) {
            if (!$groupnumber) {
                $groupnumber = $scannedpage->groupnumber;
            } else if ($groupnumber != $scannedpage->groupnumber) {
                self::save_page_status($scannedpageid, RESULT_STATUS_ERROR, RESULT_STATUS_RESULT_ALREADY_EXISTS_FOR_OTHER_GRUOP);
            }
        }
        // TO DO what happens with differend group versions?
        $scannedpage = $scannedpages[$scannedpageid];

        $conditions = ['groupnumber' => $scannedpage->groupnumber, 'offlinequizid' => $scannedpage->offlinequizid];
        $group = $DB->get_record('offlinequiz_groups', $conditions);

        $scannedpageids = array_keys($scannedpages);
        [$scannedpagesql, $params] = $DB->get_in_or_equal($scannedpageids, SQL_PARAMS_QM, 'pages');
        $sql = "SELECT * FROM {offlinequiz_choices} choice
                WHERE  scannedpageid" . $scannedpagesql;

        $resultid = self::get_result_id($scannedpages);
        if ($resultid) {
            $result = $DB->get_record('offlinequiz_results', ['id' => $resultid]);
            $quba = \question_engine::load_questions_usage_by_activity($result->usageid);
        } else {
            $result = new \stdClass();
            $result->offlinequizid = $scannedpage->offlinequizid;
            $result->userid = self::get_userid_by_userkey($scannedpage->userkey);
            // To do clone stuff.
            $quba = $this->clone_template_usage($scannedpage->offlinequizid, $group->id);
            $result->usageid = $quba->id;
            $result->status = 'partial';
            $result->teacherid = $teacherid;
            $result->timestart = time();
            $result->timemodified = $result->timestart;
            $result->id = $DB->insert_record('offlinequiz_results', $result);
            $scannedpage->resultid = $result->id;
            $DB->set_field('offlinequiz_scanned_pages', 'resultid', $result->id, ['id' => $scannedpageid]);
        }
        foreach ($scannedpages as $scannedpage) {
            $this->submit_scanned_page_to_result($quba, $scannedpage);
        }
        \question_engine::save_questions_usage_by_activity($quba);
    }
    /**
     * get if result exists errors
     * @param mixed $scannedpageid
     * @return string
     */
    private function get_result_exists_errors($scannedpageid) {
        global $DB;
        $sql = "SELECT page2.*
                    FROM {offlinequiz_scanned_pages} page1,
                         {offlinequiz_scanned_pages} page2
                    WHERE page1.id = :scannedpageid
                    AND   page1.pagenumber = page2.pagenumber
                    AND   page1.userkey = page2.userkey
                    AND   page2.resultid IS NOT NULL
                    AND   page1.id <> page2.id
                    AND   page1.offlinequizid = page2.offlinequizid";
        $otherresults = $DB->get_records_sql($sql, ['scannedpageid' => $scannedpageid]);
        if (!$otherresults) {
            return '';
        }
        if ($this->results_have_same_crosses($scannedpageid, $otherresults[0]->id)) {
            return RESULT_STATUS_RESULT_ALREADY_EXISTS_WITH_SAME_CROSSES;
        }
        return '';
    }

    /**
     * clone the template usage
     * @param mixed $offlinequizid
     * @param mixed $groupid
     */
    private function clone_template_usage($offlinequizid, $groupid) {
        global $DB;
        $group = $DB->get_record('offlinequiz_groups', ['id' => $groupid]);
        // There is no result. First we have to clone the template question usage of the offline group.
        // We have to use our own loading function in order to get the right class.
        $templateusage = offlinequiz_load_questions_usage_by_activity($group->templateusageid);

        // Get the question instances for initial maxmarks.
        $sql = "SELECT questionid, maxmark
                      FROM {offlinequiz_group_questions}
                     WHERE offlinequizid = :offlinequizid
                       AND offlinegroupid = :offlinegroupid";

        $qinstances = $DB->get_records_sql(
            $sql,
            ['offlinequizid' => $offlinequizid,
            'offlinegroupid' => $groupid]
        );

        // Clone the templateusage.
        $quba = $templateusage->get_clone($qinstances);

        // And save it. The clone contains the same question in the same order and the same order of the answers.
        \question_engine::save_questions_usage_by_activity($quba);
        return $quba;
    }
    /**
     * check if 2 results have the same choices
     * @param mixed $scannedpageid1
     * @param mixed $scannedpageid2
     * @return int
     */
    private function results_have_same_crosses($scannedpageid1, $scannedpageid2) {

        global $DB;
        $sql = "SELECT 1
                    FROM   {offlinequiz_choices} c1,
                            {offlinequiz_choices} c2
                    WHERE  c1.scannedpageid = :scannedpageid1
                            AND c2.scannedpageid = :scannedpageid2
                            AND c1.slotnumber = c2.slotnumber
                            AND c1.choicenumber = c2.choicenumber
                            AND c1.value <> c2.value";
        return $DB->count_records_sql($sql, ['scannedpageid1' => $scannedpageid1, 'scannedpageid2' => $scannedpageid2]);
    }

    /**
     *
     * We assume that choicesdata does not contain any insecure values (-1) anymore.
     *
     * @param mixed $quba
     * @param mixed $scannedpage
     */
    private function submit_scanned_page_to_result($quba, $scannedpage) {
        global $DB;

        $offlinequizconfig = get_config('offlinequiz');

        $result = $DB->get_record('offlinequiz_results', ['id' => $scannedpage->resultid]);
        $quba = \question_engine::load_questions_usage_by_activity($result->usageid);
        $slots = $quba->get_slots();
        $choicesdata = $this->get_choices_data($scannedpage);
        // To do: check if this works.
        for ($slotindex = array_key_first($slots); $slotindex < array_key_last($slots); $slotindex++) {
            $slot = $slots[$slotindex];
            $slotquestion = $quba->get_question($slot);
            $attempt = $quba->get_question_attempt($slot);
            $order = $slotquestion->get_order($attempt);  // Order of the answers.

            $count = 0;
            $response = [];

            // Go through all answers of the slot question.
            foreach ($order as $key => $notused) {
                // Check what the scanner recognised.
                if ($choicesdata[$slot][$key]->value == 1) {
                    // Also fill the response array s.t. we can grade later if possible.
                    if ($slotquestion instanceof qtype_multichoice_single_question) {
                        $response['answer'] = $key;
                        // In case of singlechoice we count the crosses.
                        // If more than 1 cross have been made, we don't submit the response.
                        $count++;
                    } else if ($slotquestion instanceof qtype_multichoice_multi_question) {
                        $response['choice' . $key] = 1;
                    }
                } else if ($choicesdata[$slot][$key]->value == 0) {
                    if ($slotquestion instanceof qtype_multichoice_multi_question) {
                        $response['choice' . $key] = 0;
                    }
                }
            }

            // We can submit the response and finish the attempt for this question.
            if ($slotquestion instanceof qtype_multichoice_single_question) {
                // We only submit the response of at most 1 cross has been made.
                if ($count <= 1) {
                    $quba->process_action($slot, $response);
                    $quba->finish_question($slot, time());
                }
            } else if ($slotquestion instanceof qtype_multichoice_multi_question) {
                $quba->process_action($slot, $response);
                $quba->finish_question($slot, time());
            }
        } // End of for slotindex...

        $scannedpage->status = 'submitted';
        $scannedpage->error = 'missingpages';
        $scannedpage->time = time();

        $DB->update_record('offlinequiz_scanned_pages', $scannedpage);

        return $scannedpage;
    }


    /**
     * Get the result ID if a set of scannedpages
     * @param mixed $scannedpages
     */
    private static function get_result_id($scannedpages) {
        foreach ($scannedpages as $currentpage) {
            if (!empty($currentpage->resultid)) {
                return $currentpage->resultid;
            }
        }
        return 0;
    }
    /**
     * Get the userid via userkey
     * @param mixed $userkey
     */
    private static function get_userid_by_userkey($userkey) {
        global $DB;
        $offlinequizconfig = get_config('offlinequiz');
        // TO DO prefix and suffix.
        return $DB->get_field('user', 'id', [$offlinequizconfig->ID_field => $userkey]);
    }
    /**
     * save the page status to the database
     * @param mixed $scannedpageid
     * @param mixed $status
     * @param mixed $error
     * @return void
     */
    private static function save_page_status($scannedpageid, $status, $error) {
        global $DB;
        $sql = "UPDATE {offlinequiz_scanned_pages} SET status = :status, error = :error
                    WHERE id=:scannedpageid";
        $params = ['scannedpageid' => $scannedpageid, 'status' => $status, 'error' => $error];
        $DB->execute($sql, $params);
    }
}
