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

/**
 * Step class.
 *
 * @package    tool_usertours
 * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace tool_usertours;

use context_system;
use stdClass;

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

/**
 * Step class.
 *
 * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class step {

    /**
     * @var     int     $id         The id of the step.
     */
    protected $id;

    /**
     * @var     int     $tourid     The id of the tour that this step belongs to.
     */
    protected $tourid;

    /**
     * @var     tour    $tour       The tour class that this step belongs to.
     */
    protected $tour;

    /**
     * @var     string  $title      The title of the step.
     */
    protected $title;

    /**
     * @var     string  $content    The content of this step.
     */
    protected $content;

    /**
     * @var     int  $contentformat    The content format: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN.
     */
    protected $contentformat;

    /**
     * @var     int     $targettype The type of target.
     */
    protected $targettype;

    /**
     * @var     string  $targetvalue    The value for this type of target.
     */
    protected $targetvalue;

    /**
     * @var     int     $sortorder  The sort order.
     */
    protected $sortorder;

    /**
     * @var     object  $config     The configuration as an object.
     */
    protected $config;

    /**
     * @var     bool    $dirty      Whether the step has been changed since it was loaded
     */
    protected $dirty = false;

    /**
     * @var bool $isimporting Whether the step is being imported or not.
     */
    protected $isimporting;

    /**
     * @var stdClass[] $files The list of attached files for this step.
     */
    protected $files = [];

    /**
     * Fetch the step instance.
     *
     * @param   int             $id         The id of the step to be retrieved.
     * @return  step
     */
    public static function instance($id) {
        $step = new step();
        return $step->fetch($id);
    }

    /**
     * Load the step instance.
     *
     * @param stdClass $record The step record to be loaded.
     * @param bool $clean Clean the values.
     * @param bool $isimporting Whether the step is being imported or not.
     * @return step
     */
    public static function load_from_record($record, $clean = false, bool $isimporting = false) {
        $step = new self();
        $step->set_importing($isimporting);
        return $step->reload_from_record($record, $clean);
    }

    /**
     * Fetch the step instance.
     *
     * @param   int             $id         The id of the step to be retrieved.
     * @return  step
     */
    protected function fetch($id) {
        global $DB;

        return $this->reload_from_record(
            $DB->get_record('tool_usertours_steps', array('id' => $id))
        );
    }

    /**
     * Refresh the current step from the datbase.
     *
     * @return  step
     */
    protected function reload() {
        return $this->fetch($this->id);
    }

    /**
     * Reload the current step from the supplied record.
     *
     * @param stdClass $record The step record to be loaded.
     * @param bool $clean Clean the values.
     * @return step
     */
    protected function reload_from_record($record, $clean = false) {
        $this->id           = $record->id;
        $this->tourid       = $record->tourid;
        if ($clean) {
            $this->title    = clean_param($record->title, PARAM_TEXT);
            $this->content  = clean_text($record->content);
        } else {
            $this->title    = $record->title;
            $this->content  = $record->content;
        }
        $this->contentformat = isset($record->contentformat) ? $record->contentformat : FORMAT_MOODLE;
        $this->targettype   = $record->targettype;
        $this->targetvalue  = $record->targetvalue;
        $this->sortorder    = $record->sortorder;
        $this->config       = json_decode($record->configdata);
        $this->dirty        = false;

        if ($this->isimporting && isset($record->files)) {
            // We are importing/exporting the step.
            $this->files = $record->files;
        }

        return $this;
    }

    /**
     * Set the import state for the step.
     *
     * @param bool $isimporting True if the step is imported, otherwise false.
     * @return void
     */
    protected function set_importing(bool $isimporting = false): void {
        $this->isimporting = $isimporting;
    }

    /**
     * Get the ID of the step.
     *
     * @return  int
     */
    public function get_id() {
        return $this->id;
    }

    /**
     * Get the Tour ID of the step.
     *
     * @return  int
     */
    public function get_tourid() {
        return $this->tourid;
    }

    /**
     * Get the Tour instance that this step belongs to.
     *
     * @return  tour
     */
    public function get_tour() {
        if ($this->tour === null) {
            $this->tour = tour::instance($this->tourid);
        }
        return $this->tour;
    }

    /**
     * Set the id of the tour.
     *
     * @param   int             $value      The id of the tour.
     * @return  self
     */
    public function set_tourid($value) {
        $this->tourid = $value;
        $this->tour = null;
        $this->dirty = true;

        return $this;
    }

    /**
     * Get the Title of the step.
     *
     * @return  string
     */
    public function get_title() {
        return $this->title;
    }

    /**
     * Set the title for this step.
     *
     * @param   string      $value      The new title to use.
     * @return  $this
     */
    public function set_title($value) {
        $this->title = clean_text($value);
        $this->dirty = true;

        return $this;
    }

    /**
     * Get the content format of the step.
     *
     * @return  int
     */
    public function get_contentformat(): int {
        return $this->contentformat;
    }

    /**
     * Get the body content of the step.
     *
     * @return  string
     */
    public function get_content() {
        return $this->content;
    }

    /**
     * Set the content value for this step.
     *
     * @param   string      $value      The new content to use.
     * @param   int         $format     The new format to use: FORMAT_MOODLE/FORMAT_HTML/FORMAT_PLAIN/FORMAT_MARKDOWN.
     * @return  $this
     */
    public function set_content($value, $format = FORMAT_HTML) {
        $this->content = clean_text($value);
        $this->contentformat = $format;
        $this->dirty = true;

        return $this;
    }

    /**
     * Get the content value for this step.
     *
     * @return  string
     */
    public function get_targettype() {
        return $this->targettype;
    }

    /**
     * Set the type of target for this step.
     *
     * @param   string      $value      The new target to use.
     * @return  $this
     */
    public function set_targettype($value) {
        $this->targettype = $value;
        $this->dirty = true;

        return $this;
    }

    /**
     * Get the target value for this step.
     *
     * @return  string
     */
    public function get_targetvalue() {
        return $this->targetvalue;
    }

    /**
     * Set the target value for this step.
     *
     * @param   string      $value      The new target value to use.
     * @return  $this
     */
    public function set_targetvalue($value) {
        $this->targetvalue = $value;
        $this->dirty = true;

        return $this;
    }

    /**
     * Get the target instance for this step.
     *
     * @return  target
     */
    public function get_target() {
        return target::get_target_instance($this);
    }

    /**
     * Get the current sortorder for this step.
     *
     * @return  int
     */
    public function get_sortorder() {
        return (int) $this->sortorder;
    }

    /**
     * Whether this step is the first step in the tour.
     *
     * @return  boolean
     */
    public function is_first_step() {
        return ($this->get_sortorder() === 0);
    }

    /**
     * Whether this step is the last step in the tour.
     *
     * @return  boolean
     */
    public function is_last_step() {
        $stepcount = $this->get_tour()->count_steps();
        return ($this->get_sortorder() === $stepcount - 1);
    }

    /**
     * Set the sortorder for this step.
     *
     * @param   int         $value      The new sortorder to use.
     * @return  $this
     */
    public function set_sortorder($value) {
        $this->sortorder = $value;
        $this->dirty = true;

        return $this;
    }

    /**
     * Get the link to move this step up in the sortorder.
     *
     * @return  \moodle_url
     */
    public function get_moveup_link() {
        return helper::get_move_step_link($this->get_id(), helper::MOVE_UP);
    }

    /**
     * Get the link to move this step down in the sortorder.
     *
     * @return  \moodle_url
     */
    public function get_movedown_link() {
        return helper::get_move_step_link($this->get_id(), helper::MOVE_DOWN);
    }

    /**
     * Get the value of the specified configuration item.
     *
     * If notvalue was found, and no default was specified, the default for the tour will be used.
     *
     * @param   string      $key        The configuration key to set.
     * @param   mixed       $default    The default value to use if a value was not found.
     * @return  mixed
     */
    public function get_config($key = null, $default = null) {
        if ($this->config === null) {
            $this->config = (object) array();
        }

        if ($key === null) {
            return $this->config;
        }

        if ($this->get_targettype() !== null) {
            $target = $this->get_target();
            if ($target->is_setting_forced($key)) {
                return $target->get_forced_setting_value($key);
            }
        }

        if (property_exists($this->config, $key)) {
            return $this->config->$key;
        }

        if ($default !== null) {
            return $default;
        }

        return $this->get_tour()->get_config($key);
    }

    /**
     * Set the configuration item as specified.
     *
     * @param   string      $key        The configuration key to set.
     * @param   mixed       $value      The new value for the configuration item.
     * @return  $this
     */
    public function set_config($key, $value) {
        if ($this->config === null) {
            $this->config = (object) array();
        }

        if ($value === null) {
            unset($this->config->$key);
        } else {
            $this->config->$key = $value;
        }
        $this->dirty = true;

        return $this;
    }

    /**
     * Get the edit link for this step.
     *
     * @return  \moodle_url
     */
    public function get_edit_link() {
        return helper::get_edit_step_link($this->tourid, $this->id);
    }

    /**
     * Get the delete link for this step.
     *
     * @return  \moodle_url
     */
    public function get_delete_link() {
        return helper::get_delete_step_link($this->id);
    }

    /**
     * Embed attached file to the json file for step.
     *
     * @return array List of files.
     */
    protected function embed_files(): array {
        $systemcontext = context_system::instance();
        $fs = get_file_storage();
        $areafiles = $fs->get_area_files($systemcontext->id, 'tool_usertours', 'stepcontent', $this->id);
        $files = [];
        foreach ($areafiles as $file) {
            if ($file->is_directory()) {
                continue;
            }
            $files[] = [
                'name' => $file->get_filename(),
                'path' => $file->get_filepath(),
                'content' => base64_encode($file->get_content()),
                'encode' => 'base64'
            ];
        }

        return $files;
    }

    /**
     * Get the embed files information and create store_file for this step.
     *
     * @return void
     */
    protected function extract_files() {
        $fs = get_file_storage();
        $systemcontext = context_system::instance();
        foreach ($this->files as $file) {
            $filename = $file->name;
            $filepath = $file->path;
            $filecontent = $file->content;
            $filerecord = [
                'contextid' => $systemcontext->id,
                'component' => 'tool_usertours',
                'filearea' => 'stepcontent',
                'itemid' => $this->get_id(),
                'filepath' => $filepath,
                'filename' => $filename,
            ];
            $fs->create_file_from_string($filerecord, base64_decode($filecontent));
        }
    }

    /**
     * Prepare this step for saving to the database.
     *
     * @param bool $isexporting Whether the step is being exported or not.
     * @return  object
     */
    public function to_record(bool $isexporting = false) {
        $record = [
            'id'            => $this->id,
            'tourid'        => $this->tourid,
            'title'         => $this->title,
            'content'       => $this->content,
            'contentformat' => $this->contentformat,
            'targettype'    => $this->targettype,
            'targetvalue'   => $this->targetvalue,
            'sortorder'     => $this->sortorder,
            'configdata'    => json_encode($this->config),
        ];
        if ($isexporting) {
            // We are exporting the step, adding files node to the json record.
            $record['files'] = $this->embed_files();
        }
        return (object) $record;
    }

    /**
     * Calculate the next sort-order value.
     *
     * @return  int
     */
    protected function calculate_sortorder() {
        $count = $this->get_tour()->count_steps();
        $this->sortorder = $count;

        return $this;
    }

    /**
     * Save the tour and it's configuration to the database.
     *
     * @param   boolean     $force      Whether to force writing to the database.
     * @return  $this
     */
    public function persist($force = false) {
        global $DB;

        if (!$this->dirty && !$force) {
            return $this;
        }

        if ($this->id) {
            $record = $this->to_record();
            $DB->update_record('tool_usertours_steps', $record);
        } else {
            $this->calculate_sortorder();
            $record = $this->to_record();
            unset($record->id);
            $this->id = $DB->insert_record('tool_usertours_steps', $record);
            $this->get_tour()->reset_step_sortorder();
        }

        $systemcontext = context_system::instance();
        if ($draftid = file_get_submitted_draft_itemid('content')) {
            // Take any files added to the stepcontent draft file area and
            // convert them into the proper event description file area. Also
            // parse the content text and replace the URLs to the draft files
            // with the @@PLUGIN_FILE@@ placeholder to be persisted in the DB.
            $this->content = file_save_draft_area_files(
                $draftid,
                $systemcontext->id,
                'tool_usertours',
                'stepcontent',
                $this->id,
                ['subdirs' => true],
                $this->content
            );
            $DB->set_field('tool_usertours_steps', 'content', $this->content, ['id' => $this->id]);
        }

        if ($this->isimporting) {
            // We are importing the step, we need to create store_file from the json record.
            $this->extract_files();
        }
        $this->reload();

        // Notify of a change to the step configuration.
        // This must be done separately to tour change notifications.
        cache::notify_step_change($this->get_tourid());

        // Notify the cache that a tour has changed.
        // Tours are only stored in the cache if there are steps.
        // If there step count has changed for some reason, this will change the potential cache results.
        cache::notify_tour_change();

        return $this;
    }

    /**
     * Remove this step.
     */
    public function remove() {
        global $DB;

        if ($this->id === null) {
            return;
        }

        $DB->delete_records('tool_usertours_steps', array('id' => $this->id));
        $this->get_tour()->reset_step_sortorder();

        // Notify of a change to the step configuration.
        // This must be done separately to tour change notifications.
        cache::notify_step_change($this->get_id());

        // Notify the cache that a tour has changed.
        // Tours are only stored in the cache if there are steps.
        // If there step count has changed for some reason, this will change the potential cache results.
        cache::notify_tour_change();
    }

    /**
     * Get the list of possible placement options.
     *
     * @return  array
     */
    public function get_placement_options() {
        return configuration::get_placement_options(true);
    }

    /**
     * The list of possible configuration keys.
     *
     * @return  array
     */
    public static function get_config_keys() {
        return [
            'placement',
            'orphan',
            'backdrop',
            'reflex',
        ];
    }

    /**
     * Add the step configuration to the form.
     *
     * @param   \MoodleQuickForm $mform     The form to add configuration to.
     * @return  $this
     */
    public function add_config_to_form(\MoodleQuickForm $mform) {
        $tour = $this->get_tour();

        $options = configuration::get_placement_options($tour->get_config('placement'));
        $mform->addElement('select', 'placement', get_string('placement', 'tool_usertours'), $options);
        $mform->addHelpButton('placement', 'placement', 'tool_usertours');

        $this->add_config_field_to_form($mform, 'orphan');
        $this->add_config_field_to_form($mform, 'backdrop');
        $this->add_config_field_to_form($mform, 'reflex');

        return $this;
    }

    /**
     * Add the specified step field configuration to the form.
     *
     * @param   \MoodleQuickForm $mform     The form to add configuration to.
     * @param   string          $key        The key to add.
     * @return  $this
     */
    public function add_config_field_to_form(\MoodleQuickForm $mform, $key) {
        $tour = $this->get_tour();

        $default = (bool) $tour->get_config($key);

        $options = [
            true    => get_string('yes'),
            false   => get_string('no'),
        ];

        if (!isset($options[$default])) {
            $default = configuration::get_default_value($key);
        }

        $options = array_reverse($options, true);
        $options[configuration::TOURDEFAULT] = get_string('defaultvalue', 'tool_usertours', $options[$default]);
        $options = array_reverse($options, true);

        $mform->addElement('select', $key, get_string($key, 'tool_usertours'), $options);
        $mform->setDefault($key, configuration::TOURDEFAULT);
        $mform->addHelpButton($key, $key, 'tool_usertours');

        return $this;
    }

    /**
     * Prepare the configuration data for the moodle form.
     *
     * @return  object
     */
    public function prepare_data_for_form() {
        $data = $this->to_record();
        foreach (self::get_config_keys() as $key) {
            $data->$key = $this->get_config($key, configuration::get_step_default_value($key));
        }

        if ($this->get_targettype() !== null) {
            $this->get_target()->prepare_data_for_form($data);
        }

        // Prepare content for editing in a form 'editor' field type.
        $draftitemid = file_get_submitted_draft_itemid('tool_usertours');
        $systemcontext = context_system::instance();
        $data->content = [
            'format' => $data->contentformat,
            'itemid' => $draftitemid,
            'text' => file_prepare_draft_area(
                $draftitemid,
                $systemcontext->id,
                'tool_usertours',
                'stepcontent',
                $this->id,
                ['subdirs' => true],
                $data->content
            ),
        ];

        return $data;
    }

    /**
     * Handle submission of the step editing form.
     *
     * @param   local\forms\editstep  $mform      The sumitted form.
     * @param   stdClass        $data       The submitted data.
     * @return  $this
     */
    public function handle_form_submission(local\forms\editstep &$mform, stdClass $data) {
        $this->set_title($data->title);
        $this->set_content($data->content['text'], $data->content['format']);
        $this->set_targettype($data->targettype);

        $this->set_targetvalue($this->get_target()->get_value_from_form($data));

        foreach (self::get_config_keys() as $key) {
            if (!$this->get_target()->is_setting_forced($key)) {
                if (isset($data->$key)) {
                    $value = $data->$key;
                } else {
                    $value = configuration::TOURDEFAULT;
                }
                if ($value === configuration::TOURDEFAULT) {
                    $this->set_config($key, null);
                } else {
                    $this->set_config($key, $value);
                }
            }
        }

        $this->persist();

        return $this;
    }

    /**
     * Attempt to fetch any matching langstring if the string is in the
     * format identifier,component.
     *
     * @deprecated since Moodle 4.0 MDL-72783. Please use helper::get_string_from_input() instead.
     * @param   string  $string
     * @return  string
     */
    public static function get_string_from_input($string) {
        debugging('Use of ' . __FUNCTION__ .
            '() have been deprecated, please update your code to use helper::get_string_from_input()', DEBUG_DEVELOPER);
        return helper::get_string_from_input($string);
    }

    /**
     * Attempt to replace PIXICON placeholder with the correct images for tour step content.
     *
     * @param string $content Tour content
     * @return string Processed tour content
     */
    public static function get_step_image_from_input(string $content): string {
        if (strpos($content, '@@PIXICON') === false) {
            return $content;
        }

        $content = preg_replace_callback('%@@PIXICON::(?P<identifier>([^::]*))::(?P<component>([^@@]*))@@%',
            function(array $matches) {
                global $OUTPUT;
                $component = $matches['component'];
                if ($component == 'moodle') {
                    $component = 'core';
                }
                return \html_writer::img($OUTPUT->image_url($matches['identifier'], $component)->out(false), '',
                    ['class' => 'img-fluid']);
            },
            $content
        );

        return $content;
    }
}
