<?php
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\Enum\EventScope;
use Newland\Toubiz\Sync\Neos\Translation\TranslatableEntity;

/**
 * An event.
 *
 * @Flow\Entity
 * @ORM\Table(indexes={
 *      @ORM\Index(name="import_ident", columns={"language", "originalid"})
 * })
 * @todo adapt http://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/Persistence.html
 */
class Event extends AbstractEntity
{
    use TranslatableEntity;

    /**
     * @var string Title of the event.
     */
    protected $title;

    /**
     * @var string Description of the event.
     * @ORM\Column(type="text", nullable=true)
     */
    protected $description;

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

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

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

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

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

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

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

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

    /**
     * @var string the link for additional information.
     * @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"})
     * @ORM\OrderBy({"beginsAt"="ASC"})
     * @var Collection<EventDate>
     */
    protected $eventDates;


    /**
     * @ORM\ManyToMany(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\EventTag", inversedBy="events",
     *     fetch="EAGER")
     * @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
     */
    protected $attributes;

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

    /**
     * Class constructor.
     *
     * This constructor is called before hydration and dependency
     * injection is happening.
     *
     * @return void
     */
    public function __construct()
    {
        $this->categories = new ArrayCollection;
        $this->eventDates = new ArrayCollection;
        $this->media = new ArrayCollection;
        $this->updatedAt = new \DateTime;
    }

    /**
     * @param string $title
     * @return void
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @param string $description
     * @return void
     */
    public function setDescription($description)
    {
        $this->description = $description;
    }

    /**
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * @param string $additionalInformation
     */
    public function setAdditionalInformation($additionalInformation)
    {
        $this->additionalInformation = $additionalInformation;
    }

    /**
     * @return string|null
     */
    public function getAdditionalInformation()
    {
        return $this->additionalInformation;
    }

    /**
     * @param string $admission
     * @return void
     */
    public function setAdmission($admission)
    {
        $this->admission = $admission;
    }

    /**
     * @return string
     */
    public function getAdmission()
    {
        return $this->admission;
    }

    /**
     * @param \DateTime $updatedAt
     * @return void
     */
    public function setUpdatedAt(\DateTime $updatedAt)
    {
        $this->updatedAt = $updatedAt;
    }

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

    /**
     * @param bool $highlight
     * @return void
     */
    public function setIsHighlight($highlight)
    {
        $this->isHighlight = (bool) $highlight;
    }

    /**
     * @return bool
     */
    public function getIsHighlight()
    {
        return $this->isHighlight;
    }

    /**
     * @param bool $tipp
     * @return void
     */
    public function setIsTipp($tipp)
    {
        $this->isTipp = (bool) $tipp;
    }

    /**
     * @return bool
     */
    public function getIsTipp()
    {
        return $this->isTipp;
    }

    /**
     * @param \DateTime $beginsAt
     * @return void
     */
    public function setBeginsAt(\DateTime $beginsAt)
    {
        $this->beginsAt = $beginsAt;
    }

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

    /**
     * @param string $ticketUri
     * @return void
     */
    public function setTicketUri($ticketUri)
    {
        $this->ticketUri = $ticketUri;
    }

    /**
     * @return string
     */
    public function getTicketUri()
    {
        return $this->ticketUri;
    }

    /**
     * @param string|null $ticketContact
     */
    public function setTicketContact($ticketContact)
    {
        $this->ticketContact = $ticketContact;
    }

    /**
     * @return string|null
     */
    public function getTicketContact()
    {
        return $this->ticketContact;
    }

    /**
     * @param string $link
     */
    public function setLink($link)
    {
        $this->link = $link;
    }

    /**
     * @return string
     */
    public function getLink()
    {
        return $this->link;
    }

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

    /**
     * @param \DateTime $endsAt
     * @return void
     */
    public function setEndsAt(\DateTime $endsAt)
    {
        $this->endsAt = $endsAt;
    }

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

    /**
     * @param Address $location
     * @return void
     */
    public function setLocation(Address $location = null)
    {
        $this->location = $location;
    }

    /**
     * @return Address|null
     */
    public function getLocation()
    {
        return $this->location;
    }

    /**
     * @param Address $organizer
     * @return void
     */
    public function setOrganizer(Address $organizer = null)
    {
        $this->organizer = $organizer;
    }

    /**
     * @return Address| null
     */
    public function getOrganizer()
    {
        return $this->organizer;
    }

    /**
     * @param Collection $categories
     * @return void
     */
    public function setCategories(Collection $categories)
    {
        $this->categories = $categories;
    }

    /**
     * @return Collection
     */
    public function getCategories()
    {
        return $this->categories;
    }

    /**
     * @param Collection $eventDates
     * @return void
     */
    public function setEventDates(Collection $eventDates)
    {
        $this->eventDates = $eventDates;
    }

    /**
     * @return Collection
     */
    public function getEventDates()
    {
        return $this->eventDates;
    }

    /**
     * @param Collection $eventTags
     * @return void
     */
    public function setEventTags(Collection $eventTags)
    {
        $this->eventTags = $eventTags;
    }

    /**
     * @return Collection
     */
    public function getEventTags()
    {
        return $this->eventTags;
    }

    /**
     * @return iterable
     */
    public function getUpcomingEventDates()
    {
        $todayDate = new \DateTime;
        $todayDate->setTime(0, 0, 0);
        $today = $todayDate->format('Y-m-d');

        return array_filter(
            $this->eventDates->toArray(),
            function (EventDate $date) use ($today) {
                return $date->getBeginsAt()->format('Y-m-d') >= $today;
            }
        );
    }

    /**
     * Get event dates having a note.
     *
     * @return array
     */
    public function getAnnotatedEventDates()
    {
        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()
    {
        $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 ($interval->format('%d') > 1 && $groupStartDate !== null && $previousDate !== null) {
                // 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.
                unset($groupStartDate, $previousDate, $groupDate);
                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;
    }

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

    /**
     * @param Collection $media
     * @return void
     */
    public function setMedia(Collection $media)
    {
        $this->media = $media;
    }

    /**
     * @return Collection
     */
    public function getMedia()
    {
        return $this->media;
    }

    /**
     * @param array $attributes
     * @return void
     */
    public function setAttributes(array $attributes)
    {
        $this->attributes = $attributes;
    }

    /**
     * @return array
     */
    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;
    }

    /**
     * @param string $sourceName
     * @return void
     */
    public function setSourceName($sourceName)
    {
        $this->sourceName = $sourceName;
    }

    /**
     * @return string|null
     */
    public function getSourceName()
    {
        return $this->sourceName;
    }

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

    public function setScope(int $scope)
    {
        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);
    }
}
