<?php

namespace pictpostpersonal;

/**
 * 
 * 特殊記述成形用プロセッサ
 * 
 */
class TagProcessor
{

    private $patterns = [];
    private $callbacks = [];
    public $data;

    /**
     * 特殊記述タグを追加する
     *
     * @param string $command 変換元のタグ名。{sample}であれば "sample"。
     * @param callable|string|array $callback 
     *                                  callableの場合は置換用関数。func(args, content):replaced_conent。
     *                                  stringの場合は置換用関数（クラス指定)。 "クラス名:関数名"。
     *                                  arrayの場合は単純置換。["置換前パターン","置換パターン"]。
     * 
     * @return void
     * 
     */
    public function add(string $command, $callback): void
    {
        $pattern = $this->buildPattern($command);
        $this->patterns[] = $pattern;
        $this->callbacks[$pattern] = $callback;
    }

    private $check_pattern = '';
    public function process(string $text): string
    {
        foreach ($this->patterns as $pattern) {
            $this->check_pattern = $pattern;
            $text = preg_replace_callback($pattern, [$this, 'handleMatch'], $text);
        }
        return $text;
    }

    private function buildPattern($command)
    {
        return '/\{' . $command . ':?([^}]*)\}(.*?)\{\/' . $command . '\}|\{' . $command . ':?([^}]*)\}/s';
    }

    private function handleMatch($matches)
    {
        $command = $this->extractCommand($matches[0]);

        if (isset($matches[2]) && $matches[2] != '') {
            // ブロックタグ
            $args = $this->parseArgs($matches[1]);
            $args['command'] = $command;
            $content = $matches[2];
        } else {
            // シングルタグ
            $args = $this->parseArgs($matches[3]);
            $args['command'] = $command;
            $content = '';
        }

        if (isset($this->callbacks[$this->check_pattern])) {
            return call_user_func($this->callbacks[$this->check_pattern], $command, $args, $content, $this->data);
        }

        //変換前のテキストを返す
        return $matches[0];
    }

    private function extractCommand($text)
    {
        if (preg_match('/\{([^\s}]+)/', $text, $match)) {
            return $match[1];
        }
        return '';
    }

    private function parseArgs($arg_string)
    {

        if (trim($arg_string, ' ') == '') {
            return [];
        }
        if (substr($arg_string, 0, 1) != ' ') {
            $arg_string = ' ' . $arg_string;
        }
        $args = [];
        preg_match_all('/([^ =:]+)( ?[:=]"?([^ }"]+)"?)?/', $arg_string, $matches, PREG_SET_ORDER);
        foreach ($matches as $idx => $match) {
            $args[$match[1]] = isset($match[3]) ? $match[3] : true;
            $args[$idx] = $match[1];
        }
        return $args;
    }
}
