<?php

/**
* Concrete renderer for JSON output.
*
* @package XenForo_Mvc
*/
class XenForo_ViewRenderer_Json extends XenForo_ViewRenderer_Abstract
{
	/**
	 * Constructor
	 * @see XenForo_ViewRenderer_Abstract::__construct()
	 */
	public function __construct(XenForo_Dependencies_Abstract $dependencies, Zend_Controller_Response_Http $response, Zend_Controller_Request_Http $request)
	{
		// TODO: Facebook sends text/javascript instead of application/json. SMV says that's a good thing.
		parent::__construct($dependencies, $response, $request);
		$this->_response->setHeader('Content-Type', 'application/json; charset=UTF-8', true);
	}

	/**
	 * Simple handler for JSON redirects - do not redirect, just send status:ok and redirect:$redirectTarget
	 *
	 * @param integer Type of redirect. See {@link XenForo_ControllerResponse_Redirect}
	 * @param string  Target to redirect to
	 * @param mixed   Redirect message
	 * @param array   Redirect parameters
	 *
	 * @return string JSON-encoded array
	 */
	public function renderRedirect($redirectType, $redirectTarget, $redirectMessage = null, array $redirectParams = array())
	{
		$redirectParams['_redirectStatus'] = 'ok';
		$redirectParams['_redirectTarget'] = $redirectTarget;
		$redirectParams['_redirectMessage'] = (is_null($redirectMessage) ? new XenForo_Phrase('redirect_changes_saved_successfully') : $redirectMessage);

		return self::jsonEncodeForOutput($redirectParams);
	}

	/**
	* Renders an error.
	* @see XenForo_ViewRenderer_Abstract::renderError()
	*
	* @param string|array Error message
	*
	* @return string JSON-encoded array
	*/
	public function renderError($error)
	{
		if (!is_array($error))
		{
			$error = array($error);
		}

		$template = $this->createTemplateObject('error', array('error' => $error, 'showHeading' => true));
		$templateHtml = $template->render();

		return self::jsonEncodeForOutput(array(
			'error' => $error,
			'templateHtml' => $templateHtml
		));
	}

	/**
	 * Renders a message.
	 *
	 * @see XenForo_ViewRenderer_Abstract::renderMessage()
	 */
	public function renderMessage($message)
	{
		return self::jsonEncodeForOutput(array(
			'status' => 'ok',
			'message' => $message
		));
	}

	/**
	* Renders a view.
	* @see XenForo_ViewRenderer_Abstract::renderView()
	*/
	public function renderView($viewName, array $params = array(), $templateName = '', XenForo_ControllerResponse_View $subView = null)
	{
		if ($subView)
		{
			return $this->renderSubView($subView);
		}

		$viewOutput = $this->renderViewObject($viewName, 'Json', $params, $templateName);

		if ($viewOutput === null)
		{
			$viewOutput = $this->getDefaultOutputArray($viewName, $params, $templateName);
		}
		else if (!is_array($viewOutput))
		{
			return $viewOutput;
		}

		if ($this->_jsonParams)
		{
			$viewOutput = array_merge($this->_jsonParams, $viewOutput);
		}

		return self::jsonEncodeForOutput($viewOutput);
	}

	/**
	* Renders the container.
	* @see XenForo_ViewRenderer_Abstract::renderContainer()
	*
	* @param string
	* @param array
	*
	* @return string
	*/
	public function renderContainer($contents, array $params = array())
	{
		return $contents;
	}

	/**
	* Fallback for rendering an "unrepresentable" message.
	* @see XenForo_ViewRenderer_Abstract::renderUnrepresentable()
	*
	* @return string JSON-encoded array
	*/
	public function renderUnrepresentable()
	{
		return $this->renderError(new XenForo_Phrase('requested_page_is_unrepresentable_in_json'));
	}

	/**
	 * Add default parameters to the provided parameters array
	 *
	 * @param array $params
	 *
	 * @return array
	 */
	protected static function _addDefaultParams(array &$params = array())
	{
		$visitor = XenForo_Visitor::getInstance();
		if ($visitor->getUserId())
		{
			$params['_visitor_conversationsUnread'] = XenForo_Locale::numberFormat($visitor->conversations_unread);
			$params['_visitor_alertsUnread'] = XenForo_Locale::numberFormat($visitor->alerts_unread);
		}
		if (XenForo_ViewRenderer_Abstract::hasManualDeferredToRun())
		{
			$params['_manualDeferred'] = true;
		}
		if (!empty(XenForo_Application::$autoDeferredIds))
		{
			$params['_autoDeferred'] = true;
		}

		return $params;
	}

	/**
	 * JSON encodes an input for direct output. This renders any objects
	 * with string representations to strings.
	 *
	 * @param mixed $input Data to JSON encode. Likely an array, but not always.
	 *
	 * @return string JSON encoded output
	 */
	public static function jsonEncodeForOutput($input, $addDefaultParams = true)
	{
		if (is_array($input))
		{
			$input = self::_stringifyObjectsInArray($input);
		}
		else if (is_object($input) && method_exists($input, '__toString'))
		{
			$input = $input->__toString();
		}

		if (is_string($input))
		{
			$input = array('_response' => $input);
		}

		if ($addDefaultParams)
		{
			self::_addDefaultParams($input);
		}

		return json_encode($input);
	}

	/**
	 * Loops through the given array (recursively) and stringifies any
	 * objects it can.
	 *
	 * @param array $array Array to search
	 *
	 * @return array Array with objects stringified
	 */
	protected static function _stringifyObjectsInArray(array $array)
	{
		foreach ($array AS $name => &$value)
		{
			if (is_array($value))
			{
				$value = self::_stringifyObjectsInArray($value);
			}
			else if (is_object($value) && method_exists($value, '__toString'))
			{
				$value = $value->__toString();
			}
		}

		return $array;
	}

	/**
	 * Builds the default data to be returned by the JSON view,
	 * including an HTML-rendered template, array of required JS and CSS,
	 * title and h1 parameters and the navigation array.
	 *
	 * @param string $viewName
	 * @param array $params
	 * @param string $templateName
	 *
	 * @return array
	 */
	public function getDefaultOutputArray($viewName, $params, $templateName)
	{
		$viewOutput = $this->renderViewObject($viewName, 'Html', $params, $templateName);

		if ($viewOutput === null)
		{
			// no class found
			$template = $this->createTemplateObject($templateName, $params);
			$viewOutput = $template->render();
		}
		else
		{
			$template = $this->createTemplateObject($templateName, array());
		}

		// replace {$requestPaths.requestUri} with a token
		$requestPaths = XenForo_Application::get('requestPaths');
		$requestUri = $this->_request->get('_xfRequestUri');
		if ($requestUri && is_string($requestUri))
		{
			$viewOutput = str_replace(
				htmlspecialchars($requestPaths['requestUri']),
				htmlspecialchars($requestUri),
				$viewOutput
			);
		}

		$output = array(
			'templateHtml' => $viewOutput,
			'css' => $template->getRequiredExternals('css'),
			'js' => $template->getRequiredExternals('js'),
		);

		$extraContainerData = $this->_dependencies->getExtraContainerData();

		if (!empty($extraContainerData['title']))
		{
			$output['title'] = $extraContainerData['title'];
		}

		if (!empty($extraContainerData['h1']))
		{
			$output['h1'] = $extraContainerData['h1'];
		}

		if (!empty($extraContainerData['sidebar']))
		{
			$output['sidebarHtml'] = $extraContainerData['sidebar'];
		}

		if (!empty($extraContainerData['navigation']))
		{
			$navigation = array();
			foreach ($extraContainerData['navigation'] AS $breadCrumb)
			{
				if (!isset($breadCrumb['href']))
				{
					$breadCrumb['href'] = '';
				}
				$navigation[] = array($breadCrumb['href'], $breadCrumb['value']);
			}
			$output['navigation'] = $navigation;
		}

		return $output;
	}
}