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

/**
 * The mod_bigbluebuttonbn files helper
 *
 * @package   mod_bigbluebuttonbn
 * @copyright 2021 onwards, Blindside Networks Inc
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 * @author    Laurent David  (laurent [at] call-learning [dt] fr)
 */

namespace mod_bigbluebuttonbn\local\helpers;

use cache;
use cache_store;
use context;
use context_module;
use context_system;
use mod_bigbluebuttonbn\instance;
use moodle_url;
use stdClass;

/**
 * Utility class for all files routines helper
 *
 * @package mod_bigbluebuttonbn
 * @copyright 2021 onwards, Blindside Networks Inc
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class files {

    /**
     * Helper for validating pluginfile.
     *
     * @param stdClass $context context object
     * @param string $filearea file area
     *
     * @return bool|null false if file not valid
     */
    public static function pluginfile_valid(stdClass $context, string $filearea): ?bool {

        // Can be in context module or in context_system (if is the presentation by default).
        if (!in_array($context->contextlevel, [CONTEXT_MODULE, CONTEXT_SYSTEM])) {
            return false;
        }

        if (!array_key_exists($filearea, self::get_file_areas())) {
            return false;
        }

        return true;
    }

    /**
     * Helper for getting pluginfile.
     *
     * @param stdClass|null $course course object
     * @param stdClass|null $cm course module object
     * @param context $context context object
     * @param string $filearea file area
     * @param array $args extra arguments
     *
     * @return \stored_file|bool
     */
    public static function pluginfile_file(?stdClass $course, ?stdClass $cm, context $context, string $filearea, array $args) {
        $filename = self::get_plugin_filename($course, $cm, $context, $args);
        if (!$filename) {
            return false;
        }
        $fullpath = "/$context->id/mod_bigbluebuttonbn/$filearea/0/" . $filename;
        $fs = get_file_storage();
        $file = $fs->get_file_by_hash(sha1($fullpath));
        if (!$file || $file->is_directory()) {
            return false;
        }
        return $file;
    }

    /**
     * Get a full path to the file attached as a preuploaded presentation
     * or if there is none, set the presentation field will be set to blank.
     *
     * @param stdClass $bigbluebuttonformdata BigBlueButtonBN form data
     * Note that $bigbluebuttonformdata->presentation is the id of the filearea whereas the bbb instance table
     * stores the file name/path
     * @return string
     */
    public static function save_media_file(stdClass &$bigbluebuttonformdata): string {
        if (!isset($bigbluebuttonformdata->presentation) || $bigbluebuttonformdata->presentation == '') {
            return '';
        }
        $context = context_module::instance($bigbluebuttonformdata->coursemodule);
        // Set the filestorage object.
        $fs = get_file_storage();
        // Save the file if it exists that is currently in the draft area.
        file_save_draft_area_files($bigbluebuttonformdata->presentation, $context->id, 'mod_bigbluebuttonbn', 'presentation', 0);
        // Get the file if it exists.
        $files = $fs->get_area_files(
            $context->id,
            'mod_bigbluebuttonbn',
            'presentation',
            0,
            'itemid, filepath, filename',
            false
        );
        // Check that there is a file to process.
        $filesrc = '';
        if (count($files) == 1) {
            // Get the first (and only) file.
            $file = reset($files);
            $filesrc = '/' . $file->get_filename();
        }
        return $filesrc;
    }

    /**
     * Helper return array containing the file descriptor for a preuploaded presentation.
     *
     * @param context $context
     * @param string $presentation matching presentation file name
     * @param int $id bigbluebutton instance id
     * @param bool $withnonce add nonce to the url
     * @return array|null the representation of the presentation as an associative array
     */
    public static function get_presentation(context $context, string $presentation, $id = null, $withnonce = false): ?array {
        global $CFG;
        $fs = get_file_storage();
        $files = [];
        $defaultpresentation = $fs->get_area_files(
            context_system::instance()->id,
            'mod_bigbluebuttonbn',
            'presentationdefault',
            0,
            "filename",
            false
        );
        $activitypresentation = $files = $fs->get_area_files(
            $context->id,
            'mod_bigbluebuttonbn',
            'presentation',
            false,
            'itemid, filepath, filename',
            false
        );
        // Presentation upload logic based on config settings.
        if (empty($defaultpresentation)) {
            if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
                return null;
            }
            $files = $activitypresentation;

        } else {
            if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
                $files = $defaultpresentation;
                $id = null;
            } else {
                $files = $activitypresentation;
            }
        }
        $pnoncevalue = 0;
        if ($withnonce) {
            $nonceid = 0;
            if (!is_null($id)) {
                $instance = instance::get_from_instanceid($id);
                $nonceid = $instance->get_instance_id();
            }
            $pnoncevalue = self::generate_nonce($nonceid);
        }

        $file = null;
        foreach ($files as $f) {
            if (basename($f->get_filename()) == basename($presentation)) {
                $file = $f;
            }
        }
        if (!$file && !empty($files)) {
            $file = reset($files);
        }
        if (empty($file)) {
            return null; // File was not found.
        }

        // Note: $pnoncevalue is an int.
        $url = moodle_url::make_pluginfile_url(
            $file->get_contextid(),
            $file->get_component(),
            $file->get_filearea(),
            $withnonce ? $pnoncevalue : null, // Hack: item id as a nonce.
            $file->get_filepath(),
            $file->get_filename()
        );
        return [
            'icondesc' => get_mimetype_description($file),
            'iconname' => file_file_icon($file),
            'name' => $file->get_filename(),
            'url' => $url->out(false),
        ];
    }

    /**
     * Helper for getting pluginfile name.
     *
     * @param stdClass|null $course course object
     * @param stdClass|null $cm course module object
     * @param context $context context object
     * @param array $args extra arguments
     *
     * @return string|null
     */
    public static function get_plugin_filename(?stdClass $course, ?stdClass $cm, context $context, array $args): ?string {
        global $DB;
        if ($context->contextlevel != CONTEXT_SYSTEM) {
            // Plugin has a file to use as default in general setting.
            // The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
            // - Context is system, so we don't need to check the cmid in this case.
            // - The area is "presentationdefault_cache".
            if (!$DB->get_record('bigbluebuttonbn', ['id' => $cm->instance])) {
                return null;
            }
        }
        // Plugin has a file to use as default in general setting.
        // The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
        // - Context is system, so we don't need to check the cmid in this case.
        // - The area is "presentationdefault_cache".
        if (count($args) > 1) {
            $id = 0;
            if ($cm) {
                $instance = instance::get_from_cmid($cm->id);
                $id = $instance->get_instance_id();
            }
            $actualnonce = self::get_nonce($id);
            return ($args['0'] == $actualnonce) ? $args['1'] : null;

        }
        if (!empty($course)) {
            require_course_login($course, true, $cm, true, true);
        } else {
            require_login(null, true, $cm, true, true);
        }

        // If the file belongs to a course module, ensure user can join the activity.
        if ($cm !== null && !has_capability('mod/bigbluebuttonbn:join', $context)) {
            return null;
        }

        return implode('/', $args);
    }

    /**
     * Helper generates a salt used for the preuploaded presentation callback url.
     *
     * @param int $id
     * @return int
     */
    protected static function get_nonce(int $id): int {
        $cache = static::get_nonce_cache();
        $pnoncekey = sha1($id);
        $existingnoncedata = $cache->get($pnoncekey);
        if ($existingnoncedata) {
            if ($existingnoncedata->counter > 0) {
                $existingnoncedata->counter--;
                $cache->set($pnoncekey, $existingnoncedata);
                return $existingnoncedata->nonce;
            }
        }
        // The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
        // the file once.
        return static::generate_nonce($id);
    }

    /**
     * Generate a nonce and store it in the cache
     *
     * @param int $id
     * @return int
     */
    protected static function generate_nonce($id): int {
        $cache = static::get_nonce_cache();
        $pnoncekey = sha1($id);
        // The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
        // the file once.
        $pnoncevalue = ((int) microtime()) + mt_rand();
        $cache->set($pnoncekey, (object) ['nonce' => $pnoncevalue, 'counter' => 2]);
        return $pnoncevalue;
    }

    /**
     * Get cache for nonce
     *
     * @return \cache_application|\cache_session|cache_store
     */
    private static function get_nonce_cache() {
        return cache::make_from_params(
            cache_store::MODE_APPLICATION,
            'mod_bigbluebuttonbn',
            'presentation_cache'
        );
    }

    /**
     * Returns an array of file areas.
     *
     * @return array a list of available file areas
     *
     */
    protected static function get_file_areas(): array {
        $areas = [];
        $areas['presentation'] = get_string('mod_form_block_presentation', 'bigbluebuttonbn');
        $areas['presentationdefault'] = get_string('mod_form_block_presentation_default', 'bigbluebuttonbn');
        return $areas;
    }

}
