const SESSION_VIEWDATA				= (1<<2);
var CONTENT_EXT						= 'bin';

var FRAME_WIDTH					= 40;
var FRAME_HEIGHT					= 22;
var FRAME_PROVIDER_LENGTH			= 23;
var FRAME_PAGE_LENGTH				= 11;
var FRAME_COST_LENGTH				= 6;
var FRAME_ATTR_LENGTH				= 1;		// Space that an attribute takes

const VIEWDATA_LEFT					= '\x08';
const VIEWDATA_RIGHT					= '\x09';
const VIEWDATA_DOWN					= '\x0a';	// \n
const VIEWDATA_UP					= '\x0b';
const VIEWDATA_CLS					= '\x0c';
const VIEWDATA_CR					= '\x0d';	// \r
const VIEWDATA_CON					= '\x11';
const VIEWDATA_COFF					= '\x14';
const VIEWDATA_HOME					= '\x1e';

const VIEWDATA_BLINK					= '\x48';
const VIEWDATA_STEADY				= '\x49';
const VIEWDATA_NORMAL				= '\x4c';
const VIEWDATA_DOUBLE				= '\x4d';
const VIEWDATA_CONCEAL				= '\x58';
const VIEWDATA_BLOCKS				= '\x59';
const VIEWDATA_SEPARATED				= '\x5a';
const VIEWDATA_BLACKBACK				= '\x5c';
const VIEWDATA_NEWBACK				= '\x5d';
const VIEWDATA_HOLD					= '\x5e';
const VIEWDATA_REVEAL				= '\x5f';

const VIEWDATA_RED					= '\x41';
const VIEWDATA_GREEN					= '\x42';
const VIEWDATA_YELLOW				= '\x43';	// C
const VIEWDATA_BLUE					= '\x44';
const VIEWDATA_MAGENTA				= '\x45';
const VIEWDATA_CYAN					= '\x46';
const VIEWDATA_WHITE					= '\x47';

const VIEWDATA_MOSIAC_RED			= '\x51';
const VIEWDATA_MOSIAC_GREEN			= '\x52';
const VIEWDATA_MOSIAC_YELLOW			= '\x53';
const VIEWDATA_MOSIAC_BLUE			= '\x54';
const VIEWDATA_MOSIAC_MAGENTA		= '\x55';
const VIEWDATA_MOSIAC_CYAN			= '\x56';
const VIEWDATA_MOSIAC_WHITE			= '\x57';	// W

/* BINARY DUMP LEVEL 1 ATTRIBUTES */
const VIEWDATA_BIN_RED				= '\x01';
const VIEWDATA_BIN_GREEN				= '\x02';
const VIEWDATA_BIN_YELLOW			= '\x03';
const VIEWDATA_BIN_BLUE				= '\x04';
const VIEWDATA_BIN_MAGENTA			= '\x05';
const VIEWDATA_BIN_CYAN				= '\x06';
const VIEWDATA_BIN_WHITE				= '\x07';

/**
 * ViewData characters are 7bit (0x00-0x7f)
 *
 * Chars 0x00-0x1f are control characters (display attributes) and are sent to the terminal with 0x1b
 * + 0x00-0x07 are foreground colors
 * + 0x08-0x09 flash/steady
 * + 0x0a-0x0b end/start box (?) *
 * + 0x0c-0x0d normal/double height
 * + 0x0e-0x0f double width (?) *
 * + 0x10-0x17 are foreground graphics (mosiac) colors
 * + 0x18/0x1f conceal/reveal
 * + 0x19-0x1a solid/seperated graphics
 * + 0x1b unused
 * + 0x1c-1x1d Black/New Background (new background converts color foreground to background)
 * + 0x1e-0x1f graphics hold/release (enables changing color and repeats previous graphics char)
 * Chars 0x20-0x7f are normal printed ASCII chars
 * Chars 0x20-0x3f & 0x60-0x7f when activated with a MOSIAC color sends a 2x3 pixel character
 *
 * We can map these into cga_defs with the following amendments:
 * 0x00-0x0f = foreground/background colors (4 bits) (8 foreground/8 background colors)
 * 0x10 - mosiac (bit 4)
 * 0x20 - conceal (bit 5)
 * 0x40 - seperated graphics (bit 6)
 * 0x80 - flash (bit 7)
 * 0x100 - double height (bit 8)
 * 0x200 - hold (bit 9)
 * 0x400 - new background (bits 10/11)
 * 0x800 - black background (bits 10/11)
 * 0xc00 - unused (bits 10/11)
 * bits (12-15) unused
 *
 * @type {number}
 */
var MOSIAC							= 0x10;

// Toggles
var CONCEAL						= 0x20;
var REVEAL							= 0x2000;	// @temp Turns off Conceal

var SEPARATED						= 0x40;
var BLOCKS							= 0x4000;	// @temp Turns off Separated

var STEADY							= 0x8000;	// @temp (turn off flash)

var DOUBLE							= 0x100;
var NORMAL							= 0x1000;	// @temp Turns off Double Height

var HOLD							= 0x200;
var RELEASE						= 0x20000;	// @temp turns off Hold

var NEWBACK						= 0x400;
var BLACKBACK						= 0x800;

/**
 * This function converts ANSI text into an array of attributes
 *
 * @param contents	- Our ANSI content to convert
 * @param width		- The width before wrapping to the next line
 * @param yoffset   - fields offset as discovered
 * @param xoffset   - fields offset as discovered
 * @param debug		- Enable debug mode
 */
function rawtoattrs(contents,width,yoffset,xoffset,debug) {
	if (debug)
		writeln('DEBUG active: '+debug);

	lines = (''+contents).split(/\r\n/);

	var i = 0;
	var bg = BG_BLACK;
	var fg = LIGHTGRAY;
	var attr = fg + bg + i;

	// Attribute state on a new line
	var new_line = attr;

	var y = 0;

	var frame = {
		content: [],
		dynamic_fields: [],
		input_fields: [],
	};

	// @todo temp hack, rework ansi variable - perhaps have a function that converts an attribute back into an ANSI sequence
	var ansi = { i: 0, f: 37, b: 40 };

	while (lines.length > 0) {
		var x = 0;
		var line = lines.shift();

		if ((debug !== undefined) && (y > debug)) {
			exit(1);
		}

		if (debug) {
			log(LOG_DEBUG,'y:'+y);
			log(LOG_DEBUG,'line:'+line);
			write('y:'+y+', line:'+line);
		}

		while (line.length > 0) {
			if (x >= width) {
				x = 0;
				// Each new line, we reset the attrs
				attr = new_line;
				y++;
			}
			//writeln('next ch:'+line[0].charCodeAt(0));

			/* parse control codes */
			var m = line.match(/^([\x00-\x1f])/);
			if (m !== null) {
				line = line.substr(m[0].length);
				attr = 0;

				match = m.shift().charCodeAt(0);

				/*
				writeln('- match:'+match);

				writeln('- match 0x0f:'+(match & 0x0f));
					if (match & 0x10) {
						writeln(' - got mosiac');
						attr += MOSIAC;
					}

				*/
				//if (match < 0x0f) {
					//switch(match & 0x07) {
				switch(match) {
					case 0x00:
						attr += BLACK;
						break;
					case 0x01:
						attr += RED;
						break;
					case 0x02:
						attr += GREEN;
						break;
					case 0x03:
						attr += YELLOW;
						break;
					case 0x04:
						attr += BLUE;
						break;
					case 0x05:
						attr += MAGENTA;
						break;
					case 0x06:
						attr += CYAN;
						break;
					case 0x07:
						attr += LIGHTGRAY;
						break;
					case 0x08:
						attr = BLINK;
						break;
					case 0x09:
						attr = STEADY;
						break;
					/*
					case 0x0a:
						//attr = ENDBOX;	// End Box (Unused?)
						break;
					case 0x0b:
						//attr = STARTBOX;	// Start Box (Unused?)
						break;
					*/
					case 0x0c:
						//attr &= ~DOUBLE;
						attr = NORMAL;
						break;
					case 0x0d:
						attr = DOUBLE;
						break;
					case 0x0e:
						attr = NORMAL;	// @todo Double Width (Unused)?
						break;
					case 0x0f:
						attr = NORMAL;	// @todo Double Width (Unused?)
						break;
					case 0x10:
						attr = MOSIAC|BLACK;
						break;
					case 0x11:
						attr += MOSIAC|RED;
						break;
					case 0x12:
						attr += MOSIAC|GREEN;
						break;
					case 0x13:
						attr += MOSIAC|YELLOW;
						break;
					case 0x14:
						attr += MOSIAC|BLUE;
						break;
					case 0x15:
						attr += MOSIAC|MAGENTA;
						break;
					case 0x16:
						attr += MOSIAC|CYAN;
						break;
					case 0x17:
						attr += MOSIAC|LIGHTGRAY;
						break;
					case 0x18:
						attr = CONCEAL;
						break;
					case 0x19:
						attr = BLOCKS;
						break;
					case 0x1a:
						attr = SEPARATED;
						break;
					/*
					case 0x1b:
						//attr = NORMAL;	// CSI
						break;
					*/
					case 0x1c:
						attr = BLACKBACK;	// Black Background
						break;
					case 0x1d:
						attr = NEWBACK;		// New Background
						break;
					case 0x1e:
						attr = HOLD;		// Mosiac Hold
						break;
					case 0x1f:
						attr = RELEASE;		// Mosiac Release
						break;

					// Catch all for other codes
					default:
						attr = 0xff00;
				}

				if (debug)
					writeln(' - got control code:'+attr+'['+y+','+x+'] - length:'+attr.length);

				store(x++,y,null,attr);
				attr = undefined;

				continue;
			}

			/* parse an input field */
			// Input field 'FIELD;valueTYPE;input char'
			// @todo remove the trailing ESC \ to end the field, just use a control code ^B \x02 (Start of Text) and ^C \x03
			var m = line.match(/^\x1b_(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/);
			if (m !== null) {
				log(LOG_DEBUG,'Got input field: '+JSON.stringify(m));
				log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi));
				// full string that matched
				match = m.shift();

				// thus, the rest of the line
				line = line.substr(match.length);
				//writeln('rest of line:'+JSON.stringify(line));

				// We are interested in our field match
				var sos = m.shift().split(';');
				//writeln('sos:'+JSON.stringify(sos));

				for (var num in sos) {
					switch (num) {
						// First value is the field name
						case '0':
							field = sos[num];
							break;

						// Second value is the length/type of the field, nnX nn=size in chars, T=type (lower case)
						case '1':
							var c = sos[num].match(/([0-9]+)([a-z])/);
							if (! c) {
								log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]);
								break;
							}

							//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]);
							fieldlen = c[1];
							fieldtype = c[2];
							break;

						// Third field is the char to to use
						case '2':
							fieldchar = sos[num];
							break;

						default:
							log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]);
					}
				}

				// If we are padding our field with a char, we need to add that back to line
				// @todo validate if this goes beyond our width (and if scrolling not enabled)
				if (fieldlen)
					line = fieldchar.repeat(fieldlen)+line;

				frame.input_fields.push({
					type: fieldtype,
					length: Number(fieldlen),
					char: fieldchar,
					name: field,
					attribute: JSON.parse(JSON.stringify(ansi)),
					x: Number(x+(xoffset !== undefined ? xoffset : 0)),
					y: Number(y+(yoffset !== undefined ? yoffset : 0)),
					value: '',
				});

				log(LOG_DEBUG,'input_field:'+JSON.stringify(frame.input_fields.last));
			}

			/* parse dynamic value field */
			// @todo remove the trailing ESC \ to end the field, just use a control code ie: ^E \x05 (Enquiry) or ^Z \x26 (Substitute)
			var m = line.match(/^\x1bX(([a-zA-Z._:^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/);
			if (m !== null) {
				// full string that matched
				match = m.shift();

				// thus, the rest of the line
				line = line.substr(match.length);
				//writeln('rest of line:'+JSON.stringify(line));

				// We are interested in our field match
				var df = m.shift().split(';');

				log(LOG_DEBUG,'- DF found at ['+x+'x'+y+'], Field: '+df[0]+', Length: '+df[1]+', Pad:'+df[2]);
				// If we are padding our field with a char, we need to add that back to line
				// @todo validate if this goes beyond our width (and if scrolling not enabled)
				line = (df[2] ? df[2] : '_').repeat(Math.abs(df[1]))+line;

				frame.dynamic_fields.push({
					name: df[0],
					length: df[1],
					pad: df[2],
					x: x+(xoffset !== undefined ? xoffset : 0),
					y: y+(yoffset !== undefined ? yoffset : 0),
					value: undefined,
				});
			}

			/* set character and attribute */
			var ch = line[0];
			line = line.substr(1);

			if (debug && (debug === y)) {
				writeln('y:'+y+', x:'+x+', ch:'+ch);
			}

			/* validate position */
			if (y < 0)
				y = 0;
			if (x < 0)
				x = 0;

			store(x,y,ch,undefined);
			x++;
		}

		// Each new line, we reset the attrs
		attr = undefined;
		y++;
	}

	return frame;

	function store(x,y,ch,attr) {
		/* set character and attribute */
		if (! frame.content[y+1])
			frame.content[y+1]=[];

		frame.content[y+1][x+1] = new Char(ch,attr,SESSION_EXT);
	}
}

load('ansitex/load/session.js');

// Our frame object
function SessionProtocol() {
	Session.apply(this,arguments);

	this.settings.MSG_SENDORNOT			= ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND';
	this.settings.MSG_LOGON				= ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN';
	this.settings.MSG_SENT				= ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE';
	this.settings.MSG_NOTSENT			= ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE';
	this.settings.ERR_NO_PARENT			= ascii(27)+'APARENT FRAME DOESNT EXIST';
	this.settings.ERR_NOT_IMPLEMENTED	= ascii(27)+'ANOT IMPLEMENTED YET?';
	this.settings.ERR_ROUTE				= ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08';
	this.settings.ERR_METHOD_NOT_EXIST	= ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08';
	this.settings.ACCESS_DENIED			= ascii(27)+'AACCESS DENIED.';
	this.settings.ALREADY_MEMBER		= ascii(27)+'AALREADY MEMBER OF CUG'
	this.settings.INACTIVITY			= ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...';
	this.settings.INACTIVE				= ascii(27)+'AINACTIVITY DISCONNECT';
	this.settings.NOACTION				= ascii(27)+'ANO ACTION PERFORMED';
	this.settings.BASESTAR				= ascii(27)+'B*';
	this.settings.INVALID_CODE			= ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN **';
	this.settings.TOKEN_EMAIL			= ascii(27)+'ATOKEN EMAILED TO YOU...';
	this.settings.TOKEN_SENT			= ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN';
	this.settings.INVALID_EMAIL			= ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00';
	this.settings.INVALID_UID			= ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00';
	this.settings.CANNOT_SEND_TOKEN		= ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00';
	this.settings.USER_EXISTS			= ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00';
	this.settings.USER_CREATE_ERROR		= ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00';
	this.settings.LOGIN_ERROR			= ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00';
	this.settings.CANCEL_MSG			= ascii(27)+'BPRESS 2 TO CANCEL';
	this.settings.SYS_ERROR				= ascii(27)+'ASYS ERR, TRY AGAIN OR TELL US ON *08';
	this.settings.LOADING				= ascii(27)+'Cloading...';
	this.settings.PROCESSING			= ESC+VIEWDATA_YELLOW+'processing...';

	var blp = 0;						// Length of data on the bottom line

	/**
	 * Set the attribute at the current position
	 */
	this.attr = function(field) {
		//NOOP - the terminal takes care of this
	}

	this.baselineClear = function(reposition) {
		msg = '';

		log(LOG_DEBUG,'- Clear Bottom Line ['+blp+'] - reposition ['+reposition+']');

		write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+
			((blp > msg.length)
				? (' '.repeat(blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : ''))
				: '')
		);

		blp = msg.length;
	}

	/**
	 * Send a message to the baseline.
	 *
	 * @param text
	 * @param reposition
	 */
	this.baselineSend = function(text,reposition) {
		var msg = this.getMessage(text);
		var x = this.strlen(msg);

		log(LOG_DEBUG,'- Bottom Line ['+msg+'] ('+x+') - reposition ['+reposition+'] BLP:'+blp);

		write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+
			((blp > x)
				? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : ''))
				: '')
		);

		blp = x;
	}

	/**
	 * Turn off the cursor
	 */
	this.cursorOff = function() {
		write_raw(VIEWDATA_COFF);
	}

	/**
	 * Turn on cursor
	 * @param x
	 * @param y
	 */
	this.cursorOn = function(x,y) {
		write_raw(VIEWDATA_CON);

		if (x && y)
			this.gotoxy(x,y);
	}

	// Field backspace, that leaves the field filler char
	this.fieldbs = function(char) {
		log(LOG_DEBUG,'- Field backspace with char:'+char);
		write_raw(VIEWDATA_LEFT+char+VIEWDATA_LEFT);
	}

	this.gotoxy = function(x,y) {
		log(LOG_DEBUG,'- Moving cursor to y:'+y+', x:'+x);

		// @todo This could be optimised to go the shortest route
		write_raw(VIEWDATA_HOME);

		if (x > 0)
			write_raw(VIEWDATA_RIGHT.repeat(x));

		if (y > 0)
			write_raw(VIEWDATA_DOWN.repeat(y));
	}

	this.strlen = function(str) {
		return str.replace(/\x1b/g,'').length;
	};

	this.qrcode = function(qr) {
		// Render the body
		var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5);
		var offset = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-1;

		for (var x = -1; x < qr.size; x=x+3) {
			var line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE;

			for (var y = -1; y < qr.size; y=y+2) {
				var char = 0;

				//TL
				char |= ((x===-1) || (y===-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0);
				//TR
				char |= ((x===-1) || (y === qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1);
				//ML
				char |= ((y===-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2);
				//MR
				char |= ((y === qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3);
				//BL
				char |= ((x===qr.size-2) || (y===-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4);
				//BR
				char |= ((x===qr.size-2) || (y === qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5);

				char += 0x20;
				if (char > 0x3f)
					char += 0x20;

				line += ascii(char);
			}

			// Render the right column
			if (y%2)
				line += '\x35';

			repeat_count = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-offset-(offset ? 1 : 2)-(y%2 === 1 ? 0 : 1);

			qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0);

			// To fix some terminals where moving right from col 40 doesnt advance to col 1 on the next line
			qrcode +=VIEWDATA_LEFT+VIEWDATA_CR+VIEWDATA_DOWN;
		}

		log(LOG_DEBUG,'WIDTH:'+this.settings.FRAME_WIDTH);
		log(LOG_DEBUG,'QR   :'+(Math.ceil(qr.size/2)+1));
		log(LOG_DEBUG,'OFF  :'+offset);
		log(LOG_DEBUG,'Y    :'+(y%2 ? 0 : 1));
		log(LOG_DEBUG,'X    :'+(x%3 ? 0 : 1));

		// Render the bottom
		if (x%3) {
			line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE;

			for (var y = 0; y < qr.size; y=y+2) {
				line += '\x23';
			}

			// Render the right column
			if (y%2 === 0) {
				line += '\x21';
			}

			qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0);
		}

		write_raw(qrcode);
	};

	/*
	this.save=function() {
		file = system.mods_dir+'ansitex/text/'+this.page+'.tex';
		w = new File(file);
		if (! w.open('w')) {
			log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page);
			exit(1);
		}

		w.write(JSON.stringify(this));
		w.close();

		log(LOG_DEBUG,'Saved file: '+this.page+'.tex');
	}
	*/
}

function videotex(data) {
	var output = '';
	//$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte);
	for (var i = 0; i < data.length; i++) {
		output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i));
	}
	return output;
}

SessionProtocol.prototype = Session.prototype;
SessionProtocol.prototype.constructor = SessionProtocol;