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

/**
 * Steps definitions related to mod_surveypro.
 *
 * @package   mod_surveypro
 * @category  test
 * @copyright 2013 onwards kordan <stringapiccola@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.

require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');

use Behat\Behat\Context\Step\Given,
    Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode,
    Behat\Mink\Exception\ExpectationException;

/**
 * Library for surveypro behat tests
 *
 * @package   mod_surveypro
 * @category  test
 * @copyright 2013 onwards kordan <stringapiccola@gmail.com>
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class behat_mod_surveypro extends behat_base
{
    use core_behat_file_helper;

    /**
     * Disabilita TinyMCE prima di ogni scenario per velocizzare i test.
     *
     * @BeforeScenario ~@use_editor
     */
    public function disable_richtext_editor(): void {
        set_config('texteditors', 'textarea,tiny,atto');
    }

    /**
     * If a test forced the Tiny MCE editor,
     * move the text area back to the top
     * to use the textarea as next editor.
     *
     * @AfterScenario @use_editor
     */
    public function restore_fast_editor(): void {
        set_config('texteditors', 'textarea,tiny,atto');
    }

    /**
     * Set textarea as default editor for the user $username
     *
     * @Given /^I switch to plaintext editor for user "([^"]*)"$/
     *
     * @param string $username The user target of this change of textaeditor.
     */
    public function i_switch_to_plaintext_editor_for_user(string $username) {
        global $DB;

        $user = $DB->get_record('user', ['username' => $username], '*', MUST_EXIST);
        set_user_preference('htmleditor', 'textarea', $user->id);
    }

    /**
     * Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'.
     *
     * Recognised page names are:
     * | pagetype            | name meaning   | description                                     |
     * | Usertemplate Import | surveypro name | The page to load user templater (utemplate.php) |
     *
     * @param string $type identifies which type of page this is, e.g. 'Attempt review'.
     * @param string $identifier identifies the particular page, e.g. 'Test surveypro > student > Attempt 1'.
     * @return moodle_url the corresponding URL.
     * @throws Exception with a meaningful error message if the specified page cannot be found.
     */
    protected function resolve_page_instance_url(string $type, string $identifier): moodle_url {
        switch ($type) {
            case 'Surveypro from secondary navigation':
                return new \moodle_url(
                    '/mod/surveypro/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id]
                );

            case 'Layout from secondary navigation':
                return new \moodle_url(
                    '/mod/surveypro/layout.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id]
                );

            case 'Reports from secondary navigation':
                return new \moodle_url(
                    '/mod/surveypro/reports.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id]
                );

            case 'Tools from secondary navigation':
                return new \moodle_url(
                    '/mod/surveypro/tools.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id]
                );

            case 'User templates from secondary navigation':
                return new \moodle_url(
                    '/mod/surveypro/utemplates.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id]
                );

            case 'Master templates from secondary navigation':
                return new \moodle_url(
                    '/mod/surveypro/mtemplates.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id]
                );

            case 'Colles > Summary report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'summary']
                );

            case 'Colles > Scales report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'scales']
                );

            case 'Colles > Relevance report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'questions', 'area' => '0']
                );

            case 'Colles > Reflective thinking report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'questions', 'area' => '1']
                );

            case 'Colles > Interactivity report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'questions', 'area' => '2']
                );

            case 'Colles > Tutor support report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'questions', 'area' => '3']
                );

            case 'Colles > Peer support report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'questions', 'area' => '4']
                );

            case 'Colles > Interpretation report':
                return new \moodle_url(
                    '/mod/surveypro/report/colles/view.php',
                    ['id' => $this->get_cm_by_surveypro_name($identifier)->id, 'type' => 'questions', 'area' => '5']
                );

            default:
                throw new Exception('Unrecognised surveypro page type "' . $type . '."');
        }
    }

    /**
     * Get a surveypro cmid from the surveypro name.
     *
     * @param string $name surveypro name.
     * @return \stdClass cm from get_coursemodule_from_instance.
     */
    protected function get_cm_by_surveypro_name(string $name): \stdClass {
        $surveypro = $this->get_surveypro_by_name($name);

        return get_coursemodule_from_instance('surveypro', $surveypro->id, $surveypro->course);
    }

    /**
     * Get a surveypro by name.
     *
     * @param string $name surveypro name.
     * @return \stdClass the corresponding DB row.
     */
    protected function get_surveypro_by_name(string $name): \stdClass {
        global $DB;

        return $DB->get_record('surveypro', ['name' => $name], '*', MUST_EXIST);
    }

    /**
     * Check the number of displayed submissions.
     *
     * @throws ExpectationException
     * @Then /^I should see "(?P<given_number>\d+)" submissions$/
     * @param int $givennumber
     * @return void|ExpectationException
     */
    public function i_should_see_submission($givennumber) {
        // Getting the container where the text should be found.
        $container = $this->get_selected_node('table', 'submissions');

        $nodes = $container->findAll('xpath', "//tr[contains(@id, 'submissionslist') and not(contains(@class, 'emptyrow'))]");
        $tablerows = count($nodes);

        if (intval($givennumber) !== $tablerows) {
            $message = sprintf('%d submissions found in the "submission" table, but should be %d.', $tablerows, $givennumber);
            throw new ExpectationException($message, $this->getsession());
        }
    }

    /**
     * Check the number of items with specified status.
     *
     * @throws ExpectationException
     * @Then /^I should see "(?P<given_number>\d+)" (?P<status>reserved|available|searchable|not searchable|visible|hidden) items$/
     * @param int $givennumber
     * @param string $status
     * @return void|ExpectationException
     */
    public function i_should_see_items($givennumber, $status) {
        // Getting the container where the text should be found.
        $container = $this->get_selected_node('table', 'manageitems');

        switch ($status) {
            case 'reserved':
                $xpath = "//a[contains(@id,'makeavailable')] | //img[contains(@id, 'makeavailable')]";
                $nodes = $container->findAll('xpath', $xpath);
                break;
            case 'available':
                $xpath = "//a[contains(@id,'makereserved')] | //img[contains(@id, 'makereserved')]";
                $nodes = $container->findAll('xpath', $xpath);
                $rednodes = $container->findAll('xpath', "//img[contains(@title, 'unreservable')]");
                break;
            case 'searchable':
                $nodes = $container->findAll('xpath', "//img[contains(@id, 'searchup')]");
                break;
            case 'not searchable':
                $nodes    = $container->findAll('xpath', "//img[contains(@id, 'searchdown')]");
                $rednodes = $container->findAll('xpath', "//img[contains(@id, 'searchoff')]");
                break;
            case 'visible':
                $xpath = "//tr[contains(@id, 'itemslist')";
                $xpath .= " and not(contains(@class, 'emptyrow'))";
                $xpath .= " and not(contains(@class, 'dimmed'))]";
                $nodes = $container->findAll('xpath', $xpath);
                break;
            case 'hidden':
                $xpath = "//tr[contains(@id, 'itemslist') and not(contains(@class, 'emptyrow')) and contains(@class, 'dimmed')]";
                $nodes = $container->findAll('xpath', $xpath);
                break;
            default:
                throw new Exception('Unrecognised status "' . $status . '."');
        }

        if (!isset($rednodes)) { // Red because red is the outline of the icons for unchangeable settings.
            $tablerows = count($nodes);
        } else {
            $tablerows = count($nodes) + count($rednodes);
        }

        if (intval($givennumber) == $tablerows) {
            return;
        }

        switch ($status) {
            case 'reserved':
                $message = sprintf('%d reserved items found in the "item" table, %d were declared.', $tablerows, $givennumber);
                break;
            case 'available':
                $message = sprintf('%d available items found in the "item" table, %d were declared.', $tablerows, $givennumber);
                break;
            case 'searchable':
                $message = sprintf('%d searchable items found in the "item" table, %d were declared.', $tablerows, $givennumber);
                break;
            case 'not searchable':
                $message = sprintf('%d unsearchable items found in the "item" table, %d were declared.', $tablerows, $givennumber);
                break;
            case 'visible':
                $message = sprintf('%d visible items found in the "item" table, %d were declared.', $tablerows, $givennumber);
                break;
            case 'hidden':
                $message = sprintf('%d hidden items found in the "item" table, %d were declared.', $tablerows, $givennumber);
                break;
            default:
                throw new Exception('Unrecognised status "' . $status . '."');
        }
        throw new ExpectationException($message, $this->getsession());
    }

    /**
     * Add the specified items to the specified surveypro.
     *
     * The first row should be column names:
     * | type | plugin |
     * that are required
     *
     * @Given /^surveypro "([^"]*)" has the following items:$/
     * @param string $surveyproname Name of the surveypro to add items to
     * @param TableNode $data information about the items to add
     */
    public function surveypro_has_the_following_items($surveyproname, TableNode $data) {
        global $DB;

        $surveypro = $DB->get_record('surveypro', ['name' => $surveyproname], '*', MUST_EXIST);
        $cm = get_coursemodule_from_instance('surveypro', $surveypro->id, $surveypro->course, false, MUST_EXIST);

        $parentids = [];
        $allowed = ['type' => 1, 'plugin' => 1, 'settings' => 1];
        // Add the questions.
        foreach ($data->getHash() as $surveyprodata) {
            // Verify "type" was provided
            if (!array_key_exists('type', $surveyprodata)) {
                throw new ExpectationException('Type column is required.', $this->getSession());
            }

            // Verify "plugin" was provided
            if (!array_key_exists('plugin', $surveyprodata)) {
                throw new ExpectationException('Plugin column is required.', $this->getSession());
            }

            // Verify only allowed keys were provided
            $extras = array_diff_key($surveyprodata, $allowed);
            if (!empty($extras)) {
                $illegal = implode(', ', array_keys($extras));
                throw new ExpectationException(
                    'Key/s "' . $illegal . '" are not allowed in surveypro_has_the_following_items',
                    $this->getSession()
                );
            }

            $type = clean_param($surveyprodata['type'], PARAM_TEXT);
            $plugin = clean_param($surveyprodata['plugin'], PARAM_TEXT);
            if (isset($surveyprodata['settings']) && core_text::strlen($surveyprodata['settings'])) {
                $settings = clean_param($surveyprodata['settings'], PARAM_RAW); // Preserve '>' or html tags.

                // If I find parentid and $parentid esists
                if (preg_match('/@@itemid_(\d+)@@/', $settings, $matches)) {
                    // $matches[0] holds "@@itemid_04@@".
                    // $matches[1] holdes "04".

                    $index = (int)$matches[1]; // Convertiamo "04" in intero (4)
                    $index--;
                    if (isset($parentids[$index])) {
                        $settings = str_replace($matches[0], (string)$parentids[$index], $settings);
                    } else {
                        $index++;
                        throw new \coding_exception('The item with the index ' . $index . ' has not yet been created.');
                    }
                }

                try {
                    $customsettings = json_decode($settings, true, 512, JSON_THROW_ON_ERROR);
                } catch (JsonException $e) {
                    throw new coding_exception('Invalid JSON at lne ' . __LINE__ . ' of ' . __FILE__ . ': ' . $e->getMessage());
                }

                if (isset($customsettings['content'])) {
                    $customsettings['content_editor'] = ['text' => $customsettings['content']];
                    unset($customsettings['content']);
                }
            } else {
                $customsettings = []; // Do not inherit previous items settings.
            }

            // Get dummy contents based on type and plugin.
            $record = surveypro_get_dummy_contents($type, $plugin, $customsettings ?? []);

            // Add the item.
            $item = surveypro_get_itemclass($cm, $surveypro, 0, $type, $plugin);
            $itemid = $item->item_save($record);

            // I save the id of each item just in case I need it to make relations.
            $parentids[] = $itemid;
        }
    }

    /**
     * Sets the specified value to the a multiline field.
     *
     * @Given /^I set the multiline field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
     * @throws ElementNotFoundException Thrown by behat_base::find
     * @param string $field
     * @param string $value
     * @return void
     */
    public function i_set_the_multiline_field_to($field, $value) {
        $string = str_replace('\n', "\n", $value);
        $this->execute('behat_forms::set_field_value', [$field, (string)$string]);
    }

    /**
     * Opens the action dropdown for the item at the given sortindex
     * and clicks the named action link inside it.
     *
     * Example:
     *   And I click action "Edit" on item 2
     *
     * @When /^I click action "(?P<action>[^"]*)" on item (?P<sortindex>\d+)$/
     *
     * @param string $action
     * @param int $sortindex
     * @return void
     */
    public function i_click_action_on_item(string $action, int $sortindex): void {
        $xpathwithsortindex = "//tr[contains(@id,'itemslist_r') or contains(@id,'relations_r')]"
            . "[.//td[contains(@class,'sortindex') and normalize-space()='$sortindex']]";
        $submissionslistxpath = "//tr[contains(@id,'submissionslist_r" . ($sortindex - 1) . "')]";

        try {
            $row = $this->find('xpath', $xpathwithsortindex);
        } catch (\Exception $e) {
            $row = $this->find('xpath', $submissionslistxpath);
        }

        $this->execute(
            'behat_action_menu::i_choose_in_the_named_menu_in_container',
            [$action, 'Actions', $row, 'NodeElement']
        );
    }

    /**
     * Verify the given item is actually in the given position
     *
     * Example:
     *   And the item in position 1 is "Text (short)"
     *
     * @Then /^the item in position (?P<sortindex>\d+) is "(?P<plugin>[^"]*)"$/
     *
     * @param int $sortindex
     * @param string $plugin
     * @return void
     */
    public function the_item_in_position_is(int $sortindex, string $plugin): void {
        $rowid = 'itemslist_r' . ($sortindex - 1);
        $xpath = "//tr[@id='$rowid'][.//img[@title='$plugin']]";

        try {
            $this->find('xpath', $xpath);
        } catch (\Exception $e) {
            throw new \Behat\Mink\Exception\ExpectationException(
                'In position #' . $sortindex . ' there is no "' . $plugin . '" item.',
                $this->getSession()
            );
        }
    }
}
