<?php
/**
 * @package    block_ned_teacher_tools
 * @subpackage output
 * @copyright  2023 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\output;
use block_ned_teacher_tools\shared_lib as SH;
use block_ned_teacher_tools\output\menu_bar as MB;
use local_ned_controller\marking_manager\marking_manager as MM;
use block_ned_teacher_tools\cta_tracker;
use local_ned_controller\marking_manager\mm_data_by_activity_user;

/**
 * Class cta_tracker_render
 *
 * Useful methods:
 *  Render class for page: {@see cta_tracker_render::render_full_page()}
 *
 *  Main render content method: {@see cta_tracker_render::_render_page_content()}
 *  Main get_data method: {@see cta_tracker_render::get_table_data()}
 *  Main get_data SQL method: {@see cta_tracker_render::_mm_get_data()}
 *
 *  Start init point: {@see cta_tracker_render::init()}
 *  Main check params: {@see cta_tracker_render::_check_params()}
 *  Export content for template: {@see cta_tracker_render::export_for_template()}
 *
 *
 * @package block_ned_teacher_tools\output
 *
 */
class cta_tracker_render extends \local_ned_controller\output\ned_base_table_page_render {
    //region Config to set up base class work
    protected const _PLUGIN = SH::TT;
    protected const _USE_COURSE_VIEW = false;
    //endregion

    //region SQL data
    //endregion

    //region Params
    const PARAMS = [
        SH::PAR_CM,
        SH::PAR_STATUS,
        SH::PAR_ACTION,
        SH::PAR_PERPAGE,
        SH::PAR_PAGE,
    ];

    const MB_PARAMS = [
        MB::PAR_COURSE,
        MB::PAR_ID,
        MB::PAR_GROUP,
        MB::PAR_USER,
        MB::PAR_SETUSER,
        MB::PAR_SHOWINACTIVE,
        MB::PAR_CMSTUDENTS,
        MB::PAR_LOCKUSER,
        MB::PAR_PREVPAGE,
    ];

    /**
     * By default, params uses _PARAM_TYPE_DEFAULT and _PARAM_VALUE_DEFAULT, but you can set defaults to others here
     * Possible keys for rewrite array:
     *  • type - change default type of param
     *  • default - change default value of param
     *  • property - load raw param value for $this as $this->{$property}
     * Also {@see \local_ned_controller\shared\C::PARAM_DATA}
     *
     * @var array[] - keys from PARAMS, value is array
     */
    const PARAM_DATA = [
        SH::PAR_STATUS => ['type' => PARAM_ALPHANUMEXT, 'default' => SH::ALL, 'property' => '_status'],
    ];
    //endregion

    //region Status
    const STATUS_UNMARKED = MM::ST_UNMARKED; // haven't grade, but need it
    const STATUS_UNSUBMITTED = MM::ST_UNSUBMITTED;
    const STATUS_MARKED = MM::ST_MARKED;
    const STATUSES = [
        self::STATUS_UNMARKED       => 'waitingforapproval',
        self::STATUS_UNSUBMITTED    => 'nosubmission',
        self::STATUS_MARKED         => 'marked',
    ];
    //endregion

    //region Action
    const ACTION_NONE = 0;
    const ACTION_CHECK_CTA = 1;
    //endregion

    //region Other Consts
    const MOD_ALL_ASSIGNS = -1;
    const MOD_ALL_FORUMS = -2;
    /** use all keys from {@see cta_tracker::CM_TYPES}*/
    const MODS_ALL = [
        SH::MOD_ASSIGN => self::MOD_ALL_ASSIGNS,
        SH::MOD_FORUM => self::MOD_ALL_FORUMS,
    ];
    //endregion

    //region Properties
    /** @var MB */
    protected $_MB;
    /** @var MM */
    protected $_MM;

    /** @var int|string */
    protected $_status = SH::ALL;
    protected $_scale_menu = [];
    //endregion

    //region Init methods
    /**
     * @constructor
     */
    public function __construct(){
        parent::__construct();
        $this->_course_view = false; // skip course-page setting
    }

    /**
     * Init class, send here additional params if you need
     * Start chains of checks and rendering
     *
     * @param bool  $setup_page      - if true, setup page for view
     * @param array $params          - default page params
     * @param bool  $load_url_params - if true, also load param from url options
     */
    public function init($setup_page=true, $params=[], $load_url_params=true){
        $this->_MB = new MB(MB::PAGE_CTA);
        $this->_courseid = $this->_MB->courseid;
        $this->_ctx = SH::ctx($this->_courseid);

        $this->_MM = MM::get_MM_by_params($this->_MB->parameters_for_MM(), true, false);

        $this->_cap_view = cta_tracker::can_view_tracker($this->courseid, $this->viewerid);
        $this->_cap_edit = cta_tracker::can_grade($this->courseid, $this->viewerid);
        $this->_scale_menu = cta_tracker::get_scale_menu($this->courseid);

        $this->set_and_check_params(true, $params, $load_url_params);
        $this->_MB->update_pageparams($this->_params);

        if ($setup_page){
            $this->setup_page();
        }
    }

    /**
     * Check loaded params
     * Normally called by {@see set_and_check_params()} after param loading
     */
    protected function _check_params(){
        parent::_check_params();
        if (empty($this->_cmid)){
            $this->_cmid = static::MOD_ALL_ASSIGNS;
        }
        if (!empty($this->_status)){
            $options = $this->get_status_options();
            $this->_status = SH::isset_key($options, $this->_status, SH::ALL);
        }
        if (!empty($this->_action)){
            if (!$this->_is_admin){
                $this->_action = static::ACTION_NONE;
            }
        }
    }
    //endregion

    //region Setup Page methods
    /**
     * @return string
     */
    public function get_page_title(){
        return SH::str('ctaactivities');
    }
    //endregion

    //region URL methods
    /**
     * Params for URL export
     *
     * @return array
     */
    protected static function _get_params_url(){
        return array_merge(static::PARAMS, static::MB_PARAMS);
    }

    /**
     * Get params for URL
     *
     * @param array $add_params
     *
     * @return array
     */
    protected function _get_url_params($add_params=[]){
        return array_merge($this->_MB->pageparams, $this->_params, $add_params);
    }

    /**
     * Return page url
     *
     * @param array $params
     * @param array $not_usual_params - url params, which are not from static::PARAMS
     *
     * @return \moodle_url
     */
    static public function get_url($params=[], $not_usual_params=[]){
        $url = MB::get_page_url(MB::PAGE_CTA);
        return new \moodle_url($url, static::_check_url_param($params, $not_usual_params));
    }
    //endregion

    //region Get filter options [get_*_options()]
    /**
     * @return array
     */
    public function get_cm_options(){
        $data = $this->_static_data[__FUNCTION__] ?? null;
        if (is_null($data)){
            $raw_data = SH::cmids_get_cta($this->_courseid);

            $data = [
                static::MOD_ALL_ASSIGNS => SH::str('allassigns'),
                static::MOD_ALL_FORUMS => SH::str('allforums'),
            ];
            foreach ($raw_data as $cmid){
                $cm = SH::get_cm_by_cmid($cmid, $this->_courseid);
                if (!$cm) continue;
                if (empty(cta_tracker::CM_TYPES[$cm->modname])) continue;

                $data[$cm->id] = strip_tags($cm->get_formatted_name());
            }
            $this->_static_data[__FUNCTION__] = $data;
        }

        return $data;
    }

    /**
     * @return array
     */
    public function get_status_options(){
        $data = $this->_static_data[__FUNCTION__] ?? null;
        if (is_null($data)){
            $data = static::get_list_as_options(static::STATUSES, true);
            $data += $this->_scale_menu;
            $this->_static_data[__FUNCTION__] = $data;
        }
        return $data;
    }

    /**
     * @return array
     */
    public function get_users_options(){
        return $this->_MB->allstudents;
    }

    /**
     * Get student class options
     *
     * @return array
     */
    public function get_group_options(){
        return $this->_MB->allgroups;
    }

    /**
     * @deprecated
     * @return array
     */
    public function get_graders_options(){
        return [];
    }

    /**
     * @deprecated
     * @return array
     */
    public function get_schools_options(){
        return [];
    }

    /**
     * @deprecated
     * @return array
     */
    public function get_courses_options(){
        return [];
    }
    //endregion

    //region Get main table data methods
    /**
     * Get data from the DB for main table
     * Also can update some pager data, {@see _db_calc_count_and_get_limitfrom()}
     *
     * @param array|string $select
     * @param array|string $joins
     * @param array|string $where
     * @param array        $params
     * @param array|string $groupby
     * @param array|string $orderby
     * @param bool         $set_limit - if true, uses page limitations to query
     *
     * @return array
     * @deprecated
     */
    public function get_db_table_data($select='', $joins=[], $where=[], $params=[], $groupby=[], $orderby=[], $set_limit=false){
        SH::debugging('Method "'.__FUNCTION__.'()" is deprecated');
        return [];
    }

    /**
     * Process SQL query for the getting data from the DB for main table
     *
     * @param array|string $select
     * @param array|string $joins
     * @param array|string $where
     * @param array        $params
     * @param array|string $groupby
     * @param array|string $orderby
     *
     * @return bool - saves result in the params, return true if you can continue query
     * @deprecated
     */
    protected function _db_process_sql_query(&$select=[], &$joins=[], &$where=[], &$params=[], &$groupby=[], &$orderby=[]){
        SH::debugging('Method "'.__FUNCTION__.'()" is deprecated');
        return false;
    }

    /**
     * Count all records by SQL conditions and return data from the DB for main table
     * Also can update some pager data, {@see _db_calc_count_and_get_limitfrom()}
     * Normally called from the {@see get_db_table_data()}
     *
     * @param array|string $select
     * @param array|string $joins
     * @param array|string $where
     * @param array        $params
     * @param array|string $groupby
     * @param array|string $orderby
     * @param bool         $set_limit - if true, uses page limitations to query
     *
     * @return array
     * @deprecated
     */
    protected function _db_calc_count_and_get_table_data($select=[], $joins=[], $where=[], $params=[], $groupby=[], $orderby=[], $set_limit=false){
        SH::debugging('Method "'.__FUNCTION__.'()" is deprecated');
        return [];
    }

    /**
     * Get data from the MM
     *
     * @return array|mm_data_by_activity_user[]|object[]
     */
    protected function _mm_get_data(){
        if (!$this->_MB->has_group_selected()) return [];

        $mod_type = SH::ASSIGN;
        $status_all = true;
        $filter = [
            MM::GET_GRADE_COMMENT => true,
            MM::BY_ACTIVITY_USER => true,
            MM::TAG_NAME_LIST_HAVE_ANY => [SH::TAG_CTA],
            MM::CTA_ORDER => true,
            MM::GET_COUNT => true,
            MM::LIMIT_FROM => $this->_page * $this->_perpage,
            MM::LIMIT_NUM => $this->_perpage,
        ];

        if ($this->_cmid > 0){
            $cm = SH::get_cm_by_cmid($this->_cmid, $this->_courseid);
            if ($cm){
                if (isset(cta_tracker::CM_TYPES[$cm->modname])){
                    $mod_type = $cm->modname;
                    $filter[MM::COURSEMODULE_IDS] = $cm->id;
                }
            }
        } else {
            switch ($this->_cmid){
                default:
                case static::MOD_ALL_ASSIGNS:
                    /** @noinspection PhpConditionAlreadyCheckedInspection */
                    $mod_type = SH::ASSIGN;
                    break;
                case static::MOD_ALL_FORUMS:
                    $mod_type = SH::FORUM;
                    break;
            }
        }

        if (!empty($this->_status)){
            if (isset(static::STATUSES[$this->_status])){
                $status_all = false;
                $filter[$this->_status] = true;
            } elseif (is_numeric($this->_status)){
                $grade_filter = MM::F_GRADE_EQ;
                if ($this->_status == array_key_first($this->_scale_menu)){
                    $grade_filter = MM::F_GRADE_LTE;
                } elseif ($this->_status == array_key_last($this->_scale_menu)){
                    $grade_filter = MM::F_GRADE_GTE;
                }
                $filter[$grade_filter] = $this->_status;
            }
        }

        if ($status_all){
            $filter[MM::ST_ALL] = true;
        }

        $this->_MM->load_types($mod_type);
        $data = $this->_MM->get_raw_data($filter, true);
        $this->_total_count = $this->_MM->get_records_count();
        if ($this->_perpage*$this->_page >= $this->_total_count){
            $this->_page = floor(($this->_total_count-1)/$this->_perpage);
        }

        return $data;
    }

    /**
     * Get and render data for main page table
     * @see get_db_table_data() for DB information
     * @see _process_table_raw_records() for process/render DB records
     *
     * @param bool $only_keys - if true, return only records key (normally it will be listed of record ids)
     *
     * @return array|mm_data_by_activity_user[]|object[] - list of records or keys
     */
    public function get_table_data($only_keys=false){
        if (isset($this->_static_data[__FUNCTION__])){
            $records = $this->_static_data[__FUNCTION__];
            if ($only_keys){
                return array_keys($records);
            }

            return $records;
        }

        if (!$this->can_see()){
            return [];
        }


        $raw_records = $this->_mm_get_data();
        if ($only_keys){
            return array_keys($raw_records);
        }

        $records = $this->_process_table_raw_records($raw_records);
        $this->_static_data[__FUNCTION__] = $records;
        return $records;
    }

    /**
     * Process raw records from the DB and return rendered result
     * Normally called from the {@see get_table_data()}
     *
     * @param array|mm_data_by_activity_user[]|object[] $raw_records
     *
     * @return array
     */
    protected function _process_table_raw_records(&$raw_records=[]){
        $records = [];
        $format_options = ['context' => $this->_ctx];
        $only_unmarked = $this->_status === static::STATUS_UNMARKED;
        $only_unsubmitted = $this->_status === static::STATUS_UNSUBMITTED;

        $single_groupid = $this->_MB->has_specific_group_selected() ? $this->_MB->groupid : false;
        $ct_ids = SH::get_classroom_teachers_ids($this->_courseid, $single_groupid, null, !$this->_MB->show_inactive);
        $constant_cts = null;
        if ($single_groupid){
            $constant_cts = [];
            foreach ($ct_ids as $ct_id){
                $constant_cts[] = SH::q_user_link($ct_id, $this->_courseid);
            }
            $constant_cts = join(', ', $constant_cts);
        }

        foreach ($raw_records as $r){
            $record = (object)[];
            $record->classname = '';
            $record->ct = '';

            if (!empty($this->_MB->students[$r->userid]->group->name)){
                $group = $this->_MB->students[$r->userid]->group;
                $record->classname = SH::link($this->_MB->get_url(null, [MB::PAR_GROUP => $group->id]), $group->name);

                if ($single_groupid){
                    $record->ct = $constant_cts;
                } elseif (!empty($ct_ids[$this->_MB->students[$r->userid]->group->id])){
                    $cts = [];
                    foreach ($ct_ids[$this->_MB->students[$r->userid]->group->id] as $ct_id){
                        $cts[] = SH::q_user_link($ct_id, $this->_courseid);
                    }
                    $record->ct = join(', ', $cts);
                }
            }

            $record->cm = SH::q_cm_student_link($r->cmid, $r->userid, $this->_courseid);
            $record->student = SH::q_user_link($r->userid, $this->_courseid);
            $record->grade = round($r->finalgrade);
            $record->grader = '';

            // Grade
            $grade_link = true;
            if ($only_unmarked || !empty($r->unmarked)){
                // Require grade
                $record->status_class = 'unmarked';
                $grade_text = SH::str('waitingforapproval');
            } elseif (!isset($r->finalgrade) && ($only_unsubmitted || !empty($r->unsubmitted))){
                // Not submitted
                $record->status_class = 'unsubmitted';
                $grade_text = SH::str('nosubmission');
                $grade_link = false;
            } else {
                // Graded
                $record->status_class = 'graded';
                if (empty($this->_scale_menu)){
                    $grade_text = isset($r->finalgrade) ? round($r->finalgrade) : '';
                } else {
                    $grade_text = SH::grade_get_scale_item_by_grade($this->_scale_menu, $r->finalgrade ?? null) ?? '';
                }
                $record->grader = $r->graderid ? SH::q_user_link($r->graderid, $this->_courseid) : '';
            }

            if ($grade_link){
                $record->status = SH::link(SH::cm_get_grader_url($r->cmid, $r->userid, $this->_courseid), $grade_text, '', ['target' => '_blank']);
            } else {
                $record->status = $grade_text;
            }

            // Feedback
            if (!empty($r->grade_feedback)){
                $record->comment = format_text($r->grade_feedback, $r->grade_feedback_format, $format_options);
            } else {
                $record->comment = '';
            }

            $records[$r->uniqid] = $record;
        }

        return $records;
    }
    //endregion

    //region Main render page content methods
    /**
     * Init some content values before {@see export_for_template()}
     * Normally calling from the {@see export_for_template()}
     */
    protected function _before_start_export(){
        parent::_before_start_export();
    }

    /**
     * Some last content changes at final {@see export_for_template()}
     * Normally calling from the {@see export_for_template()}
     */
    protected function _before_finish_export(){
        parent::_before_finish_export();
        if ($this->content->has_data){
            SH::$C::js_call_amd('add_sorter','add_sort', ['table.nedtable']);
        }
    }

    /**
     * Call this method before header output, then you can redirect here
     */
    public function before_header_output(){
        switch ($this->_action){
            default:
            case static::ACTION_NONE:
                break;

            case static::ACTION_CHECK_CTA:
                if (!$this->_is_admin) break;

                try {
                    cta_tracker::update_cta_graders_by_course($this->_courseid);
                    [$msg, $msg_type] = [SH::str('cta_graders_update_success'), SH::NOTIFY_SUCCESS];
                } catch (\Throwable $e){
                    $e_info = get_exception_info($e);
                    [$msg, $msg_type] = [SH::str('cta_graders_update_error', $e_info->message), SH::NOTIFY_ERROR];
                }

                SH::redirect($this->get_my_url([SH::PAR_ACTION => static::ACTION_NONE]), $msg, null, $msg_type);
                die;
        }
    }

    /**
     * Call this method after header output, you can add something between header and main page content
     */
    public function after_header_output(){
        $this->_MB->render();
    }

    /**
     * Render page content
     * Normally calling from the {@see export_for_template()}
     * You can rewrite this method and not changing original {@see export_for_template()}
     */
    protected function _render_page_content(){
        if (!cta_tracker::is_tracker_on($this->_courseid)){
            $this->add_notification(SH::str('ctatrackerdisabled'), SH::NOTIFY_WARNING);
            return;
        }
        if (!$this->_MB->has_group_selected()) return;

        $this->content->show_data = true;
        $this->content->data = array_values($this->get_table_data());

        $this->content->show_class = $this->_MB->groupid == SH::GROUP_ALL && count($this->_MB->allgroups) > 1;


        $this->content->control_panel1[] = $this->r_status_selector();
        $this->content->control_panel1[] = $this->r_cm_selector();
        // should be last in the panel
        $this->content->control_panel1[] = $this->r_cta_update_button();

        if ($this->_total_count > static::PERPAGES[0]){
            $this->content->control_panel3[] = $this->r_perpage_selector();
        }
        $this->content->pager = $this->r_pager();
    }
    //endregion

    //region Render utils methods, including r_*_selector() functions (filter selectors)
    /**
     * @return string
     */
    public function r_status_selector(){
        $options = $this->get_status_options();
        if (!empty($options)){
            return SH::single_select($this->get_my_url(), SH::PAR_STATUS, $options, $this->_status, SH::str('submissionstatus'));
        }
        return '';
    }

    /**
     * @return string
     */
    public function r_cta_update_button(){
        if (!$this->_is_admin) return '';

        return SH::button_link($this->get_my_url([SH::PAR_ACTION => static::ACTION_CHECK_CTA]), 'cta_graders_update', 'ml-auto');
    }
    //endregion

    //region Main rendering class/page methods
    /** For render_full_page {@see static::render_full_page()} */
    //endregion
}
