<?php
namespace Newland\Toubiz\Sync\Neos\Service;

use Newland\Toubiz\Sync\Neos\Domain\Model\Attribute;
use Traversable;

/**
 * Collection of attributes that can either be used as a simple string value or
 * to iterate over all available values.
 *
 * This class was added to make the usage of attribute in templates easier as the
 * fact that some attributes may have multiple values is hidden from the template
 * developer when they don't need multiple values.
 *
 * In a template, all of the following are valid:
 *
 * @example
 * {article.flatAttributes.bestSeason}
 *
 * @example
 * {article.flatAttributes.bestSeason.0}
 * => jan
 *
 * @example
 * {article.flatAttributes.bestSeason.jan}
 * => jan
 *
 * @example
 * <f:for each="{article.flatAttributes.bestSeason}" as="{month}">
 *      ...
 * </f:for>
 *
 * @implements \ArrayAccess<string, Attribute>
 * @implements \IteratorAggregate<string, Attribute>
 */
class AttributeCollection implements \IteratorAggregate, \ArrayAccess, \Countable
{

    /**
     * @var Attribute[]
     */
    private $attributes = [];

    public function add(Attribute $attribute): void
    {
        if ($attribute->getData() || $attribute->getType() === Attribute::TYPE_NUMBER) {
            $this->attributes[] = $attribute;
        }
    }

    public function getName(): string
    {
        return $this->attributes[0]->getName();
    }

    public function getIntValue(): int
    {
        if ($this->getHasContents()) {
            return (int) $this->attributes[0]->getData();
        }

        return 0;
    }

    public function getBoolValue(): bool
    {
        if ($this->getHasContents()) {
            return (bool) $this->attributes[0]->getData();
        }

        return false;
    }

    public function __toString(): string
    {
        $mapAttributes = array_map(
            function (Attribute $attribute) {
                if (is_array($attribute->getData())) {
                    return implode(', ', $attribute->getData() ?? []);
                }

                return (string) $attribute->getData();
            },
            $this->attributes
        );

        return implode(', ', $mapAttributes);
    }

    public function getHasContents(): bool
    {
        return !empty($this->attributes);
    }

    public function attributeByData(string $data): ?Attribute
    {
        foreach ($this->attributes as $attribute) {
            if ($attribute->getData() === $data) {
                return $attribute;
            }
        }
        return null;
    }

    /**
     * @return mixed[]
     */
    public function getData(): array
    {
        return array_map(
            function (Attribute $attribute) {
                return $attribute->getData();
            },
            $this->attributes
        );
    }

    /**
     * Retrieve an external iterator
     *
     * @link https://php.net/manual/en/iteratoraggregate.getiterator.php
     * @return Traversable An instance of an object implementing <b>Iterator</b> or
     * <b>Traversable</b>
     * @since 5.0.0
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->getData());
    }

    /**
     * Whether a offset exists
     *
     * @link https://php.net/manual/en/arrayaccess.offsetexists.php
     * @param mixed $offset <p>
     * An offset to check for.
     * </p>
     * @return boolean true on success or false on failure.
     * </p>
     * <p>
     * The return value will be casted to boolean if non-boolean was returned.
     * @since 5.0.0
     */
    public function offsetExists($offset)
    {
        if (array_key_exists($offset, $this->attributes)) {
            return true;
        }
        if ($this->attributeByData($offset) !== null) {
            return true;
        }
        return false;
    }

    /**
     * Offset to retrieve
     *
     * @link https://php.net/manual/en/arrayaccess.offsetget.php
     * @param mixed $offset <p>
     * The offset to retrieve.
     * </p>
     * @return mixed Can return all value types.
     * @since 5.0.0
     */
    public function offsetGet($offset)
    {
        if (array_key_exists($offset, $this->attributes)) {
            return $this->attributes[$offset]->getData();
        }

        $attribute = $this->attributeByData($offset);
        if ($attribute !== null) {
            return $attribute->getData();
        }

        return null;
    }

    /**
     * Offset to set
     *
     * @link https://php.net/manual/en/arrayaccess.offsetset.php
     * @param mixed $offset <p>
     * The offset to assign the value to.
     * </p>
     * @param mixed $value <p>
     * The value to set.
     * </p>
     * @return void
     * @since 5.0.0
     */
    public function offsetSet($offset, $value)
    {
        throw new \LogicException('AttributeCollection does not allow modification using array accessors');
    }

    /**
     * Offset to unset
     *
     * @link https://php.net/manual/en/arrayaccess.offsetunset.php
     * @param mixed $offset <p>
     * The offset to unset.
     * </p>
     * @return void
     * @since 5.0.0
     */
    public function offsetUnset($offset)
    {
        throw new \LogicException('AttributeCollection does not allow modification using array accessors');
    }

    /**
     * Count elements of an object
     *
     * @link https://php.net/manual/en/countable.count.php
     * @return int The custom count as an integer.
     * </p>
     * <p>
     * The return value is cast to an integer.
     * @since 5.1.0
     */
    public function count()
    {
        return \count($this->attributes);
    }
}
