<?php

namespace EasyRdf;

/**
 * EasyRdf
 *
 * LICENSE
 *
 * Copyright (c) 2013-2014 Nicholas J Humfrey.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author 'Nicholas J Humfrey" may be used to endorse or
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @copyright  Copyright (c) 2013-2014 Nicholas J Humfrey
 * @license    https://www.opensource.org/licenses/bsd-license.php
 */

/**
 * Sub-class of EasyRdf\Resource that represents an RDF container
 * (rdf:Alt, rdf:Bag and rdf:Seq)
 *
 * This class can be used to iterate through a list of items.
 *
 * @see       http://www.w3.org/TR/xmlschema-2/#date
 *
 * @copyright  Copyright (c) 2013-2014 Nicholas J Humfrey
 * @license    https://www.opensource.org/licenses/bsd-license.php
 */
class Container extends Resource implements \ArrayAccess, \Countable, \SeekableIterator
{
    private $position;

    /** Create a new container - do not use this directly
     *
     * @ignore
     */
    public function __construct($uri, $graph)
    {
        $this->position = 1;
        parent::__construct($uri, $graph);
    }

    /** Seek to a specific position in the container
     *
     * The first item is postion 1
     *
     * @param int $position The position in the container to seek to
     *
     * @throws \OutOfBoundsException
     * @throws \InvalidArgumentException
     */
    #[\ReturnTypeWillChange]
    public function seek($position)
    {
        if (\is_int($position) && $position > 0) {
            if ($this->hasProperty('rdf:_'.$position)) {
                $this->position = $position;
            } else {
                throw new \OutOfBoundsException("Unable to seek to position $position in the container");
            }
        } else {
            throw new \InvalidArgumentException('Container position must be a positive integer');
        }
    }

    /** Rewind the iterator back to the start of the container (item 1)
     *
     */
    #[\ReturnTypeWillChange]
    public function rewind()
    {
        $this->position = 1;
    }

    /** Return the current item in the container
     *
     * @return mixed The current item
     */
    #[\ReturnTypeWillChange]
    public function current()
    {
        return $this->get('rdf:_'.$this->position);
    }

    /** Return the key / current position in the container
     *
     * @return int The current position
     */
    #[\ReturnTypeWillChange]
    public function key()
    {
        return $this->position;
    }

    /** Move forward to next item in the container
     *
     */
    #[\ReturnTypeWillChange]
    public function next()
    {
        ++$this->position;
    }

    /** Checks if current position is valid
     *
     * @return bool True if the current position is valid
     */
    #[\ReturnTypeWillChange]
    public function valid()
    {
        return $this->hasProperty('rdf:_'.$this->position);
    }

    /** Counts the number of items in the container
     *
     * Note that this is an slow method - it is more efficient to use
     * the iterator interface, if you can.
     *
     * @return int The number of items in the container
     */
    #[\ReturnTypeWillChange]
    public function count()
    {
        $pos = 1;
        while ($this->hasProperty('rdf:_'.$pos)) {
            ++$pos;
        }

        return $pos - 1;
    }

    /** Append an item to the end of the container
     *
     * @param mixed $value The value to append
     *
     * @return int The number of values appended (1 or 0)
     */
    public function append($value)
    {
        // Find the end of the list
        $pos = 1;
        while ($this->hasProperty('rdf:_'.$pos)) {
            ++$pos;
        }

        // Add the item
        return $this->add('rdf:_'.$pos, $value);
    }

    /** Array Access: check if a position exists in container using array syntax
     *
     * Example: isset($seq[2])
     */
    #[\ReturnTypeWillChange]
    public function offsetExists($offset)
    {
        if (\is_int($offset) && $offset > 0) {
            return $this->hasProperty('rdf:_'.$offset);
        } else {
            throw new \InvalidArgumentException('Container position must be a positive integer');
        }
    }

    /** Array Access: get an item at a specified position in container using array syntax
     *
     * Example: $item = $seq[2];
     */
    #[\ReturnTypeWillChange]
    public function offsetGet($offset)
    {
        if (\is_int($offset) && $offset > 0) {
            return $this->get('rdf:_'.$offset);
        } else {
            throw new \InvalidArgumentException('Container position must be a positive integer');
        }
    }

    /**
     * Array Access: set an item at a positon in container using array syntax
     *
     * Example: $seq[2] = $item;
     *
     * Warning: creating gaps in the sequence will result in unexpected behavior
     *
     * @todo Change return type to void, because this violates offsetSet:void
     *       see: https://php.net/manual/en/arrayaccess.offsetset.php
     */
    #[\ReturnTypeWillChange]
    public function offsetSet($offset, $value)
    {
        if (\is_int($offset) && $offset > 0) {
            // currently ignored by phpstan (see phpstan.neon)
            return $this->set('rdf:_'.$offset, $value);
        } elseif (null === $offset) {
            // currently ignored by phpstan (see phpstan.neon)
            return $this->append($value);
        } else {
            throw new \InvalidArgumentException('Container position must be a positive integer');
        }
    }

    /**
     * Array Access: delete an item at a specific postion using array syntax
     *
     * Example: unset($seq[2]);
     *
     * Warning: creating gaps in the sequence will result in unexpected behavior
     *
     * @todo Change return type to void, because this violates offsetUnset:void
     *       see: https://php.net/manual/en/arrayaccess.offsetunset.php
     */
    #[\ReturnTypeWillChange]
    public function offsetUnset($offset)
    {
        if (\is_int($offset) && $offset > 0) {
            // currently ignored by phpstan (see phpstan.neon)
            return $this->delete('rdf:_'.$offset);
        } else {
            throw new \InvalidArgumentException('Container position must be a positive integer');
        }
    }
}
