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

/**
 * Upgrade library code for the randomsamatch question type.
 *
 * @package   qtype_randomsamatch
 * @copyright 2013 Jean-Michel Vedrine
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */


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


/**
 * Class for converting attempt data for randomsamatch questions when upgrading
 * attempts to the new question engine.
 *
 * This class is used by the code in question/engine/upgrade/upgradelib.php.
 *
 * @copyright 2013 Jean-Michel Vedrine
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class qtype_randomsamatch_qe2_attempt_updater extends question_qtype_attempt_updater {
    /** @var array of question stems. */
    protected $stems;
    /** @var array of question stems format. */
    protected $stemformat;
    /** @var array of choices that can be matched to each stem. */
    protected $choices;
    /** @var array index of the right choice for each stem. */
    protected $right;
    /** @var array id of the right answer for each stem (used by {@link lookup_choice}). */
    protected $rightanswerid;
    /** @var array shuffled stem indexes. */
    protected $stemorder;
    /** @var array shuffled choice indexes. */
    protected $choiceorder;
    /** @var array flipped version of the choiceorder array. */
    protected $flippedchoiceorder;
    /** @var array of right answer for each stem. */
    protected $rightanswer;

    public function question_summary() {
        return ''; // Done later, after we know which shortanswer questions are used.
    }

    public function right_answer() {
        return ''; // Done later, after we know which shortanswer questions are used.
    }

    /**
     * Explode the answer saved as a string in state
     *
     * @param string $answer comma separated list of dash separated pairs
     * @return array
     */
    protected function explode_answer($answer) {
        if (!$answer) {
            return array();
        }
        $bits = explode(',', $answer);
        $selections = array();
        foreach ($bits as $bit) {
            list($stem, $choice) = explode('-', $bit);
            $selections[$stem] = $choice;
        }
        return $selections;
    }

    protected function make_summary($pairs) {
        $bits = array();
        foreach ($pairs as $stem => $answer) {
            $bits[] = $stem . ' -> ' . $answer;
        }
        return implode('; ', $bits);
    }

    /**
     * Find the index corresponding to a choice
     *
     * @param integer $choice
     * @return integer
     */
    protected function lookup_choice($choice) {
        if (array_key_exists($choice, $this->choices)) {
            // Easy case: choice is a key in the choices array.
            return $choice;
        } else {
            // But choice can also be the id of a shortanser correct answer
            // without been a key of the choices array, in that case we need
            // to first find the shortanswer id, then find the choices index
            // associated to it.
            $questionid = array_search($choice, $this->rightanswerid);
            if ($questionid) {
                return $this->right[$questionid];
            }
        }
        return null;
    }

    public function response_summary($state) {
        $choices = $this->explode_answer($state->answer);
        if (empty($choices)) {
            return null;
        }

        $pairs = array();
        foreach ($choices as $stemid => $choicekey) {
            if (array_key_exists($stemid, $this->stems) && $choices[$stemid]) {
                $choiceid = $this->lookup_choice($choicekey);
                if ($choiceid) {
                    $pairs[$this->stems[$stemid]] = $this->choices[$choiceid];
                } else {
                    $this->logger->log_assumption("Dealing with a place where the
                            student selected a choice that was later deleted for
                            randomsamatch question {$this->question->id}");
                    $pairs[$this->stems[$stemid]] = '[CHOICE THAT WAS LATER DELETED]';
                }
            }
        }

        if ($pairs) {
            return $this->make_summary($pairs);
        } else {
            return '';
        }
    }

    public function was_answered($state) {
        $choices = $this->explode_answer($state->answer);
        foreach ($choices as $choice) {
            if ($choice) {
                return true;
            }
        }
        return false;
    }

    public function set_first_step_data_elements($state, &$data) {
        $this->stems = array();
        $this->stemformat = array();
        $this->choices = array();
        $this->right = array();
        $this->rightanswer = array();
        $choices = $this->explode_answer($state->answer);
        $this->stemorder = array();
        foreach ($choices as $key => $notused) {
            $this->stemorder[] = $key;
        }
        $wrappedquestions = array();
        // TODO test what happen when some questions are missing.
        foreach ($this->stemorder as $questionid) {
            $wrappedquestions[] = $this->load_question($questionid);
        }
        foreach ($wrappedquestions as $wrappedquestion) {

            // We only take into account the first correct answer.
            $foundcorrect = false;
            foreach ($wrappedquestion->options->answers as $answer) {
                if ($foundcorrect || $answer->fraction != 1.0) {
                    unset($wrappedquestion->options->answers[$answer->id]);
                } else if (!$foundcorrect) {
                    $foundcorrect = true;
                    // Store right answer id, so we can use it later in lookup_choice.
                    $this->rightanswerid[$wrappedquestion->id] = $answer->id;
                    $key = array_search($answer->answer, $this->choices);
                    if ($key === false) {
                        $key = $answer->id;
                        $this->choices[$key] = $answer->answer;
                        $data['_choice_' . $key] = $answer->answer;
                    }
                    $this->stems[$wrappedquestion->id] = $wrappedquestion->questiontext;
                    $this->stemformat[$wrappedquestion->id] = $wrappedquestion->questiontextformat;
                    $this->right[$wrappedquestion->id] = $key;
                    $this->rightanswer[$wrappedquestion->id] = $answer->answer;

                    $data['_stem_' . $wrappedquestion->id] = $wrappedquestion->questiontext;
                    $data['_stemformat_' . $wrappedquestion->id] = $wrappedquestion->questiontextformat;
                    $data['_right_' . $wrappedquestion->id] = $key;

                }
            }
        }
        $this->choiceorder = array_keys($this->choices);
        // We don't shuffle the choices as that seems unnecessary for old upgraded attempts.
        $this->flippedchoiceorder = array_combine(
                array_values($this->choiceorder), array_keys($this->choiceorder));

        $data['_stemorder'] = implode(',', $this->stemorder);
        $data['_choiceorder'] = implode(',', $this->choiceorder);

        $this->updater->qa->questionsummary = $this->to_text($this->question->questiontext) . ' {' .
                implode('; ', $this->stems) . '} -> {' . implode('; ', $this->choices) . '}';

        $answer = array();
        foreach ($this->stems as $key => $stem) {
            $answer[$stem] = $this->choices[$this->right[$key]];
        }
        $this->updater->qa->rightanswer = $this->make_summary($answer);
    }

    public function supply_missing_first_step_data(&$data) {
        throw new coding_exception('qtype_randomsamatch_updater::supply_missing_first_step_data ' .
                'not tested');
        $data['_stemorder'] = array();
        $data['_choiceorder'] = array();
    }

    public function set_data_elements_for_step($state, &$data) {
        $choices = $this->explode_answer($state->answer);

        foreach ($this->stemorder as $i => $key) {
            if (empty($choices[$key])) {
                $data['sub' . $i] = 0;
                continue;
            }
            $choice = $this->lookup_choice($choices[$key]);

            if (array_key_exists($choice, $this->flippedchoiceorder)) {
                $data['sub' . $i] = $this->flippedchoiceorder[$choice] + 1;
            } else {
                $data['sub' . $i] = 0;
            }
        }
    }

    public function load_question($questionid) {
        return $this->qeupdater->load_question($questionid);
    }
}
