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

/**
 * Abstract event factory.
 *
 * @package    core_calendar
 * @copyright  2017 Cameron Ball <cameron@cameron1729.xyz>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace core_calendar\local\event\factories;

defined('MOODLE_INTERNAL') || die();

use core_calendar\local\event\entities\event;
use core_calendar\local\event\entities\repeat_event_collection;
use core_calendar\local\event\exceptions\invalid_callback_exception;
use core_calendar\local\event\proxies\cm_info_proxy;
use core_calendar\local\event\proxies\coursecat_proxy;
use core_calendar\local\event\proxies\std_proxy;
use core_calendar\local\event\value_objects\event_description;
use core_calendar\local\event\value_objects\event_times;
use core_calendar\local\event\entities\event_interface;

/**
 * Abstract factory for creating calendar events.
 *
 * @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
abstract class event_abstract_factory implements event_factory_interface {
    /**
     * @var callable $actioncallbackapplier Function to apply component action callbacks.
     */
    protected $actioncallbackapplier;

    /**
     * @var callable $visibilitycallbackapplier Function to apply component visibility callbacks.
     */
    protected $visibilitycallbackapplier;

    /**
     * @var array Course cache for use with get_course_cached.
     */
    protected $coursecachereference;

    /**
     * @var array Module cache reference for use with get_module_cached.
     */
    protected $modulecachereference;

    /**
     * @var callable Bail out check for create_instance.
     */
    protected $bailoutcheck;

    /**
     * Applies component actions to the event.
     *
     * @param event_interface $event The event to be updated.
     * @return event_interface The potentially modified event.
     */
    protected abstract function apply_component_action(event_interface $event);

    /**
     * Exposes the event (or not).
     *
     * @param event_interface $event The event to potentially expose.
     * @return event_interface|null The exposed event or null.
     */
    protected abstract function expose_event(event_interface $event);

    /**
     * Constructor.
     *
     * @param callable $actioncallbackapplier     Function to apply component action callbacks.
     * @param callable $visibilitycallbackapplier Function to apply component visibility callbacks.
     * @param callable $bailoutcheck              Function to test if we can return null early.
     * @param array    $coursecachereference      Cache to use with get_course_cached.
     * @param array    $modulecachereference      Cache to use with get_module_cached.
     */
    public function __construct(
        callable $actioncallbackapplier,
        callable $visibilitycallbackapplier,
        callable $bailoutcheck,
        array &$coursecachereference,
        array &$modulecachereference
    ) {
        $this->actioncallbackapplier = $actioncallbackapplier;
        $this->visibilitycallbackapplier = $visibilitycallbackapplier;
        $this->bailoutcheck = $bailoutcheck;
        $this->coursecachereference = &$coursecachereference;
        $this->modulecachereference = &$modulecachereference;
    }

    public function create_instance(\stdClass $dbrow) {
        if ($dbrow->modulename && $dbrow->instance && $dbrow->courseid == 0) {
            // Some events (for example user overrides) may contain module instance but not course id. Find course id.
            $cm = calendar_get_module_cached($this->modulecachereference, $dbrow->modulename, $dbrow->instance);
            $dbrow->courseid = $cm->course;
        }

        $bailcheck = $this->bailoutcheck;
        $bail = $bailcheck($dbrow);

        if (!is_bool($bail)) {
            throw new invalid_callback_exception(
                'Bail check must return true or false'
            );
        }

        if ($bail) {
            return null;
        }

        $category = null;
        $course = null;
        $group = null;
        $user = null;
        $module = null;
        $subscription = null;
        $component = null;

        if ($dbrow->modulename && $dbrow->instance) {
            $module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid);
        }

        if ($dbrow->categoryid) {
            $category = new coursecat_proxy($dbrow->categoryid);
        }

        $course = new std_proxy($dbrow->courseid, function($id) {
            return calendar_get_course_cached($this->coursecachereference, $id);
        });

        if ($dbrow->groupid) {
            $group = new std_proxy($dbrow->groupid, function($id) {
                return calendar_get_group_cached($id);
            });
        }

        if ($dbrow->userid) {
            $user = new std_proxy($dbrow->userid, function($id) {
                global $DB;
                return $DB->get_record('user', ['id' => $id]);
            });
        }

        if ($dbrow->subscriptionid) {
            $subscription = new std_proxy($dbrow->subscriptionid, function($id) {
                return calendar_get_subscription($id);
            });
        }

        if (!empty($dbrow->repeatid)) {
            $repeatcollection = new repeat_event_collection($dbrow, $this);
        } else {
            $repeatcollection = null;
        }

        if (!empty($dbrow->component)) {
            $component = $dbrow->component;
        }

        $event = new event(
            $dbrow->id,
            $dbrow->name,
            new event_description($dbrow->description, $dbrow->format),
            $category,
            $course,
            $group,
            $user,
            $repeatcollection,
            $module,
            $dbrow->eventtype,
            new event_times(
                (new \DateTimeImmutable())->setTimestamp($dbrow->timestart),
                (new \DateTimeImmutable())->setTimestamp($dbrow->timestart + $dbrow->timeduration),
                (new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? $dbrow->timesort : $dbrow->timestart),
                (new \DateTimeImmutable())->setTimestamp($dbrow->timemodified),
                (new \DateTimeImmutable())->setTimestamp($dbrow->timesort ? usergetmidnight($dbrow->timesort) : 0)
            ),
            !empty($dbrow->visible),
            $subscription,
            $dbrow->location,
            $component
        );

        $isactionevent = !empty($dbrow->type) && $dbrow->type == CALENDAR_EVENT_TYPE_ACTION;

        return $isactionevent ? $this->expose_event($this->apply_component_action($event)) : $event;
    }
}
