<?php
// This file is part of mod_datalynx for Moodle - http://moodle.org/
//
// It 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.
//
// It 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 with the datalynx activity.
 *
 * @package mod_datalynx
 * @category test
 * @copyright  2018 David Bogner
 * @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\Gherkin\Node\TableNode;

/**
 * Steps definition for mod_datalynx
 *
 * @package mod_datalynx
 * @category test
 * @copyright 2018 David Bogner
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class behat_mod_datalynx extends behat_base {
    /**
     * Sets up fields for the given datalynx instance.
     * Optional, but must be used after instance declaration.
     *
     * @Given /^I add to the "(?P<activityname_string>(?:[^"]|\\")*)" datalynx the following fields:$/
     *
     * @param string $activityname
     * @param TableNode $table
     */
    public function i_add_to_the_datalynx_the_following_fields($activityname, TableNode $table) {

        $this->execute("behat_general::click_link", $this->escape($activityname));
        $csstarget = '.nav-item [title="Manage"]';
        $this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
        $this->execute("behat_general::click_link", "Fields");

        $fields = $table->getHash();
        foreach ($fields as $field) {
            $this->execute("behat_forms::i_select_from_the_singleselect", [$field['type'], 'type']);
            $field['name'] = "Datalynx field {$field['name']}";
            $this->execute("behat_forms::set_field_value", ['name', $field['name']]);
            $this->execute("behat_forms::set_field_value", ['description', $field['description']]);
            switch ($field['type']) {
                case "checkbox":
                case "radiobutton":
                    $field['addoptions'] = str_replace(',', "\n", $field['param1']);
                    $this->execute("behat_forms::set_field_value", ['addoptions', $field['addoptions']]);
                    $field['param2'] ? "New line" : $field['param2'];
                    $field['param3'] ? "No" : $field['param3'];
                    $this->execute("behat_forms::set_field_value", ['param2', $field['param2']]);
                    $this->execute("behat_forms::set_field_value", ['param3', $field['param3']]);
                    break;
                case "gradeitem":
                    break;
                case "coursegroup":
                    break;
                case "datalynxview":
                    break;
                case "duration":
                    break;
                case "fieldgroup":
                    break;
                case "file":
                    break;
                case "identifier":
                    break;
                case "number":
                    if (!empty($field['param1'])) {
                        $this->execute("behat_forms::set_field_value", ['param1', $field['param1']]);
                    }
                    break;
                case "picture":
                    break;
                case "select":
                    $field['addoptions'] = str_replace(',', "\n", $field['param1']);
                    $this->execute("behat_forms::set_field_value", ['addoptions', $field['addoptions']]);
                    if (!empty($field['param2'])) {
                        $this->execute("behat_forms::set_field_value", ['param2', $field['param2']]);
                    }
                    if (!empty($field['param6'])) {
                        $this->execute("behat_forms::i_select_from_the_singleselect", ['param6', $field['param6']]);
                    }
                    if (!empty($field['param4'])) {
                        $this->execute("behat_forms::i_select_from_the_singleselect", ['param4', $field['param4']]);
                    }
                    if (!empty($field['param5'])) {
                        $this->execute("behat_forms::i_select_from_the_singleselect", ['param5', $field['param5']]);
                    }
                    break;
                case "multiselect":
                    $field['addoptions'] = str_replace(',', "\n", $field['param1']);
                    $this->execute("behat_forms::set_field_value", ['addoptions', $field['addoptions']]);
                    $field['param2'] ? "New line" : $field['param2'];
                    $field['param3'] ? "No" : $field['param3'];
                    $this->execute("behat_forms::set_field_value", ['param2', $field['param2']]);
                    $this->execute("behat_forms::set_field_value", ['param3', $field['param3']]);
                    break;
                case "tag":
                    break;
                case "teammemberselect":
                    if (!empty($field['param1'])) {
                        $this->execute("behat_forms::set_field_value", ['param1', $field['param1']]);
                    }
                    if (!empty($field['param2'])) {
                        $roles = explode(',', $field['param2']);
                        foreach ($roles as $roleid) {
                            $this->execute("behat_forms::set_field_value", ['param2' . "[{$roleid}]", 1]);
                        }
                    }

                    break;
                case "text":
                    if (!empty($field['param1'])) {
                        $this->execute("behat_forms::set_field_value", ['param1', $field['param1']]);
                    }
                    if (!empty($field['param8'])) {
                        $this->execute("behat_forms::i_select_from_the_singleselect", ['param8', $field['param8']]);
                    }
                    if (!empty($field['param9'])) {
                        $this->execute("behat_forms::i_select_from_the_singleselect", ['param9', $field['param9']]);
                    }
                    if (!empty($field['param4'])) {
                        $this->execute("behat_forms::i_select_from_the_singleselect", ['param4', $field['param4']]);
                    }
                    if (!empty($field['param5'])) {
                        $this->execute("behat_forms::i_select_from_the_singleselect", ['param5', $field['param5']]);
                    }
                    break;
                case "textarea":
                    if (!empty($field['param2'])) {
                        $this->execute("behat_forms::set_field_value", ['param2', $field['param2']]);
                    }
                    if (!empty($field['param3'])) {
                        $this->execute("behat_forms::set_field_value", ['param3', $field['param3']]);
                    }
                    break;
                case "editor":
                    break;
                case "time":
                    break;
                case "url":
                    break;
                case "userinfo":
                    break;
                default:
                    break;
            }
            $this->execute('behat_forms::press_button', get_string('savechanges'));
        }
    }

    // phpcs:disable moodle.Files.LineLength
    /**
     * Sets up a view for the specified datalynx instance using the specified viewtype.
     *
     * @Given /^I add to "(?P<activityname_string>(?:[^"]|\\")*)" datalynx the view of "(?P<viewtype_string>(?:[^"]|\\")*)" type with:$/
     *
     * @param string $activityname
     * @param string $viewtype
     * @param TableNode $viewformdata
     * @throws coding_exception
     */
    public function i_add_to_datalynx_the_view_of_type_with($activityname, $viewtype, TableNode $viewformdata) {
        $this->execute("behat_general::click_link", $this->escape($activityname));
        $csstarget = '.nav-item [title="Manage"]';
        $this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
        $this->execute("behat_forms::i_select_from_the_singleselect", [$viewtype, 'type']);
        $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $viewformdata);
        $this->execute('behat_forms::press_button', get_string('savechanges'));
    }
    // phpcs:enable moodle.Files.LineLength

    /**
     * Sets the entry form field to the given value.
     *
     * @Given /^I fill in the entry form fields$/
     *
     * @param TableNode $fielddata
     * @throws coding_exception
     * @throws Exception
     */
    public function i_fill_in_the_entry_form_fields(TableNode $fielddata) {
        $fields = $fielddata->getHash();
        foreach ($fields as $field) {
            switch ($field['type']) {
                case "checkbox":
                    $values = explode(',', $field['value']);
                    foreach ($values as $value) {
                        $fieldvalue = substr($value, -1);
                        $field = substr($value, 0, -2);
                        $this->execute("behat_forms::i_set_the_field_to", [$field, $fieldvalue]);
                    }
                    break;
                case "select":
                    $this->execute(
                        "behat_forms::i_set_the_field_with_xpath_to",
                        ["//div[@data-field-type='select']//select", $field['value']]
                    );
                    break;
                case "multiselect":
                    $this->execute("behat_forms::i_open_the_autocomplete_suggestions_list");
                    $this->execute("behat_forms::i_click_on_item_in_the_autocomplete_list", $field['value']);
                    break;
                case "radio":
                    $this->execute(
                        "behat_forms::i_set_the_field_with_xpath_to",
                        ["//div[@data-field-type='radiobutton']//label[contains(.,'{$field['value']}')]/input",
                        "selected"]
                    );
                    break;
                case "text":
                case "number":
                    $this->execute(
                        "behat_forms::i_set_the_field_with_xpath_to",
                        ["//div[@data-field-name='Datalynx field {$field['name']}']//input", $field['value']]
                    );
                    break;
                case "textarea":
                    $this->execute(
                        "behat_forms::i_set_the_field_with_xpath_to",
                        ["//div[@data-field-name='Datalynx field {$field['name']}']//textarea", $field['value']]
                    );
                    break;
                case "file":
                    break;
                case "teammemberselect":
                    $this->execute("behat_forms::i_open_the_autocomplete_suggestions_list");
                    $this->execute("behat_forms::i_click_on_item_in_the_autocomplete_list", $field['value']);
                    break;
                case "duration":
                    $values = explode(" ", $field['value']);
                    $this->execute(
                        "behat_forms::i_set_the_field_with_xpath_to",
                        ["//div[@data-field-name='Datalynx field {$field['name']}']//select", $values[1]]
                    );
                    $this->execute(
                        "behat_forms::i_set_the_field_with_xpath_to",
                        ["//div[@data-field-name='Datalynx field {$field['name']}']//input", $values[0]]
                    );
                    break;
                case "time":
                    $values = explode(".", $field['value']);
                    $this->execute(
                        "behat_forms::i_set_the_field_with_xpath_to",
                        ["//div[@data-field-name='Datalynx field {$field['name']}']//input", 1]
                    );
                    foreach ($values as $key => $value) {
                        $number = $key + 1;
                        $this->execute(
                            "behat_forms::i_set_the_field_with_xpath_to",
                            ["(//div[@data-field-name='Datalynx field {$field['name']}']//select)[{$number}]", $value]
                        );
                    }
                    break;
            }
        }
    }

    /**
     * Inserts text into a TinyMCE editor by ID without replacing existing content.
     * Unlike set_value (which calls setContent and wipes existing content), this uses
     * insertContent so previously inserted field-tag buttons are preserved.
     *
     * @Then I add to :editorid editor the text :newvalue
     *
     * @param string $editorid  The textarea ID backing the TinyMCE editor, e.g. "id_eparam2_editor"
     * @param string $newvalue  Content to insert at the current cursor position
     * @throws coding_exception
     */
    public function i_add_to_editor_the_text($editorid, $newvalue) {
        if (!$this->running_javascript()) {
            throw new coding_exception('Updating text requires javascript.');
        }
        $safeid      = addslashes($editorid);
        $safecontent = addslashes($newvalue);
        $this->getSession()->executeScript(
            "var ed = window.tinyMCE ? window.tinyMCE.get('{$safeid}') : null;" .
            "if (ed) { ed.insertContent('{$safecontent}'); }"
        );
    }

    /**
     * Replaces the entire content of a TinyMCE editor by ID.
     * Uses setContent so any previously existing editor content is wiped first.
     *
     * @Then I set the :editorid editor to :newvalue
     *
     * @param string $editorid  The textarea ID backing the TinyMCE editor, e.g. "id_eparam2_editor"
     * @param string $newvalue  New content to set in the editor
     * @throws coding_exception
     */
    public function i_set_the_editor_to($editorid, $newvalue) {
        if (!$this->running_javascript()) {
            throw new coding_exception('Updating text requires javascript.');
        }
        $safeid      = addslashes($editorid);
        $safecontent = addslashes($newvalue);
        $this->getSession()->executeScript(
            "var ed = window.tinyMCE ? window.tinyMCE.get('{$safeid}') : null;" .
            "if (ed) { ed.setContent('{$safecontent}'); }"
        );
    }

    /**
     * Directly selects options in a hidden select element by visible text, bypassing the
     * form-autocomplete widget entirely to avoid timing / pending-key issues.
     * Multiple values can be provided as a comma-separated string.
     *
     * @When I select :value in the datalynx fieldgroup fields selector
     *
     * @param string $value  Comma-separated option texts to select, e.g. "Field A, Field B"
     * @throws coding_exception
     */
    public function i_select_in_fieldgroup_fields_selector($value) {
        if (!$this->running_javascript()) {
            throw new coding_exception('This step requires JavaScript.');
        }
        $values = array_map('trim', explode(',', $value));
        $valuesjson = json_encode($values);
        $this->getSession()->executeScript(
            "(function(values) {" .
            "  var sel = document.getElementById('id_param1');" .
            "  if (!sel) { return; }" .
            "  Array.from(sel.options).forEach(function(opt) {" .
            "    if (values.indexOf(opt.text.trim()) !== -1) {" .
            "      opt.selected = true;" .
            "    }" .
            "  });" .
            "})($valuesjson);"
        );
    }

    /**
     * Directly selects a user option in the nth datalynx teammemberselect field, bypassing
     * the form-autocomplete widget to avoid timing / pending-key issues on slow machines.
     *
     * @When I select :value for the :nth datalynx teammemberselect field
     *
     * @param string $value  Full display text of the option, e.g. "Student 1 (student1 at example.com)"
     * @param int    $nth    1-based index of the teammemberselect on the page
     * @throws coding_exception
     */
    public function i_select_for_teammemberselect($value, $nth) {
        if (!$this->running_javascript()) {
            throw new coding_exception('This step requires JavaScript.');
        }
        $nth = (int) $nth;
        $valuejson = json_encode($value);
        $result = $this->getSession()->evaluateScript(
            "(function(nth, value) {" .
            "  var sels = document.querySelectorAll('[data-fieldtype=\"autocomplete\"] select[multiple]');" .
            "  var sel = sels[nth - 1];" .
            "  if (!sel) { return 'select-not-found:' + sels.length; }" .
            "  var opt = Array.from(sel.options).find(function(o) {" .
            "    return o.text.trim() === value || o.text.trim().indexOf(value) !== -1;" .
            "  });" .
            "  if (!opt) { return 'option-not-found in ' + sel.id +" .
            "   ' options:' + Array.from(sel.options).map(function(o){return o.text.trim();}).join('|'); }" .
            "  opt.selected = true;" .
            "  sel.dispatchEvent(new Event('change', {bubbles: true}));" .
            "  return 'ok';" .
            "})($nth, $valuejson);"
        );
        if ($result !== 'ok') {
            throw new \Exception("Could not select '$value' in teammemberselect #$nth: $result");
        }
    }

    /**
     * Close the auto-complete suggestions list (Assuming there is only one on the page.).
     *
     * @Given /^I close the autocomplete suggestions list$/
     */
    public function i_close_the_autocomplete_suggestions_list() {
        $csstarget = ".col-form-label";
        $this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
    }

    /**
     * Follow the datalynx link of the elements matching the selector.
     *
     * @When /^I follow the datalynx "(?P<link_string>(?:[^"]|\\")*)" link$/
     * @param string $linktext The link text to follow.
     */
    public function i_follow_the_datalynx_link($linktext) {
        $page = $this->getSession()->getPage();
        switch ($linktext) {
            case 'View Filters':
                // Code to handle the "View Filters" case.
                $link = $page->find('css', sprintf('.nav-item [title="View Filters"]', $linktext));
                break;
            case 'Manage':
                $link = $page->find('css', sprintf('.nav-item [title="Manage"]', $linktext));
                break;
            case 'Views':
                $link = $page->find('css', sprintf('.nav-item [title="Views"]', $linktext));
                break;
        }
        if (null === $link) {
            throw new \RuntimeException(sprintf('The link "%s" was not found or is not visible', $linktext));
        }
        $link->click();
    }
}
