//
// Created by MightyPork on 2017/08/20.
//
// Handle CSI sequences
// CSI <symbol?> Pm <symbol?> <char>
//
// Example of those are cursor manipulation sequences and SGR.
//
// For details, see:
// http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Functions-using-CSI-_-ordered-by-the-final-character_s_
//

#include <esp8266.h>
#include "apars_csi.h"
#include "screen.h"
#include "apars_logging.h"
#include "ansi_parser.h"
#include "ascii.h"
#include "ansi_parser_callbacks.h"
#include "uart_driver.h"
#include "sgr.h"
#include "version.h"
#include "syscfg.h"

/** Struct passed to subroutines */
typedef struct {
	char lead;
	const int *n;
	int count;
	char inter;
	char key;
} CSI_Data;

// Disambiguations
static inline void switch_csi_Plain(CSI_Data *opts);
static inline void switch_csi_NoLeadInterBang(CSI_Data *opts);
static inline void switch_csi_NoLeadInterSpace(CSI_Data *opts);
static inline void switch_csi_LeadGreater(CSI_Data *opts);
static inline void switch_csi_LeadQuest(CSI_Data *opts);
static inline void switch_csi_LeadEquals(CSI_Data *opts);

// Subroutines
static inline void do_csi_sgr(CSI_Data *opts);
static inline void do_csi_decreqtparm(CSI_Data *opts);
static inline void do_csi_set_option(CSI_Data *opts);
static inline void do_csi_xterm_screen_cmd(CSI_Data *opts);
static inline void do_csi_set_private_option(CSI_Data *opts);

/**
 * Show warning and dump context for invalid CSI
 */
static void ICACHE_FLASH_ATTR
warn_bad_csi(void)
{
	ansi_noimpl_r("Unknown CSI");
	apars_show_context();
}

/**
 * Handle fully received CSI ANSI sequence
 *
 * @param leadchar - leading character
 * @param params - array of CSI_N_MAX ints holding the numeric arguments
 * @param count - actual amount of received numeric arguments
 * @param keychar - intermediate character
 * @param keychar - final character
 */
void ICACHE_FLASH_ATTR
apars_handle_csi(char leadchar, const int *params, int count, char interchar, char keychar)
{
	CSI_Data opts = {leadchar, params, count, interchar, keychar};

	switch(leadchar) {
		case '?':
			switch_csi_LeadQuest(&opts);
			break;

		case '>':
			switch_csi_LeadGreater(&opts);
			break;

		case '=':
			switch_csi_LeadEquals(&opts);
			break;

		case NUL:
			// No leading character, switch by intermediate character
			switch(interchar) {
				case NUL:
					switch_csi_Plain(&opts);
					break;

				case '!':
					switch_csi_NoLeadInterBang(&opts);
					break;

//				case '\'':
//					switch_csi_NoLeadInterApos(opts);
//					break;

//				case '*':
//					switch_csi_NoLeadInterStar(opts);
//					break;

//				case '+':
//					switch_csi_NoLeadInterPlus(opts);
//					break;

//				case '"':
//					switch_csi_NoLeadInterQuote(opts);
//					break;

//				case '|':
//					switch_csi_NoLeadInterDollar(opts);
//					break;

				case ' ':
					switch_csi_NoLeadInterSpace(&opts);
					break;

//				case ',':
//					switch_csi_NoLeadInterComma(opts);
//					break;

//				case ')':
//					switch_csi_NoLeadInterRparen(opts);
//					break;

//				case '&':
//					switch_csi_NoLeadInterAmpers(opts);
//					break;

//				case '-':
//					switch_csi_NoLeadInterDash(opts);
//					break;

				default:
					warn_bad_csi();
			}
			break;

		default:
			warn_bad_csi();
	}
}


/**
 * CSI none Pm none key
 * @param opts
 */
static inline void ICACHE_FLASH_ATTR
switch_csi_Plain(CSI_Data *opts)
{
	char resp_buf[20];
	int n1 = opts->n[0];
	int n2 = opts->n[1];

	// fix arguments (default values etc)
	switch (opts->key) {
		// Single argument, 1-based
		case 'A': // up
		case 'e': // down (old)
		case 'B': // down
		case 'a': // right (old)
		case 'C': // right
		case 'D': // left
		case 'E': // cursor next line
		case 'F': // cursor prev line
		case 'b': // repeat last char
		case 'G': // set X
		case '`': // set X (alias)
		case 'd': // set Y
		case 'X': // clear in line
		case 'S': // scroll up
		case 'T': // scroll down
		case 'L': // Insert lines
		case 'M': // Delete lines
		case '@': // Insert in line
		case 'P': // Delete in line
		case 'I': // Tab forward
		case 'Z': // Tab backward
			if (n1 == 0) n1 = 1;
			break;

			// Two arguments, 1-based
		case 'H': // Absolute positioning
		case 'f':
			if (n1 == 0) n1 = 1;
			if (n2 == 0) n2 = 1;
			break;

			// Erase modes 0,1,2
		case 'J': // Erase in screen
		case 'K': // Erase in line
			if (n1 > 2) n1 = 0;
			break;

			// No defaults
		case 't': // Xterm window commands & reports
		case 's': // Cursor save no attr
		case 'r': // Set scrolling region
		case 'u': // Cursor restore no attr
		case 'h': // Option ON
		case 'l': // Option OFF
		case 'm': // SGR
		case 'g': // Clear tabs
		case 'n': // Queries 1 - device status
		case 'c': // Queries 2 - primary DA
		case 'x': // Queries 3 - DECREQTPARM
		default:
			// leave as is
			break;
	}

	switch (opts->key) {
		// CUU CUD CUF CUB
		case 'A': // Up
			screen_cursor_move(-n1, 0, false);
			break;

		case 'e':
		case 'B': // Down
			screen_cursor_move(n1, 0, false);
			break;

		case 'a': // some archaic form of "Go Right"
		case 'C': // Right (forward)
			screen_cursor_move(0, n1, false);
			break;

		case 'D': // Left (backward)
			screen_cursor_move(0, -n1, false);
			break;

		case 'E': // CNL - Cursor Next Line
			screen_cursor_move(n1, 0, false);
			screen_cursor_set_x(0);
			break;

		case 'F': // CPL - Cursor Prev Line
			screen_cursor_move(-n1, 0, false);
			screen_cursor_set_x(0);
			break;

		case 'b':
			screen_repeat_last_character(n1);
			return;

			// Set X
		case 'G':
		case '`': // alternate code
			screen_cursor_set_x(n1 - 1);
			break; // 1-based

			// Set Y
		case 'd':
			screen_cursor_set_y(n1 - 1);
			break; // 1-based

			// Clear in line
		case 'X':
			screen_clear_in_line(n1);
			break; // 1-based

			// SU, SD - scroll up/down
		case 'S':
			screen_scroll_up(n1);
			break;

		case 'T':
			screen_scroll_down(n1);
			break;

		case 't': // xterm window commands
			do_csi_xterm_screen_cmd(opts);
			break;

			// CUP,HVP - set position
		case 'H':
		case 'f':
			screen_cursor_set(n1-1, n2-1);
			break; // 1-based

		case 'J': // Erase screen
			if (n1 == 0) {
				screen_clear(CLEAR_FROM_CURSOR);
			} else if (n1 == 1) {
				screen_clear(CLEAR_TO_CURSOR);
			} else {
				screen_clear(CLEAR_ALL);
				screen_cursor_set(0, 0);
			}
			break;

		case 'K': // Erase lines
			if (n1 == 0) {
				screen_clear_line(CLEAR_FROM_CURSOR);
			} else if (n1 == 1) {
				screen_clear_line(CLEAR_TO_CURSOR);
			} else {
				screen_clear_line(CLEAR_ALL);
			}
			break;

			// SCP, RCP - save/restore position
		case 's':
			screen_cursor_save(0);
			break;

		case 'r':
			screen_set_scrolling_region(n1-1, n2-1);
			break;

		case 'u':
			screen_cursor_restore(0);
			break;

		case 'h': // DEC feature enable
		case 'l': // DEC feature disable
			// --- DEC standard attributes ---
			do_csi_set_option(opts);
			break;

		case 'm': // SGR - set graphics rendition
			do_csi_sgr(opts);
			break;

		case 'L': // Insert lines (shove down)
			screen_insert_lines(n1);
			break;

		case 'M': // Delete lines (pull up)
			screen_delete_lines(n1);
			break;

		case '@': // Insert in line (shove right)
			screen_insert_characters(n1);
			break;

		case 'P': // Delete in line (pull left)
			screen_delete_characters(n1);
			break;

		case 'g': // Clear tabs
			if (n1 == 3) {
				screen_clear_all_tabs();
			}
			else if (n1 == 0) {
				screen_clear_tab();
			}
			break;

		case 'Z': // Tab backward
			screen_tab_reverse(n1);
			break;

		case 'I': // Tab forward
			screen_tab_forward(n1);
			break;

		case 'n': // Queries
			if (n1 == 6) {
				// Query cursor position
				int x, y;
				screen_cursor_get(&y, &x);
				sprintf(resp_buf, "\033[%d;%dR", y + 1, x + 1);
				apars_respond(resp_buf);
			}
			else if (n1 == 5) {
				// Query device status - reply "Device is OK"
				apars_respond("\033[0n");
			}
			else {
				warn_bad_csi();
			}
			break;

		case 'c': // CSI-c - report capabilities
			// Primary device attributes
			apars_respond("\033[?64;22;9c"); // pretend we're vt420 with national character sets and colors.
			break;

		case 'x': // DECREQTPARM -> DECREPTPARM
			do_csi_decreqtparm(opts);
			break;

		default:
			warn_bad_csi();
	}
}


/**
 * CSI none Pm ! key
 */
static inline void  ICACHE_FLASH_ATTR
switch_csi_NoLeadInterBang(CSI_Data *opts)
{
	switch(opts->key) {
		case 'p':
			// RIS - CSI ! p
			// On real VT there are differences between soft and hard reset, we treat both equally
			screen_reset();
			break;

		default:
			warn_bad_csi();
	}
}


/**
 * CSI none Pm SP key
 */
static inline void  ICACHE_FLASH_ATTR
switch_csi_NoLeadInterSpace(CSI_Data *opts)
{
	int n;
	switch(opts->key) {
		case 'q':
			// DECSCUSR
			// CSI Ps SP q
			//		Set cursor style (DECSCUSR, VT520).
			//		Ps = 0  -> blinking block.
			//		Ps = 1  -> blinking block (default).
			//		Ps = 2  -> steady block.
			//		Ps = 3  -> blinking underline.
			//		Ps = 4  -> steady underline.
			//		Ps = 5  -> blinking bar (xterm).
			//		Ps = 6  -> steady bar (xterm).
			n = opts->n[0];
			if (n > 6) n = 1; // use default if bad value set
			screen_cursor_shape((enum CursorShape) n);
			break;

		default:
			warn_bad_csi();
	}
}


/**
 * CSI > Pm inter key
 */
static inline void ICACHE_FLASH_ATTR
switch_csi_LeadGreater(CSI_Data *opts)
{
	char resp_buf[20];
	switch(opts->key) {
		case 'c': // CSI > c - secondary device attributes query
			// 41 - we're "VT400", FV wers, 0 - ROM cartridge number
			sprintf(resp_buf, "\033[>41;%d;0c", FIRMWARE_VERSION_NUM);
			apars_respond(resp_buf);
			break;

		default:
			warn_bad_csi();
	}
}


/**
 * CSI = Pm inter key
 */
static inline void ICACHE_FLASH_ATTR
switch_csi_LeadEquals(CSI_Data *opts)
{
	char resp_buf[20];
	u8 mac[6];
	switch(opts->key) {
		case 'c': // CSI = c - tertiary device attributes query
			// report our unique ID number
			wifi_get_macaddr(SOFTAP_IF, mac);
			sprintf(resp_buf, "\033P!|%02X%02X%02X\033\\", mac[3], mac[4], mac[5]);
			apars_respond(resp_buf);
			break;

		default:
			warn_bad_csi();
	}
}

/**
 * CSI ? Pm inter key
 */
static inline void  ICACHE_FLASH_ATTR
switch_csi_LeadQuest(CSI_Data *opts)
{
	int n = 0;
	switch(opts->key) {
		case 's':
			// Save private attributes
			for (int i = 0; i < opts->count; i++) {
				n = opts->n[i];
				screen_save_private_opt(n);
			}
			break;

		case 'r':
			// Restore private attributes
			for (int i = 0; i < opts->count; i++) {
				n = opts->n[i];
				screen_restore_private_opt(n);
			}
			break;

		case 'J': // Erase screen selectively
			// TODO selective erase
			switch_csi_Plain(opts); // ignore the ?, do normal erase
			break;

		case 'K': // Erase line selectively
			// TODO selective erase
			switch_csi_Plain(opts); // ignore the ?, do normal erase
			break;

		case 'l':
		case 'h':
			do_csi_set_private_option(opts);
			break;

		default:
			warn_bad_csi();
	}
}


/**
 * CSI Pm m
 * @param opts
 */
static inline void ICACHE_FLASH_ATTR
do_csi_sgr(CSI_Data *opts)
{
	int count = opts->count;

	if (count == 0) {
		count = 1; // this makes it work as 0 (reset)
	}

	// iterate arguments
	for (int i = 0; i < count; i++) {
		int n = opts->n[i];

		if (n == SGR_RESET) screen_reset_sgr();
			// -- set color --
		else if (n >= SGR_FG_START && n <= SGR_FG_END) screen_set_fg((Color) (n - SGR_FG_START)); // ANSI normal fg
		else if (n >= SGR_BG_START && n <= SGR_BG_END) screen_set_bg((Color) (n - SGR_BG_START)); // ANSI normal bg
			//  AIX bright colors --
		else if (n >= SGR_FG_BRT_START && n <= SGR_FG_BRT_END) screen_set_fg((Color) ((n - SGR_FG_BRT_START) + 8)); // AIX bright fg
		else if (n >= SGR_BG_BRT_START && n <= SGR_BG_BRT_END) screen_set_bg((Color) ((n - SGR_BG_BRT_START) + 8)); // AIX bright bg
			// reset color
		else if (n == SGR_FG_DEFAULT) screen_set_default_fg();
		else if (n == SGR_BG_DEFAULT) screen_set_default_bg();
			// 256 colors
		else if (n == SGR_FG_256 || n == SGR_BG_256) {
			if (i >= count-2) {
				ansi_warn("SGR syntax err");
				apars_show_context();
				break; // abandon further
			}

			if (opts->n[i + 1] != 5) {
				ansi_warn("SGR syntax err");
				apars_show_context();
				break; // abandon further
			}

			u8 color = (u8) opts->n[i + 2];
			bool fg = n == SGR_FG_256;
			if (fg) screen_set_fg(color);
			else screen_set_bg(color);
			i += 2;
		}
			// -- set attr --
		else if (n == SGR_BOLD) screen_set_sgr(ATTR_BOLD, 1);
		else if (n == SGR_FAINT) screen_set_sgr(ATTR_FAINT, 1);
		else if (n == SGR_ITALIC) screen_set_sgr(ATTR_ITALIC, 1);
		else if (n == SGR_UNDERLINE) screen_set_sgr(ATTR_UNDERLINE, 1);
		else if (n == SGR_BLINK || n == SGR_BLINK_FAST) screen_set_sgr(ATTR_BLINK, 1); // 6 - rapid blink, not supported
		else if (n == SGR_STRIKE) screen_set_sgr(ATTR_STRIKE, 1);
		else if (n == SGR_FRAKTUR) screen_set_sgr(ATTR_FRAKTUR, 1);
		else if (n == SGR_INVERSE) screen_set_sgr(ATTR_INVERSE, 1);
		else if (n == SGR_CONCEAL) screen_set_sgr_conceal(1);
		else if (n == SGR_OVERLINE) screen_set_sgr(ATTR_OVERLINE, 1);
			// -- clear attr --
		else if (n == SGR_NO_BOLD) screen_set_sgr(ATTR_BOLD, 0); // can also mean "Double Underline"
		else if (n == SGR_NO_BOLD_FAINT) screen_set_sgr(ATTR_FAINT | ATTR_BOLD, 0); // "normal"
		else if (n == SGR_NO_ITALIC_FRACTUR) screen_set_sgr(ATTR_ITALIC | ATTR_FRAKTUR, 0); // there is no dedicated OFF code for Fraktur
		else if (n == SGR_NO_UNDERLINE) screen_set_sgr(ATTR_UNDERLINE, 0);
		else if (n == SGR_NO_BLINK) screen_set_sgr(ATTR_BLINK, 0);
		else if (n == SGR_NO_STRIKE) screen_set_sgr(ATTR_STRIKE, 0);
		else if (n == SGR_NO_INVERSE) screen_set_sgr(ATTR_INVERSE, 0);
		else if (n == SGR_NO_CONCEAL) screen_set_sgr_conceal(0);
		else if (n == SGR_NO_OVERLINE) screen_set_sgr(ATTR_OVERLINE, 0);
		else {
			ansi_noimpl("SGR %d", n);
		}
	}
}


/**
 * CSI Pm h or l
 * @param opts
 */
static inline void ICACHE_FLASH_ATTR
do_csi_set_option(CSI_Data *opts)
{
	bool yn = (opts->key == 'h');

	for (int i = 0; i < opts->count; i++) {
		int n = opts->n[i];

		if (n == 4) {
			screen_set_insert_mode(yn);
		}
		else if (n == 12) {
			// SRM is inverted, according to vt510 manual
			termconf_live.loopback = !yn;
		}
		else if (n == 20) {
			screen_set_newline_mode(yn);
		}
		else if (n == 33) {
			ansi_noimpl("Steady cursor");
			// reference https://ttssh2.osdn.jp/manual/en/about/ctrlseq.html
		}
		else if (n == 34) {
			ansi_noimpl("Underline cursor");
			// reference https://ttssh2.osdn.jp/manual/en/about/ctrlseq.html
		}
		else {
			ansi_noimpl("OPTION %d", n);
		}
	}
}


/**
 * CSI ? Pm h or l
 * @param opts
 */
static inline void ICACHE_FLASH_ATTR
do_csi_set_private_option(CSI_Data *opts)
{
	bool yn = (opts->key == 'h');

	// --- DEC private attributes ---
	for (int i = 0; i < opts->count; i++) {
		int n = opts->n[i];
		if (n == 1) {
			screen_set_cursors_alt_mode(yn);
		}
		else if (n == 2) {
			// should reset all Gx to USASCII and reset to VT100 (which we use always)
			screen_set_charset(0, 'B');
			screen_set_charset(1, 'B');
		}
		else if (n == 3) {
			// DECCOLM 132 column mode - not implemented due to RAM demands
			// XXX 132col
			// ansi_noimpl("132col");

			// DECCOLM side effects as per
			// https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/deccolm-cls.html
			screen_clear(CLEAR_ALL);
			screen_set_scrolling_region(0, 0);
			screen_cursor_set(0, 0);
		}
		else if (n == 4) {
			// Smooth scroll - not implemented
		}
		else if (n == 5) {
			screen_set_reverse_video(yn);
		}
		else if (n == 6) {
			screen_set_origin_mode(yn);
		}
		else if (n == 7) {
			screen_wrap_enable(yn);
		}
		else if (n == 8) {
			// Key auto-repeat
			// We don't implement this currently, but it could be added
			// - discard repeated keypress events between keydown and keyup.
			// XXX auto repeat toggle
		}
		else if (n == 9 || (n >= 1000 && n <= 1006) || n == 1015) {
			// 9 - X10 tracking
			// 1000 - X11 mouse - Send Mouse X & Y on button press and release.
			// 1001 - Hilite mouse tracking - not impl
			// 1002 - Cell Motion Mouse Tracking
			// 1003 - All Motion Mouse Tracking
			// 1004 - Send FocusIn/FocusOut events
			// 1005 - Enable UTF-8 Mouse Mode - we implement this as an alias to X10 mode
			// 1006 - SGR mouse mode
			if (n == 9) mouse_tracking.mode = yn ? MTM_X10 : MTM_NONE;
			else if (n == 1000) mouse_tracking.mode = yn ? MTM_NORMAL : MTM_NONE;
			else if (n == 1002) mouse_tracking.mode = yn ? MTM_BUTTON_MOTION : MTM_NONE;
			else if (n == 1003) mouse_tracking.mode = yn ? MTM_ANY_MOTION : MTM_NONE;
			else if (n == 1004) mouse_tracking.focus_tracking = yn;
			else if (n == 1005) mouse_tracking.encoding = yn ? MTE_UTF8 : MTE_SIMPLE;
			else if (n == 1006) mouse_tracking.encoding = yn ? MTE_SGR : MTE_SIMPLE;
			else if (n == 1015) mouse_tracking.encoding = yn ? MTE_URXVT : MTE_SIMPLE;

			ansi_dbg("Mouse mode=%d, enc=%d, foctr=%d",
				mouse_tracking.mode,
				mouse_tracking.encoding,
				mouse_tracking.focus_tracking);

			screen_notifyChange(TOPIC_CHANGE_SCREEN_OPTS);
		}
		else if (n == 12) {
			screen_cursor_blink(yn);
		}
		else if (n == 25) {
			screen_set_cursor_visible(yn);
		}
		else if (n == 40) {
			// allow/disallow 80->132 mode
			// not implemented because of RAM demands
			// ansi_noimpl("132col enable");
		}
		else if (n == 45) {
			// reverse wrap-around
			screen_reverse_wrap_enable(yn);
		}
		else if (n == 69) {
			// horizontal margins
			ansi_noimpl("Left/right margin");
		}
		else if (n == 47 || n == 1047) {
			// Switch to/from alternate screen
			//  - not implemented fully due to RAM demands
			screen_swap_state(yn);
		}
		else if (n == 1048) {
			// same as DECSC - save/restore cursor with attributes
			screen_cursor_save(yn);
		}
		else if (n == 1049) {
			// save/restore cursor and screen and clear it
			if (yn) {
				screen_cursor_save(true);
				screen_swap_state(true); // this should save the screen - can't because of RAM size
				screen_clear(CLEAR_ALL);
			}
			else {
				screen_clear(CLEAR_ALL);
				screen_swap_state(false); // this should restore the screen - can't because of RAM size
				screen_cursor_restore(true);
			}
		}
		else if (n == 2004) {
			// Bracketed paste mode
			screen_set_bracketed_paste(yn);
		}
		else if (n == 800) { // ESPTerm: Toggle display of buttons
			termconf_live.show_buttons = yn;
			screen_notifyChange(TOPIC_CHANGE_SCREEN_OPTS); // this info is included in the screen preamble
		}
		else if (n == 801) { // ESPTerm: Toggle display of config links
			termconf_live.show_config_links = yn;
			screen_notifyChange(TOPIC_CHANGE_SCREEN_OPTS); // this info is included in the screen preamble
		}
		else {
			ansi_noimpl("?OPTION %d", n);
		}
	}
}


/**
 * CSI Ps ; Ps ; Ps t
 * @param opts
 */
static inline void ICACHE_FLASH_ATTR
do_csi_xterm_screen_cmd(CSI_Data *opts)
{
	char resp_buf[20];
	switch (opts->n[0]) {
		case 8: // set size
			screen_resize(opts->n[1], opts->n[2]);
			break;

		case 18: // Report the size of the text area in characters.
		case 19: // Report the size of the screen in characters.
			sprintf(resp_buf, "\033[8;%d;%dt", termconf_live.height, termconf_live.width);
			apars_respond(resp_buf);
			break;

		case 20: // Report icon
		case 21: // Report title
			apars_respond("\033]l");
			apars_respond(termconf_live.title);
			apars_respond("\033\\");
			break;

		case 22:
			ansi_noimpl("Push title");
			break;
		case 23:
			ansi_noimpl("Pop title");
			break;

		case 24: // Set Height only
			screen_resize(opts->n[1], termconf_live.width);
			break;

		default:
			ansi_noimpl("Xterm win report %d", opts->n[0]);
			break;
	}
}


// data tables for the DECREPTPARM command response
struct DECREPTPARM_parity { int parity; const char * msg; };
static const struct DECREPTPARM_parity DECREPTPARM_parity_arr[] ESP_CONST_DATA = {
	{PARITY_NONE, "1"},
	{PARITY_ODD, "4"},
	{PARITY_EVEN, "5"},
	{-1, 0}
};

struct DECREPTPARM_baud { int baud; const char * msg; };
static const struct DECREPTPARM_baud DECREPTPARM_baud_arr[] ESP_CONST_DATA = {
	{BIT_RATE_300, "48"},
	{BIT_RATE_600, "56"},
	{BIT_RATE_1200, "64"},
	{BIT_RATE_2400, "88"},
	{BIT_RATE_4800, "96"},
	{BIT_RATE_9600  , "104"},
	{BIT_RATE_19200 , "112"},
	{BIT_RATE_38400 , "120"},
	{BIT_RATE_57600 , "128"}, // this is the last in the spec, follow +8
	{BIT_RATE_74880 , "136"},
	{BIT_RATE_115200, "144"},
	{BIT_RATE_230400, "152"},
	{BIT_RATE_460800, "160"},
	{BIT_RATE_921600, "168"},
	{BIT_RATE_1843200, "176"},
	{BIT_RATE_3686400, "184"},
	{-1, 0}
};

/**
 * CSI Ps x
 * @param opts
 */
static inline void ICACHE_FLASH_ATTR
do_csi_decreqtparm(CSI_Data *opts)
{
	const int n1 = opts->n[0];

	// reference http://vt100.net/docs/vt100-ug/chapter3.html - search DECREPTPARM
	if (n1 <= 1) {
		apars_respond("\033["); // this is a response on request (2 would be gratuitous)

		apars_respond(n1 == 0 ? "2;" : "3;");

		// Parity
		for(const struct DECREPTPARM_parity *p = DECREPTPARM_parity_arr; p->parity != -1; p++) {
			if (p->parity == sysconf->uart_parity) {
				apars_respond(p->msg);
				break;
			}
		}

		// bits per character (uart byte) - 1 = 8, 2 = 7
		apars_respond(";1;");

		// Baud rate
		for(const struct DECREPTPARM_baud *p = DECREPTPARM_baud_arr; p->baud != -1; p++) {
			if (p->baud == sysconf->uart_baudrate) {
				apars_respond(p->msg);
				apars_respond(";");
				apars_respond(p->msg);
				break;
			}
		}

		// multiplier 1, flags 0
		apars_respond(";1;0x"); // ROM cartridge number ??
	}
}