/**
 * @file
 * @brief Keyboard & screen functions library for CP/M & MESCC.
 *
 * This library offers a common interface for keyboard and screen devices
 * under CP/M.
 *
 * Supported TTYs:
 * - KS_VT52      for generic VT52   80x24
 * - KS_VT100     for generic VT100  80x25
 * - KS_PCW       for Amstrad PCW    90x31
 * - KS_CPC       for Amstrad CPC    24x80 under CP/M Plus
 * - KS_SPECTRUM  for Spectrum +3    51x23
 * - KS_KAYPRO    for Kaypro         24x80
 *
 * Capabilities:
 * - KS_CAN_HIDE      can hide & show cursor
 * - KS_CAN_REVERSE   can reverse text
 * - KS_CAN_UNDERLINE can underline text
 */

/*	ks.h

	Keyboard & screen functions library for CP/M & MESCC - Mike's Enhanced Small C Compiler.

	Copyright (c) 2016, 2017 Miguel I. Garcia Lopez / FloppySoftware, Spain

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the
	Free Software Foundation; either version 3, or (at your option) any
	later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

	Author's contact:

		www.floppysoftware.es
		cpm-connections.blogspot.com
		floppysoftware@gmail.com

	Revisions:

		03 Sep 2016 : Work begins.
		29 Sep 2016 : Rename functions.
		03 Oct 2016 : Reworked KsInit() and others. Added support for reverse and underline.
		14 Jul 2017 : Added TTY names.
		07 Apr 2020 : TTY names in uppercase.
		08 Apr 2020 : Added KsGetNames(), KsGetHowMany().
*/

/* Public defines
   --------------
*/
#define KS_VT52     0
#define KS_VT100    1
#define KS_PCW      2
#define KS_CPC      3
#define KS_SPECTRUM 4
#define KS_KAYPRO   5
#define KS_VT100COL 6

#define KS_CAN_HIDE      0 // Can hide & show cursor
#define KS_CAN_REVERSE   1 // Can reverse text
#define KS_CAN_UNDERLINE 2 // Can underline text
#define KS_CAN_COLOR     3 // Can display color

/* Define color numbers
   --------------------
*/
// usable as foreground + background colors:
#define BLK 0  // black
#define RED 1  // red
#define GRN 2  // green
#define YEL 3  // yellow
#define BLU 4  // blue
#define MAG 5  // magenta
#define CYN 6  // cyan
#define WHT 7  // gray
// the following colors are "bright" colors, these do not work as background colors
#define BBLK 8   // dark gray ("bright black")
#define BRED 9   // bright red
#define BGRN 10  // bright green
#define BYEL 11  // bright yellow
#define BBLU 12  // bright blue
#define BMAG 13  // bright magenta
#define BCYN 14  // bright cyan
#define BWHT 15  // bright white

/* Private defines
   ---------------
*/
#define XKS_TTYS    7  // Number of supported TTYs

/* Private globals
   ---------------
*/
WORD xks_names[XKS_TTYS];  // TTY names by code -- char *xks_names[]

int xks_rows;      // Screen rows
int xks_cols;      // Screen columns
BYTE *xks_clrscr;  // Clear screen
BYTE *xks_poscur;  // Position cursor
BYTE *xks_shwcur;  // Show cursor
BYTE *xks_hidcur;  // Hide cursor
BYTE *xks_yrever;  // Reverse on
BYTE *xks_nrever;  // Reverse off
BYTE *xks_yuline;  // Underline on
BYTE *xks_nuline;  // Underline off
int xks_color;    // TTY can display color

/**
 * @fn     int KsHello(char *tty_name)
 * @brief  Initialize the KS library.
 * Call this function before any other function (with a few exceptions).
 * @param  code - TTY code
 * @return 0 on success, -1 on failure
 */
KsHello(code)
int code;
{
	// Setup BIOS jumps, etc.
	xKsInit();
	
	xks_color = FALSE;
	
	// Setup TTY
	switch(code) {
		case KS_VT52 :
			xKsInit24x80();
			xks_clrscr = "\eH\eJ";
			xks_poscur = "A\eY%r%c";
			break;

		case KS_VT100 :
			xks_rows   = 25;
			xks_cols   = 80;
			xks_clrscr = "\e[H\e[J";
			xks_poscur = "B\e[%r;%cH";
			xks_yrever = "\e[7m";
			xks_nrever = "\e[m";
			xks_yuline = "\e[4m";
			xks_nuline = "\e[m";			
			break;

		case KS_PCW :
			xks_rows   = 31;
			xks_cols   = 90;
			xKsInitAmstrad();
			break;

		case KS_CPC :
			xKsInit24x80();
			xKsInitAmstrad();
			break;

		case KS_SPECTRUM :
			xks_rows   = 23;
			xks_cols   = 51;
			xKsInitAmstrad();

			KsPutRawCh('\e'); KsPutRawCh('3'); KsPutRawCh(1);
			break;

		case KS_KAYPRO :
			xKsInit24x80();
			xks_clrscr = "?"; *xks_clrscr = 26; // ^Z
			xks_poscur = "A\e=%r%c";
			break;

		case KS_VT100COL :
			xks_rows   = 25;
			xks_cols   = 80;
			xks_clrscr = "\e[H\e[J";
			xks_poscur = "B\e[%r;%cH";
			xks_shwcur = "\e[?25h";
			xks_hidcur = "\e[?25l";
			xks_yrever = "\e[7m";
			xks_nrever = "\e[m";
			xks_yuline = "\e[4m";
			xks_nuline = "\e[m";
			xks_color  = TRUE;
			break;
			
		default :
			// Unknown tty code
			return -1;
	}

	// Success
	return 0;
}

/**
 * @fn    void KsBye(void)
 * @brief Reset the KS library.
 * Call this function before you stop your program, in order to reset the TTY.
 */
KsBye()
{
	// Nothing yet
}

/**
 * @fn     int KsGetCode(char *name)
 * @brief  Get the code for a TTY name.
 * This function can be called before KsHello().
 * @param  name - TTY name
 * @return TTY code if found, or -1 on unknown name
 */
KsGetCode(name)
char *name;
{
	int i;

	if(!xks_names[0]) {
		xKsNames();
	}

	for(i = 0; i < XKS_TTYS; ++i) {
		if(!strcmp(name, xks_names[i])) {
			// Found
			return i;
		}
	}

	// Unknown tty name
	return -1;
}

/**
 * @fn     int KsGetName(int code)
 * @brief  Get the name for a TTY code.
 * This function can be called before KsHello().
 * @param  code - TTY code
 * @return TTY name, or NULL on unknown code
 */
KsGetName(code)
int code;
{
	if(!xks_names[0]) {
		xKsNames();
	}

	if(code >= 0 && code < XKS_TTYS) {
		// Found
		return xks_names[code];
	}

	// Unknown tty code
	return NULL;
}

/**
 * @fn     char **KsGetNames(void)
 * @brief  Get all supported TTY names.
 * This function can be called before KsHello().
 * @return Pointer to an array of pointers to char
 */
KsGetNames()
{
	if(!xks_names[0]) {
		xKsNames();
	}

	return xks_names;
}

/**
 * @fn     int KsGetHowMany(void)
 * @brief  Get the number of supported TTYs.
 * This function can be called before KsHello().
 * @return Number of supported TTYs
 */
KsGetHowMany()
{
	return XKS_TTYS;
}

/**
 * @fn     void KsClear(void)
 * @brief  Clear the screen and move the cursor to 0,0.
 */
KsClear()
{
	KsPutRawStr(xks_clrscr);
}

/**
 * @fn     void KsPosCursor(int row, int col)
 * @brief  Move the cursor on screen.
 * @param  row - screen row from 0
 * @param  col - screen column from 0
 */
KsPosCursor(row, col)
int row, col;
{
	char *p; int v;

	for(p = xks_poscur + 1; *p; ++p) {
		if(*p != '%') {
			KsPutRawCh(*p);
		}
		else {
			v = (*(++p) == 'r' ? row : col);

			if(*xks_poscur == 'A') {
				// A
				KsPutRawCh(32 + v);
			}
			else {
				// B
				xKsPutDec(1 + v);
			}
		}
	}
}

/**
 * @fn     int KsCan(int cap)
 * @brief  Check if the TTY has a capability.
 * @param  cap - capatility code
 * @return 0 on NO, other on YES
 */
KsCan(cap)
int cap;
{
	switch(cap) {
		case KS_CAN_HIDE      : return xks_shwcur != NULL;
		case KS_CAN_REVERSE   : return xks_yrever != NULL;
		case KS_CAN_UNDERLINE : return xks_yuline != NULL;
		case KS_CAN_COLOR     : return xks_color;
	}

	return 0;
}

/**
 * @fn     void KsSetCursor(int toggle)
 * @brief  Show or hide the cursor on screen.
 * @param  toggle - 0 to hide, other to show
 */
KsSetCursor(toggle)
int toggle;
{
	if(xks_shwcur) {
		KsPutRawStr(toggle ? xks_shwcur : xks_hidcur);
	}
}

/**
 * @fn     void KsUnderline(int toggle)
 * @brief  Set or reset underline text.
 * If the TTY does not have this capability, does nothing.
 * @param  toggle - 0 to reset, other to set
 */
KsUnderline(toggle)
int toggle;
{
	if(xks_yuline) {
		KsPutRawStr(toggle ? xks_yuline : xks_nuline);
	}
}

/**
 * @fn     void KsReverse(int toggle)
 * @brief  Set or reset reverse text.
 * If the TTY does not have this capability, does nothing.
 * @param  toggle - 0 to reset, other to set
 */
KsReverse(toggle)
int toggle;
{
	if(xks_yrever) {
		KsPutRawStr(toggle ? xks_yrever : xks_nrever);
	}
}

/**
 * @fn     char *KsFgCol(int color)
 * @brief  Set foreground color
 * If the TTY does not have this capability, does nothing.
 * @param  color - color number, see #defines
 */
KsFgCol(color)
int color;
{
	char rc[12];
	if(xks_color) {
		if(color<8) {
			sprintf(rc, "\e[3%dm", color);
			KsPutStr(rc);
		}
		if(color>=8) {
			sprintf(rc, "\e[3%d;1m", color-8); // bright colors begin at 8
			KsPutStr(rc);
		}
	}
}

/**
 * @fn     void KsBgCol(int color)
 * @brief  Set background color
 * If the TTY does not have this capability, does nothing.
 * @param  color - color number, see #defines
 */
KsBgCol(color)
int color;
{
	char str[6];
	if(xks_color) {
		sprintf(str, "\e[4%dm", color);
		KsPutStr(str);
	}
}

/**
 * @fn     void KsResetAttr()
 * If the TTY does not support color, does nothing
 * @brief  Resets all attributes
 */
KsResetAttr()
{
	if(xks_color)
		KsPutStr("\e[m");
}

/**
 * @fn     void KsCenterStr(int row, char *s)
 * @brief  Print a string centered on screen.
 * @param  row - screen row
 * @param  s - string
 */
KsCenterStr(row, s)
int row; char *s;
{
	KsPosCursor(row, (KsGetCols() - strlen(s)) / 2);
	KsPutStr(s);
}

/**
 * @fn     int KsGetRows(void)
 * @brief  Get TTY rows.
 * @return  TTY rows.
 */
KsGetRows()
{
	return xks_rows;
}

/**
 * @fn     int KsGetCols(void)
 * @brief  Get TTY columns.
 * @return  TTY columns.
 */
KsGetCols()
{
	return xks_cols;
}

/**
 * @fn     void KsPutRawCh(int ch)
 * @brief  Send a character to the TTY.
 * @param  ch - character
 */
#asm
KsPutRawCh
	ld   c,l
	jp   xKsConOut
#endasm

/**
 * @fn     void KsPutRawStr(char *s)
 * @brief  Send a string to the TTY using KsPutRawCh().
 * @param  s - string
 */
KsPutRawStr(s)
char *s;
{
	while(*s) {
		KsPutRawCh(*s++);
	}
}

/**
 * @fn     void KsPutCh(int ch)
 * @brief  Send a character to the TTY.
 * This functions performs the following translations:
 * - '\n' to '\r' + '\n'
 * @param  ch - character
 */
#asm
KsPutCh
	ld   a,l
	cp   10
	jp   nz,KsPutRawCh
	ld   l,13
	call KsPutRawCh
	ld   l,10
	jp   KsPutRawCh
#endasm

/**
 * @fn     void KsPutStr(char *s)
 * @brief  Send a string to the TTY using KsPutCh().
 * @param  s - string
 */
KsPutStr(s)
char *s;
{
	while(*s) {
		KsPutCh(*s++);
	}
}

/**
 * @fn     int KsGetKb(void)
 * @brief  Check if there is an input character from the keyboard.
 * @return 0 for NO, other for YES
 */
#asm
KsGetKb:
	call xKsConInSt
	ld   h,a
	ld   l,a
	ret
#endasm

/**
 * @fn     int KsGetCh(void)
 * @brief  Get character from the keyboard, waiting for one if necessary.
 * @return character
 */
#asm
KsGetCh
	call xKsConIn
	ld   h,0
	ld   l,a
	ret
#endasm

/* Private functions
   -----------------
*/

// void xKsInit(void) : Set BIOS JUMPs.
#asm
xKsInit
	ld   hl,(1)
	inc  hl
	inc  hl
	inc  hl
	ld   de,xKsConInSt
	ld   bc,9
	ldir
	ret

xKsConInSt
	jp 0  ; BIOS ConSt
xKsConIn
	jp 0  ; BIOS ConIn
xKsConOut
	jp 0  ; BIOS ConOut
#endasm

// void xKsNames(void) : Set TTY names.
xKsNames()
{
	xks_names[KS_VT52]     = "VT52";
	xks_names[KS_VT100]    = "VT100";
	xks_names[KS_PCW]      = "PCW";
	xks_names[KS_CPC]      = "CPC";
	xks_names[KS_SPECTRUM] = "SPECTRUM";
	xks_names[KS_KAYPRO]   = "KAYPRO";
	xks_names[KS_VT100COL] = "VT100COL";
}

// void xKsInit24x80(void) : Set TTY to 24 rows and 80 columns.
xKsInit24x80()
{
	xks_rows = 24;
	xks_cols = 80;
}

// void xKsInitAmstrad(void) : Set generic Amstrad mode.
xKsInitAmstrad()
{
	xks_clrscr = "\eH\eE";
	xks_poscur = "A\eY%r%c";
	xks_shwcur = "\ee";
	xks_hidcur = "\ef";
	xks_yrever = "\ep";
	xks_nrever = "\eq";
	xks_yuline = "\er";
	xks_nuline = "\eu";
}

// void xKsPutDec(int num) : Send positive decimal number to TTY.
xKsPutDec(num)
int num;
{
#ifdef SPRINTF_H
	char buf[6];

	sprintf(buf, "%d", num);

	KsPutRawStr(buf);
#else
	char buf[6];
	int i;

	for(i = 0; i < 6; ++i) {
		if(num > 9) {
			buf[i] = num % 10;
			num /= 10;
		}
		else {
			buf[i++] = num;
			break;
		}
	}

	do {
		KsPutRawCh('0' + buf[--i]);
	} while(i);
#endif
}


