<?php
/**
 * @see       https://github.com/zendframework/zend-session for the canonical source repository
 * @copyright Copyright (c) 2005-2019 Zend Technologies USA Inc. (https://www.zend.com)
 * @license   https://github.com/zendframework/zend-session/blob/master/LICENSE.md New BSD License
 */

namespace Zend\Session\Storage;

use ArrayAccess;
use Traversable;
use Zend\Session\Exception;
use Zend\Stdlib\ArrayObject;
use Zend\Stdlib\ArrayUtils;

abstract class Factory
{
    /**
     * Create and return a StorageInterface instance
     *
     * @param  string                             $type
     * @param  array|Traversable                  $options
     * @return StorageInterface
     * @throws Exception\InvalidArgumentException for unrecognized $type or individual options
     */
    public static function factory($type, $options = [])
    {
        if (! is_string($type)) {
            throw new Exception\InvalidArgumentException(sprintf(
                '%s expects the $type argument to be a string class name; received "%s"',
                __METHOD__,
                (is_object($type) ? get_class($type) : gettype($type))
            ));
        }
        if (! class_exists($type)) {
            $class = __NAMESPACE__ . '\\' . $type;
            if (! class_exists($class)) {
                throw new Exception\InvalidArgumentException(sprintf(
                    '%s expects the $type argument to be a valid class name; received "%s"',
                    __METHOD__,
                    $type
                ));
            }
            $type = $class;
        }

        if ($options instanceof Traversable) {
            $options = ArrayUtils::iteratorToArray($options);
        }
        if (! is_array($options)) {
            throw new Exception\InvalidArgumentException(sprintf(
                '%s expects the $options argument to be an array or Traversable; received "%s"',
                __METHOD__,
                (is_object($options) ? get_class($options) : gettype($options))
            ));
        }

        switch (true) {
            case (in_array('Zend\Session\Storage\AbstractSessionArrayStorage', class_parents($type))):
                return static::createSessionArrayStorage($type, $options);
            case ($type === 'Zend\Session\Storage\ArrayStorage'):
            case (in_array('Zend\Session\Storage\ArrayStorage', class_parents($type))):
                return static::createArrayStorage($type, $options);
            case (in_array('Zend\Session\Storage\StorageInterface', class_implements($type))):
                return new $type($options);
            default:
                throw new Exception\InvalidArgumentException(sprintf(
                    'Unrecognized type "%s" provided; expects a class implementing %s\StorageInterface',
                    $type,
                    __NAMESPACE__
                ));
        }
    }

    /**
     * Create a storage object from an ArrayStorage class (or a descendent)
     *
     * @param  string       $type
     * @param  array        $options
     * @return ArrayStorage
     */
    protected static function createArrayStorage($type, $options)
    {
        $input         = [];
        $flags         = ArrayObject::ARRAY_AS_PROPS;
        $iteratorClass = 'ArrayIterator';

        if (isset($options['input']) && null !== $options['input']) {
            if (! is_array($options['input'])) {
                throw new Exception\InvalidArgumentException(sprintf(
                    '%s expects the "input" option to be an array; received "%s"',
                    $type,
                    (is_object($options['input']) ? get_class($options['input']) : gettype($options['input']))
                ));
            }
            $input = $options['input'];
        }

        if (isset($options['flags'])) {
            $flags = $options['flags'];
        }

        if (isset($options['iterator_class'])) {
            if (! class_exists($options['iterator_class'])) {
                throw new Exception\InvalidArgumentException(sprintf(
                    '%s expects the "iterator_class" option to be a valid class; received "%s"',
                    $type,
                    (is_object($options['iterator_class'])
                        ? get_class($options['iterator_class'])
                        : gettype($options['iterator_class'])
                    )
                ));
            }
            $iteratorClass = $options['iterator_class'];
        }

        return new $type($input, $flags, $iteratorClass);
    }

    /**
     * Create a storage object from a class extending AbstractSessionArrayStorage
     *
     * @param  string                             $type
     * @param  array                              $options
     * @return AbstractSessionArrayStorage
     * @throws Exception\InvalidArgumentException if the input option is invalid
     */
    protected static function createSessionArrayStorage($type, array $options)
    {
        $input = null;
        if (isset($options['input'])) {
            $input = $options['input'];
            if (! is_array($input)
                && ! $input instanceof ArrayAccess
            ) {
                throw new Exception\InvalidArgumentException(sprintf(
                    '%s expects the "input" option to be null, an array, or to implement ArrayAccess; received "%s"',
                    $type,
                    is_object($input) ? get_class($input) : gettype($input)
                ));
            }
        }

        return new $type($input);
    }
}
