<?php declare(strict_types=1);
namespace Newland\Toubiz\Sync\Neos\Domain\Model;

/*
 * This file is part of the "toubiz-sync-neos" package.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Neos\Flow\Annotations as Flow;
use Newland\Toubiz\Api\ObjectAdapter\Concern\EventConstants;
use Newland\Toubiz\Sync\Neos\Domain\Model\Traits\UrlIdentifierTrait;
use Newland\Toubiz\Sync\Neos\Enum\EventScope;
use Newland\Toubiz\Sync\Neos\Orm\Uuid\CustomUuidGeneration;
use Newland\Toubiz\Sync\Neos\Orm\Uuid\UuidGenerator;
use Newland\Toubiz\Sync\Neos\Translation\TranslatableEntity;
use Ramsey\Uuid\UuidInterface;
use Ramsey\Uuid\Uuid;

/**
 * @Flow\Entity
 * @ORM\Table(indexes={
 *      @ORM\Index(name="newland_toubiz_sync_neos_event_import_ident", columns={"language", "originalid"})
 * })
 */
class Event extends AbstractEntity implements CustomUuidGeneration
{
    use TranslatableEntity;
    use UrlIdentifierTrait;

    public function generateUuid(): UuidInterface
    {
        return UuidGenerator::uuidFromProperties([ $this->originalId, $this->language ]);
    }

    /** @var string */
    protected $title;

    /**
     * @var string|null
     * @ORM\Column(type="text", nullable=true)
     */
    protected $description;

    /**
     * Additional information about the location.
     * @var string|null
     * @ORM\Column(type="text", nullable=true)
     */
    protected $additionalInformation;

    /**
     * Information about admission.
     * @var string|null
     * @ORM\Column(type="text", nullable=true)
     */
    protected $admission;

    /** @var bool */
    protected $isHighlight = false;

    /** @var bool */
    protected $isTipp = false;

    /**
     * Date and time of first day's beginning.
     * @var \DateTime
     */
    protected $beginsAt;

    /**
     * Date and time of last day's ending.
     * @var \DateTime
     */
    protected $endsAt;

    /**
     * URI to buy a ticket.
     * @var string|null
     * @ORM\Column(type="text", nullable=true)
     */
    protected $ticketUri;

    /**
     * Contact options for buying tickets.
     * @var string|null
     * @ORM\Column(type="text", nullable=true)
     */
    protected $ticketContact;

    /**
     * The link for additional information.
     * @var string|null
     * @ORM\Column(type="text", nullable=true)
     */
    protected $link;

    /**
     * @ORM\ManyToMany(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\Category", inversedBy="events",
     *     fetch="EAGER")
     * @var Collection<Category>
     */
    protected $categories;

    /**
     * @ORM\OneToMany(mappedBy="event", cascade={"remove"}, orphanRemoval=true)
     * @ORM\OrderBy({"beginsAt"="ASC"})
     * @var Collection<EventDate>
     */
    protected $eventDates;


    /**
     * @ORM\ManyToMany(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\EventTag", inversedBy="events",
     *     fetch="EAGER", orphanRemoval=true, cascade={"persist"})
     * @var Collection<EventTag>
     */
    protected $eventTags;

    /**
     * @ORM\ManyToMany(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\Medium", inversedBy="events", fetch="LAZY")
     * @var Collection<Medium>
     */
    protected $media;

    /** @var \DateTime */
    protected $updatedAt;

    /**
     * @var Address|null
     * @ORM\ManyToOne(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\Address")
     */
    protected $location;

    /**
     * @var Address|null
     * @ORM\ManyToOne(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\Address")
     */
    protected $organizer;

    /**
     * @var string|null
     * @ORM\Column(nullable=true)
     */
    protected $sourceName;

    /**
     * @ORM\Column(type="array", nullable=true)
     * @var array|null
     */
    protected $attributes;

    /**
     * @ORM\Column(type="smallint", nullable=true)
     * @var int|null
     */
    protected $scope;

    public function __construct()
    {
        $this->categories = new ArrayCollection();
        $this->eventDates = new ArrayCollection();
        $this->media = new ArrayCollection();
        $this->eventTags = new ArrayCollection();
        $this->updatedAt = new \DateTime;
    }

    public function setTitle($title): void
    {
        $this->title = $title;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function setDescription(?string $description): void
    {
        $this->description = $description;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setAdditionalInformation(?string $additionalInformation): void
    {
        $this->additionalInformation = $additionalInformation;
    }

    public function getAdditionalInformation(): ?string
    {
        return $this->additionalInformation;
    }

    public function setAdmission(?string $admission): void
    {
        $this->admission = $admission;
    }

    public function getAdmission(): ?string
    {
        return $this->admission;
    }

    public function setUpdatedAt(\DateTime $updatedAt): void
    {
        $this->updatedAt = $updatedAt;
    }

    public function getUpdatedAt(): \DateTime
    {
        return $this->updatedAt;
    }

    public function setIsHighlight(bool $highlight): void
    {
        $this->isHighlight = $highlight;
    }

    public function getIsHighlight(): bool
    {
        return $this->isHighlight;
    }

    public function setIsTipp(bool $tipp): void
    {
        $this->isTipp = $tipp;
    }

    public function getIsTipp(): bool
    {
        return $this->isTipp;
    }

    public function setBeginsAt(\DateTime $beginsAt): void
    {
        $this->beginsAt = $beginsAt;
    }

    public function getBeginsAt(): \DateTime
    {
        return $this->beginsAt;
    }

    public function setTicketUri($ticketUri): void
    {
        $this->ticketUri = $ticketUri;
    }

    public function getTicketUri(): ?string
    {
        return $this->ticketUri;
    }

    public function setTicketContact(?string $ticketContact): void
    {
        $this->ticketContact = $ticketContact;
    }

    public function getTicketContact(): ?string
    {
        return $this->ticketContact;
    }

    public function setLink(?string $link): void
    {
        $this->link = $link;
    }

    public function getLink(): ?string
    {
        return $this->link;
    }

    public function getBeginsAtSpecificTime(): bool
    {
        return ((int) $this->beginsAt->format('H') > 0);
    }

    public function setEndsAt(\DateTime $endsAt): void
    {
        $this->endsAt = $endsAt;
    }

    public function getEndsAt(): \DateTime
    {
        return $this->endsAt;
    }

    public function setLocation(Address $location = null): void
    {
        $this->location = $location;
    }

    public function getLocation(): ?Address
    {
        return $this->location;
    }

    public function setOrganizer(Address $organizer = null): void
    {
        $this->organizer = $organizer;
    }

    public function getOrganizer(): ?Address
    {
        return $this->organizer;
    }

    public function setCategories(Collection $categories): void
    {
        $this->categories = $categories;
    }

    public function getCategories(): Collection
    {
        return $this->categories;
    }

    public function setEventDates(Collection $eventDates): void
    {
        $this->eventDates = $eventDates;
    }

    public function getEventDates(): Collection
    {
        return $this->eventDates;
    }

    public function setEventTags(Collection $eventTags): void
    {
        $this->eventTags = $eventTags;
    }

    public function getEventTags(): Collection
    {
        return $this->eventTags;
    }

    public function getUpcomingEventDates(): array
    {
        $todayDate = new \DateTime;
        $todayDate->setTime(0, 0, 0);
        $today = $todayDate->format('Y-m-d');

        // TODO Also return collection instead of array to have uniform return types
        return array_filter(
            $this->eventDates->toArray(),
            function (EventDate $date) use ($today) {
                return $date->getBeginsAt()->format('Y-m-d') >= $today;
            }
        );
    }

    public function getAnnotatedEventDates(): array
    {
        // TODO Also return collection instead of array to have uniform return types
        return array_filter(
            $this->eventDates->toArray(),
            function (EventDate $date) {
                return !empty($date->getNote());
            }
        );
    }

    /**
     * Get event dates in groups.
     *
     * Each event date record represents one single day
     * which may be grouped into a date group in case
     * a bunch of days represents multiple days (like a "week").
     *
     * @return array
     */
    public function getUpcomingEventDateGroups(): array
    {
        // TODO Also return collection instead of array to have uniform return types
        $groups = [];
        $groupStartDate = null;
        $groupDate = null;
        $currentDate = null;

        /** @var EventDate $currentDate */
        foreach ($this->eventDates->toArray() as $currentDate) {
            if ($currentDate->getIsInThePast()) {
                continue;
            }

            if (!isset($previousDate)) {
                // Initialize on a new group search and skip current iteration.
                $groupStartDate = $currentDate;
                $previousDate = $currentDate;
                continue;
            }

            $interval = $currentDate->getBeginsAt()->diff($previousDate->getBeginsAt());
            if ($groupStartDate !== null && $previousDate !== null && $interval->format('%d') > 1) {
                // On a difference bigger than one day, a group ends.
                $groupDate = new EventDate();
                $groupDate->setBeginsAt($groupStartDate->getBeginsAt());
                $groupDate->setEndsAt($previousDate->getEndsAt());
                $groups[] = $groupDate;

                // Reset previous date to ensure new initialization on next iteration.
                $groupStartDate = $previousDate = $groupDate = null;
                continue;
            }

            // Assign previous date for next iteration.
            $previousDate = $currentDate;
        }

        /*
         * There is either an unclosed group date or the last iteration does
         * not count its current date in, hence it must be added manually.
         */
        if (!isset($groupDate) && $currentDate !== null) {
            $groupDate = new EventDate;
            $groupDate->setBeginsAt($currentDate->getBeginsAt());
        }
        if ($groupDate !== null && $currentDate !== null) {
            $groupDate->setEndsAt($currentDate->getEndsAt());
        }
        $groups[] = $groupDate;

        return $groups;
    }

    public function getEndsOnSameDay(): bool
    {
        return ($this->beginsAt->format('Y-m-d') === $this->endsAt->format('Y-m-d'));
    }

    public function setMedia(Collection $media): void
    {
        $this->media = $media;
    }

    public function getMedia(): Collection
    {
        return $this->media;
    }

    public function setAttributes(array $attributes): void
    {
        $this->attributes = $attributes;
    }

    public function getAttributes(): array
    {
        $attributes = (array) $this->attributes;

        if ($this->getGuestCardAccepted()) {
            $key = array_search(EventConstants::ATTRIBUTE_GUEST_CARD, $attributes['features'], true);
            unset($attributes['features'][$key]);
        }

        return $attributes;
    }

    public function setSourceName(?string $sourceName): void
    {
        $this->sourceName = $sourceName;
    }

    public function getSourceName(): ?string
    {
        return $this->sourceName;
    }

    public function getScope(): int
    {
        return (int) $this->scope;
    }

    public function setScope(int $scope): void
    {
        if (EventScope::validValue($scope)) {
            $this->scope = $scope;
        }
    }

    /**
     * The guest card attribute is imported as a regular attribute but needs to be displayed
     * separately. This is a helper method for that.
     *
     * @return bool
     */
    public function getGuestCardAccepted(): bool
    {
        return is_array($this->attributes)
            && array_key_exists('features', $this->attributes)
            && is_array($this->attributes['features'])
            && in_array(EventConstants::ATTRIBUTE_GUEST_CARD, $this->attributes['features'], true);
    }
}
