<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  hal
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('F0F_INCLUDED') or die;

/**
 * Implementation of the Hypertext Application Language document in PHP. It can
 * be used to provide hypermedia in a web service context.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class F0FHalDocument
{
	/**
	 * The collection of links of this document
	 *
	 * @var   F0FHalLinks
	 */
	private $_links = null;

	/**
	 * The data (resource state or collection of resource state objects) of the
	 * document.
	 *
	 * @var   array
	 */
	private $_data = null;

	/**
	 * Embedded documents. This is an array of F0FHalDocument instances.
	 *
	 * @var   array
	 */
	private $_embedded = array();

	/**
	 * When $_data is an array we'll output the list of data under this key
	 * (JSON) or tag (XML)
	 *
	 * @var   string
	 */
	private $_dataKey = '_list';

	/**
	 * Public constructor
	 *
	 * @param   mixed  $data  The data of the document (usually, the resource state)
	 */
	public function __construct($data = null)
	{
		$this->_data = $data;
		$this->_links = new F0FHalLinks;
	}

	/**
	 * Add a link to the document
	 *
	 * @param   string      $rel        The relation of the link to the document.
	 *                                  See RFC 5988 http://tools.ietf.org/html/rfc5988#section-6.2.2 A document MUST always have
	 *                                  a "self" link.
	 * @param   F0FHalLink  $link       The actual link object
	 * @param   boolean     $overwrite  When false and a link of $rel relation exists, an array of links is created. Otherwise the
	 *                                  existing link is overwriten with the new one
	 *
	 * @see F0FHalLinks::addLink
	 *
	 * @return  boolean  True if the link was added to the collection
	 */
	public function addLink($rel, F0FHalLink $link, $overwrite = true)
	{
		return $this->_links->addLink($rel, $link, $overwrite);
	}

	/**
	 * Add links to the document
	 *
	 * @param   string   $rel        The relation of the link to the document. See RFC 5988
	 * @param   array    $links      An array of F0FHalLink objects
	 * @param   boolean  $overwrite  When false and a link of $rel relation exists, an array of
	 *                               links is created. Otherwise the existing link is overwriten
	 *                               with the new one
	 *
	 * @see F0FHalLinks::addLinks
	 *
	 * @return  boolean
	 */
	public function addLinks($rel, array $links, $overwrite = true)
	{
		return $this->_links->addLinks($rel, $links, $overwrite);
	}

	/**
	 * Add data to the document
	 *
	 * @param   stdClass  $data       The data to add
	 * @param   boolean   $overwrite  Should I overwrite existing data?
	 *
	 * @return  void
	 */
	public function addData($data, $overwrite = true)
	{
		if (is_array($data))
		{
			$data = (object) $data;
		}

		if ($overwrite)
		{
			$this->_data = $data;
		}
		else
		{
			if (!is_array($this->_data))
			{
				$this->_data = array($this->_data);
			}

			$this->_data[] = $data;
		}
	}

	/**
	 * Add an embedded document
	 *
	 * @param   string          $rel        The relation of the embedded document to its container document
	 * @param   F0FHalDocument  $document   The document to add
	 * @param   boolean         $overwrite  Should I overwrite existing data with the same relation?
	 *
	 * @return  boolean
	 */
	public function addEmbedded($rel, F0FHalDocument $document, $overwrite = true)
	{
		if (!array_key_exists($rel, $this->_embedded) || !$overwrite)
		{
			$this->_embedded[$rel] = $document;
		}
		elseif (array_key_exists($rel, $this->_embedded) && !$overwrite)
		{
			if (!is_array($this->_embedded[$rel]))
			{
				$this->_embedded[$rel] = array($this->_embedded[$rel]);
			}

			$this->_embedded[$rel][] = $document;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Returns the collection of links of this document
	 *
	 * @param   string  $rel  The relation of the links to fetch. Skip to get all links.
	 *
	 * @return  array
	 */
	public function getLinks($rel = null)
	{
		return $this->_links->getLinks($rel);
	}

	/**
	 * Returns the collection of embedded documents
	 *
	 * @param   string  $rel  Optional; the relation to return the embedded documents for
	 *
	 * @return  array|F0FHalDocument
	 */
	public function getEmbedded($rel = null)
	{
		if (empty($rel))
		{
			return $this->_embedded;
		}
		elseif (isset($this->_embedded[$rel]))
		{
			return $this->_embedded[$rel];
		}
		else
		{
			return array();
		}
	}

	/**
	 * Return the data attached to this document
	 *
	 * @return   array|stdClass
	 */
	public function getData()
	{
		return $this->_data;
	}

	/**
	 * Instantiate and call a suitable renderer class to render this document
	 * into the specified format.
	 *
	 * @param   string  $format  The format to render the document into, e.g. 'json'
	 *
	 * @return  string  The rendered document
	 *
	 * @throws  RuntimeException  If the format is unknown, i.e. there is no suitable renderer
	 */
	public function render($format = 'json')
	{
		$class_name = 'F0FHalRender' . ucfirst($format);

		if (!class_exists($class_name, true))
		{
			throw new RuntimeException("Unsupported HAL Document format '$format'. Render aborted.");
		}

		$renderer = new $class_name($this);

		return $renderer->render(
			array(
				'data_key'		=> $this->_dataKey
			)
		);
	}
}
