<?php
/**
 * A class to render Diffs in different formats.
 *
 * This class renders the diff in classic diff format. It is intended that
 * this class be customized via inheritance, to obtain fancier outputs.
 *
 * $Horde: framework/Text_Diff/Diff/Renderer.php,v 1.5 2004/10/13 09:30:20 jan Exp $
 *
 * @package Text_Diff
 */
class Text_Diff_Renderer
{
	/**
	 * Number of leading context "lines" to preserve.
	 *
	 * This should be left at zero for this class, but subclasses may want to
	 * set this to other values.
	 */
	protected $_leading_context_lines = 0;

	/**
	 * Number of trailing context "lines" to preserve.
	 *
	 * This should be left at zero for this class, but subclasses may want to
	 * set this to other values.
	 */
	protected $_trailing_context_lines = 0;

	/**
	 * Constructor.
	 */
	public function __construct($params = [])
	{
		foreach ($params as $param => $value) {
			$v = '_' . $param;
			if (isset($this->$v)) {
				$this->$v = $value;
			}
		}
	}

	/**
	 * Renders a diff.
	 *
	 * @param Text_Diff $diff  A Text_Diff object.
	 *
	 * @return string  The formatted output.
	 */
	public function render($diff)
	{
		$xi = $yi = 1;
		$block = false;
		$context = [];

		$nlead = $this->_leading_context_lines;
		$ntrail = $this->_trailing_context_lines;

		$this->_startDiff();

		foreach ($diff->getDiff() as $edit) {
			if (is_a($edit, 'Text_Diff_Op_copy')) {
				if (is_array($block)) {
					if (count($edit->orig) <= $nlead + $ntrail) {
						$block[] = $edit;
					} else {
						if ($ntrail) {
							$context = array_slice($edit->orig, 0, $ntrail);
							$block[] = new Text_Diff_Op_copy($context);
						}
						$this->_block(
							$x0,
							$ntrail + $xi - $x0,
							$y0,
							$ntrail + $yi - $y0,
							$block
						);
						$block = false;
					}
				}
				$context = $edit->orig;
			} else {
				if (! is_array($block)) {
					$context = array_slice($context, count($context) - $nlead);
					$x0 = $xi - count($context);
					$y0 = $yi - count($context);
					$block = [];
					if ($context) {
						$block[] = new Text_Diff_Op_copy($context);
					}
				}
				$block[] = $edit;
			}

			if ($edit->orig) {
				$xi += count($edit->orig);
			}
			if ($edit->final) {
				$yi += count($edit->final);
			}
		}

		if (is_array($block)) {
			$this->_block(
				$x0,
				$xi - $x0,
				$y0,
				$yi - $y0,
				$block
			);
		}

		return $this->_endDiff();
	}

	protected function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
	{
		$this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));
		foreach ($edits as $edit) {
			switch (strtolower(get_class($edit))) {
				case 'text_diff_op_copy':
					$this->_context($edit->orig);
					break;

				case 'text_diff_op_add':
					$this->_added($edit->final);
					break;

				case 'text_diff_op_delete':
					$this->_deleted($edit->orig);
					break;

				case 'text_diff_op_change':
					$this->_changed($edit->orig, $edit->final);
					break;

				default:
					trigger_error("Unknown edit type", E_USER_ERROR);
			}

			$this->_endBlock();
		}
	}

	protected function _startDiff()
	{
		ob_start();
	}

	protected function _endDiff()
	{
		$val = ob_get_contents();
		ob_end_clean();
		return $val;
	}

	protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
	{
		if ($xlen > 1) {
			$xbeg .= ',' . ($xbeg + $xlen - 1);
		}
		if ($ylen > 1) {
			$ybeg .= ',' . ($ybeg + $ylen - 1);
		}

		return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
	}

	protected function _startBlock($header)
	{
		// TODO: What's this output for? It breaks XML pages
		// echo $header . "\n";
	}

	protected function _endBlock()
	{
	}

	protected function _lines($lines, $prefix = '', $suffix = '', $type = '')
	{
		foreach ($lines as $line) {
			echo "$prefix$line$suffix\n";
		}
	}

	protected function _context($lines)
	{
		$this->_lines($lines, ' ');
	}

	protected function _added($lines)
	{
		$this->_lines($lines, '>');
	}

	protected function _deleted($lines)
	{
		$this->_lines($lines, '<');
	}

	protected function _changed($orig, $final)
	{
		$this->_deleted($orig);
		echo "---\n";
		$this->_added($final);
	}
}
