<?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\Attributes\TourAttributes;
use Newland\Toubiz\Api\ObjectAdapter\Concern\ArticleConstants;
use Newland\Toubiz\Sync\Neos\Domain\Model\Traits\SortableRelationsTrait;
use Newland\Toubiz\Sync\Neos\Service\AttributeCollection;
use Newland\Toubiz\Sync\Neos\Translation\TranslatableEntity;

/**
 * An article.
 *
 * This represents several article types, like a "point of interest",
 * a lodging, a tour, etc.
 *
 * @Flow\Entity
 * @ORM\Table(indexes={
 *      @ORM\Index(name="import_ident", columns={"language", "originalid"})
 * })
 */
class Article extends AbstractEntity
{
    use SortableRelationsTrait;
    use TranslatableEntity;

    /**
     * @var int
     */
    protected $mainType;

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

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

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

    /**
     * @var Address|null
     * @ORM\ManyToOne(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\Address", cascade={"persist"})
     */
    protected $mainAddress;

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

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

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

    /**
     * @Flow\Transient
     * @Flow\IgnoreValidation
     * @var Medium[]
     */
    protected $mediaSorted;

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

    /**
     * @ORM\OneToMany(targetEntity="Newland\Toubiz\Sync\Neos\Domain\Model\Attribute", mappedBy="article",
     *     orphanRemoval=true, fetch="EAGER", cascade={"persist", "remove"})
     * @var Collection<Attribute>
     */
    protected $attributes;

    /**
     * Transient property for simple caching.
     *
     * @Flow\Transient()
     * @Flow\IgnoreValidation()
     * @var AttributeCollection[]|null
     */
    private $flatAttributes;

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

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

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

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

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

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

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

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

    /**
     * URI for booking this article.
     * @ORM\Column(nullable=true)
     * @var string|null
     */
    protected $bookingUri;

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

    /**
     * @ORM\Column(nullable=true)
     * @var string|null Specific detail URI for this article.
     */
    protected $detailUri;

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

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

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

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

    /**
     * Opening times as raw data as given by the source api.
     * Will be parsed by JavaScript.
     *
     * @ORM\Column(type="text", nullable=true)
     * @var string|null
     */
    protected $openingTimes;

    /**
     * The format of the opening times data.
     * There should be a JavaScript parser available for every format.
     *
     * @ORM\Column(nullable=true)
     * @var string|null
     */
    protected $openingTimesFormat;

    public function __construct()
    {
        $this->addresses = new ArrayCollection();
        $this->categories = new ArrayCollection();
        $this->files = new ArrayCollection();
        $this->media = new ArrayCollection();
        $this->attributes = new ArrayCollection();
    }

    public function getMainType(): int
    {
        return $this->mainType;
    }

    public function setMainType(int $type): void
    {
        $this->mainType = $type;
    }

    public function getIsAttraction(): bool
    {
        return $this->mainType === ArticleConstants::TYPE_ATTRACTION;
    }

    public function getIsGastronomy(): bool
    {
        return $this->mainType === ArticleConstants::TYPE_GASTRONOMY;
    }

    public function getIsTour(): bool
    {
        return $this->mainType === ArticleConstants::TYPE_TOUR;
    }

    public function getIsLodging(): bool
    {
        return $this->mainType === ArticleConstants::TYPE_LODGING;
    }

    public function getIsDirectMarketer(): bool
    {
        return $this->mainType === ArticleConstants::TYPE_DIRECT_MARKETER;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }

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

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

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

    public function getAbstract(): ?string
    {
        return $this->abstract;
    }

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

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

    public function setAddresses(Collection $addresses): void
    {
        $this->addresses = $addresses;
    }

    public function getAddresses(): Collection
    {
        return $this->addresses;
    }

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

    public function getAttributes(): Collection
    {
        return $this->attributes;
    }

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

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

    public function getMediaSorted(): array
    {
        // Sort and cache relations only on first call.
        if ($this->mediaSorted === null) {
            $this->mediaSorted = $this->getSortedRelations('media', 'originalId');
        }

        return $this->mediaSorted;
    }

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

    public function getFacebookUri(): ?string
    {
        return $this->facebookUri;
    }

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

    public function getTwitterUri(): ?string
    {
        return $this->twitterUri;
    }

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

    public function getInstagramUri(): ?string
    {
        return $this->instagramUri;
    }

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

    public function getYoutubeUri(): ?string
    {
        return $this->youtubeUri;
    }

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

    public function getFlickrUri(): ?string
    {
        return $this->flickrUri;
    }

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

    public function getWikipediaUri(): ?string
    {
        return $this->wikipediaUri;
    }

    public function setFiles(Collection $files): void
    {
        $this->files = $files;
    }

    public function getFiles(): Collection
    {
        return $this->files;
    }

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

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

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

    public function getAuthorName(): ?string
    {
        return $this->authorName;
    }

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

    public function getBookingUri(): ?string
    {
        return $this->bookingUri;
    }

    public function getClient(): ?string
    {
        return $this->client;
    }

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

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

    public function getDetailUri(): ?string
    {
        return $this->detailUri;
    }

    public function getOpeningTimes(): ?string
    {
        return $this->openingTimes;
    }

    public function setOpeningTimes(string $openingTimes): void
    {
        $this->openingTimes = $openingTimes;
    }

    public function getOpeningTimesFormat(): ?string
    {
        return $this->openingTimesFormat;
    }

    public function setOpeningTimesFormat(string $openingTimesFormat): void
    {
        $this->openingTimesFormat = $openingTimesFormat;
    }

    public function getStarClassification(): int
    {
        return $this->starClassification ?? 0;
    }

    public function setStarClassification(int $starClassification): void
    {
        $this->starClassification = $starClassification;
    }

    public function getAverageRating(): int
    {
        return $this->averageRating ?? 0;
    }

    public function setAverageRating(int $averageRating): void
    {
        $this->averageRating = $averageRating;
    }

    public function getNumberOfRatings(): int
    {
        return $this->numberOfRatings ?? 0;
    }

    public function setNumberOfRatings(int $numberOfRatings): void
    {
        $this->numberOfRatings = $numberOfRatings;
    }

    public function jsonSerialize(): array
    {
        $category = null;
        if ($this->categories->count() > 0) {
            $category = $this->categories->first();
        }

        $tour = null;
        if ($this->getIsTour() && $this->hasAttribute(TourAttributes::GEOMETRY)) {
            $tour = $this->getFirstAttribute(TourAttributes::GEOMETRY);
        }

        $image = null;
        if ($this->media->count() > 0) {
            $image = [
                'url' => $this->media->first()->getSourceUri(),
                'alt' => $this->media->first()->getTitle(),
            ];
        }

        $latitude = $this->mainAddress ? $this->mainAddress->getLatitude() : null;
        $longitude = $this->mainAddress ? $this->mainAddress->getLongitude() : null;

        return [
            'name' => $this->name,
            'lat' => $latitude,
            'lng' => $longitude,
            'category' => $category ? $category->getOriginalId() : null,
            'categoryTitle' => $category ? $category->getTitle() : null,
            'tour' => $tour,
            'image' => $image,
            'isTour' => $this->getIsTour(),
            'difficultyRating' => $this->getDifficultyRating(),
            'outdoorActiveTrackingId' => $this->getOutdoorActiveId(),
            'url' => null,
        ];
    }

    public function isOutdoorActiveTour(): bool
    {
        if (!$this->getIsTour() || !$this->hasAttribute(TourAttributes::DATA_SOURCE)) {
            return false;
        }

        $source = $this->getFirstAttribute(TourAttributes::DATA_SOURCE);
        return $source === TourAttributes::DATA_SOURCE_OUTDOOR_ACTIVE;
    }

    public function getOutdoorActiveId(): ?string
    {
        if (!$this->isOutdoorActiveTour()) {
            return null;
        }
        return  $this->getFirstAttribute(TourAttributes::DATA_SOURCE_ID);
    }

    public function getMainAddress(): ?Address
    {
        return $this->mainAddress;
    }

    public function setMainAddress(?Address $mainAddress): void
    {
        $this->mainAddress = $mainAddress;
    }

    /**
     * @return mixed|null
     */
    private function getDifficultyRating()
    {
        return $this->getFirstAttribute(TourAttributes::DIFFICULTY_RATING);
    }

    private function hasAttribute(string $name): bool
    {
        foreach ($this->attributes as $attribute) {
            if ($attribute->getName() === $name) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param string $name
     * @param mixed $default
     * @return mixed|null
     */
    private function getFirstAttribute(string $name, $default = null)
    {
        $value = $default;

        /** @var Attribute $attribute */
        $attributeObject = $this->attributes->filter(
            function (Attribute $attribute) use ($name) {
                return $attribute->getName() === $name;
            }
        )->first();

        if ($attributeObject) {
            $value = $attributeObject->getData();
        }

        return $value;
    }

    public function getFlatAttributes(): array
    {
        if (empty($this->flatAttributes)) {
            $this->generateFlatAttributes();
        }

        return (array) $this->flatAttributes;
    }

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

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

    private function generateFlatAttributes(): void
    {
        $this->flatAttributes = [];

        /** @var Attribute $attribute */
        foreach ($this->attributes as $attribute) {
            if (!array_key_exists($attribute->getName(), $this->flatAttributes)) {
                $this->flatAttributes[$attribute->getName()] = new AttributeCollection();
            }
            $this->flatAttributes[$attribute->getName()]->add($attribute);
        }
    }
}
