<?php
// This file is part of mod_datalynx for Moodle - http://moodle.org/
//
// It 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.
//
// It 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/>.

/**
 *
 * Contains class customfilter_manager
 *
 * @package mod_datalynx
 * @copyright 2016 Thomas Niedermaier
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace mod_datalynx\local\filter;
use html_table;
use html_writer;
use mod_datalynx\form\datalynx_customfilter_backend_form;
use moodle_url;
use stdClass;

/**
 * Class manager
 *
 * @package mod_datalynx\customfilter
 */
class datalynx_customfilter_manager {
    /** @var int Maximum number of user filters allowed. */
    const USER_FILTER_MAX_NUM = 5;

    /** @var int Constant representing a blank filter. */
    const BLANK_FILTER = -1;

    /** @var int Constant representing a user filter set. */
    const USER_FILTER_SET = -2;

    /** @var int Starting id for user filters. */
    const USER_FILTER_ID_START = -10;

    /** @var object The datalynx instance. */
    protected $dl;

    /** @var array The loaded customfilters. */
    protected $customfilters;

    /**
     * manager constructor.
     *
     * @param mixed $dl
     */
    public function __construct($dl) {
        $this->dl = $dl;
        $this->customfilters = [];
    }

    /**
     * Get filter
     *
     * @param int $filterid
     * @param ?array $options
     * @return mixed|datalynx_customfilter
     */
    public function get_filter_from_id($filterid = 0, ?array $options = null) {
        $dl = $this->dl;
        $dlid = $dl->id();

        if ($filterid == self::BLANK_FILTER) {
            $filter = new stdClass();
            $filter->dataid = $dl->id();
            $filter->name = get_string('filternew', 'datalynx');

            return new datalynx_customfilter($filter);
        }

        if ($filterid < 0) {
            $view = !empty($options['view']) ? $options['view'] : null;
            $viewid = $view ? $view->id() : 0;

            if ($filterid == self::USER_FILTER_SET && $view && $view->is_active()) {
                $filter = $this->set_user_filter($filterid, $view);
                return new datalynx_customfilter($filter);
            }

            if (
                $filterid != self::USER_FILTER_SET &&
                    $filter = get_user_preferences(
                        "datalynxcustomfilter-$dlid-$viewid-$filterid",
                        null
                    )
            ) {
                $filter = unserialize($filter);
                $filter->dataid = $dlid;
                return new datalynx_customfilter($filter);
            }

            $filterid = 0;
        }

        if ($filterid == 0) {
            if (!$dl->data->defaultfilter) {
                $filter = new stdClass();
                $filter->dataid = $dl->id();

                return new datalynx_customfilter($filter);
            } else {
                $filterid = $dl->data->defaultfilter;
            }
        }

        if ($this->get_filters() && isset($this->customfilters[$filterid])) {
            return clone ($this->customfilters[$filterid]);
        } else {
            $filter = new stdClass();
            $filter->dataid = $dl->id();

            return new datalynx_customfilter($filter);
        }
    }

    /**
     * Get filter from url.
     *
     * @param string $url
     * @param bool $raw
     * @return ?datalynx_customfilter
     */
    public function get_filter_from_url($url, $raw = false) {

        $dl = $this->dl;
        $dlid = $dl->id();

        if ($options = self::get_filter_options_from_url($url)) {
            $options['dataid'] = $dlid;
            $filter = new datalynx_customfilter((object) $options);

            if ($raw) {
                return $filter->get_filter_obj();
            } else {
                return $filter;
            }
        }
        return null;
    }

    /**
     * Get all customfilters, optionally filtered.
     *
     * @param null $exclude
     * @param bool $menu
     * @param bool $forceget
     * @return array|bool
     */
    public function get_filters($exclude = null, $menu = false, $forceget = false) {
        global $DB;
        if (!$this->customfilters || $forceget) {
            $this->customfilters = [];
            if ($filters = $DB->get_records('datalynx_customfilters', ['dataid' => $this->dl->id()])) {
                foreach ($filters as $filterid => $filterdata) {
                    $this->customfilters[$filterid] = new datalynx_customfilter($filterdata);
                }
            }
        }

        if ($this->customfilters) {
            if (empty($exclude) && !$menu) {
                return $this->customfilters;
            } else {
                $filters = [];
                foreach ($this->customfilters as $filterid => $filter) {
                    if (!empty($exclude) && in_array($filterid, $exclude)) {
                        continue;
                    }
                    if ($menu) {
                        if (
                            $filter->visible ||
                            has_capability('mod/datalynx:managetemplates', $this->dl->context)
                        ) {
                            $filters[$filterid] = $filter->name;
                        }
                    } else {
                        $filters[$filterid] = $filter;
                    }
                }
                return $filters;
            }
        } else {
            return false;
        }
    }

    /**
     * Process filter actions (update, duplicate, visible, delete).
     *
     * @param string $action
     * @param array $fids
     * @param bool $confirmed
     * @return array|bool
     */
    public function process_filters($action, $fids, $confirmed = false) {
        global $DB, $OUTPUT;

        $dl = $this->dl;

        $filters = [];
        if (has_capability('mod/datalynx:managetemplates', $dl->context)) {
            if ($fids) {
                $filters = $DB->get_records_select('datalynx_customfilters', "id IN ($fids)");
            } else if ($action == 'update') {
                $filters[] = $this->get_filter_from_id(self::BLANK_FILTER);
            }
        }
        $processedlids = [];
        $strnotify = '';

        // Update action should be role-based.
        if (empty($filters)) {
            $dl->notifications['bad'][] = get_string("filternoneforaction", 'datalynx');
            return false;
        } else {
            if (!$confirmed) {
                $dl->print_header('customfilters');
                $message = get_string("filtersconfirm$action", 'datalynx', count($filters));
                $url1 = new moodle_url(
                    '/mod/datalynx/customfilter/index.php',
                    ['d' => $dl->id(), $action => implode(',', array_keys($filters)),
                    'sesskey' => sesskey(),
                    'confirmed' => 1]
                );
                $url2 = new moodle_url(
                    '/mod/datalynx/customfilter/index.php',
                    ['d' => $dl->id()]
                );
                echo $OUTPUT->confirm($message, $url1, $url2);
                echo $OUTPUT->footer();
                exit();
            } else {
                switch ($action) {
                    case 'update':
                        $filter = reset($filters);
                        $mform = $this->get_customfilter_backend_form($filter);
                        if ($mform->is_cancelled()) {
                            break;
                        }

                        $formdata = $mform->get_submitted_data();
                        $filter = $this->get_filter_from_form($filter, $formdata);
                        $filterform = $this->get_customfilter_backend_form($filter);

                        if ($filterform->no_submit_button_pressed()) {
                            $this->display_filter_form($filterform, $filter);
                        } else if ($formdata = $filterform->get_data()) {
                            $filter = $this->get_filter_from_form($filter, $formdata);

                            if ($filter->id) {
                                $DB->update_record('datalynx_customfilters', $filter);
                                $processedlids[] = $filter->id;
                                $strnotify = 'filtersupdated';

                                $other = ['dataid' => $this->dl->id()];
                                $event = \mod_datalynx\event\field_updated::create(
                                    ['context' => $this->dl->context,
                                    'objectid' => $filter->id,
                                    'other' => $other]
                                );
                                $event->trigger();
                            } else {
                                $filter->id = $DB->insert_record(
                                    'datalynx_customfilters',
                                    $filter,
                                    true
                                );
                                $processedlids[] = $filter->id;
                                $strnotify = 'filtersadded';

                                $other = ['dataid' => $this->dl->id()];
                                $event = \mod_datalynx\event\field_created::create(
                                    ['context' => $this->dl->context,
                                    'objectid' => $filter->id,
                                    'other' => $other]
                                );
                                $event->trigger();
                            }
                            $this->customfilters[$filter->id] = $filter;
                        } else {
                            // Form validation failed so return to form.
                            $this->display_filter_form($filterform, $filter);
                        }
                        break;

                    case 'duplicate':
                        if (!empty($filters)) {
                            foreach ($filters as $filter) {
                                // Check for limit before duplicating.
                                while ($dl->name_exists('customfilters', $filter->name)) {
                                    $filter->name = 'Copy of ' . $filter->name;
                                }
                                $filterid = $DB->insert_record('datalynx_customfilters', $filter);

                                $processedlids[] = $filterid;

                                $other = ['dataid' => $this->dl->id()];
                                $event = \mod_datalynx\event\field_created::create(
                                    ['context' => $this->dl->context,
                                    'objectid' => $filterid,
                                    'other' => $other]
                                );
                                $event->trigger();
                            }
                        }
                        $strnotify = 'filtersadded';
                        break;

                    case 'visible':
                        $updatefilter = new stdClass();
                        foreach ($filters as $filter) {
                            $updatefilter->id = $filter->id;
                            $updatefilter->visible = (int) !$filter->visible;
                            $DB->update_record('datalynx_customfilters', $updatefilter);
                            $filter->visible = $updatefilter->visible;

                            $processedlids[] = $filter->id;

                            $other = ['dataid' => $this->dl->id()];
                            $event = \mod_datalynx\event\field_updated::create(
                                ['context' => $this->dl->context,
                                'objectid' => $filter->id,
                                'other' => $other]
                            );
                            $event->trigger();
                        }

                        $strnotify = '';
                        break;

                    case 'delete':
                        foreach ($filters as $filter) {
                            $DB->delete_records('datalynx_customfilters', [
                                'id' => $filter->id]);

                            $processedlids[] = $filter->id;

                            $other = ['dataid' => $this->dl->id()];
                            $event = \mod_datalynx\event\field_deleted::create(
                                ['context' => $this->dl->context,
                                'objectid' => $filter->id,
                                'other' => $other]
                            );
                            $event->trigger();
                        }
                        $strnotify = 'filtersdeleted';
                        break;

                    default:
                        break;
                }

                if (!empty($strnotify)) {
                    $filtersprocessed = $processedlids ? count($processedlids) : 'No';
                    $dl->notifications['good'][] = get_string(
                        $strnotify,
                        'datalynx',
                        $filtersprocessed
                    );
                }
                return $processedlids;
            }
        }
    }

    /**
     * Get the backend form for managing a customfilter.
     *
     * @param datalynx_customfilter $filter
     * @return datalynx_customfilter_backend_form
     */
    public function get_customfilter_backend_form($filter) {

        $formurl = new moodle_url(
            '/mod/datalynx/customfilter/index.php',
            ['d' => $this->dl->id(), 'fid' => $filter->id, 'update' => 1]
        );
        $mform = new datalynx_customfilter_backend_form($this->dl, $filter, $formurl);
        return $mform;
    }

    /**
     * Display the filter form for editing.
     *
     * @param datalynx_customfilter_backend_form $mform
     * @param datalynx_customfilter $filter
     * @param ?array $urlparams
     */
    public function display_filter_form($mform, $filter, $urlparams = null) {
        $stredittitle = $filter->id ? get_string('filteredit', 'datalynx', $filter->name)
            : get_string('customfilternew', 'datalynx');
        $heading = html_writer::tag('h2', format_string($stredittitle), ['class' => 'mdl-align']);

        $this->dl->print_header(['tab' => 'customfilters', 'urlparams' => $urlparams]);
        echo $heading;
        $mform->display();
        $this->dl->print_footer();

        exit();
    }

    /**
     * Build a customfilter object from form data.
     *
     * @param datalynx_customfilter $filter
     * @param stdClass $formdata
     * @return \mod_datalynx\local\filter\datalynx_customfilter
     */
    public function get_filter_from_form($filter, $formdata) {
        $filter->name = $formdata->name;
        $filter->description = !empty($formdata->description) ? $formdata->description : '';
        $filter->visible = !isset($formdata->visible) ? 0 : $formdata->visible;
        $filter->fulltextsearch = !isset($formdata->fulltextsearch) ? 0 : $formdata->fulltextsearch;
        $filter->timecreated = empty($formdata->timecreated) ? 0 : $formdata->timecreated;
        $filter->timecreatedsortable = empty($formdata->timecreatedsortable) ? 0 : $formdata->timecreatedsortable;
        $filter->timemodified = empty($formdata->timemodified) ? 0 : $formdata->timemodified;
        $filter->timemodifiedsortable = empty($formdata->timemodifiedsortable) ? 0 : $formdata->timemodifiedsortable;
        $filter->authorsearch = !isset($formdata->authorsearch) ? 0 : $formdata->authorsearch;
        $filter->approve = empty($formdata->approve) ? 0 : $formdata->approve;
        $filter->status = empty($formdata->status) ? 0 : $formdata->status;
        $filter->fieldlist = empty($formdata->fieldlist) ? 0 : $formdata->fieldlist;

        return $filter;
    }

    /**
     * Print a table of all customfilters with management actions.
     *
     * @throws \coding_exception
     * @throws \moodle_exception
     */
    public function print_filter_list() {
        global $OUTPUT;

        $dl = $this->dl;

        $filterbaseurl = '/mod/datalynx/customfilter/index.php';
        $linkparams = ['d' => $dl->id(), 'sesskey' => sesskey()];

        $strfilters = get_string('name');
        $strdescription = get_string('description');
        $strvisible = get_string('visible');
        $strfulltextsearch = get_string('fulltextsearch', 'datalynx');
        $strfieldlist = get_string('fieldlist', 'datalynx');
        $strhide = get_string('hide');
        $strshow = get_string('show');
        $stredit = get_string('edit');
        $strdelete = get_string('delete');
        $strduplicate = get_string('duplicate');

        $table = new html_table();
        $table->head = [$strfilters, $strdescription, $strfulltextsearch, $strfieldlist,
            $strvisible, $stredit, $strduplicate, $strdelete];
        $table->align = ['left', 'left', 'center', 'left', 'center', 'center', 'center',
            'center'];
        $table->wrap = [false, false, false, false, false, false, false, false];
        $table->attributes['align'] = 'center';

        $yesstr = get_string("yes");
        $nostr = "---";
        foreach ($this->customfilters as $filterid => $filter) {
            $filtername = html_writer::link(
                new moodle_url(
                    $filterbaseurl,
                    $linkparams + ['fedit' => $filterid, 'fid' => $filterid]
                ),
                $filter->name
            );
            $filterdescription = shorten_text($filter->description, 30);
            $fulltextsearch = $filter->fulltextsearch ? $yesstr : $nostr;
            if ($filter->fieldlist) {
                $fieldlist = "";
                $connector = "";
                $fieldlistdecode = json_decode($filter->fieldlist);
                foreach ($fieldlistdecode as $listfield) {
                    $fieldlist .= $connector . $listfield->name;
                    $connector = ", ";
                }
            } else {
                $fieldlist = $nostr;
            }

            if ($filter->visible) {
                $visibleicon = $OUTPUT->pix_icon('t/hide', $strhide);
            } else {
                $visibleicon = $OUTPUT->pix_icon('t/show', $strshow);
            }
            $visibleurl = html_writer::link(
                new moodle_url($filterbaseurl, $linkparams + ['visible' => $filterid]),
                $visibleicon
            );
            $filteredit = html_writer::link(
                new moodle_url(
                    $filterbaseurl,
                    $linkparams + ['fedit' => $filterid, 'fid' => $filterid]
                ),
                $OUTPUT->pix_icon('t/edit', $stredit)
            );
            $filterduplicate = html_writer::link(
                new moodle_url($filterbaseurl, $linkparams + ['duplicate' => $filterid]),
                $OUTPUT->pix_icon('t/copy', $strduplicate)
            );
            $filterdelete = html_writer::link(
                new moodle_url($filterbaseurl, $linkparams + ['delete' => $filterid]),
                $OUTPUT->pix_icon('t/delete', $strdelete)
            );
            $table->data[] = [$filtername, $filterdescription, $fulltextsearch, $fieldlist,
                $visibleurl, $filteredit, $filterduplicate, $filterdelete];
        }

        echo html_writer::table($table);
    }

    /**
     * Print an "add filter" link.
     *
     * @throws \coding_exception
     * @throws \moodle_exception
     */
    public function print_add_filter() {
        echo html_writer::empty_tag('br');
        echo html_writer::start_tag('div', ['class' => 'fieldadd mdl-align']);
        echo html_writer::link(
            new moodle_url(
                '/mod/datalynx/customfilter/index.php',
                ['d' => $this->dl->id(), 'sesskey' => sesskey(), 'new' => 1]
            ),
            get_string('customfilteradd', 'datalynx')
        );
        echo html_writer::end_tag('div');
        echo html_writer::empty_tag('br');
    }

    /**
     * Get the user's saved filter menu for a given view.
     *
     * @param int $viewid
     * @return array
     * @throws \coding_exception
     */
    public function get_user_filters_menu($viewid) {
        $filters = [];

        $dl = $this->dl;
        $dlid = $dl->id();
        if ($filternames = get_user_preferences("datalynxcustomfilter-$dlid-$viewid-userfilters", '')) {
            foreach (explode(';', $filternames) as $filteridname) {
                [$filterid, $name] = explode(' ', $filteridname, 2);
                $filters[$filterid] = $name;
            }
        }
        return $filters;
    }

    /**
     * Set and save a user filter preference for a given view.
     *
     * @param int $filterid
     * @param mixed $view
     * @return ?datalynx_customfilter
     * @throws \coding_exception
     */
    public function set_user_filter($filterid, $view) {
        $dl = $this->dl;
        $dlid = $dl->id();
        $viewid = $view->id();

        if ($filterid >= $this->USER_FILTER_ID_START) {
            $filter = $this->get_filter_from_userpreferences($filterid);
        } else {
            $filter = $this->get_filter_from_url(null, true);
        }
        if (!$filter) {
            return null;
        }

        if ($userfilters = $this->get_user_filters_menu($viewid)) {
            if (empty($userfilters[$filterid])) {
                $filterid = key($userfilters) - 1;
            }
        } else {
            $filterid = self::USER_FILTER_ID_START;
        }

        if (count($userfilters) >= self::USER_FILTER_MAX_NUM) {
            $fids = array_keys($userfilters);
            while (count($fids) >= self::USER_FILTER_MAX_NUM) {
                $fid = array_pop($fids);
                unset($userfilters[$fid]);
                unset_user_preference("datalynxfilter-$dlid-$viewid-$fid");
            }
        }

        $filter->id = $filterid;
        $filter->dataid = $dlid;
        if (empty($filter->name)) {
            $filter->name = get_string('filtermy', 'datalynx') . ' ' . abs($filterid);
        }
        set_user_preference("datalynxfilter-$dlid-$viewid-$filterid", serialize($filter));

        $userfilters = [$filterid => $filter->name] + $userfilters;
        foreach ($userfilters as $filterid => $name) {
            $userfilters[$filterid] = "$filterid $name";
        }
        set_user_preference("datalynxfilter-$dlid-$viewid-userfilters", implode(';', $userfilters));

        return $filter;
    }

    // HELPERS.

    /**
     * Get filter options from URL parameters.
     *
     * @param null $url
     * @return array
     * @throws \coding_exception
     */
    public static function get_filter_options_from_url($url = null) {
        $filteroptions = [      // Left: filteroption-names, right: urlparameter-names.
            'filterid' => ['filter', 0, PARAM_INT],
            'perpage' => ['uperpage', 0, PARAM_INT],
            'selection' => ['uselection', 0, PARAM_INT],
            'groupby' => ['ugroupby', 0, PARAM_INT],
            'customsort' => ['usort', '', PARAM_RAW],
            'customsearch' => ['usearch', '', PARAM_RAW],
            'page' => ['page', 0, PARAM_INT],
            'eids' => ['eids', 0, PARAM_INT],
            'users' => ['users', '', PARAM_SEQUENCE],
            'groups' => ['groups', '', PARAM_SEQUENCE],
            'afilter' => ['afilter', 0, PARAM_INT],
            'usersearch' => ['usersearch', 0, PARAM_RAW]];

        $options = [];

        // Url provided.
        if ($url) {
            if ($url instanceof moodle_url) {
                foreach ($filteroptions as $option => $args) {
                    [$name, , ] = $args;
                    if ($val = $url->get_param($name)) {
                        if ($option == 'customsort') {
                            $options[$option] = self::get_sort_options_from_query($val);
                        } else if ($option == 'customsearch') {
                            $searchoptions = self::get_search_options_from_query($val);
                            if (is_array($searchoptions)) {
                                $options['customsearch'] = $searchoptions;
                            } else {
                                $options['search'] = $searchoptions;
                            }
                        } else {
                            $options[$option] = $val;
                        }
                    }
                }
            }
            return $options;
        }

        // Optional params.
        foreach ($filteroptions as $option => $args) {
            [$name, $default, $type] = $args;
            if ($val = optional_param($name, $default, $type)) {
                if ($option == 'customsort') {
                    $options[$option] = self::get_sort_options_from_query($val);
                } else if ($option == 'customsearch') {
                    $searchoptions = self::get_search_options_from_query($val);
                    if (is_array($searchoptions)) {
                        $options['customsearch'] = $searchoptions;
                    } else {
                        $options['search'] = $searchoptions;
                    }
                } else {
                    $options[$option] = $val;
                }
            }
        }

        return $options;
    }

    /**
     * Get filter options from user preferences.
     *
     * @return array
     * @throws \coding_exception
     */
    public static function get_filter_options_from_userpreferences() {
        $filteroptions = [ // Left: urlparam-names, right: userpreferences-names.
        'perpage' => 'uperpage', 'selection' => 'uselection', 'groupby' => 'ugroupby',
            'customsort' => 'usort', 'customsearch' => 'usearch', 'page' => 'page',
            'eids' => 'eids', 'users' => 'users', 'groups' => 'groups', 'afilter' => 'afilter',
            'usersearch' => 'usersearch'];

        $options = [];

        $userfilter = false;
        $filterid = optional_param('filter', 0, PARAM_INT);
        if ($filterid < 0) {
            $viewid = optional_param('view', 0, PARAM_INT);
            $dlid = optional_param('d', 0, PARAM_INT);
            if ($viewid) {
                $userfilter = get_user_preferences("datalynxfilter-$dlid-$viewid-$filterid", null);
                $userfilter = unserialize($userfilter);
            }
        }

        if ($userfilter) {
            // Optional params.
            foreach ($filteroptions as $option => $name) {
                if ($val = $userfilter->$name) {
                    if ($option == 'customsort') {
                        $options[$option] = self::get_sort_options_from_query($val);
                    } else if ($option == 'customsearch') {
                        $searchoptions = self::get_search_options_from_query($val);
                        if (is_array($searchoptions)) {
                            $options['customsearch'] = $searchoptions;
                        } else {
                            $options['search'] = $searchoptions;
                        }
                    } else if ($option == 'usersearch') {
                        $options['search'] = $val;
                    } else {
                        $options[$option] = $val;
                    }
                }
            }
        }

        return $options;
    }
}
