<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */

namespace Icinga\Web;

use Exception;
use RecursiveIteratorIterator;
use Icinga\Application\Logger;

/**
 * A renderer to draw a menu with its sub-menus using an unordered html list
 */
class MenuRenderer extends RecursiveIteratorIterator
{
    /**
     * The relative url of the current request
     *
     * @var string
     */
    protected $url;

    /**
     * The html tags to assemble the menu with
     *
     * @var array
     */
    protected $tags = array();

    /**
     * @var bool
     */
    protected $useCustomRenderer = false;

    /**
     * Create a new MenuRenderer
     *
     * @param   Menu    $menu   The menu to render
     * @param   string  $url    A relative url to identify "active" children with
     */
    public function __construct(Menu $menu, $url = null)
    {
        if ($url instanceof Url) {
            $this->url = $url;
        } else {
            $this->url = Url::fromPath($url);
        }
        parent::__construct($menu, RecursiveIteratorIterator::CHILD_FIRST);
    }

    /**
     * @param bool $value
     */
    public function useCustomRenderer($value = true)
    {
        $this->useCustomRenderer = $value;
        return $this;
    }

    /**
     * Register the outer ul opening html-tag
     */
    public function beginIteration()
    {
        $this->tags[] = '<ul role="navigation">';
    }

    /**
     * Register the outer ul closing html-tag
     */
    public function endIteration()
    {
        $this->tags[] = '</ul>';
    }

    /**
     * Register a inner ul opening html-tag
     */
    public function beginChildren()
    {
        // The iterator runs in mode CHILD_FIRST so we need to remember
        // where to insert the parent's opening html tag once its rendered
        $parent = $this->getSubIterator(0)->current();
        $this->tags[$parent->getId() . '_begin'] = null;

        $this->tags[] = '<ul>';
    }

    /**
     * Register a inner ul closing html-tag
     */
    public function endChildren()
    {
        $this->tags[] = '</ul>';

        // Remember the position of the parent's closing html-tag
        $parent = $this->getSubIterator(0)->current();
        $this->tags[$parent->getId() . '_end'] = null;
    }

    /**
     * Render the given child
     *
     * @param   Menu    $child      The menu's child to render
     *
     * @return  string              The child rendered as html
     */
    public function renderChild(Menu $child)
    {
        if ($child->getRenderer() !== null && $this->useCustomRenderer) {
            try {
                return $child->getRenderer()->render($child);
            } catch (Exception $e) {
                Logger::error('Could not invoke custom renderer. Exception: '. $e->getMessage());
            }
        }
        if ($child->getIcon() && strpos($child->getIcon(), '.') === false) {
            return sprintf(
                '<a href="%s" class="icon-%s">%s</a>',
                $child->getUrl() ?: '#',
                $child->getIcon(),
                htmlspecialchars($child->getTitle())
            );
        }
        return sprintf(
            '<a href="%s">%s%s</a>',
            $child->getUrl() ?: '#',
            $child->getIcon() ? '<img src="' . Url::fromPath($child->getIcon()) . '" class="icon" /> ' : '',
            htmlspecialchars($child->getTitle())
        );
    }

    /**
     * Return the menu rendered as html
     *
     * @return  string
     */
    public function render()
    {
        $passedActiveChild = false;
        foreach ($this as $child) {
            $childIsActive = $this->isActive($child);
            if ($childIsActive && $this->getDepth() > 0) {
                $passedActiveChild = true;
            }

            if ($childIsActive || ($passedActiveChild && $this->getDepth() === 0)) {
                $passedActiveChild &= $this->getDepth() !== 0;
                $openTag = '<li class="active" id="' . $child->getUniqueId() . '">';
            } else {
                $openTag = '<li id="' . $child->getUniqueId() . '">';
            }
            $content = $this->renderChild($child);
            $closingTag = '</li>';

            if (array_key_exists($child->getId() . '_begin', $this->tags)) {
                $this->tags[$child->getId() . '_begin'] = $openTag . $content;
                $this->tags[$child->getId() . '_end'] = $closingTag;
            } else {
                $this->tags[] = $openTag . $content . $closingTag;
            }
        }

        return implode("\n", $this->tags);
    }

    /**
     * @see MenuRenderer::render()
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * Return whether the current request url references the child's url
     *
     * @param   Menu    $child      The menu's child to check
     *
     * @return  bool
     */
    protected function isActive(Menu $child)
    {
        if (! $this->url) return false;
        if (! ($childUrl = $child->getUrl())) return false;

        return $this->url && $this->url->matches($childUrl);
    }
}