<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2012 - 2016 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flWidgetTreeNode Class
 *
 * Class to define a node in the tree view.
 *
 * @package fusionLib
 */
class flWidgetTreeNode {

	/**#@+
	 * Tree view node types.
	 */
	const NODE = 'node';
	const CONTAINER = 'container';
	const CONTAINEROPEN = 'containeropen';
	const DOC = 'doc';
	const LINK = 'link';
	const VIDEO = 'video';
	const IMAGE = 'image';
	const SOUND = 'sound';
	const PREF = 'pref';
	const CODE = 'code';
	const PHP = 'php';
	const JS = 'js';
	const CSS = 'css';
	const XML = 'xml';
	const PLUGIN = 'plugin';
	const SNIPPET = 'snippet';
	const TEMPLATE = 'template';
	const CHUNK = 'chunk';
	const RESOURCE = 'resource';
	const FIELD = 'field';
	const TASK = 'task';
	/**#@-*/

	/**
	 * The display name for the node.
	 *
	 * @var string
	 */
	protected $name;

	/**
	 * The node type.
	 *
	 * @var string
	 */
	protected $type;

	/**
	 * Flag if the node is the active node.
	 *
	 * @var bool
	 */
	protected $active = false;

	/**
	 * Optional ID for the node, required for drag n drop.
	 *
	 * @var string
	 */
	protected $id;

	/**
	 * Flags if the node can be dragged.
	 *
	 * @var bool
	 */
	protected $canDrag = true;

	/**
	 * Construct the object.
	 *
	 * @param string $name The node display name.
	 * @param string $type The name of the node.
	 * @param string $id The ID of the node.
	 */
	function __construct($name, $type = 'node', $id = null) {
		$this->name = str_replace('[', '&#91;', fusionLib::htmlChars($name));
		$this->type = $type;
		$this->id = $id;
	}

	/**
	 * Set the ID of the node.
	 *
	 * @param string $id The ID of the tree node.
	 * @return flWidgetTreeNode This object.
	 */
	function setID($id) {
		$this->id = $id;
		return $this;
	}

	/**
	 * Set if the node can be dragged or not.
	 *
	 * @param bool $canDrag True if the node can be dragged; else false.
	 * @return flWidgetTreeNode This Object.
	 */
	function setCanDrag($canDrag) {
		$this->canDrag = $canDrag;
		return $this;
	}

	/**
	 * Set the node as the active node.
	 *
	 * @param bool $active true to flag the node as the active node; else false.
	 * @return flWidgetTreeNode This object.
	 */
	function setActive($active = true) {
		$this->active = $active;
		return $this;
	}

	/**
	 * Render the node.
	 *
	 * @param flWidgetTreeView $tree The root tree rendering the node.
	 * @return string The HTML for the node.
	 */
	function render($tree) {
		return "<li class=\"$this->type\">" . $this->_getName() . "</li>\n";
	}

	/**
	 * Get the node name wrapped in the active flag if needed.
	 *
	 * @return string The node name including the active flag.
	 */
	protected function _getName() {
		$html = '<span';

		if($this->id !== null)
			$html .= ' data-treeview-nodeid="' . $this->id . '"';

		if(!$this->canDrag)
			$html .= ' data-treeview-draggable="false"';

		$html .= '>';
		$html .= $this->active ? "<em>$this->name</em>" : $this->name;
		$html .= '</span>';

		return $html;
	}

	/**
	 * Get the name of the node.
	 *
	 * @return string
	 */
	function getName() {
		return $this->name;
	}
}

/**
 * flWidgetTreeLinkNode Class
 *
 * Class to hold a tree node with a link.
 *
 * @package fusionLib
 */
class flWidgetTreeLinkNode extends flWidgetTreeNode {
	/**
	 * The link.
	 *
	 * @var string
	 */
	protected $link;

	/**
	 * Construct the object.
	 *
	 * @param string $name The node display name.
	 * @param string $link The link.
	 * @param string $type The name of the node.
	 * @param string $id The ID of the node.
	 */
	function __construct($name, $link, $type = 'node', $id = null) {
		parent::__construct($name, $type, $id);
		$this->link = $link;
	}

	/**
	 * Render the node.
	 *
	 * @param flWidgetTreeView $tree The root tree rendering the node.
	 * @return string The HTML for the node.
	 */
	function render($tree) {
		return "<li class=\"$this->type\"><a href=\"$this->link\">" . $this->_getName() . "</a></li>\n";
	}
}

/**
 * flWidgetTreeChildNode Class
 *
 * Class to hold a child tree for the tree view.
 *
 * @package fusionLib
 */
class flWidgetTreeChildNode extends flWidgetTreeNode {
	/**
	 * The child tree.
	 *
	 * @var flWidgetTreeView
	 */
	protected $child;

	/**
	 * The open/close state of the child tree.
	 *
	 * @var bool
	 */
	protected $open;

	/**
	 * The link.
	 *
	 * @var string
	 */
	protected $link;

	/**
	 * Construct the object.
	 *
	 * @param string $name The node display name.
	 * @param flWidgetTreeView $child The child tree.
	 * @param bool $open true if the node should appear open, false if closed and null if open but not closable.
	 * @param string $link The link target or null if not used.
	 * @param string $type The name of the node.
	 * @param string $id The ID of the node.
	 */
	function __construct($name, $child, $open, $link = null, $type = 'container', $id = null) {
		parent::__construct($name, $type, $id);
		$this->child = $child;
		$this->open = $open;
		$this->link = $link;
	}

	/**
	 * Render the node.
	 *
	 * @param flWidgetTreeView $tree The root tree rendering the node.
	 * @return string The HTML for the node.
	 */
	function render($tree) {
		$t = $this->link === null ? $this->_getName() : "<a href=\"$this->link\">" . $this->_getName() . "</a>";
		$id = $tree->_getContainerID();
		$html = '<li class="' . $this->type . ($this->open === null || $this->open ? ' open' : '') . '" aria-expanded="' . ($this->open === null || $this->open ? 'true' : 'false') . "\" data-treeviewbranchid=\"$id\">";
		$html .= ($this->open !== null ? '<b><i class="sr-only">+/-</i></b>' : '') . $t;
		return $html . $this->child->_render($tree) . "</li>\n";
	}
}

/**
 * flWidgetTreeView Class
 *
 * Class to build and manage tree views.
 *
 * @package fusionLib
 */
class flWidgetTreeView {

	/**
	 * Count of unnamed trees.
	 *
	 * @var int
	 */
	protected static $treeCounter = 1;

	/**
	 * The CSS class to apply.
	 *
	 * @var string
	 */
	protected $css = '';

	/**
	 * Array of nodes.
	 *
	 * @var array
	 */
	protected $nodes = array();

	/**
	 * The name of the tree.
	 *
	 * @var string
	 */
	protected $treeName = null;

	/**
	 * The code buffer object if any.
	 *
	 * @var flBufferCode
	 */
	protected $codeBuffer = null;

	/**
	 * Node path tracking during rendering.
	 *
	 * @var array
	 */
	protected $pathStack = array();

	/**
	 * Tracking of container hashes to avoid clashes.
	 *
	 * @var array
	 */
	protected $containerHashes = array();

	/**
	 * The AJAX move handler.
	 *
	 * @var string
	 */
	protected $ajaxMoveHandler = null;

	/**
	 * Disable move into.
	 *
	 * @var bool
	 */
	protected $disableInto = false;

	/**
	 * Construct the tree.
	 *
	 * @param string $name The unique name of the tree view or null if not remembering the state.
	 */
	function __construct($name = null) {
		if($name == null)
			$name = 'flWidgetTreeViewUnnamed' . self::$treeCounter++;
		$this->treeName = $name;
	}

	/**
	 * Internal function to generate an ID for each container.
	 *
	 * @return string The generated ID.
	 * @throws flWidgetException
	 */
	function _getContainerID() {
		$h = '';
		do {
			$h = md5($h . join(',', $this->pathStack));
		} while(isset($this->containerHashes[$h]));
		$this->containerHashes[$h] = 1;
		return $h;
	}

	/**
	 * Get the code buffer for the tree view.
	 *
	 * @return flBufferCode The tree views code buffer.
	 * @throws flWidgetException
	 */
	function _getCodeBuffer() {
		if($this->codeBuffer === null)
			throw new flWidgetException('No code buffer has been set for trackable tree view.');
		return $this->codeBuffer;
	}

	/**
	 * Set the CSS class to use.
	 *
	 * @param string $css The CSS class, default is 'treeviewwidget'.
	 * @return flWidgetTreeView This object.
	 */
	function setClass($css) {
		$this->css = $css;
		return $this;
	}

	/**
	 * Add a node to the tree view.
	 *
	 * @param flWidgetTreeNode $node The node to add to the tree. Multiple nodes can be specified.
	 * @return flWidgetTreeView This object;
	 */
	function addNode($node) {
		$node = func_get_args();
		$this->nodes = array_merge($this->nodes, $node);
		return $this;
	}

	/**
	 * Set the state of the last node added to active.
	 *
	 * @param bool $active true if the last node should be active; else false.
	 */
	function setLastNodeActive($active = true) {
		end($this->nodes)->setActive($active);
	}

	/**
	 * Set the address of the optional AJAX node move handler.
	 *
	 * @param string $handler The URI of the AJAX handler.
	 */
	function setAJAXMoveHandler($handler) {
		$this->ajaxMoveHandler = $handler;
	}

	/**
	 * Disable drag into.
	 *
	 * @param bool $disable true to disable; else false.
	 */
	function disableDragInto($disable = true) {
		$this->disableInto = $disable;
	}

	/**
	 * Internal function to render the tree.
	 *
	 * @param flWidgetTreeView $tree The root tree object.
	 * @return string The HTML generated.
	 */
	function _render($tree) {
		if(count($this->nodes)) {
			$html = "<ol>\n";
			foreach($this->nodes as $node) {
				array_push($tree->pathStack, $node->getName());
				$html .= $node->render($tree);
				array_pop($tree->pathStack);
			}
			$html .= "</ol>\n";
			return $html;
		} else
			return '';
	}

	/**
	 * Render the tree view.
	 *
	 * @param flBufferCode Code buffer object if any.
	 * @param bool $innerOnly true to render inner tree portion only; else false.
	 * @return string The HTML to render the tree.
	 */
	function render($codeBuffer = null, $innerOnly = false) {
		if($codeBuffer !== null) {
			$this->codeBuffer = $codeBuffer;
			$codeBuffer->requireCore()
				->bufferHeadJS($codeBuffer->getSysJSURI() . 'fusionLib.widgets.js');
		}

		$html = '';
		if(count($this->nodes)) {
			$html = '<div class="treeviewwidget ' . $this->css . "\" roll=\"tree\" data-treeviewname=\"$this->treeName\"" . ($this->ajaxMoveHandler ? ' data-treeview-dndajax-move="' . $this->ajaxMoveHandler . '"' : '') . ($this->disableInto ? ' data-treeview-disableinto="true"' : '') . "\">\n" . $this->_render($this) . "</div>\n";

			if (!$innerOnly)
				$html = '<div class="treeviewwidgetContainer">' . $html . "</div>\n";
		}

		return $html;
	}
}
