<?php

namespace App\Classes;

use Illuminate\Support\Arr;

/**
 * Page layout is as follows
 *
 * |----------|--------------|
 * | LOGO     | HEADER       |
 * | LEFT BOX | TEXT         |
 * |----------|--------------|
 */
class Page
{
	protected const MSG_WIDTH = 78;
	private const LOGO_OFFSET_WIDTH = 1;

	private Font $header;
	private string $header_foot = '';
	private bool $header_right;
	private int $header_underline = 0;

	private Font $left_box;

	private ANSI $logo;
	private int $step = 0;

	private string $text = '';
	private bool $text_right = FALSE;

	public function __construct(bool $crlf=FALSE)
	{
		$this->header = new Font;
		$this->logo = new ANSI;
		$this->left_box = new Font;
		$this->crlf = $crlf;
		$this->text = '';
	}

	public function __get($key)
	{
		switch ($key) {
			// Height of the header
			case 'header_height':
				return $this->header->height+($this->header_foot ? 1 : 0)+($this->header_underline ? 1 : 0);

			// The width of the left column
			case 'left_width':
				return $this->logo->width->max() ?: $this->left_box->width;

			// The height of the left column
			case 'left_height':
				return $this->logo->height+$this->left_box->height+self::LOGO_OFFSET_WIDTH;

			// The right width
			case 'right_width':
				return self::MSG_WIDTH-$this->left_width;

			default:
				throw new \Exception(sprintf('Unknown key %s',$key));
		}
	}

	/**
	 * Message header - goes at top, right of logo
	 *
	 * @param Font $text
	 * @param string $foot
	 * @param bool $right
	 * @param int $underline
	 * @throws \Exception
	 */
	public function addHeader(Font $text,string $foot='',bool $right=FALSE,int $underline=0): void
	{
		if (($text->width > $this->right_width) || (strlen($foot) > $this->right_width))
			throw new \Exception(sprintf('Header or Header Footer greater than available width'));

		$this->header = $text;
		$this->header_foot = $foot;
		$this->header_right = $right;
		$this->header_underline = $underline;
	}

	/**
	 * Content that can go below logo, to the left of the text, if text $logo_left_border is TRUE
	 *
	 * @param Font $text
	 * @throws \Exception
	 */
	public function addLeftBoxContent(Font $text): void
	{
		if ($this->left_width && ($text->width > $this->left_width))
			throw new \Exception(sprintf('Leftbox content greater than icon width'));

		$this->left_box = $text;
	}

	/**
	 * Message logo - goes at top left
	 *
	 * @param ANSI $ansi
	 */
	public function addLogo(ANSI $ansi): void
	{
		$this->logo = $ansi;
	}

	/**
	 * Message text, goes after header, and if a logo, to the right of it
	 *
	 * @param string $text Main Body Text
	 * @param bool $right Right Aligned
	 */
	public function addText(string $text,bool $right=FALSE)
	{
		$this->text .= $text;

		$this->text_right = $right;
	}

	/**
	 * Render the page.
	 *
	 * @return string
	 * @throws \Exception
	 */
	public function render(): string
	{
		$result = '';
		$result_height = 0;
		$current_pos = 0;
		$text_length = strlen($this->text);
		$this->step = 0;	// @todo temp
		$text_current_color = NULL;

		while (TRUE) {
			$result_line = '';			// Line being created
			$lc = 0;					// Line length count (without ANSI control codes)

			// The buffer represents how many spaces need to pad between the left_width and whatever is drawn on the left
			if (! $this->step) {
				if (($this->left_height > $this->header_height) || ($result_height < $this->header_height-1))
					$buffer = $this->left_width+($this->left_width ? self::LOGO_OFFSET_WIDTH*2 : 0);
				else
					$buffer = 0;

			} else {
				$buffer = 0;
				/*
				// @todo
				$buffer = $this->step
					? $this->logo->width->skip(intdiv($result_height,$this->step)*$this->step)->take($this->step)->max()
					: 1;
				*/
			}

			if ($this->left_width) {
				// Add our logo
				if ($result_height < $this->logo->height) {
					$line = ANSI::bin_to_ansi([$this->logo->line($result_height)],FALSE);
					$lc = $this->logo->line_width($this->logo->line_raw($result_height),FALSE);

					$result_line = str_repeat(' ',self::LOGO_OFFSET_WIDTH)
						.$line
						.str_repeat(' ',$buffer-$lc-($this->left_width ? self::LOGO_OFFSET_WIDTH : 0));

				} elseif (self::LOGO_OFFSET_WIDTH && $this->logo->height && ($result_height === $this->logo->height) && $this->left_box->height) {
					$result_line = str_repeat(' ',$buffer);

				} elseif ($result_height < $this->left_height-($this->logo->height ? 0 : self::LOGO_OFFSET_WIDTH)) {
					$line = $this->left_box->render_line($result_height-($this->logo->height ? self::LOGO_OFFSET_WIDTH : 0)-$this->logo->height);
					$lc = $this->left_box->width;

					$result_line = str_repeat(' ',($this->left_box->width ? self::LOGO_OFFSET_WIDTH : 0))
						.$line
						.str_repeat(' ',($this->left_box->width ? $buffer-$lc-($this->left_width ? self::LOGO_OFFSET_WIDTH : 0) : (($current_pos < $text_length) ? $buffer : 0)));

				} else {
					if ($current_pos < $text_length)
						$result_line = str_repeat(' ',$buffer);
				}
			}

			// Add our header
			if ($result_height <= $this->header->height-1) {
				$result_line .= str_repeat(' ',$this->header_right ? self::MSG_WIDTH-($this->left_width ? self::LOGO_OFFSET_WIDTH*2 : 0)-$this->left_width-$this->header->width : 0)
					.$this->header->render_line($result_height);
			}

			// Add our header footer
			if ($x=ANSI::line_width($this->header_foot,FALSE)) {
				if ($result_height === $this->header->height) {
					$result_line .= str_repeat(' ',($this->header_right ? self::MSG_WIDTH-($this->left_width ? self::LOGO_OFFSET_WIDTH*2 : 0)-$this->left_width-$x : 0))
						.ANSI::text_to_ansi($this->header_foot);
				}
			}

			// Add our header underline
			if ($this->header_underline) {
				if ($result_height === $this->header->height+($this->header_foot ? 1 : 0)) {
					$result_line .= str_repeat(chr($this->header_underline),self::MSG_WIDTH-$buffer);
				}
			}

			$subtext = '';
			$subtext_ansi = '';
			$subtext_length = 0;

			if ($current_pos < $text_length) {
				if ($result_height >= $this->header_height) {
					// Look for a return
					$return_pos = strpos($this->text,"\r",$current_pos);

					// We have a return
					if ($return_pos !== FALSE) {
						// If the remaining text is within our width, we'll use it all.
						if ($return_pos-$current_pos < self::MSG_WIDTH-$buffer) {
							$subtext = substr($this->text,$current_pos,$return_pos-$current_pos);

						// Look for the space.
						} else {
							$space_pos = strrpos(substr($this->text,$current_pos,($return_pos-$current_pos > static::MSG_WIDTH-$this->left_width ? static::MSG_WIDTH-$this->left_width : $return_pos-$current_pos)),' ');
							$subtext = substr($this->text,$current_pos,$space_pos);
						}

					// If the reset of the string will fit on the current line
					} elseif ($text_length-$current_pos < static::MSG_WIDTH-$buffer) {
						$subtext = substr($this->text,$current_pos);

					// Get the next lines worth of chars
					} else {
						$subtext = $this->text_substr(substr($this->text,$current_pos),static::MSG_WIDTH-$buffer);

						// Include the text up to the last space
						if (substr($this->text,$current_pos+strlen($subtext),1) !== ' ')
							$subtext = substr($subtext,0,strrpos($subtext,' '));
					}

					$current_pos += strlen($subtext)+1;
				}

				if ($subtext) {
					$subtext_length = ANSI::line_width($subtext,FALSE);
					$subtext_ansi = ANSI::text_to_ansi(($text_current_color ? "\x1b".$text_current_color : '').$subtext);

					// Get our last color used, for the next line.
					$m = [];
					preg_match('/^.*(\x1b(.))+(.*?)$/s',$subtext,$m);
					if (Arr::get($m,2))
						$text_current_color = $m[2];
				}
			}

			if ($result_line || $subtext) {
				$result .= $result_line.
					($subtext_ansi
						? str_repeat(' ',
						($this->text_right && ($result_height > $this->header_height-1) ? static::MSG_WIDTH-$subtext_length-$buffer : 0)).$subtext_ansi
						: '');

				$result .= $this->crlf ? "\n\r" : "\r";
			}

			$result_height++;

			if (($result_height > $this->logo->height) &&
				($result_height > $this->left_height) &&
				($current_pos >= $text_length))
			{
				break;
			}
		}

		return $result;
	}

	private function text_substr(string $text,int $goal): string
	{
		$chars = $goal;

		while (($x=ANSI::line_width($subtext=substr($text,0,$chars),FALSE)) < $goal) {
			$chars += ($chars-$x);
		}

		// If the last char is an escape, we need to more chars until the last char is no longer an escape.
		while (preg_match('/\x1b$/',$subtext) && (strlen($subtext) < strlen($text))) {
			$subtext .= substr($text,strlen($subtext),2);
		}

		return $subtext;
	}
}