/* jslint node: true */
'use strict';

//
//	ANSI Terminal Support Resources
//	
//	ANSI-BBS
//		* http://ansi-bbs.org/
//
//	CTerm / SyncTERM
//		* https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
//
//	BananaCom
//		* http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt
//
//	ANSI.SYS
//		* http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/ansisys.txt
//		* http://academic.evergreen.edu/projects/biophysics/technotes/program/ansi_esc.htm
//
//	VTX
//		* https://github.com/codewar65/VTX_ClientServer/blob/master/vtx.txt
//
//	General
//		* http://en.wikipedia.org/wiki/ANSI_escape_code
//		* http://www.inwap.com/pdp10/ansicode.txt
//
//	Other Implementations
//		* https://github.com/chjj/term.js/blob/master/src/term.js
//
//
//	For a board, we need to support the semi-standard ANSI-BBS "spec" which
//	is bastardized mix of DOS ANSI.SYS, cterm.txt, bansi.txt and a little other.
//	This gives us NetRunner, SyncTERM, EtherTerm, most *nix terminals, compatibilitiy
//	with legit oldschool DOS terminals, and so on. 
//

//	ENiGMA½
const miscUtil	= require('./misc_util.js');

//	deps
const assert	= require('assert');
const _			= require('lodash');

exports.getFullMatchRegExp			= getFullMatchRegExp;
exports.getFGColorValue				= getFGColorValue;
exports.getBGColorValue				= getBGColorValue;
exports.sgr							= sgr;
exports.getSGRFromGraphicRendition	= getSGRFromGraphicRendition;
exports.clearScreen					= clearScreen;
exports.resetScreen					= resetScreen;
exports.normal						= normal;
exports.goHome						= goHome;
exports.disableVT100LineWrapping	= disableVT100LineWrapping;
exports.setSyncTERMFont				= setSyncTERMFont;
exports.getSyncTERMFontFromAlias	= getSyncTERMFontFromAlias;
exports.setSyncTermFontWithAlias	= setSyncTermFontWithAlias;
exports.setCursorStyle				= setCursorStyle;
exports.setEmulatedBaudRate			= setEmulatedBaudRate;
exports.vtxHyperlink		= vtxHyperlink;

//
//	See also
//	https://github.com/TooTallNate/ansi.js/blob/master/lib/ansi.js

const ESC_CSI 	= '\u001b[';

const CONTROL = {
	up				: 'A',
	down			: 'B',

	forward			: 'C',
	right			: 'C',

	back			: 'D',
	left			: 'D',

	nextLine		: 'E',
	prevLine		: 'F',
	horizAbsolute	: 'G',

	//
	//	CSI [ p1 ] J
	//	Erase in Page / Erase Data
	//	Defaults: p1 = 0
	//	Erases from the current screen according to the value of p1
	//	0 - Erase from the current position to the end of the screen.
	//	1 - Erase from the current position to the start of the screen.
	//	2 - Erase entire screen.  As a violation of ECMA-048, also moves
	//	    the cursor to position 1/1 as a number of BBS programs assume
	//		this behaviour.
	//	Erased characters are set to the current attribute.
	//
	//	Support:
	//	* SyncTERM: Works as expected
	//	* NetRunner: Always clears a screen *height* (e.g. 25) regardless of p1
	//	  and screen remainder
	//
	eraseData		: 'J',

	eraseLine		: 'K',
	insertLine		: 'L',

	//
	//	CSI [ p1 ] M
	//	Delete Line(s) / "ANSI" Music
	//	Defaults: p1 = 1
	//	Deletes the current line and the p1 - 1 lines after it scrolling the
	//	first non-deleted line up to the current line and filling the newly
	//	empty lines at the end of the screen with the current attribute.
	//	If "ANSI" Music is fully enabled (CSI = 2 M), performs "ANSI" music
	//	instead.
	//	See "ANSI" MUSIC section for more details.
	//
	//	Support:
	//	* SyncTERM: Works as expected
	//	* NetRunner: 	
	//
	//	General Notes:
	//	See also notes in bansi.txt and cterm.txt about the various
	//	incompatibilities & oddities around this sequence. ANSI-BBS
	//	states that it *should* work with any value of p1.
	//
	deleteLine		: 'M',
	ansiMusic		: 'M',

	scrollUp		: 'S',
	scrollDown		: 'T',
	setScrollRegion	: 'r',
	savePos			: 's',
	restorePos		: 'u',
	queryPos		: '6n',
	queryScreenSize	: '255n',	//	See bansi.txt
	goto			: 'H',	//	row Pr, column Pc -- same as f
	gotoAlt			: 'f',	//	same as H

	blinkToBrightIntensity : '?33h',
	blinkNormal				: '?33l',

	emulationSpeed	: '*r',	//	Set output emulation speed. See cterm.txt

	hideCursor		: '?25l',	//	Nonstandard - cterm.txt
	showCursor		: '?25h',	//	Nonstandard - cterm.txt

	queryDeviceAttributes	: 'c',	//	Nonstandard - cterm.txt

	//	:TODO: see https://code.google.com/p/conemu-maximus5/wiki/AnsiEscapeCodes
	//	apparently some terms can report screen size and text area via 18t and 19t
};

//
//	Select Graphics Rendition
//	See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
//
const SGRValues = {
	reset			: 0,
	bold			: 1,
	dim				: 2,
	blink			: 5,
	fastBlink		: 6,
	negative		: 7,
	hidden			: 8,

	normal			: 22,	//	
	steady			: 25,
	positive		: 27,

	black			: 30,
	red				: 31,
	green			: 32,
	yellow			: 33,
	blue			: 34,
	magenta			: 35,
	cyan			: 36,
	white			: 37,

	blackBG			: 40,
	redBG			: 41,
	greenBG			: 42,
	yellowBG		: 43,
	blueBG			: 44,
	magentaBG		: 45,
	cyanBG			: 46,
	whiteBG			: 47,
};

function getFullMatchRegExp(flags = 'g') {
	//	:TODO: expand this a bit - see strip-ansi/etc.
	//	:TODO: \u009b ?
	return new RegExp(/[\u001b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/, flags);	//	eslint-disable-line no-control-regex
}

function getFGColorValue(name) {
	return SGRValues[name];
}

function getBGColorValue(name) {
	return SGRValues[name + 'BG'];
}


//	See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
//	:TODO: document
//	:TODO: Create mappings for aliases... maybe make this a map to values instead
//	:TODO: Break this up in to two parts:
//	1) FONT_AND_CODE_PAGES (e.g. SyncTERM/cterm)
//	2) SAUCE_FONT_MAP: Sauce name(s) -> items in FONT_AND_CODE_PAGES. 
//	...we can then have getFontFromSAUCEName(sauceFontName)
//	Also, create a SAUCE_ENCODING_MAP: SAUCE font name -> encodings

//
//	An array of CTerm/SyncTERM font/encoding values. Each entry's index
//	corresponds to it's escape sequence value (e.g. cp437 = 0)
//
//	See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
//
const SYNCTERM_FONT_AND_ENCODING_TABLE = [
	'cp437',
	'cp1251', 
	'koi8_r', 
	'iso8859_2', 
	'iso8859_4', 
	'cp866',
	'iso8859_9', 
	'haik8', 
	'iso8859_8', 
	'koi8_u', 
	'iso8859_15', 
	'iso8859_4',
	'koi8_r_b', 
	'iso8859_4', 
	'iso8859_5', 
	'ARMSCII_8', 
	'iso8859_15',
	'cp850', 
	'cp850', 
	'cp885', 
	'cp1251', 
	'iso8859_7', 
	'koi8-r_c',
	'iso8859_4', 
	'iso8859_1', 
	'cp866', 
	'cp437', 
	'cp866', 
	'cp885',
	'cp866_u', 
	'iso8859_1', 
	'cp1131', 
	'c64_upper', 
	'c64_lower',
	'c128_upper', 
	'c128_lower', 
	'atari', 
	'pot_noodle', 
	'mo_soul',
	'microknight_plus', 
	'topaz_plus',
	'microknight',
	'topaz',
];

//
//	A map of various font name/aliases such as those used
//	in SAUCE records to SyncTERM/CTerm names
//
//	This table contains lowercased entries with any spaces
//	replaced with '_' for lookup purposes.
//
const FONT_ALIAS_TO_SYNCTERM_MAP = {
	'cp437'					: 'cp437',
	'ibm_vga'				: 'cp437',
	'ibmpc'					: 'cp437',
	'ibm_pc'				: 'cp437',
	'pc'					: 'cp437',
	'cp437_art'	 			: 'cp437',
	'ibmpcart'				: 'cp437',
	'ibmpc_art'				: 'cp437',
	'ibm_pc_art'			: 'cp437',
	'msdos_art'				: 'cp437',
	'msdosart'				: 'cp437',
	'pc_art'				: 'cp437',
	'pcart'					: 'cp437',

	'ibm_vga50'				: 'cp437',
	'ibm_vga25g'			: 'cp437',
	'ibm_ega'				: 'cp437',
	'ibm_ega43'				: 'cp437',

	'topaz'					: 'topaz',
	'amiga_topaz_1'			: 'topaz',
	'amiga_topaz_1+'		: 'topaz_plus',
	'topazplus'				: 'topaz_plus',	
	'topaz_plus'			: 'topaz_plus',
	'amiga_topaz_2'			: 'topaz',
	'amiga_topaz_2+'		: 'topaz_plus',
	'topaz2plus'			: 'topaz_plus',

	'pot_noodle'			: 'pot_noodle',
	'p0tnoodle'				: 'pot_noodle',
	'amiga_p0t-noodle'		: 'pot_noodle',

	'mo_soul'				: 'mo_soul',
	'mosoul'				: 'mo_soul',
	'mO\'sOul'				: 'mo_soul',

	'amiga_microknight'		: 'microknight',
	'amiga_microknight+'	: 'microknight_plus',

	'atari'					: 'atari',
	'atarist'				: 'atari',

};

function setSyncTERMFont(name, fontPage) {
	const p1 = miscUtil.valueWithDefault(fontPage, 0);

	assert(p1 >= 0 && p1 <= 3);

	const p2 = SYNCTERM_FONT_AND_ENCODING_TABLE.indexOf(name);
	if(p2 > -1) {
		return `${ESC_CSI}${p1};${p2} D`;
	}

	return '';
}

function getSyncTERMFontFromAlias(alias) {
	return FONT_ALIAS_TO_SYNCTERM_MAP[alias.toLowerCase().replace(/ /g, '_')];
}

function setSyncTermFontWithAlias(nameOrAlias) {
	nameOrAlias = getSyncTERMFontFromAlias(nameOrAlias) || nameOrAlias;
	return setSyncTERMFont(nameOrAlias);
}

const DEC_CURSOR_STYLE = {
	'blinking block'		: 0,
	'default'				: 1,
	'steady block'			: 2,
	'blinking underline'	: 3,
	'steady underline'		: 4,
	'blinking bar'			: 5,
	'steady bar'			: 6,
};

function setCursorStyle(cursorStyle) {
	const ps = DEC_CURSOR_STYLE[cursorStyle];
	if(ps) {
		return `${ESC_CSI}${ps} q`;
	}
	return '';
	
}

//	Create methods such as up(), nextLine(),...
Object.keys(CONTROL).forEach(function onControlName(name) {
	const code = CONTROL[name];

	exports[name] = function() {
		let c = code;
		if(arguments.length > 0) {
			//	arguments are array like -- we want an array
			c = Array.prototype.slice.call(arguments).map(Math.round).join(';') + code;
		}
		return `${ESC_CSI}${c}`;
	};
});

//	Create various color methods such as white(), yellowBG(), reset(), ...
Object.keys(SGRValues).forEach( name => {
	const code = SGRValues[name];

	exports[name] = function() {
		return `${ESC_CSI}${code}m`;
	};
});

function sgr() {
	//
	//	- Allow an single array or variable number of arguments
	//	- Each element can be either a integer or string found in SGRValues
	//	  which in turn maps to a integer
	//
	if(arguments.length <= 0) {
		return '';
	}

	let result	= [];
	const args	= Array.isArray(arguments[0]) ? arguments[0] : arguments;

	for(let i = 0; i < args.length; ++i) {
		const arg = args[i];
		if(_.isString(arg) && arg in SGRValues) {
			result.push(SGRValues[arg]);
		} else if(_.isNumber(arg)) {
			result.push(arg);
		}
	}

	return `${ESC_CSI}${result.join(';')}m`;
}

//
//	Converts a Graphic Rendition object used elsewhere
//	to a ANSI SGR sequence.
//
function getSGRFromGraphicRendition(graphicRendition, initialReset) {
	let sgrSeq		= [];
	let styleCount	= 0;

	[ 'intensity', 'underline', 'blink', 'negative', 'invisible' ].forEach( s => {
		if(graphicRendition[s]) {
			sgrSeq.push(graphicRendition[s]);
			++styleCount;
		}
	});

	if(graphicRendition.fg) {
		sgrSeq.push(graphicRendition.fg);
	}

	if(graphicRendition.bg) {
		sgrSeq.push(graphicRendition.bg);
	}

	if(0 === styleCount || initialReset) {
		sgrSeq.unshift(0);
	}

	return sgr(sgrSeq);
}

///////////////////////////////////////////////////////////////////////////////
//	Shortcuts for common functions
///////////////////////////////////////////////////////////////////////////////

function clearScreen() {
	return exports.eraseData(2);
}

function resetScreen() {
	return `${exports.reset()}${exports.eraseData(2)}${exports.goHome()}`;
}

function normal() {
	return sgr( [ 'normal', 'reset' ] );
}

function goHome() {
	return exports.goto();	//	no params = home = 1,1
}

//
//	Disable auto line wraping @ termWidth
//
//	See:
//	http://stjarnhimlen.se/snippets/vt100.txt
//	https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
//
//	WARNING:
//	* Not honored by all clients
//	* If it is honored, ANSI's that rely on this (e.g. do not have \r\n endings
//	  and use term width -- generally 80 columns -- will display garbled!
//
function disableVT100LineWrapping() {
	return `${ESC_CSI}?7l`;
}

function setEmulatedBaudRate(rate) {
	const speed = {
		unlimited	: 0,
		off			: 0,
		0			: 0,
		300			: 1,
		600			: 2,
		1200		: 3,
		2400		: 4,
		4800		: 5,
		9600		: 6,
		19200		: 7,
		38400		: 8,
		57600		: 9,
		76800		: 10,
		115200		: 11,
	}[rate] || 0;
	return 0 === speed ? exports.emulationSpeed() : exports.emulationSpeed(1, speed);
}

function vtxHyperlink(client, url, len) {
	if(!client.terminalSupports('vtx_hyperlink')) {
		return '';
	}

	len = len || url.length;

	url = url.split('').map(c => c.charCodeAt(0)).join(';');
	return `${ESC_CSI}1;${len};1;1;${url}\\`;
}