<?php namespace FluentMail\App\Services\DB\Viocon;

class Container
{
    /**
     * @var array
     */
    public $registry = array();

    /**
     * Singleton instances
     *
     * @var array
     */
    public $singletons = array();

    public function __construct($alias = null)
    {
        if ($alias) {
            AliasFacade::setVioconInstance($this);
            class_alias('\\FluentMail\\App\\Services\\DB\\Viocon\\AliasFacade', $alias);
        }
    }

    /**
     * Register an object with a key
     *
     * @param  string $key
     * @param  mixed  $object
     * @param  bool   $singleton
     *
     * @return void
     */
    public function set($key, $object, $singleton = false)
    {
        $this->registry[$key] = compact('object', 'singleton');
    }

    /**
     * If we have a registry for the given key
     *
     * @param  string $key
     *
     * @return bool
     */
    public function has($key)
    {
        return array_key_exists($key, $this->registry);
    }

    /**
     * Register as singleton.
     *
     * @param  string $key
     * @param  mixed  $object
     *
     * @return void
     */
    public function singleton($key, $object)
    {
        $this->set($key, $object, true);
    }

    /**
     * Register or replace an instance as a singleton.
     * Useful for replacing with Mocked instance
     *
     * @param  string $key
     * @param  mixed  $instance
     *
     * @return void
     */
    public function setInstance($key, $instance)
    {
        $this->singletons[$key] = $instance;
    }

    /**
     * Build from the given key.
     * If there is a class registered with Container::set() then it's instance
     * will be returned. If a closure is registered, a closure's return value
     * will be returned. If nothing is registered then it will try to build an
     * instance with new $key(...).
     *
     * $parameters will be passed to closure or class constructor.
     *
     *
     * @param  string $key
     * @param  array  $parameters
     *
     * @return mixed
     */
    public function build($key, $parameters = array())
    {
        // If we have a singleton instance registered the just return it
        if (array_key_exists($key, $this->singletons)) {
            return $this->singletons[$key];
        }

        // If we don't have a registered object with the key then assume user
        // is trying to build a class with the given key/name

        if (!array_key_exists($key, $this->registry)) {
            $object = $key;
        } else {
            $object = $this->registry[$key]['object'];
        }


        $instance = $this->instanciate($object, $parameters);

        // If the key is registered as a singleton, we can save the instance as singleton
        // for later use
        if (isset($this->registry[$key]['singleton']) && $this->registry[$key]['singleton'] === true) {
            $this->singletons[$key] = $instance;
        }

        return $instance;
    }

    /**
     * Instantiate an instance of the given type.
     *
     * @param  string $key
     * @param  array  $parameters
     *
     * @throws \Exception
     * @return mixed
     */
    protected function instanciate($key, $parameters = null)
    {

        if ($key instanceof \Closure) {
            return call_user_func_array($key, $parameters);
        }

        $reflection = new \ReflectionClass($key);
        return $reflection->newInstanceArgs($parameters);
    }
}
