<?php

namespace App\Classes\BBS\Frame;

use App\Classes\BBS\Page\{Ansi,Viewdata};
use App\Models\BBS\Mode;

class Char {
	/** @var int|null Attributes for the character (ie: color) */
	private ?int $attr;
	/** @var string|null Character to be shown */
	private ?string $ch;

	public function __construct(string $ch=NULL,int $attr=NULL)
	{
		$this->ch = $ch;
		$this->attr = $attr;
	}

	public function __get(string $key): mixed
	{
		switch ($key) {
			case 'attr': return $this->attr;
			case 'ch': return $this->ch;

			default:
				throw new \Exception('Unknown key:'.$key);
		}
	}

	public function __isset($key): bool
	{
		return isset($this->{$key});
	}

	public function __set(string $key,mixed $value): void
	{
		switch ($key) {
			case 'ch':
				if (strlen($value) !== 1)
					throw new \Exception(sprintf('CH can only be 1 char: [%s]',$value));

				$this->{$key} = $value;
				break;

			default:
				throw new \Exception('Unknown key:'.$key);
		}
	}

	public function __toString()
	{
		return sprintf('%04x [%s]|',$this->attr,$this->ch);
	}

	/**
	 * Return the color codes required to draw the current character
	 *
	 * @param Mode $mo Service we are rendering for
	 * @param int|null $last last rendered char
	 * @param bool $debug debug mode
	 * @return string|NULL
	 * @throws \Exception
	 */
	public function attr(Mode $mo,int $last=NULL,bool $debug=FALSE): string|NULL
	{
		$ansi = collect();

		if ($debug)
			dump('- last:'.$last.', this:'.$this->attr);

		switch ($mo->name) {
			case 'ansi':
				if ($debug) {
					dump(' - this BG_BLACK:'.($this->attr & Ansi::BG_BLACK));
					dump(' - last BG_BLACK:'.($last & Ansi::BG_BLACK));

					dump(' - this HIGH:'.($this->attr & Ansi::HIGH));
					dump(' - last HIGH:'.($last & Ansi::HIGH));

					dump(' - this BLINK:'.($this->attr & Ansi::BLINK));
					dump(' - last BLINK:'.($last & Ansi::BLINK));
				}

				// If high was in the last, and we dont have high now, we need 0, but we need to turn back on flash if it was there
				// If flash was in the last, and we dont have flash now, we need to 0 but we need to turn on high if it was there
				$reset = FALSE;
				if ((($this->attr & Ansi::BG_BLACK) && (! ($last & Ansi::BG_BLACK)))
					|| ((! ($this->attr & Ansi::BLINK)) && ($last & Ansi::BLINK))
					|| ((! ($this->attr & Ansi::HIGH)) && ($last & Ansi::HIGH)))
				{
					$ansi->push(Ansi::I_CLEAR_CODE);
					$reset = TRUE;
					$last = Ansi::BG_BLACK|Ansi::LIGHTGRAY;
				}

				if (($this->attr & Ansi::HIGH)
					&& ((($this->attr & Ansi::HIGH) !== ($last & Ansi::HIGH)) || ($reset && ($last & Ansi::HIGH)))) {
					$ansi->push(Ansi::I_HIGH_CODE);
				}

				if (($this->attr & Ansi::BLINK)
					&& ((($this->attr & Ansi::BLINK) !== ($last & Ansi::BLINK)) || ($reset && ($last & Ansi::BLINK)))) {
					$ansi->push(Ansi::I_BLINK_CODE);
				}

				$c = ($this->attr & 0x07);
				$l = ($last & 0x07);

				// Foreground
				switch ($c) {
					case Ansi::BLACK:
						$r = Ansi::FG_BLACK_CODE;
						break;
					case Ansi::RED:
						$r = Ansi::FG_RED_CODE;
						break;
					case Ansi::GREEN:
						$r = Ansi::FG_GREEN_CODE;
						break;
					case Ansi::BROWN:
						$r = Ansi::FG_BROWN_CODE;
						break;
					case Ansi::BLUE:
						$r = Ansi::FG_BLUE_CODE;
						break;
					case Ansi::MAGENTA:
						$r = Ansi::FG_MAGENTA_CODE;
						break;
					case Ansi::CYAN:
						$r = Ansi::FG_CYAN_CODE;
						break;
					case Ansi::LIGHTGRAY:
						$r = Ansi::FG_LIGHTGRAY_CODE;
						break;
				}

				if ($r && ($c !== $l))
					$ansi->push($r);

				// Background
				if ($this->attr & 0x70) {
					$c = ($this->attr & 0x70);
					$l = ($last & 0x70);

					switch ($this->attr & 0x70) {
						case Ansi::BG_BLACK:
							$r = Ansi::BG_BLACK_CODE;
							break;
						case Ansi::BG_RED:
							$r = Ansi::BG_RED_CODE;
							break;
						case Ansi::BG_GREEN:
							$r = Ansi::BG_GREEN_CODE;
							break;
						case Ansi::BG_BROWN:
							$r = Ansi::BG_BROWN_CODE;
							break;
						case Ansi::BG_BLUE:
							$r = Ansi::BG_BLUE_CODE;
							break;
						case Ansi::BG_MAGENTA:
							$r = Ansi::BG_MAGENTA_CODE;
							break;
						case Ansi::BG_CYAN:
							$r = Ansi::BG_CYAN_CODE;
							break;
						case Ansi::BG_LIGHTGRAY:
							$r = Ansi::BG_LIGHTGRAY_CODE;
							break;
					}

					if ($r && ($c !== $l))
						$ansi->push($r);
				}

				if ($debug)
					dump([' - ansi:' =>$ansi]);

				return $ansi->count() ? sprintf('%s[%sm',($debug ? '': "\x1b"),$ansi->join(';')) : NULL;

			case 'viewdata':
				if ($debug)
					dump(sprintf('Last: %02x, Attr: %02x',$last,$this->attr));

				switch ($this->attr) {
					// \x08
					case Viewdata::BLINK:
						$r = Viewdata::I_BLINK_CODE;
						break;
					// \x09
					case Viewdata::STEADY:
						$r = Viewdata::I_STEADY;
						break;
					// \x0c
					case Viewdata::NORMAL:
						$r = Viewdata::I_NORMAL;
						break;
					// \x0d
					case Viewdata::DOUBLE:
						$r = Viewdata::I_DOUBLE_CODE;
						break;
					// \x18
					case Viewdata::CONCEAL:
						$r = Viewdata::I_CONCEAL;
						break;
					// \x19
					case Viewdata::BLOCKS:
						$r = Viewdata::I_BLOCKS;
						break;
					// \x1a
					case Viewdata::SEPARATED:
						$r = Viewdata::I_SEPARATED;
						break;
					// \x1c
					case Viewdata::BLACKBACK:
						$r = Viewdata::I_BLACKBACK;
						break;
					// \x1d
					case Viewdata::NEWBACK:
						$r = Viewdata::I_NEWBACK;
						break;
					// \x1e
					case Viewdata::HOLD:
						$r = Viewdata::I_HOLD;
						break;
					// \x1f
					case Viewdata::RELEASE:
						$r = Viewdata::I_REVEAL;
						break;

					// Not handled
					// \x0a-b,\x0e-f,\x1b
					case 0xff00:
						dump($this->attr);
						break;

					default:
						$mosiac = ($this->attr & Viewdata::MOSIAC);
						$c = ($this->attr & 0x07);

						if ($debug)
							dump(sprintf('Last: %02x, Attr: %02x, Color: %02x',$last,$this->attr,$c));

						// Color control \x00-\x07, \x10-\x17
						switch ($c) {
							/*
							case Viewdata::BLACK:
								$r = Viewdata::FG_BLACK_CODE;
								break;
							*/
							case Viewdata::RED:
								$r = $mosiac ? Viewdata::MOSIAC_RED_CODE : Viewdata::FG_RED_CODE;
								break;
							case Viewdata::GREEN:
								$r = $mosiac ? Viewdata::MOSIAC_GREEN_CODE : Viewdata::FG_GREEN_CODE;
								break;
							case Viewdata::YELLOW:
								$r = $mosiac ? Viewdata::MOSIAC_YELLOW_CODE : Viewdata::FG_YELLOW_CODE;
								break;
							case Viewdata::BLUE:
								$r = $mosiac ? Viewdata::MOSIAC_BLUE_CODE : Viewdata::FG_BLUE_CODE;
								break;
							case Viewdata::MAGENTA:
								$r = $mosiac ? Viewdata::MOSIAC_MAGENTA_CODE : Viewdata::FG_MAGENTA_CODE;
								break;
							case Viewdata::CYAN:
								$r = $mosiac ? Viewdata::MOSIAC_CYAN_CODE : Viewdata::FG_CYAN_CODE;
								break;
							case Viewdata::WHITE:
								$r = $mosiac ? Viewdata::MOSIAC_WHITE_CODE : Viewdata::FG_WHITE_CODE;
								break;

							default:
								if ($debug)
									dump('Not a color?:'.$c);
								return NULL;
						}
				}

				if ($debug)
					dump(sprintf('= result: ESC[%s](%02x) for [%s]',chr($r),$r,$this->ch));

				return chr($r);

			default:
				throw new \Exception($this->type.': has not been implemented');
		}
	}
}