<?php
namespace Mapbender\CoreBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\UniqueConstraint;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Layerset configuration entity
 *
 * @author Christian Wygoda
 *
 * @ORM\Entity(repositoryClass="Mapbender\CoreBundle\Entity\Repository\LayersetRepository")
 * @ORM\Table(name="mb_core_layerset", uniqueConstraints={@UniqueConstraint(name="layerset_idx", columns={"application_id", "title"})})
 * @UniqueEntity(fields={"application", "title"}, message ="Duplicate entry for key 'title'.")
 */
class Layerset
{

    /**
     * @var integer $id
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @var string $title The layerset title
     * @ORM\Column(type="string", length=128)
     * @Assert\NotBlank()
     */
    protected $title;

    /**
     * @var Application The configuration entity for the application
     * @ORM\ManyToOne(targetEntity="Application", inversedBy="layersets")
     * @ORM\JoinColumn(onDelete="CASCADE")
     */
    protected $application;

    /**
     * @ORM\OneToMany(targetEntity="SourceInstance", mappedBy="layerset", cascade={"remove", "persist"})
     * @ORM\JoinColumn(name="instances", referencedColumnName="id")
     * @ORM\OrderBy({"weight" = "asc"})
     */
    protected $instances;

    /**
     * Reusable source instance assignments
     * Relation to actual SourceInstance goes via separate assignment entity that stores sorting weight and enabled state
     * @var ReusableSourceInstanceAssignment[]|ArrayCollection
     * @ORM\OneToMany(targetEntity="ReusableSourceInstanceAssignment", mappedBy="layerset", cascade={"remove", "persist"})
     * @ORM\OrderBy({"weight"="ASC"})
     */
    protected $reusableInstanceAssignments;

    /**
     * Layerset constructor.
     */
    public function __construct()
    {
        $this->instances = new ArrayCollection();
        $this->reusableInstanceAssignments = new ArrayCollection();
    }

    /**
     * Set id.
     *
     * @param $id
     * @return $this
     */
    public function setId($id)
    {
        if (null !== $id) {
            $this->id = $id;
        }

        return $this;
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

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

        return $this;
    }

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

    /**
     * Set application
     *
     * @param Application $application
     * @return $this
     */
    public function setApplication(Application $application)
    {
        $this->application = $application;

        return $this;
    }

    /**
     * Get application
     *
     * @return Application
     */
    public function getApplication()
    {
        return $this->application;
    }

    /**
     * Add SourceInstance
     *
     * @param SourceInstance $instance
     */
    public function addInstance(SourceInstance $instance)
    {
        $this->instances->add($instance);
    }

    /**
     * Set instances
     *
     * @param Collection $instances
     * @return $this
     */
    public function setInstances($instances)
    {
        $this->instances = $instances;

        return $this;
    }

    /**
     * @return ReusableSourceInstanceAssignment[]|ArrayCollection
     */
    public function getReusableInstanceAssignments()
    {
        return $this->reusableInstanceAssignments;
    }

    /**
     * @param ReusableSourceInstanceAssignment[]|ArrayCollection $reusableInstanceAssignments
     * @return $this
     */
    public function setReusableInstanceAssignments($reusableInstanceAssignments)
    {
        $this->reusableInstanceAssignments = $reusableInstanceAssignments;
        return $this;
    }

    /**
     * Get instances
     *
     * @param bool $includeUnowned NOTE: cannot be true by default to avoid erroneous doctrine behaviour
     * @return SourceInstance[]|Collection
     */
    public function getInstances($includeUnowned = false)
    {
        if ($includeUnowned) {
            return $this->getCombinedInstances();
        } else {
            return $this->instances;
        }
    }

    /**
     * Get BOTH owned instances and assigned reusable instances in
     * a single collection.
     *
     * @return SourceInstance[]|ArrayCollection
     */
    public function getCombinedInstances()
    {
        return $this->getCombinedInstanceAssignments()->map(function ($assignment) {
            /** @var SourceInstanceAssignment $assignment */
            return $assignment->getInstance();
        });
    }

    /**
     * Returns a list of source instance assignments, both directly owned and reusable.
     *
     * @return SourceInstanceAssignment[]|ArrayCollection
     */
    public function getCombinedInstanceAssignments()
    {
        $owned = $this->instances->getValues();
        $unowned = $this->getReusableInstanceAssignments()->getValues();
        $combined = new ArrayCollection(array_merge($owned, $unowned));
        return $combined->matching(Criteria::create()->orderBy(array(
            'weight' => Criteria::ASC
        )));
    }

    /**
     * Read-only informative pseudo-relation
     *
     * @param Source $source
     * @param bool $includeUnowned
     * @return SourceInstance[]|ArrayCollection
     */
    public function getInstancesOf(Source $source, $includeUnowned = true)
    {
        return $this->getInstances($includeUnowned)->filter(function ($instance) use ($source) {
            /** @var SourceInstance $instance */
            return $instance->getSource() === $source;
        });
    }

    /**
     * @return ArrayCollection|SourceInstance[]
     */
    public function getAssignedReusableInstances()
    {
        return $this->getReusableInstanceAssignments()->map(function($assignment) {
            /** @var ReusableSourceInstanceAssignment $assignment */
            return $assignment->getInstance();
        });
    }

    /**
     * @return string Layerset ID
     */
    public function __toString()
    {
        return (string) $this->getId();
    }
}
