/*
 * \brief  Char-cell-array-based implementation of a character screen
 * \author Norman Feske
 * \date   2013-01-20
 */

/*
 * Copyright (C) 2013-2019 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _TERMINAL__CHAR_CELL_ARRAY_CHARACTER_SCREEN_H_
#define _TERMINAL__CHAR_CELL_ARRAY_CHARACTER_SCREEN_H_

/* terminal includes */
#include <terminal/cell_array.h>
#include <terminal/font_face.h>
#include <terminal/types.h>

namespace Terminal {
	struct Char_cell;
	class  Char_cell_array_character_screen;
}


struct Terminal::Char_cell
{
	uint16_t value { 0 };

	unsigned char attr;
	unsigned char color;

	enum { ATTR_COLIDX_MASK = 0x07U,
	       ATTR_CURSOR      = 0x10U,
	       ATTR_INVERSE     = 0x20U,
	       ATTR_HIGHLIGHT   = 0x40U };

	enum { COLOR_MASK = 0x3f }; /* 111111 */

	Char_cell() : attr(0), color(0) { }

	Char_cell(Character c, Font_face f,
	          int colidx, bool inv, bool highlight)
	:
		value(c.value),
		attr((unsigned char)(f.attr_bits() | (inv       ? ATTR_INVERSE   : 0)
		                                   | (highlight ? ATTR_HIGHLIGHT : 0))),
		color(colidx & COLOR_MASK)
	{ }

	Font_face font_face() const
	{
		return Font_face((Font_face::Type)(attr & Font_face::attr_mask()));
	}

	unsigned colidx_fg() const { return color        & ATTR_COLIDX_MASK; }
	unsigned colidx_bg() const { return (color >> 3) & ATTR_COLIDX_MASK; }
	bool inverse()   const { return attr         & ATTR_INVERSE;     }
	bool highlight() const { return attr         & ATTR_HIGHLIGHT;   }

	void set_cursor()   { attr |=  ATTR_CURSOR; }
	void clear_cursor() { attr &= (unsigned char)(~ATTR_CURSOR); }

	bool has_cursor() const { return attr & ATTR_CURSOR; }

	Codepoint codepoint() const { return Codepoint { value }; }
};


class Terminal::Char_cell_array_character_screen : public Character_screen
{
	private:

		enum Cursor_visibility { CURSOR_INVISIBLE,
		                         CURSOR_VISIBLE,
		                         CURSOR_VERY_VISIBLE };

		enum Irm { REPLACE, INSERT };

		Cell_array<Char_cell> &_char_cell_array;

		Boundary _boundary;
		Position _cursor_store { };
		Position _cursor_pos   { };

		/**
		 * Color index contains the fg color in the first 3 bits
		 * and the bg color in the second 3 bits (0bbbbfff).
		 */
		int                    _color_index;
		bool                   _inverse;
		bool                   _highlight;
		Cursor_visibility      _cursor_visibility;
		int                    _region_start;
		int                    _region_end;
		int                    _tab_size;

		Irm                    _irm = REPLACE;

		bool                   _wrap = false;

		bool                   _overflowed = false;

		enum { DEFAULT_COLOR_INDEX_BG = 0, DEFAULT_COLOR_INDEX = 7, DEFAULT_TAB_SIZE = 8 };

		struct Cursor_guard
		{
			Char_cell_array_character_screen &cs;

			Position old_cursor_pos;

			Cursor_guard(Char_cell_array_character_screen &cs)
			:
				cs(cs), old_cursor_pos(cs._cursor_pos)
			{
				/* temporarily remove cursor */
				cs._char_cell_array.cursor(old_cursor_pos, false);
			}

			~Cursor_guard()
			{
				/* restore original cursor */
				cs._char_cell_array.cursor(old_cursor_pos, true);

				/* if cursor position changed, move cursor */
				Position       &new_cursor_pos = cs._cursor_pos;
				Boundary const &boundary       = cs._boundary;
				if (old_cursor_pos != new_cursor_pos) {
					cs._overflowed = (new_cursor_pos.x >= boundary.width);

					/* make sure cursor pos is within range */
					new_cursor_pos.constrain(boundary);

					cs._char_cell_array.cursor(old_cursor_pos, false, true);
					cs._char_cell_array.cursor(
						new_cursor_pos, cs._cursor_visibility != CURSOR_INVISIBLE,  true);
				}
			}
		};

		static void _missing(char const *method_name)
		{
			warning(method_name, " not implemented");
		}

		static void _missing(char const *method_name, int arg)
		{
			warning(method_name, " not implemented for ", arg);
		}

		void _new_line()
		{
			Cursor_guard guard(*this);

			_cursor_pos.x = 0;
			_cursor_pos.y++;

			if (_cursor_pos.y > _region_end) {
				_char_cell_array.scroll_up(_region_start, _region_end);
				_cursor_pos.y = _region_end;
			}
		}

		void _carriage_return()
		{
			Cursor_guard guard(*this);

			_cursor_pos.x = 0;
		}

	public:

		Char_cell_array_character_screen(Cell_array<Char_cell> &char_cell_array)
		:
			_char_cell_array(char_cell_array),
			_boundary(_char_cell_array.num_cols(), _char_cell_array.num_lines()),
			_color_index(DEFAULT_COLOR_INDEX),
			_inverse(false),
			_highlight(false),
			_cursor_visibility(CURSOR_VISIBLE),
			_region_start(0),
			_region_end(_boundary.height - 1),
			_tab_size(DEFAULT_TAB_SIZE)
		{ }


		/********************************
		 ** Character_screen interface **
		 ********************************/

		Position cursor_pos() const { return _cursor_pos; }

		void cursor_pos(Position pos)
		{
			Cursor_guard guard(*this);

			_cursor_pos = pos;
		}

		void output(Character c) override
		{
			if (_irm == INSERT)
				_missing("insert mode");

			switch (c.value) {

			case '\n': /* 10 */
				_new_line();
				break;

			case '\r': /* 13 */
				_carriage_return();
				break;

			/* 14: shift-out */
			/* 15: shift-in  */

			case 8: /* backspace */
				{
					Cursor_guard guard(*this);

					if (_cursor_pos.x > 0)
						_cursor_pos.x--;

					break;
				}

			case 9: /* tab */
				{
					Cursor_guard guard(*this);
					_cursor_pos.x += _tab_size - (_cursor_pos.x % _tab_size);
					break;
				}

			default:
				if (c.value > 0x1f && !_overflowed) {
					Cursor_guard guard(*this);
					_char_cell_array.set_cell(_cursor_pos.x, _cursor_pos.y,
					                          Char_cell(c, Font_face::REGULAR,
					                          _color_index, _inverse, _highlight));
					_cursor_pos.x++;
				}
				break;
			}

			if (_wrap && _overflowed) {
				_new_line();
			}
		}

		void cha(int pn) override
		{
			using namespace Genode;

			Cursor_guard guard(*this);
			_cursor_pos.x = pn - 1;
		}

		void civis() override
		{
			_cursor_visibility = CURSOR_INVISIBLE;
			_char_cell_array.cursor(_cursor_pos, false);
		}

		void cnorm() override
		{
			_cursor_visibility = CURSOR_VISIBLE;
			_char_cell_array.cursor(_cursor_pos, true);
		}

		void cvvis() override
		{
			_cursor_visibility = CURSOR_VERY_VISIBLE;
			_char_cell_array.cursor(_cursor_pos, true);
		}

		void csr(int start, int end) override
		{
			/* the arguments are specified use coordinate origin (1, 1) */
			start--;
			end--;

			_region_start = max(start, 0);
			_region_end   = min(end, _boundary.height - 1);

			/* preserve invariant of region size >= 0 */
			_region_end = max(_region_end, _region_start);
		}

		void cub(int dx) override
		{
			Cursor_guard guard(*this);

			_cursor_pos.x -= dx;
		}

		void cud(int dy) override
		{
			Cursor_guard guard(*this);

			_cursor_pos.y += dy;
		}

		void cuf(int dx) override
		{
			Cursor_guard guard(*this);

			_cursor_pos.x += dx;
		}

		void cup(int y, int x) override
		{
			Cursor_guard guard(*this);

			/* top-left cursor position is reported as (1, 1) */
			x--;
			y--;

			_cursor_pos = Position(x, y);
		}

		void cuu(int dy) override
		{
			Cursor_guard guard(*this);

			_cursor_pos.y -= dy;
		}

		void da(int) override { _missing(__func__); }

		void dch(int pn) override
		{
			pn = min(_boundary.width - _cursor_pos.x, pn);
			for (int x = _cursor_pos.x; x < _boundary.width; ++x) {
				_char_cell_array.set_cell(x, _cursor_pos.y,
					_char_cell_array.get_cell(x+pn, _cursor_pos.y));
			}
			for (int x = _boundary.width - pn; x < _boundary.width; ++x) {
				_char_cell_array.set_cell(x, _cursor_pos.y, Char_cell());
			}
		}

		void dl(int num_lines) override
		{
			/* delete number of lines */
			for (int i = 0; i < num_lines; i++)
				_char_cell_array.scroll_up(_cursor_pos.y, _region_end);
		}

		/**
		 * Erase character
		 */
		void ech(int pn) override
		{
			int y = _cursor_pos.y;
			int x = _cursor_pos.x;

			do {
				while (x < _boundary.width && pn) {
					_char_cell_array.set_cell(x++, y, Char_cell());
					--pn;
				}
				x = 0;
				++y;
			} while (pn);
		}

		/**
		 * Erase in page
		 */
		void ed(int ps) override
		{
			/* clear to end of screen */
			switch(ps) {

			case 0:
				for (int x = _cursor_pos.x; x < _boundary.width; ++x)
					_char_cell_array.set_cell(x, _cursor_pos.y, Char_cell());
				_char_cell_array.clear(_cursor_pos.y + 1, _boundary.height-1);
				return;

			case 1:
				_char_cell_array.clear(0, _cursor_pos.y-1);
				for (int x = 0; x <= _cursor_pos.x; ++x)
					_char_cell_array.set_cell(x, _cursor_pos.y, Char_cell());
				return;

			case 2:
				_char_cell_array.clear(0, _boundary.height-1);
				return;

			default:
				warning(__func__, " not implemented for ", ps);
				break;
			}
		}

		void el(int ps) override
		{
			switch (ps) {
			case 0: /* clear to end of line */
				for (int x = _cursor_pos.x; x < _boundary.width; ++x)
					_char_cell_array.set_cell(x, _cursor_pos.y, Char_cell());
				return;

			case 1: /* clear from begining of line */
				for (int x = 0; x <= _cursor_pos.x; ++x)
					_char_cell_array.set_cell(x, _cursor_pos.y, Char_cell());
				return;

			case 2:
				_char_cell_array.clear(_cursor_pos.y, _cursor_pos.y);
				return;

			default: _missing(__func__, ps);
			}
		}

		void enacs() override { _missing(__func__); }
		void flash() override { _missing(__func__); }

		void home() override
		{
			Cursor_guard guard(*this);

			_cursor_pos.x = 0;
		}

		void hts() override
		{
			_tab_size = _cursor_pos.x;
		}

		void ich(int pn) override
		{
			pn = max(0, min(_boundary.width - _cursor_pos.x, pn));

			for (int x = _boundary.width-1; _cursor_pos.x+pn < x; --x) {
				_char_cell_array.set_cell(x, _cursor_pos.y,
					_char_cell_array.get_cell(x-1, _cursor_pos.y));
			}
			for (int i = 0; i < pn; ++i) {
				_char_cell_array.set_cell(
					_cursor_pos.x+i, _cursor_pos.y, Char_cell());
			}
		}

		void il(int value) override
		{
			Cursor_guard guard(*this);

			if (_cursor_pos.y > _region_end)
				return;

			_char_cell_array.cursor(_cursor_pos, false);

			for (int i = 0; i < value; i++)
				_char_cell_array.scroll_down(_cursor_pos.y, _region_end);

			_char_cell_array.cursor(_cursor_pos, true);
		}

		void is2() override { _missing(__func__); }
		void nel() override { _missing(__func__); }

		void op() override
		{
			_color_index = DEFAULT_COLOR_INDEX | (DEFAULT_COLOR_INDEX_BG << 3);
		}

		void rm(int ps) override
		{
			switch (ps) {
			case 4: /* INSERTION REPLACEMENT MODE */
				_irm = REPLACE;
				break;
			case 34: /* cursor visibility */
				return cnorm();
			default:
				_missing(__func__, ps);
				break;
			}
		}

		void sm(int ps) override
		{
			switch (ps) {
			case 4: /* INSERTION REPLACEMENT MODE */
				_irm = INSERT;
				break;
			case 34: /* cursor visibility */
				return civis();
				break;
			default:
				_missing(__func__, ps);
				break;
			}
		}

		void rc()    override { _missing(__func__); }
		void rs2()   override { _missing(__func__); }
		void rmir()  override { _missing(__func__); }
		void rmcup() override { }
		void rmkx()  override { }

		void sd(int pn) override
		{
			for (int i = 0; i < pn; ++i)
			_char_cell_array.scroll_down(_region_start, _region_end);
		}

		void setab(int value) override
		{
			_color_index &= ~0x38; /* clear 111000 */
			_color_index |= (((value == 9) ? DEFAULT_COLOR_INDEX_BG : value) << 3);
		}

		void setaf(int value) override
		{
			_color_index &= ~0x7; /* clear 000111 */
			_color_index |= (value == 9) ? DEFAULT_COLOR_INDEX : value;
		}

		void sgr(int value) override
		{
			switch (value) {
			case 0:
				/* sgr 0 is the command to reset all attributes, including color */
				_highlight = false;
				_inverse = false;
				_color_index = DEFAULT_COLOR_INDEX | (DEFAULT_COLOR_INDEX_BG << 3);
				break;
			case 1:
				_highlight = true;
				break;
			case 7:
				_inverse = true;
				break;
			default:
				break;
			}
		}

		void smcup() override { }
		void smir()  override { _missing(__func__); }
		void smkx()  override { }

		void su(int pn) override
		{
			for (int i = 0; i < pn; ++i)
				_char_cell_array.scroll_up(_region_start, _region_end);
		}

		void tbc()    override { _missing(__func__); }

		void tsr(int pn) override
		{
			_missing(__func__, pn);
			/*
			int x = pn;
			for (int y = _cursor_pos.y; y < _boundary.height-1; ++y) {
				for (int i = 0; i < _tab_size; ++i) {

				}
			}
			 */
		}

		void vpa(int pn) override
		{
			Cursor_guard guard(*this);
			_cursor_pos.y = pn - 1;
		}

		void vpb(int pn) override
		{
			Cursor_guard guard(*this);
			_cursor_pos.y -= pn;
		}

		void decsc() override
		{
			_cursor_store = _cursor_pos;
		}

		void decrc() override
		{
			Cursor_guard guard(*this);
			_cursor_pos = _cursor_store;
		}

		void decsm(int p1, int) override
		{
			switch (p1) {
			case    1: _missing("Application Cursor Keys"); return; //return smkx();
			case    7: _wrap = false; return;
			case   25:
			case   34: return cnorm(); // Visible Cursor
			case 1000: return _missing("VT200 mouse tracking");
			case 1002: return _missing("xterm butten event mouse");
			case 1003: return _missing("xterm any event mouse");
			case 1049: return _missing("Alternate Screen (new xterm code)"); //smcup();
			default: break;
			}
			_missing(__func__, p1);
		}

		void decrm(int p1, int) override
		{
			switch (p1) {
			case    1: _missing("Application Cursor Keys"); return; //return rmkx();
			case    7: _wrap = true; return;
			case   25:
			case   34: return civis();
			case 1000: return _missing("VT200 mouse tracking"); //rs2();
			case 1002: return _missing("xterm butten event mouse");
			case 1003: return _missing("xterm any event mouse");
			case 1049: return _missing("Alternate Screen (new xterm code)"); //rmcup();
			default: break;
			}
			_missing(__func__, p1);
		}

		void scs_g0(int charset) override { _missing(__func__, charset); }

		void scs_g1(int charset) override { _missing(__func__, charset); }

		void reverse_index() override
		{
			Cursor_guard guard(*this);
			if (_cursor_pos.y) {
				_cursor_pos.y = _cursor_pos.y - 1;
			} else {
				_char_cell_array.scroll_down(_region_start, _region_end);
			}
		};
};

#endif /* _TERMINAL__CHAR_CELL_ARRAY_CHARACTER_SCREEN_H_ */
