<?php
namespace Consolidation\Config\Util;

use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;

/**
 * Provides configuration objects with an 'interpolate' method
 * that may be used to inject config values into tokens embedded
 * in strings..
 */
class Interpolator
{
    /**
     * interpolate replaces tokens in a string with the correspnding
     * value from this config object. Tokens are surrounded by double
     * curley braces, e.g. "{{key}}".
     *
     * Example:
     * If the message is 'Hello, {{user.name}}', then the key user.name
     * is fetched from the config object, and the token {{user.name}} is
     * replaced with the result.
     *
     * @param string $message Message containing tokens to be replaced
     * @param string|bool $default The value to substitute for tokens that
     *   are not found in the configuration. If `false`, then missing
     *   tokens are not replaced.
     * @return string
     */
    public function interpolate($data, $message, $default = '')
    {
        $replacements = $this->replacements($data, $message, $default);
        return strtr($message, $replacements);
    }

    /**
     * @inheritdoc
     */
    public function mustInterpolate($data, $message)
    {
        $result = $this->interpolate($data, $message, false);
        $tokens = $this->findTokens($result);
        if (!empty($tokens)) {
            throw new \Exception('The following required keys were not found in configuration: ' . implode(',', $tokens));
        }
        return $result;
    }

    /**
     * findTokens finds all of the tokens in the provided message
     *
     * @param string $message String with tokens
     * @return string[] map of token to key, e.g. {{key}} => key
     */
    public function findTokens($message)
    {
        if (!preg_match_all('#{{([a-zA-Z0-9._-]+)}}#', $message, $matches, PREG_SET_ORDER)) {
            return [];
        }
        $tokens = [];
        foreach ($matches as $matchSet) {
            list($sourceText, $key) = $matchSet;
            $tokens[$sourceText] = $key;
        }
        return $tokens;
    }

    /**
     * Replacements looks up all of the replacements in the configuration
     * object, given the token keys from the provided message. Keys that
     * do not exist in the configuration are replaced with the default value.
     */
    public function replacements($data, $message, $default = '')
    {
        $tokens = $this->findTokens($message);

        $replacements = [];
        foreach ($tokens as $sourceText => $key) {
            $replacementText = $this->get($data, $key, $default);
            if ($replacementText !== false) {
                    $replacements[$sourceText] = $replacementText;
            }
        }
        return $replacements;
    }

    protected function get($data, $key, $default)
    {
        if (is_array($data)) {
            return array_key_exists($key, $data) ? $data[$key] : $default;
        }
        if ($data instanceof ConfigInterface) {
            return $data->get($key, $default);
        }
        throw new \Exception('Bad data type provided to Interpolator');
    }
}
