<?php

namespace App\Http\Controllers\DAV\Backend\CalDAV;

use Sabre\DAV;
use App\Traits\WithUser;
use Sabre\CalDAV\Backend\SyncSupport;
use Sabre\CalDAV\Backend\AbstractBackend;

class CalDAVBackend extends AbstractBackend implements SyncSupport
{
    use WithUser;

    /**
     * Set the Calendar backends.
     *
     * @return array
     */
    private function getBackends(): array
    {
        return [
            app(CalDAVBirthdays::class)->init($this->user),
            app(CalDAVTasks::class)->init($this->user),
        ];
    }

    /**
     * Get the backend for this id.
     *
     * @return AbstractCalDAVBackend|null
     */
    private function getBackend($id)
    {
        return collect($this->getBackends())->first(function ($backend) use ($id) {
            return $backend->backendUri() === $id;
        });
    }

    /**
     * Returns a list of calendars for a principal.
     *
     * Every project is an array with the following keys:
     *  * id, a unique id that will be used by other functions to modify the
     *    calendar. This can be the same as the uri or a database key.
     *  * uri, which is the basename of the uri with which the calendar is
     *    accessed.
     *  * principaluri. The owner of the calendar. Almost always the same as
     *    principalUri passed to this method.
     *
     * Furthermore it can contain webdav properties in clark notation. A very
     * common one is '{DAV:}displayname'.
     *
     * Many clients also require:
     * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
     * For this property, you can just return an instance of
     * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
     *
     * If you return {http://sabredav.org/ns}read-only and set the value to 1,
     * ACL will automatically be put in read-only mode.
     *
     * @param  string  $principalUri
     * @return array
     */
    public function getCalendarsForUser($principalUri)
    {
        return array_map(function ($backend) {
            return $backend->getDescription();
        }, $this->getBackends());
    }

    /**
     * The getChanges method returns all the changes that have happened, since
     * the specified syncToken in the specified calendar.
     *
     * This function should return an array, such as the following:
     *
     * [
     *   'syncToken' => 'The current synctoken',
     *   'added'   => [
     *      'new.txt',
     *   ],
     *   'modified'   => [
     *      'modified.txt',
     *   ],
     *   'deleted' => [
     *      'foo.php.bak',
     *      'old.txt'
     *   ]
     * );
     *
     * The returned syncToken property should reflect the *current* syncToken
     * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
     * property This is * needed here too, to ensure the operation is atomic.
     *
     * If the $syncToken argument is specified as null, this is an initial
     * sync, and all members should be reported.
     *
     * The modified property is an array of nodenames that have changed since
     * the last token.
     *
     * The deleted property is an array with nodenames, that have been deleted
     * from collection.
     *
     * The $syncLevel argument is basically the 'depth' of the report. If it's
     * 1, you only have to report changes that happened only directly in
     * immediate descendants. If it's 2, it should also include changes from
     * the nodes below the child collections. (grandchildren)
     *
     * The $limit argument allows a client to specify how many results should
     * be returned at most. If the limit is not specified, it should be treated
     * as infinite.
     *
     * If the limit (infinite or not) is higher than you're willing to return,
     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
     *
     * If the syncToken is expired (due to data cleanup) or unknown, you must
     * return null.
     *
     * The limit is 'suggestive'. You are free to ignore it.
     *
     * @param  string  $calendarId
     * @param  string  $syncToken
     * @param  int  $syncLevel
     * @param  int  $limit
     * @return array
     */
    public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null)
    {
        $backend = $this->getBackend($calendarId);
        if ($backend) {
            return $backend->getChanges($calendarId, $syncToken);
        }

        return [];
    }

    /**
     * Returns all calendar objects within a calendar.
     *
     * Every item contains an array with the following keys:
     *   * calendardata - The iCalendar-compatible calendar data
     *   * uri - a unique key which will be used to construct the uri. This can
     *     be any arbitrary string, but making sure it ends with '.ics' is a
     *     good idea. This is only the basename, or filename, not the full
     *     path.
     *   * lastmodified - a timestamp of the last modification time
     *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
     *   '"abcdef"')
     *   * size - The size of the calendar objects, in bytes.
     *   * component - optional, a string containing the type of object, such
     *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
     *     the Content-Type header.
     *
     * Note that the etag is optional, but it's highly encouraged to return for
     * speed reasons.
     *
     * The calendardata is also optional. If it's not returned
     * 'getCalendarObject' will be called later, which *is* expected to return
     * calendardata.
     *
     * If neither etag or size are specified, the calendardata will be
     * used/fetched to determine these numbers. If both are specified the
     * amount of times this is needed is reduced by a great degree.
     *
     * @param  mixed  $calendarId
     * @return array
     */
    public function getCalendarObjects($calendarId)
    {
        $backend = $this->getBackend($calendarId);
        if ($backend) {
            $objs = $backend->getObjects($calendarId);

            return $objs
                ->map(function ($date) use ($backend) {
                    return $backend->prepareData($date);
                })
                ->filter(function ($event) {
                    return $event !== null;
                })
                ->toArray();
        }

        return [];
    }

    /**
     * Returns information from a single calendar object, based on it's object
     * uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * The returned array must have the same keys as getCalendarObjects. The
     * 'calendardata' object is required here though, while it's not required
     * for getCalendarObjects.
     *
     * This method must return null if the object did not exist.
     *
     * @param  mixed  $calendarId
     * @param  string  $objectUri
     * @return array|null
     */
    public function getCalendarObject($calendarId, $objectUri)
    {
        $backend = $this->getBackend($calendarId);
        if ($backend) {
            $obj = $backend->getObject($calendarId, $objectUri);

            if ($obj) {
                return $backend->prepareData($obj);
            }
        }

        return [];
    }

    /**
     * Creates a new calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible to return an etag from this function, which will be used
     * in the response to this PUT request. Note that the ETag must be
     * surrounded by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param  mixed  $calendarId
     * @param  string  $objectUri
     * @param  string  $calendarData
     * @return string|null
     */
    public function createCalendarObject($calendarId, $objectUri, $calendarData)
    {
        return $this->updateCalendarObject($calendarId, $objectUri, $calendarData);
    }

    /**
     * Updates an existing calendarobject, based on it's uri.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * It is possible return an etag from this function, which will be used in
     * the response to this PUT request. Note that the ETag must be surrounded
     * by double-quotes.
     *
     * However, you should only really return this ETag if you don't mangle the
     * calendar-data. If the result of a subsequent GET to this object is not
     * the exact same as this request body, you should omit the ETag.
     *
     * @param  mixed  $calendarId
     * @param  string  $objectUri
     * @param  string  $calendarData
     * @return string|null
     */
    public function updateCalendarObject($calendarId, $objectUri, $calendarData): ?string
    {
        $backend = $this->getBackend($calendarId);

        return $backend ?
            $backend->updateOrCreateCalendarObject($calendarId, $objectUri, $calendarData)
            : null;
    }

    /**
     * Deletes an existing calendar object.
     *
     * The object uri is only the basename, or filename and not a full path.
     *
     * @param  mixed  $calendarId
     * @param  string  $objectUri
     * @return void
     */
    public function deleteCalendarObject($calendarId, $objectUri)
    {
        $backend = $this->getBackend($calendarId);
        if ($backend) {
            $backend->deleteCalendarObject($objectUri);
        }
    }

    /**
     * Creates a new calendar for a principal.
     *
     * If the creation was a success, an id must be returned that can be used to
     * reference this calendar in other methods, such as updateCalendar.
     *
     * The id can be any type, including ints, strings, objects or array.
     *
     * @param  string  $principalUri
     * @param  string  $calendarUri
     * @param  array  $properties
     * @return void
     */
    public function createCalendar($principalUri, $calendarUri, array $properties): void
    {
    }

    /**
     * Delete a calendar and all its objects.
     *
     * @param  mixed  $calendarId
     * @return void
     */
    public function deleteCalendar($calendarId)
    {
    }

    /**
     * Creates a new subscription for a principal.
     *
     * If the creation was a success, an id must be returned that can be used to reference
     * this subscription in other methods, such as updateSubscription.
     *
     * @param  string  $principalUri
     * @param  string  $uri
     * @param  array  $properties
     * @return mixed
     */
    public function createSubscription($principalUri, $uri, array $properties)
    {
        return false;
    }

    /**
     * Updates a subscription.
     *
     * The list of mutations is stored in a Sabre\DAV\PropPatch object.
     * To do the actual updates, you must tell this object which properties
     * you're going to process with the handle() method.
     *
     * Calling the handle method is like telling the PropPatch object "I
     * promise I can handle updating this property".
     *
     * Read the PropPatch documentation for more info and examples.
     *
     * @param  mixed  $subscriptionId
     * @param  \Sabre\DAV\PropPatch  $propPatch
     * @return void
     */
    public function updateSubscription($subscriptionId, DAV\PropPatch $propPatch)
    {
    }

    /**
     * Deletes a subscription.
     *
     * @param  mixed  $subscriptionId
     * @return void
     */
    public function deleteSubscription($subscriptionId)
    {
    }
}
