<?php
namespace Misuzu\Config;

use InvalidArgumentException;
use Misuzu\Pagination;

class ScopedConfig implements IConfig {
    private IConfig $config;
    private string $prefix;
    private int $prefixLength;

    private const SCOPE_CHAR = '.';

    public function __construct(IConfig $config, string $prefix) {
        if($prefix === '')
            throw new InvalidArgumentException('$prefix may not be empty.');
        if(!str_ends_with($prefix, self::SCOPE_CHAR))
            $prefix .= self::SCOPE_CHAR;

        $this->config = $config;
        $this->prefix = $prefix;
        $this->prefixLength = strlen($prefix);
    }

    private function prefixNames(string|array $names): array {
        if(is_string($names))
            return [$this->prefix . $names];

        foreach($names as $key => $name)
            $names[$key] = $this->prefix . $name;

        return $names;
    }

    private function prefixName(string $name): string {
        return $this->prefix . $name;
    }

    public function scopeTo(string $prefix): IConfig {
        return $this->config->scopeTo($this->prefixName($prefix));
    }

    public function hasValues(string|array $names): bool {
        return $this->config->hasValues($this->prefixNames($names));
    }

    public function removeValues(string|array $names): void {
        $this->config->removeValues($this->prefixNames($names));
    }

    public function getAllValueInfos(?Pagination $pagination = null): array {
        $infos = $this->config->getAllValueInfos($pagination);
        foreach($infos as $key => $info)
            $infos[$key] = new ScopedConfigValueInfo($info, $this->prefixLength);
        return $infos;
    }

    public function getValueInfos(string|array $names): array {
        $infos = $this->config->getValueInfos($this->prefixNames($names));
        foreach($infos as $key => $info)
            $infos[$key] = new ScopedConfigValueInfo($info, $this->prefixLength);
        return $infos;
    }

    public function getValueInfo(string $name): ?IConfigValueInfo {
        $info = $this->config->getValueInfo($this->prefixName($name));
        if($info !== null)
            $info = new ScopedConfigValueInfo($info, $this->prefixLength);
        return $info;
    }

    public function getValues(array $specs): array {
        foreach($specs as $key => $spec) {
            if(is_string($spec))
                $specs[$key] = $this->prefixName($spec);
            elseif(is_array($spec) && !empty($spec))
                $specs[$key][0] = $this->prefixName($spec[0]);
            else
                throw new InvalidArgumentException('$specs array contains an invalid entry.');
        }

        $results = [];
        foreach($this->config->getValues($specs) as $name => $result)
            $results[substr($name, $this->prefixLength)] = $result;

        return $results;
    }

    public function getString(string $name, string $default = ''): string {
        return $this->config->getString($this->prefixName($name), $default);
    }

    public function getInteger(string $name, int $default = 0): int {
        return $this->config->getInteger($this->prefixName($name), $default);
    }

    public function getFloat(string $name, float $default = 0): float {
        return $this->config->getFloat($this->prefixName($name), $default);
    }

    public function getBoolean(string $name, bool $default = false): bool {
        return $this->config->getBoolean($this->prefixName($name), $default);
    }

    public function getArray(string $name, array $default = []): array {
        return $this->config->getArray($this->prefixName($name), $default);
    }

    public function setValues(array $values): void {
        if(empty($values))
            return;

        $prefixed = [];
        foreach($values as $name => $value)
            $prefixed[$this->prefixName($name)] = $value;
        $this->config->setValues($values);
    }

    public function setString(string $name, string $value): void {
        $this->config->setString($this->prefixName($name), $value);
    }

    public function setInteger(string $name, int $value): void {
        $this->config->setInteger($this->prefixName($name), $value);
    }

    public function setFloat(string $name, float $value): void {
        $this->config->setFloat($this->prefixName($name), $value);
    }

    public function setBoolean(string $name, bool $value): void {
        $this->config->setBoolean($this->prefixName($name), $value);
    }

    public function setArray(string $name, array $value): void {
        $this->config->setArray($this->prefixName($name), $value);
    }
}
