<?php

namespace JBBCode;

require_once 'Node.php';

/**
 * An element within the tree. Consists of a tag name which defines the type of the
 * element and any number of Node children. It also contains a CodeDefinition matching
 * the tag name of the element.
 *
 * @author jbowens
 */
class ElementNode extends Node
{
    /** @var string The tagname of this element, for i.e. "b" in [b]bold[/b] */
    protected $tagName;

    /** @var string[] The attributes, if any, of this element node */
    protected $attribute;

    /** @var Node[] The child nodes contained within this element */
    protected $children;

    /** @var CodeDefinition The code definition that defines this element's behavior */
    protected $codeDefinition;

    /** @var integer How deeply this node is nested */
    protected $nestDepth;

    /**
     * Constructs the element node
     */
    public function __construct()
    {
        $this->children = array();
        $this->nestDepth = 0;
    }

    public function accept(NodeVisitor $nodeVisitor)
    {
        $nodeVisitor->visitElementNode($this);
    }

    /**
     * Gets the CodeDefinition that defines this element.
     *
     * @return CodeDefinition this element's code definition
     */
    public function getCodeDefinition()
    {
        return $this->codeDefinition;
    }

    /**
     * Sets the CodeDefinition that defines this element.
     *
     * @param CodeDefinition $codeDef the code definition that defines this element node
     */
    public function setCodeDefinition(CodeDefinition $codeDef)
    {
        $this->codeDefinition = $codeDef;
        $this->setTagName($codeDef->getTagName());
    }

    /**
     * Returns the tag name of this element.
     *
     * @return string the element's tag name
     */
    public function getTagName()
    {
        return $this->tagName;
    }

    /**
     * Returns the attribute (used as the option in bbcode definitions) of this element.
     *
     * @return string[] the attributes of this element
     */
    public function getAttribute()
    {
        return $this->attribute;
    }

    /**
     * Returns all the children of this element.
     *
     * @return Node[] an array of this node's child nodes
     */
    public function getChildren()
    {
        return $this->children;
    }

    /**
     * (non-PHPdoc)
     * @see JBBCode.Node::getAsText()
     *
     * Returns the element as text (not including any bbcode markup)
     *
     * @return string the plain text representation of this node
     */
    public function getAsText()
    {
        if ($this->codeDefinition) {
            return $this->codeDefinition->asText($this);
        } else {
            $s = "";
            foreach ($this->getChildren() as $child)
                $s .= $child->getAsText();
            return $s;
        }
    }

    /**
     * (non-PHPdoc)
     * @see JBBCode.Node::getAsBBCode()
     *
     * Returns the element as bbcode (with all unclosed tags closed)
     *
     * @return string the bbcode representation of this element
     */
    public function getAsBBCode()
    {
        $str = "[".$this->tagName;
        if (!empty($this->attribute)) {
            if(isset($this->attribute[$this->tagName])) {
                $str .= "=".$this->attribute[$this->tagName];
            }

            foreach($this->attribute as $key => $value){
                if($key == $this->tagName){
                    continue;
                }
                else{
                    $str .= " ".$key."=" . $value;
                }
            }
        }
        $str .= "]";
        foreach ($this->getChildren() as $child) {
            $str .= $child->getAsBBCode();
        }
        $str .= "[/".$this->tagName."]";

        return $str;
    }

    /**
     * (non-PHPdoc)
     * @see JBBCode.Node::getAsHTML()
     *
     * Returns the element as html with all replacements made
     *
     * @return string the html representation of this node
     */
    public function getAsHTML()
    {
        if($this->codeDefinition) {
            return $this->codeDefinition->asHtml($this);
        } else {
            return "";
        }
    }

    /**
     * Adds a child to this node's content. A child may be a TextNode, or
     * another ElementNode... or anything else that may extend the
     * abstract Node class.
     *
     * @param Node $child the node to add as a child
     */
    public function addChild(Node $child)
    {
        $this->children[] = $child;
        $child->setParent($this);
    }

    /**
     * Removes a child from this node's content.
     *
     * @param Node $child the child node to remove
     */
    public function removeChild(Node $child)
    {
        foreach ($this->children as $key => $value) {
            if ($value == $child)
                unset($this->children[$key]);
        }
    }

    /**
     * Sets the tag name of this element node.
     *
     * @param string $tagName the element's new tag name
     */
    public function setTagName($tagName)
    {
        $this->tagName = $tagName;
    }

    /**
     * Sets the attribute (option) of this element node.
     *
     * @param string[] $attribute the attribute(s) of this element node
     */
    public function setAttribute($attribute)
    {
        $this->attribute = $attribute;
    }

    /**
     * Traverses the parse tree upwards, going from parent to parent, until it finds a
     * parent who has the given tag name. Returns the parent with the matching tag name
     * if it exists, otherwise returns null.
     *
     * @param string $str the tag name to search for
     *
     * @return ElementNode|null the closest parent with the given tag name
     */
    public function closestParentOfType($str)
    {
        $str = strtolower($str);
        $currentEl = $this;

        while (strtolower($currentEl->getTagName()) != $str && $currentEl->hasParent()) {
            $currentEl = $currentEl->getParent();
        }

        if (strtolower($currentEl->getTagName()) != $str) {
            return null;
        } else {
            return $currentEl;
        }
    }

}
