<?php
/**
* @package   Warp Theme Framework
* @author    YOOtheme http://www.yootheme.com
* @copyright Copyright (C) YOOtheme GmbH
* @license   http://www.gnu.org/licenses/gpl.html GNU/GPL
*/

namespace Warp\Config;

use Warp\Config\Loader\PhpLoader;
use Warp\Config\Loader\JsonLoader;
use Warp\Config\Loader\LoaderChain;
use Warp\Config\Loader\LoaderInterface;

class Repository implements \ArrayAccess
{
    /**
     * @var array
     */
    protected $files = array();

    /**
     * @var array
     */
    protected $values = array();

    /**
     * @var array
     */
    protected $replacements = array();

    /**
     * @var LoaderInterface
     */
    protected $loader;

    /**
     * Create a new configuration repository.
     *
     * @param array           $replacements
     * @param LoaderInterface $loader
     */
    public function __construct($replacements = array(), LoaderInterface $loader = null)
    {
        $this->replacements = $replacements;
        $this->loader = $loader ?: new LoaderChain(array(new PhpLoader, new JsonLoader));
    }

    /**
     * Returns the all replacements.
     *
     * @return array
     */
    public function getReplacements()
    {
        return $this->replacements;
    }

    /**
     * Add a replacement.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function addReplacement($key, $value)
    {
        $this->replacements[$key] = $value;
    }

    /**
     * Get all configuration values.
     *
     * @return array
     */
    public function getValues()
    {
        $this->doLoad();

        return $this->values;
    }

    /**
     * Set configuration values.
     */
    public function setValues(array $values)
    {
        $this->doLoad();

        $this->values = $this->merge($this->values, $values);
    }

    /**
     * Determine if the given configuration value exists.
     *
     * @param string $key
     * 
     * @return bool
     */
    public function has($key)
    {
        $default = microtime(true);

        return $this->get($key, $default) != $default;
    }

    /**
     * Get the specified configuration value.
     *
     * @param string $key
     * @param mixed  $default
     * 
     * @return mixed
     */
    public function get($key, $default = null)
    {
        $this->doLoad();
        
        $array = $this->values;

        if (isset($array[$key])) {
            return $array[$key];            
        }

        foreach (explode('.', $key) as $segment) {

            if (!is_array($array) || !array_key_exists($segment, $array)) {
                return $default;
            }

            $array = $array[$segment];
        }

        return $array;
    }

    /**
     * Set a given configuration value.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function set($key, $value)
    {
        $this->doLoad();

        $keys = explode('.', $key);
        $array =& $this->values;

        while (count($keys) > 1) {

            $key = array_shift($keys);

            if (!isset($array[$key]) || !is_array($array[$key])) {
                $array[$key] = array();
            }

            $array =& $array[$key];
        }

        $array[array_shift($keys)] = $value;
    }

    /**
     * Determine if the given configuration option exists.
     *
     * @param string $key
     * 
     * @return bool
     */
    public function offsetExists($key)
    {
        return $this->has($key);
    }

    /**
     * Get a configuration option.
     *
     * @param string $key
     * 
     * @return bool
     */
    public function offsetGet($key)
    {
        return $this->get($key);
    }

    /**
     * Set a configuration option.
     *
     * @param string $key
     * @param string $value
     */
    public function offsetSet($key, $value)
    {
        $this->set($key, $value);
    }

    /**
     * Unset a configuration option.
     *
     * @param string $key
     */
    public function offsetUnset($key)
    {
        $this->set($key, null);
    }

    /**
     * Load a configuration file.
     *
     * @param string $file
     */
    public function load($file)
    {
        $this->files[] = $file;
    }

    /**
     * Dumps the configuration.
     *
     * @return string The dumped configuration.
     */
    public function dump()
    {
        return '<?php return '.var_export($this->values, true).';';
    }

    /**
     * Load the configuration from files.
     */
    protected function doLoad()
    {
        while ($file = array_shift($this->files)) {
            $this->values = $this->merge($this->values, $this->loader->load($file));
        }
    }

    protected function merge(array $current, array $new)
    {
        foreach ($new as $name => $value) {

            if ($important = $name[0] == '!') {
                $name = ltrim($name, '!');
            }

            if (isset($current[$name]) && is_array($value) && !$important) {
                $current[$name] = $this->merge($current[$name], $value);
            } elseif (is_string($name)) {
                $current[$name] = $this->doReplacements($value);
            } else {
                $current[] = $this->doReplacements($value);               
            }
        }

        return $current;
    }

    protected function doReplacements($value)
    {
        if (!$this->replacements) {
            return $value;
        }

        if (is_array($value)) {

            foreach ($value as $key => $val) {
                $value[$key] = $this->doReplacements($val);
            }

            return $value;
        }

        if (is_string($value) && false !== strpos($value, '%')) {
            return preg_replace_callback('/\%([\w\.]+)\%/', array($this, 'doReplacementCallback'), $value);
        }

        return $value;
    }

    protected function doReplacementCallback($matches)
    {
        if (array_key_exists($key = $matches[1], $this->replacements)) {

            if (($closure = $this->replacements[$key]) instanceof \Closure) {
                $this->replacements[$key] = $closure();
            }

            return $this->replacements[$key];
        }

        return $matches[0];
    }
}
