<?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\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 Type of the article.
     */
    protected $mainType;

    /**
     * @var string Name of the article.
     */
    protected $name;

    /**
     * @ORM\Column(type="text", nullable=true)
     * @var string Abstract for this article.
     */
    protected $abstract;

    /**
     * @ORM\Column(type="text", nullable=true)
     * @var string Descriptive text for this article.
     */
    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[]
     */
    private $flatAttributes;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @ORM\Column(nullable=true)
     * @var \DateTime.
     */
    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;

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

    /**
     * @return int
     */
    public function getMainType()
    {
        return $this->mainType;
    }

    /**
     * @param int $type
     * @return void
     */
    public function setMainType($type)
    {
        $this->mainType = $type;
    }

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

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

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

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

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

    /**
     * @var string $name
     * @return void
     */
    public function setName($name)
    {
        $this->name = $name;
    }

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

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

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

    /**
     * @var string $abstract
     * @return void
     */
    public function setAbstract($abstract)
    {
        $this->abstract = $abstract;
    }

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

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

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

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

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

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

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

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

    /**
     * @return Collection
     */
    public function getMedia()
    {
        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;
    }

    /**
     * @var string|null $facebookUri
     * @return void
     */
    public function setFacebookUri($facebookUri)
    {
        $this->facebookUri = $facebookUri;
    }

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

    /**
     * @var string|null $twitterUri
     * @return void
     */
    public function setTwitterUri($twitterUri)
    {
        $this->twitterUri = $twitterUri;
    }

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

    /**
     * @var string|null $instagramUri
     * @return void
     */
    public function setInstagramUri($instagramUri)
    {
        $this->instagramUri = $instagramUri;
    }

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

    /**
     * @var string|null $youtubeUri
     * @return void
     */
    public function setYoutubeUri($youtubeUri)
    {
        $this->youtubeUri = $youtubeUri;
    }

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

    /**
     * @var string|null $flickrUri
     * @return void
     */
    public function setFlickrUri($flickrUri)
    {
        $this->flickrUri = $flickrUri;
    }

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

    /**
     * @var string|null $wikipediaUri
     * @return void
     */
    public function setWikipediaUri($wikipediaUri)
    {
        $this->wikipediaUri = $wikipediaUri;
    }

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

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

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

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

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

    /**
     * @var string|null $authorName
     * @return void
     */
    public function setAuthorName($authorName)
    {
        $this->authorName = $authorName;
    }

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

    /**
     * @var string|null $bookingUri
     * @return void
     */
    public function setBookingUri($bookingUri)
    {
        $this->bookingUri = $bookingUri;
    }

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

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

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

    /**
     * @var string|null $detailUri
     * @return void
     */
    public function setDetailUri($detailUri)
    {
        $this->detailUri = $detailUri;
    }

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

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

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

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

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

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

    /**
     * @param int $starClassification
     */
    public function setStarClassification(int $starClassification)
    {
        $this->starClassification = $starClassification;
    }

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

    /**
     * @param int $averageRating
     */
    public function setAverageRating(int $averageRating)
    {
        $this->averageRating = $averageRating;
    }

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

    /**
     * @param int $numberOfRatings
     */
    public function setNumberOfRatings(int $numberOfRatings)
    {
        $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;
    }

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

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

    /**
     * @param Address|null $mainAddress
     */
    public function setMainAddress($mainAddress)
    {
        $this->mainAddress = $mainAddress;
    }

    private function getDifficultyRating()
    {
        return $this->getFirstAttribute(TourAttributes::DIFFICULTY_RATING);
    }

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

    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 $this->flatAttributes;
    }

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

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

    private function generateFlatAttributes()
    {
        $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);
        }
    }
}
