<?php
// This file is part of the Studyplan plugin for Moodle
//
// 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 <https://www.gnu.org/licenses/>.

/**
 * No file description
 * @package    local_treestudyplan
 * @copyright  2023 P.M. Kuipers
 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace local_treestudyplan\form;
defined('MOODLE_INTERNAL') || die();

require_once($CFG->dirroot . '/repository/lib.php');
use local_treestudyplan\aggregator;
use local_treestudyplan\studyplan;
use local_treestudyplan\studyplanservice;
use local_treestudyplan\courseservice;
use local_treestudyplan\contextinfo;
use core\context;
use core\context\system as context_system;

/**
 * Moodleform class for the studyplan editor. A Moodleform is used here to facilitate a rich editor
 * in the studyplan description
 */
class studyplan_editform extends formbase {
    /**
     * Capability required to edit study plans
     * @var string
     */
    const CAP_EDIT = "local/treestudyplan:editstudyplan";

    /**
     * Translate parameters into customdata.
     *
     * @param object $params The parameters for form initialization
     * @return object Form data based on parameters
     */
    public static function init_customdata(object $params) {
        $customdata = new \stdClass();
        $customdata->create = $params->mode == 'create' ? true : false;
        if ($customdata->create) {
            $customdata->context = context::instance_by_id($params->contextid);
        } else {
            $customdata->plan = studyplan::find_by_id($params->studyplan_id);
            $customdata->context = $customdata->plan->context();
            $customdata->simplemodel = $customdata->plan->simple_model();
        }

        $customdata->editoroptions = [
            'trusttext' => true,
            'subdirs' => true,
            'maxfiles' => 20,
            'maxbytes' => 20 * 1024 * 1024,
            'context' => context_system::instance(), // Keep the files in system context.
        ];
        $customdata->fileoptions = [
            'subdirs' => 0,
            'maxbytes' => 10 * 1024 * 1024, // Max 10MiB should be sufficient for a picture.
            'areamaxbytes' => 10485760,
            'maxfiles' => 1, // Just one file.
            'accepted_types' => ['.jpg', '.png', '.jpeg', '.svg', '.svgz'],
            'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL,
        ];
        return $customdata;
    }

    /**
     * Generate form data from parameters
     * Also validate parameters and access permissions here
     *
     * @param object $customdata The parameters for form initialization
     * @return object Form data based on parameters
     */
    public function init_formdata(object $customdata) {
        global $DB;
        /*  Use direct database retrieval to avoid our abstractions causing trouble
            with existing moodle code assumptions.
            The form API does seem needlessly convoluted in it's use, but we need the editor...
        */
        if ($customdata->create) {
            $entry = new \stdClass();
            $entry->context_id = $customdata->context->id;
            $entry->aggregation = get_config("local_treestudyplan", "aggregation_mode");
            $agcfg = json_decode(aggregator::create($entry->aggregation, "")->config_string(), true);

            // Determine the next august 1st for default value purposes.
            $august = strtotime("first day of august this year");
            if ($august < time()) {
                $august = strtotime("first day of august next year");
            }
            $entry->startdate = $august;
            $entry->enddate = $august + (364 * 24 * 60 * 60); // Not bothering about leap years here.
            $entry->periods = 4;
        } else {
            $entry = $DB->get_record(studyplan::TABLE, ['id' => $customdata->plan->id()]);

            $entry->startdate = strtotime($customdata->simplemodel['pages'][0]['startdate']);
            $entry->enddate = strtotime($customdata->simplemodel['pages'][0]['enddate']);
            $entry->periods = $customdata->simplemodel['pages'][0]['periods'];

            $agcfg = json_decode($customdata->plan->aggregator()->config_string(), true);
        }

        // Prepare the editor.
        $entry = file_prepare_standard_editor(
            $entry,
            'description',
            $customdata->editoroptions,
            context_system::instance(),
            'local_treestudyplan',
            'studyplan',
            ($customdata->create) ? null : $customdata->plan->id()
        );

        // Prepare file area for the icon.
        // Get an unused draft itemid which will be used for this form.
        $draftitemid = file_get_submitted_draft_itemid('icon');
        file_prepare_draft_area(
            // The $draftitemid is the target location.
            $draftitemid,
            // The combination of contextid / component / filearea / itemid.
            // Form the virtual bucket that files are currently stored in.
            // And will be copied from.
            context_system::instance()->id,
            'local_treestudyplan',
            'icon',
            ($customdata->create) ? null : $customdata->plan->id(),
            $customdata->fileoptions
        );
        $entry->icon = $draftitemid;

        // Make sure proper defaults are set when switching aggregation methods.
        foreach (aggregator::list_model() as $method) {
            if (!$method["deprecated"]) {
                $cfg = json_decode($method["defaultconfig"], true);
                foreach ($cfg as $key => $val) {
                    $entrykey = $method['id'] . "_" . $key;
                    $entry->$entrykey = $val;
                }
            }
        }

        // Overwrite actual aggregation configs to entry.
        foreach ($agcfg as $key => $val) {
            $entrykey = $entry->aggregation . "_" . $key;
            $entry->$entrykey = $val;
        }

        return $entry;
    }

    /**
     * Set up the form definition
     */
    public function definition() {
        $mform = $this->_form;
        $customdata = (object)$this->_customdata;

        // Register integer type.
        text_integer::register();

        // Define the form.
        $field = 'name';
        $mform->addElement(
            'text',
            $field,
            get_string('studyplan_name', 'local_treestudyplan'),
            []
        );
        $mform->addRule($field, null, 'required', null, 'client');

        $field = 'shortname';
        $mform->addElement(
            'text',
            $field,
            get_string('studyplan_shortname', 'local_treestudyplan'),
            []
        );
        $mform->addRule($field, null, 'required', null, 'client');

        $field = 'idnumber';
        $mform->addElement(
            'text',
            $field,
            get_string('studyplan_idnumber', 'local_treestudyplan'),
            []
        );

        $contextlist = [];
        // Add system if the user has permissions.
        if (has_all_capabilities([courseservice::CAP_EDIT, 'moodle/category:viewcourselist'], context_system::instance())) {
            $contextlist[1] = get_string("coresystem");
        }
        // Add any other contexts the user has access to.
        foreach (courseservice::categories_by_capability(courseservice::CAP_EDIT) as $c) {
            $ctx = $c->get_context();
            $ci = new contextinfo($ctx);
            $contextlist[$ctx->id] = implode(" / ", $ci->path());
        }

        $mform->addElement(
            'autocomplete',
            'context_id',
            get_string('studyplan_context', 'local_treestudyplan'),
            $contextlist
        );

        $mform->addRule('context_id', null, 'required', null, 'client');

        $mform->addElement(
            'filemanager',
            'icon',
            get_string('studyplan_icon', 'local_treestudyplan'),
            null,
            $customdata->fileoptions
        );

        if ($customdata->create) {
            $timeless = \get_config("local_treestudyplan", "timelessperiods");
            if (!$timeless) {
                // Only add these fields if a new studyplan is created, to easily initialize the first page.
                $field = 'startdate';
                $mform->addElement(
                    'date_selector',
                    $field,
                    get_string('studyplan_startdate', 'local_treestudyplan'),
                    []
                );
                $mform->addRule($field, null, 'required', null, 'client');

                $field = 'enddate';
                $mform->addElement(
                    'date_selector',
                    $field,
                    get_string('studyplan_enddate', 'local_treestudyplan'),
                    []
                );
                $mform->addRule($field, null, 'required', null, 'client');
            }
            $field = 'periods';
            $mform->addElement(
                'text_integer',
                $field,
                get_string('studyplan_slots', 'local_treestudyplan'),
                ["unsigned" => true]
            );
            $mform->setType($field, PARAM_INT);
            $mform->addRule($field, null, 'required', null, 'client');
        } else {
            // These fields are only relevant if the studyplan is edited.
            $field = 'suspended';
            $mform->addElement(
                'advcheckbox',
                $field,
                get_string('studyplan_suspend', 'local_treestudyplan'),
                get_string('studyplan_suspend_details', 'local_treestudyplan'),
                []
            );

            $field = 'template';
            $mform->addElement(
                'advcheckbox',
                $field,
                get_string('studyplan_template', 'local_treestudyplan'),
                get_string('studyplan_template_details', 'local_treestudyplan'),
                []
            );
        }

        $aggregators = [];
        foreach (aggregator::list_model() as $a) {
            // Add method only if not deprecated or currently used.
            if ($customdata->simplemodel['aggregation'] == $a['id'] || !($a['deprecated'])) {
                $aggregators[$a['id']] = $a['name'];
            }
        }
        $field = 'aggregation';
        $mform->addElement(
            'select',
            $field,
            get_string('choose_aggregation_style', 'local_treestudyplan'),
            $aggregators
        );

        /* Start Bistate aggregation specific items */
        $field = 'bistate_thresh_excellent';
        $mform->addElement(
            'text_integer',
            $field,
            get_string('setting_bistate_thresh_excellent', 'local_treestudyplan'),
            ["unsigned" => false]
        );
        $mform->setType($field, PARAM_INT);
        $mform->hideif($field, "aggregation", "neq", "bistate");

        $field = 'bistate_thresh_good';
        $mform->addElement(
            'text_integer',
            $field,
            get_string('setting_bistate_thresh_good', 'local_treestudyplan'),
            ["unsigned" => true]
        );
        $mform->setType($field, PARAM_INT);
        $mform->hideif($field, "aggregation", "neq", "bistate");

        $field = 'bistate_thresh_completed';
        $mform->addElement(
            'text_integer',
            $field,
            get_string('setting_bistate_thresh_completed', 'local_treestudyplan'),
            ["unsigned" => true]
        );
        $mform->setType($field, PARAM_INT);
        $mform->hideif($field, "aggregation", "neq", "bistate");

        $field = 'bistate_use_failed';
        $mform->addElement(
            'checkbox',
            $field,
            get_string('setting_bistate_support_failed', 'local_treestudyplan'),
            []
        );
        $mform->hideif($field, "aggregation", "neq", "bistate");

        $field = 'bistate_accept_pending_as_submitted';
        $mform->addElement(
            'checkbox',
            $field,
            get_string('setting_bistate_accept_pending_submitted', 'local_treestudyplan'),
            []
        );
        $mform->hideif($field, "aggregation", "neq", "bistate");

        /* End Bistate aggregation specific items */

        /* Begin competency aggregation specific items */

        $field = 'competency_thresh_completed';
        $mform->addElement(
            'text_integer',
            $field,
            get_string('setting_competency_thresh_completed', 'local_treestudyplan'),
            ["unsigned" => true]
        );
        $mform->setType($field, PARAM_INT);
        $mform->hideif($field, "aggregation", "neq", "competency");

        $field = 'competency_use_failed';
        $mform->addElement(
            'checkbox',
            $field,
            get_string('setting_competency_support_failed', 'local_treestudyplan'),
            []
        );
        $mform->hideif($field, "aggregation", "neq", "competency");

        /* End competency aggregation specific items */

        $mform->addElement(
            'editor',
            'description_editor',
            get_string('studyplan_description', 'local_treestudyplan'),
            null,
            $customdata->editoroptions
        );
        $mform->setType('description_editor', PARAM_RAW);
    }

    /**
     * Process the submitted data and perform necessary actions
     * @param object $entry The processed form data;
     * @return mixed Data to pass to receiver if submission successful
     * @throws \moodle_exception if an error must be given for a specific reason.
     */
    protected function process_submitted_data($entry) {
        $customdata = (object)$this->_customdata;

        // Add aggregation configs to entry.
        // Retrieve default config string from selected aggregation method.
        $agcfg = json_decode(aggregator::create($entry->aggregation, "")->config_string(), true);
        if (!empty($agcfg)) {
            foreach (array_keys($agcfg) as $key) {
                $entrykey = $entry->aggregation . "_" . $key;
                $agcfg[$key] = $entry->$entrykey;
            }
            $aggregationconfig = json_encode($agcfg);
        }

        if ($customdata->create) {
            // Use our own abstraction to create the record, so caches are maintained.
            $plan = studyplan::add([
                'name' => $entry->name,
                'shortname' => $entry->shortname,
                'idnumber' => $entry->idnumber,
                'context_id' => $entry->context_id,
                'aggregation' => $entry->aggregation,
                'aggregation_config' => $aggregationconfig,
                'startdate' => date("Y-m-d", $entry->startdate),
                'enddate' => date("Y-m-d", $entry->enddate),
                'periods' => $entry->periods,
            ]);
            // Process the provided files in the description editor.
            $entry = file_postupdate_standard_editor(
                $entry,
                'description',
                $customdata->editoroptions,
                context_system::instance(),
                'local_treestudyplan',
                'studyplan',
                $plan->id()
            );
            // Update the description.
            $plan->edit([
                'description' => $entry->description,
                'descriptionformat' => $entry->descriptionformat,
            ]);
        } else {
            $plan = $customdata->plan;

            // Process the provided files in the description editor.
            $entry = file_postupdate_standard_editor(
                $entry,
                'description',
                $customdata->editoroptions,
                context_system::instance(),
                'local_treestudyplan',
                'studyplan',
                $plan->id()
            );

            // Use our own abstraction to update the record, so caches are maintained.
            $plan->edit([
                'name' => $entry->name,
                'shortname' => $entry->shortname,
                'idnumber' => $entry->idnumber,
                'context_id' => $entry->context_id,
                'description' => $entry->description,
                'descriptionformat' => $entry->descriptionformat,
                'aggregation' => $entry->aggregation,
                'aggregation_config' => $aggregationconfig,
                'suspended' => $entry->suspended,
                'template' => $entry->template,
            ]);
        }

        // Now save the icon file in correct part of the File API.
        file_save_draft_area_files(
            // The $entry->icon property contains the itemid of the draft file area.
            $entry->icon,
            // The combination of contextid / component / filearea / itemid.
            // Form the virtual bucket that file are stored in.
            context_system::instance()->id,
            'local_treestudyplan',
            'icon',
            $plan->id(),
            $customdata->fileoptions
        );

        /* Return the simple model of the plan to make sure we can update stuff.
           Parse it through the clean_returnvalue function of exernal api (of which studyplanservice is a subclass)
           so we return it in a consistent way
        */
        $response = studyplanservice::clean_returnvalue(studyplan::simple_structure(), $plan->simple_model());
        return $response;
    }
}
