<?php
/**
 * @package qfq
 * @author kputyr
 * @date: 09.11.2023
 */

namespace IMATHUZH\Qfq\Core\Parser;

/**
 * Class KVPairListParser
 *
 * A parser for lists of key-value pairs with simple values.
 *
 * @package qfq
 */
class KVPairListParser extends SimpleParser {

    /** @var string the separator for different pairs */
    private string $listsep;

    /** @var string the character separating a key from its value */
    private string $kvsep;


    public function __construct(string $listsep, string $kvsep, array $options = []) {
        parent::__construct("$listsep$kvsep", $options);
        $this->listsep = $listsep;
        $this->kvsep = $kvsep;
    }

    /**
     * Iterates over the string and returns keys with values. Keys are always
     * treated as strings and only values are processed and, when necessary,
     * converted to numbers or special values.
     *
     * Usage:
     *
     *   foreach($parser->iterate($input) as $key => $value) {
     *       ...
     *   }
     *
     * Examples
     *   pair separator:       |
     *   key-value separator:  =
     *
     *     a=43|b=15         -->   'a'=>43    'b'=>15
     *     a='x|y' | 13=87   -->   'a'=>'x|y' '13'=>87
     *
     * @param string $data
     * @return \Generator
     */
    public function iterate(string $data): \Generator {
        // Iterate over token provided by the base tokenizer class
        $tokens = $this->tokenized($data);
        while ($tokens->valid()) {
            // Get the key first
            list($keyToken, $delimiter) = $tokens->current();
            $key = strval($keyToken);
            $tokens->next();
            if ($delimiter == $this->kvsep) {
                // The key-value separator is found - find the corresponding value
                if ($tokens->valid()) {
                    list($valueToken, $delimiter) = $tokens->current();
                    $tokens->next();
                    $empty = $valueToken->empty();
                    $value = $this->process($valueToken);
                } else {
                    // The end of the string - the value is empty
                    $delimiter = null;
                    $empty = true;
                }
                // Replace an empty token with the empty value
                yield $key => $empty ? (
                $this->options[self::OPTION_KEY_IS_VALUE] ? $key : $this->options[self::OPTION_EMPTY_VALUE]
                ) : $value;
            } elseif ($key) {
                // When no key-value separator, then do nothing it the key is empty
                // In other words: ignore trailing commas
                yield $key => $this->options[self::OPTION_KEY_IS_VALUE] ? $key : $this->options[self::OPTION_EMPTY_VALUE];
            }
            // Check if the current delimiter is a correct one
            if ($delimiter && $delimiter != $this->listsep) {
                $this->raiseUnexpectedDelimiter(
                    $delimiter,
                    $this->offset(),
                    $this->listsep
                );
            }
        };
    }
}