<?php
namespace Thunder\Shortcode\Serializer;

use Thunder\Shortcode\Parser\RegexParser;
use Thunder\Shortcode\Shortcode\Shortcode;
use Thunder\Shortcode\Shortcode\ShortcodeInterface;
use Thunder\Shortcode\Syntax\Syntax;
use Thunder\Shortcode\Syntax\SyntaxInterface;

/**
 * @author Tomasz Kowalczyk <tomasz@kowalczyk.cc>
 */
final class TextSerializer implements SerializerInterface
{
    /** @var SyntaxInterface */
    private $syntax;

    /** @param SyntaxInterface|null $syntax */
    public function __construct($syntax = null)
    {
        if(null !== $syntax && false === $syntax instanceof SyntaxInterface) {
            throw new \LogicException('Parameter $syntax must be an instance of SyntaxInterface.');
        }

        $this->syntax = $syntax ?: new Syntax();
    }

    /** @inheritDoc */
    public function serialize(ShortcodeInterface $shortcode)
    {
        $open = $this->syntax->getOpeningTag();
        $close = $this->syntax->getClosingTag();
        $marker = $this->syntax->getClosingTagMarker();

        $parameters = $this->serializeParameters($shortcode->getParameters());
        $bbCode = null !== $shortcode->getBbCode()
            ? $this->serializeValue($shortcode->getBbCode())
            : '';
        $return = $open.$shortcode->getName().$bbCode.$parameters;

        return null === $shortcode->getContent()
            ? $return.' '.$marker.$close
            : $return.$close.(string)$shortcode->getContent().$open.$marker.$shortcode->getName().$close;
    }

    /**
     * @psalm-param array<string,string|null> $parameters
     *
     * @return string
     */
    private function serializeParameters(array $parameters)
    {
        // unfortunately array_reduce() does not support keys
        $return = '';
        foreach ($parameters as $key => $value) {
            $return .= ' '.$key.$this->serializeValue($value);
        }

        return $return;
    }

    /**
     * @param string|null $value
     *
     * @return string
     */
    private function serializeValue($value)
    {
        if (null === $value) {
            return '';
        }

        $delimiter = $this->syntax->getParameterValueDelimiter();
        $separator = $this->syntax->getParameterValueSeparator();

        return $separator.(preg_match('/^\w+$/u', $value)
            ? $value
            : $delimiter.$value.$delimiter);
    }

    public function unserialize($text)
    {
        $parser = new RegexParser();

        $shortcodes = $parser->parse($text);

        if (empty($shortcodes)) {
            $msg = 'Failed to unserialize shortcode from text %s!';
            throw new \InvalidArgumentException(sprintf($msg, $text));
        }
        if (count($shortcodes) > 1) {
            $msg = 'Provided text %s contains more than one shortcode!';
            throw new \InvalidArgumentException(sprintf($msg, $text));
        }

        /** @var ShortcodeInterface $parsed */
        $parsed = array_shift($shortcodes);

        $name = $parsed->getName();
        $parameters = $parsed->getParameters();
        $content = $parsed->getContent();
        $bbCode = $parsed->getBbCode();

        return new Shortcode($name, $parameters, $content, $bbCode);
    }
}
