<?php
// This file is part of 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/>.

/**
 * @package    moodlecore
 * @subpackage backup-moodle2
 * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */


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

require_once($CFG->dirroot . '/question/type/multianswer/questiontype.php');
/**
 * restore plugin class that provides the necessary information
 * needed to restore one multianswer qtype plugin
 *
 * @copyright  2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class restore_qtype_multianswer_plugin extends restore_qtype_plugin {

    /**
     * Returns the paths to be handled by the plugin at question level
     */
    protected function define_question_plugin_structure() {
        $paths = array();

        // This qtype uses question_answers, add them.
        $this->add_question_question_answers($paths);

        // Add own qtype stuff.
        $elename = 'multianswer';
        $elepath = $this->get_pathfor('/multianswer');
        $paths[] = new restore_path_element($elename, $elepath);

        return $paths; // And we return the interesting paths.
    }

    /**
     * Process the qtype/multianswer element
     */
    public function process_multianswer($data) {
        global $DB;

        $data = (object)$data;
        $oldid = $data->id;

        // Detect if the question is created or mapped.
        $oldquestionid   = $this->get_old_parentid('question');
        $newquestionid   = $this->get_new_parentid('question');
        $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;

        // If the question has been created by restore, we need to create its
        // question_multianswer too.
        if ($questioncreated) {
            // Adjust some columns.
            $data->question = $newquestionid;
            // Note: multianswer->sequence is a list of question->id values. We aren't
            // recoding them here (because some questions can be missing yet). Instead
            // we'll perform the recode in the {@link after_execute} method of the plugin
            // that gets executed once all questions have been created.
            // Insert record.
            $newitemid = $DB->insert_record('question_multianswer', $data);
            // Create mapping (need it for after_execute recode of sequence).
            $this->set_mapping('question_multianswer', $oldid, $newitemid);
        }
    }

    /**
     * This method is executed once the whole restore_structure_step
     * this step is part of ({@link restore_create_categories_and_questions})
     * has ended processing the whole xml structure. Its name is:
     * "after_execute_" + connectionpoint ("question")
     *
     * For multianswer qtype we use it to restore the sequence column,
     * containing one list of question ids
     */
    public function after_execute_question() {
        global $DB;
        // Now that all the questions have been restored, let's process
        // the created question_multianswer sequences (list of question ids).
        $rs = $DB->get_recordset_sql("
                SELECT qma.id, qma.sequence
                  FROM {question_multianswer} qma
                  JOIN {backup_ids_temp} bi ON bi.newitemid = qma.question
                 WHERE bi.backupid = ?
                   AND bi.itemname = 'question_created'",
                array($this->get_restoreid()));
        foreach ($rs as $rec) {
            $sequencearr = preg_split('/,/', $rec->sequence, -1, PREG_SPLIT_NO_EMPTY);
            if (substr_count($rec->sequence, ',') + 1 != count($sequencearr)) {
                $this->task->log('Invalid sequence found in restored multianswer question ' . $rec->id, backup::LOG_WARNING);
            }

            foreach ($sequencearr as $key => $question) {
                $sequencearr[$key] = $this->get_mappingid('question', $question);
            }
            $sequence = implode(',', array_filter($sequencearr));
            $DB->set_field('question_multianswer', 'sequence', $sequence,
                    array('id' => $rec->id));
            if (!empty($sequence)) {
                // Get relevant data indexed by positionkey from the multianswers table.
                $wrappedquestions = $DB->get_records_list('question', 'id',
                    explode(',', $sequence), 'id ASC');
                foreach ($wrappedquestions as $wrapped) {
                    if ($wrapped->qtype == 'multichoice') {
                        question_bank::get_qtype($wrapped->qtype)->get_question_options($wrapped);
                        if (isset($wrapped->options->shuffleanswers)) {
                            preg_match('/'.ANSWER_REGEX.'/s', $wrapped->questiontext, $answerregs);
                            if (isset($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE]) &&
                                    $answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE] !== '') {
                                $wrapped->options->shuffleanswers = 0;
                                $DB->set_field_select('qtype_multichoice_options', 'shuffleanswers', '0', "id =:select",
                                    array('select' => $wrapped->options->id) );
                            }
                        }
                    }
                }
            }
        }
        $rs->close();
    }

    public function recode_response($questionid, $sequencenumber, array $response) {
        global $DB;

        $qtypes = $DB->get_records_menu('question', array('parent' => $questionid),
                '', 'id, qtype');

        $sequence = $DB->get_field('question_multianswer', 'sequence',
                array('question' => $questionid));

        $fakestep = new question_attempt_step_read_only($response);

        foreach (explode(',', $sequence) as $key => $subqid) {
            $i = $key + 1;

            $substep = new question_attempt_step_subquestion_adapter($fakestep, 'sub' . $i . '_');
            $recodedresponse = $this->step->questions_recode_response_data($qtypes[$subqid],
                    $subqid, $sequencenumber, $substep->get_all_data());

            foreach ($recodedresponse as $name => $value) {
                $response[$substep->add_prefix($name)] = $value;
            }
        }

        return $response;
    }

    /**
     * Given one question_states record, return the answer
     * recoded pointing to all the restored stuff for multianswer questions
     *
     * answer is one comma separated list of hypen separated pairs
     * containing sequence (pointing to questions sequence in question_multianswer)
     * and mixed answers. We'll delegate
     * the recoding of answers to the proper qtype
     */
    public function recode_legacy_state_answer($state) {
        global $DB;
        $answer = $state->answer;
        $resultarr = array();
        // Get sequence of questions.
        $sequence = $DB->get_field('question_multianswer', 'sequence',
                array('question' => $state->question));
        $sequencearr = explode(',', $sequence);
        // Let's process each pair.
        foreach (explode(',', $answer) as $pair) {
            $pairarr = explode('-', $pair);
            $sequenceid = $pairarr[0];
            $subanswer = $pairarr[1];
            // Calculate the questionid based on sequenceid.
            // Note it is already one *new* questionid that doesn't need mapping.
            $questionid = $sequencearr[$sequenceid - 1];
            // Fetch qtype of the question (needed for delegation).
            $questionqtype = $DB->get_field('question', 'qtype', array('id' => $questionid));
            // Delegate subanswer recode to proper qtype, faking one question_states record.
            $substate = new stdClass();
            $substate->question = $questionid;
            $substate->answer = $subanswer;
            $newanswer = $this->step->restore_recode_legacy_answer($substate, $questionqtype);
            $resultarr[] = implode('-', array($sequenceid, $newanswer));
        }
        return implode(',', $resultarr);
    }

    #[\Override]
    public function define_excluded_identity_hash_fields(): array {
        return [
            '/options/sequence',
            '/options/question',
        ];
    }

    #[\Override]
    public static function remove_excluded_question_data(stdClass $questiondata, array $excludefields = []): stdClass {
        if (isset($questiondata->options->questions)) {
            unset($questiondata->options->questions);
        }
        return parent::remove_excluded_question_data($questiondata, $excludefields);
    }
}
