<?php

namespace JBBCode\visitors;

require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'CodeDefinition.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'DocumentElement.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'ElementNode.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'NodeVisitor.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'TextNode.php';

/**
 * This visitor is used by the jBBCode core to enforce nest limits after
 * parsing. It traverses the parse graph depth first, removing any subtrees
 * that are nested deeper than an element's code definition allows.
 *
 * @author jbowens
 * @since May 2013
 */
class NestLimitVisitor implements \JBBCode\NodeVisitor
{

    /** @var integer[] A map from tag name to current depth. */
    protected $depth = array();

    public function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
    {
        foreach ($documentElement->getChildren() as $child) {
            $child->accept($this);
        }
    }

    public function visitTextNode(\JBBCode\TextNode $textNode)
    {
        /* Nothing to do. Text nodes don't have tag names or children. */
    }

    public function visitElementNode(\JBBCode\ElementNode $elementNode)
    {
        $tagName = strtolower($elementNode->getTagName());

        /* Update the current depth for this tag name. */
        if (isset($this->depth[$tagName])) {
            $this->depth[$tagName]++;
        } else {
            $this->depth[$tagName] = 1;
        }

        /* Check if $elementNode is nested too deeply. */
        if ($elementNode->getCodeDefinition()->getNestLimit() != -1 &&
                $elementNode->getCodeDefinition()->getNestLimit() < $this->depth[$tagName]) {
            /* This element is nested too deeply. We need to remove it and not visit any
             * of its children. */
            $elementNode->getParent()->removeChild($elementNode);
        } else {
            /* This element is not nested too deeply. Visit all of its children. */
            foreach ($elementNode->getChildren() as $child) {
                $child->accept($this);
            }
        }

        /* Now that we're done visiting this node, decrement the depth. */
        $this->depth[$tagName]--;
    }
}
