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

/**
 * @package    block_ned_teacher_tools
 * @subpackage NED
 * @copyright  2022 NED {@link http://ned.ca}
 * @author     NED {@link http://ned.ca}
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace block_ned_teacher_tools\task;

use block_ned_teacher_tools\grading_tracker as GT;
use block_ned_teacher_tools\shared_lib as SH;

defined('MOODLE_INTERNAL') || die;
require_once(__DIR__. '/../../lib.php');

/**
 * Class adhoc_crongt_course_update
 * Do all record updates (check existing, add new ones) for GT table
 *
 * @package block_ned_teacher_tools\task
 */
class adhoc_crongt_course_update extends adhoc_base_crongt {
    use \local_ned_controller\task\base_task;

    const DATA_KEY_COURSES = 'course_ids';
    const DATA_KEY_INFO = 'info';

    protected $_blocking = true;
    protected $_concurrency_limit = 1;

    /**
     * Do task job without any checks
     *
     * @param array|object|adhoc_crongt_course_update|null $task_or_data
     *
     * @return void
     */
    static public function do_job($task_or_data=[]){
        $t_task_start = SH::hrtime();
        $data = static::get_data_from_task_or_data($task_or_data);
        $course_ids = $data[static::DATA_KEY_COURSES] ?? [];
        if (empty($course_ids)){
            static::print("There are no courses to check, pass.");
            return;
        }

        $info = $data[static::DATA_KEY_INFO] ?? [];
        $run_info = array_fill_keys(GT::STATUSES_UPD, 0);
        if (empty($info)){
            $info = $run_info;
            $info['task_times'] = [];
            $info['courses_times'] = [];
        } else {
            $info = (array)$info;
        }
        $i_task = count($info['task_times']) + 1;

        $config = GT::get_config();
        $courseid = 0;
        $debug = function($txt) use(&$courseid) {static::print("[C.$courseid] ".$txt);};

        $checked_courses = 0;
        $count_courses = count($course_ids);
        $courses_times = [];
        $i = 0;
        static::print("Task-$i_task: there are $count_courses courses to check.\n");

        if (empty($config->crongt_runtime)){
            $crongt_runtime = 12*HOURMINS;
        } else {
            $crongt_runtime = $config->crongt_runtime*MINSECS;
        }
        $timeout = time() + $crongt_runtime;

        while (time() < $timeout && !empty($course_ids)){
            $i++;
            $courseid = array_pop($course_ids);
            if (empty($courseid)) continue;

            try {
                if ($courseid == SITEID){
                    // Do global GT checks and continue
                    static::print('Do global GT checks');
                    GT::check_and_update_global_gt();
                    static::print("Global GT checks are done.\n");
                    continue;
                }

                $course_name = SH::q_course_link($courseid, true, true)."[$courseid]";
                if (!GT::is_gt_courseid($courseid)){
                    static::print("Course $course_name is not in the GT, pass");
                    continue;
                }

                $t_course_start = SH::hrtime();
                static::print("Check ($i/$count_courses) course: $course_name");

                static::print("Check and update $course_name suspended users");
                GT::check_and_update_course_suspended($courseid);

                static::print("Check $course_name records from active users");
                $res = GT::check_and_update_course($courseid, null, ['suspended = :suspended'], ['suspended' => 0], $debug);

                // IMPORTANT: Reset cache for checked course
                GT::reset_caches();

                foreach ($res as $st_key => $st_value){
                    if (empty($st_value) || $st_key == GT::UPD_ERROR) continue;

                    $run_info[$st_key] += $st_value;
                }

                if (!empty($res[GT::UPD_ERROR])){
                    static::print("There was an error during $course_name check: \n\"".$res[GT::UPD_ERROR].'"');
                } else {
                    $res['coursename'] = $course_name;
                    static::print(SH::str('gt_checked_adhoc_crongt_course_update', $res));
                }
            } catch (\Exception $e){
                $e_info = get_exception_info($e);
                $logerrmsg = "!!! Error !!!" .
                    "\nException handler: ".$e_info->message.
                    "\nDebug: ".$e_info->debuginfo."\n".format_backtrace($e_info->backtrace, true);
                static::print($logerrmsg, true);
                continue;
            }

            $t_course =  SH::hrtime() - $t_course_start;
            $courses_times[] = $t_course;

            static::print("Course check time: ".SH::time_diff_to_str_max_hr($t_course, 0)."\n");
            $checked_courses++;
        }

        // update info stats
        $t_task = SH::hrtime() - $t_task_start;
        $info['task_times'][] = $t_task;
        $info['courses_times'] = array_merge($info['courses_times'], $courses_times);
        $all_courses_checked = count($info['courses_times']);

        foreach ($run_info as $st_key => $st_value){
            if (empty($st_value) || $st_key == GT::UPD_ERROR) continue;

            $info[$st_key] += $st_value;
        }

        // some log info
        static::print('=== === === ===');
        $t_task_str = SH::time_diff_to_str_max_hr($t_task, 0);
        static::print("Task-$i_task is ending, after working for $t_task_str...");
        static::print("Successfully checked $checked_courses (now) & $all_courses_checked (all) GT course(s) so far.");

        // log course time checks
        if (!empty($course_ids)){
            $run_str = 'this run';
        } else {
            $courses_times = $info['courses_times'];
            $run_str = 'the all runs';
        }

        if (!empty($courses_times)){
            $t_average = array_sum($courses_times)/count($courses_times);
            $t_average_str = SH::time_diff_to_str_max_hr($t_average, 0);
            static::print("Average check time per course during $run_str: $t_average_str.");

            $t_max = SH::max(...$courses_times);
            $t_max_str = SH::time_diff_to_str_max_hr($t_max, 0);
            static::print("Max course check time during $run_str: $t_max_str.");
        }

        // Final
        if (!empty($course_ids)){
            // log current task and create new task for other courses
            $run_info['coursename'] = 'during this run';
            static::print(SH::str('gt_checked_adhoc_crongt_course_update', $run_info));

            $c_more = count($course_ids);
            static::print("Time is up for this task-$i_task, create the new one for the other $c_more course(s)");

            $custom_data = [static::DATA_KEY_COURSES => $course_ids, static::DATA_KEY_INFO => $info];
            static::add_new_job(1, $custom_data);
        } else {
            // log all tasks info and really the end
            static::print('=== === === === === === === === === === === ===');
            static::print("All courses checked");

            $info['coursename'] = 'during all runs';
            static::print(SH::str('gt_checked_adhoc_crongt_course_update', $info));

            $task_times = $info['task_times'] ?? [0];
            $task_times_sum = array_sum($task_times);
            $t_average = $task_times_sum/count($task_times);

            $t_average_str = SH::time_diff_to_str_max_hr($t_average, 0);
            static::print("Average check time per task: $t_average_str.");

            $task_times_sum_str = SH::time_diff_to_str_max_hr($task_times_sum, 0);
            static::print("All time for all tasks: $task_times_sum_str.");
        }
    }

    /**
     * Print log text about adding task
     *
     * @param string      $name - name of task
     * @param int|numeric $time_delay - time delay of task starting, in minutes
     * @param array|mixed $custom_data - some other data
     * @param static|null $task - added task
     */
    static protected function _print_adding_result($name, $time_delay, $custom_data, $task){
        $course_count = count($custom_data[static::DATA_KEY_COURSES] ?? []);
        if ($time_delay){
            static::print("Next run $name through $time_delay minutes for $course_count course(s).");
        } else {
            static::print("Queue $name task for $course_count course(s) (zero delay).");
        }
    }

    /**
     * Queue new task
     * Use it instead of just_add_new_job
     *
     * @param int|numeric $multiplier
     * @param array|mixed $course_ids_or_custom_data - array of $course_ids to check, or custom data to the task
     * @param bool        $check_same_tasks
     * @param null|static $task
     *
     * @return mixed|static|null
     * @noinspection PhpParameterNameChangedDuringInheritanceInspection
     */
    static public function add_new_job($multiplier=1, $course_ids_or_custom_data=null, $check_same_tasks=false, $task=null){
        if (empty(GT::get_gt_all_courseids())){
            static::print("There are no courses to check, pass.");
            return null;
        }

        if (is_array($course_ids_or_custom_data) && isset($course_ids_or_custom_data[static::DATA_KEY_COURSES])){
            $custom_data = $course_ids_or_custom_data;
            $course_ids = $course_ids_or_custom_data[static::DATA_KEY_COURSES];
        } else {
            $custom_data = [];
            $course_ids = $course_ids_or_custom_data;
        }

        if (is_null($course_ids)){
            // settings can be changed during the execution, so adding all courses to query
            $courses = SH::get_all_courses(true);
            $course_ids = array_keys($courses);
            // courses will be checked starting from the end
            $course_ids[] = SITEID;
        } else {
            if (empty($course_ids)){
                static::print("There are no courses to check, pass.");
                return null;
            }

            $course_ids = SH::val2arr($course_ids);
        }

        $custom_data[static::DATA_KEY_COURSES] = array_values($course_ids);
        return static::_crongt_add_new_job($multiplier, $custom_data, $check_same_tasks, $task);
    }
}
