/*
 * print - versatile printing routines. See also: n_font.js for similar functionality
 *
 * print(x, y, msg, type, alignH, alignV) - print a message
 *
 * @param {integer} x Print at X
 * @param {integer} y Print at Y
 * @param {string} msg Text to print
 * @param {string} type String with font effects, Multiple options are posible. Or one summed number.
 *                      1 = 'S'hadow = print.fx.SHADOW (change print.color.shadow if you like)
 *                      2 = 'O'utlined = print.fx.OUTLINED
 *                      4 = 'B'old = print.fx.BOLD
 *                      8 = 'U'nderlined = print.fx.UNDERLINE
 *                     16 = 'I'talic = print.fx.ITALIC
 *                     32 = 'W' Zoom X-axis = print.fx.ZOOMX
 *                     64 = 'H' Zoom Y-axis = print.fx.ZOOMY
 *                    128 = 'R' Rotate = print.fx.ROTATE
 *                    256 = 'X' Skew X = print.fx.SKEWX
 *                    512 = 'Y' Skew Y = print.fx.SKEWY
 *                   1024 = '-' Strikethrough = print.fx.STRIKETHROUGH
 *                   2048 = '=' double Strikethrough = print.fx.DOUBLESTRIKETHROUGH
 *                   4096 = 'N' Nervous text = print.fx.NERVOUS (you can change the value of print.nervous=3 to make it shake harder)
 *
 *                      So Bold+Italic is: "BI", "IB", 20, print.fx.BOLD+print.fx.ITALIC or even "bi"
 *
 * @param {integer} alignH Horizontal Justification. -1/'left' (default), '0'/center', 1/'right'
 * @param {integer} alignV Vertical Justification. -1/'top' (default), 0/'center', 1/'bottom'
 *                         Alignment works like this: The (x,y) parameters given are by default at the top and left of the printed font.
 *                         To align something at the topright of the screen you set 'right' and 'top' (see examples below)
 *
 * the parameters x and y are optional. print() stores its last cursor position in print.pos.x and print.pos.y and uses those if no x and y are given.
 * It also stores where it started printing in print.X and print.Y, should you need it.
 *
 * Other functions:
 *
 * print.ln(x, y, msg, type, alignH, alignV) - same as print(x, y, msg+"\n", type, alignH, alignV)
 * print.addFont(font, fontname) - Add fonts we can then set with print.ml() and print.setFont();
 * print.setAngle(radians) - To change the italic skew (default: Math.PI/10)
 * print.setVSpace(space) - To change the autoprint interline space (default: 2)
 * print.tint(color) - To change the colormask of the font (default: no colormask)
 * print.setZoom(zoomfactorX, zoomfactorY) - Zoom factor (you also need to tell print() to print.fx.ZOOMX / 'W' and/or print.fx.ZOOMY / 'H')
 * print.setVSpace(pixels) - Space between two lines (default: 2) when using "\n" or print.ln()
 * print.setItalicAngle(angle) - Angle for Italics (default: Math.PI / 10)
 * print.setAngle(angle) - Angle for rotation. (Dont forget to also set the 'R' effect in your print statement)
 * print.setSkew(skewXpixels, skewYpixels) - skew the image.
 * print.ml(x, y, msg, type, alignH, alignV) - similar to print(), but the string is markup language.
 *         The following syntax is supported:
 *         <p> Paragraph - double linebreak
 *         <br /> Linebreak (similar to \n for print() )
 *         <#ABCDEF> A font color, in hex. Examples: white = <#FFFFFF>, red = <#FF0000>
 *         <@fontname> Changes font, you must load the font with print.addfont() first.
 *         <hn> Headers n=1 to 5. Font effects are defined in the object print.Hfx, for example: print.HFX.TYPE[0] contains the type for <h1>
 *         <^n> superscript n=1 to 9, makes the font move up in steps of quarter of the font height.
 *         <_n> subscript n=1 to 9, makes the font move down in steps of one quarter of the font height.
 *         <li> unnumbered listitem
 *         After this I stopped adding features... 
 *         note: align parameters are buggy and not fixable without a complete rewrite.
 *
 * Advanced:
 * print.setup() will create print.surface, a surface you can then re-use.
 * print.font the current font.
 * Most of the commands return print, so you can chain like so: print.setAngle(0.4).setVspace(1)(0,0)("This is my text\n.", "R");
 *
 * examples:
 *   print(0,0, "This is printed on the topleft in bold and italics\n", 'BI');
 *   print("This line is just below the first one and has a shadow.", 'S');
 *   print(" This is the continuation of the second line", 'S');
 *   print(GetScreenWidth()>>1,GetScreenHeight(), "This is plain text centered at the bottom", undefined, 0, 1);
 *   print(GetScreenWidth(), 0, "This is text is right-justified at the top", undefined, 1, -1);
 *
 */
function print(x, y, msg, type, alignH, alignV){

	// You can skip x and y:
	if(typeof x == 'string'){
		alignV = type;
		alignH = msg;
		type = y;
		msg = x;
		y = undefined;
		x = undefined;
	}

	// If you want to set the print cursor, use an empty msg string.
	if (msg == "" || msg === undefined){
		print.calcXY(x, y, print.font.getStringWidth(msg), print.font.getHeight(), alignH, alignV);
		return print;
	}

	// Replaced  type.match(/S/) by numbers:
	if(typeof type == 'string'){
		var i=type.length;
		var n=0;
		while(i--){
			n += print.fx[type[i]];
		}
		type = n;
	}
	var newline = msg[msg.length-1] == "\n"; 
	if(newline)
		msg = msg.slice(0,-1);

	// multiline string...
	if(msg.match(/\n/)){
		print(x,y, "");
		var A = msg.split(/\n/);
		var i;
		for (i=0;i<A.length;++i){
			print(A[i]+"\n", type, alignH, alignV);
		}
		return print;
	}

	// Optimize normal prints...
	if(!type){
		print.calcXY(x, y, print.font.getStringWidth(msg), print.font.getHeight(), alignH, alignV, newline)
		print.font.drawText(print.X, print.Y, msg);
		return print;
	}else if(type == print.fx.SHADOW){
		print.calcXY(x, y, print.font.getStringWidth(msg), print.font.getHeight(), alignH, alignV, newline)
		print.font.setColorMask(print.color.shadow);
		print.font.drawText(print.X+1, print.Y+1, msg);
		print.font.setColorMask(print.color.none);
		print.font.drawText(print.X, print.Y, msg);
		return print;
	}else if(type == print.fx.BOLD){
		print.calcXY(x, y, print.font.getStringWidth(msg), print.font.getHeight(), alignH, alignV, newline)
		print.font.drawText(print.X+1, print.Y, msg);
		print.font.drawText(print.X, print.Y, msg);
		return print;
	}

	// Else, the hard way:
	print.calcXY(x, y, print.font.getStringWidth(msg), print.font.getHeight(), alignH, alignV, newline)
	var x = print.X;
	var y = print.Y;
		
	print.setup(msg, type, alignH, alignV);

	if(type & print.fx.OUTLINED){
		--x; --y;
	}

	if(type & print.fx.NERVOUS){
		x += Math.random()*print.nervous - (print.nervous>>1);
		y += Math.random()*print.nervous - (print.nervous>>1);
	}


	// Easy blit, if possible
	if(type < 16){
		print.surface.blit(x,y);
		return print;
	}

	// Calculate the four corners of our image
	var x1 = x;
	var y1 = y;
	var x2 = x + print.W -1;
	var y2 = y ;
	var x3 = x2;
	var y3 = y  + print.H -1;
	var x4 = x;
	var y4 = y3;

	if(type & print.fx.ZOOMX){
		var factor = print.W*(print.zoomX-1);
		if(alignH !== undefined){
			if(alignH == 1 || alignH == 'right'){
				x1 -= factor;
				x4 -= factor;
			}else if (alignH == 0 || alignH == 'center'){
				x1 -= factor>>1;
				x2 += factor>>1;
				x3 += factor>>1;
				x4 -= factor>>1;
			}
		}else{
			x2 += factor;
			x3 += factor;
		}
	}
	if(type & print.fx.ZOOMY){
		var factor = print.H*(print.zoomY-1);
		if(alignV !== undefined){
			if(alignV == 1 || alignV == 'bottom'){
				y1 -= factor;
				y2 -= factor;
			}else if (alignV == 0 || alignV == 'center'){
				y1 -= factor>>1;
				y2 -= factor>>1;
				y3 += factor>>1;
				y4 += factor>>1;
			}
		}else{
			y3 += factor;
			y4 += factor;
		}
	}

	if(type & print.fx.ITALIC){
		print.calcSkew();
		x1 += print.skew;
		x2 += print.skew;
	}

	if(type & print.fx.SKEWX){
		x1 += print.skewX;
		x2 += print.skewX;
	}
	if(type & print.fx.SKEWY){
		y2 += print.skewY;
		y3 += print.skewY;
	}

	if(type & print.fx.ROTATE){
		var ox;
		var oy;
		if(alignV === undefined || alignV == -1 || alignV == 'top'){
			if(alignH === undefined || alignH == -1 || alignH == 'left'){
				ox = x1;
				oy = y1;
			}else if (alignH == 0 || alignH == 'center'){
				ox = (x1+x2)>>1;
				oy = (y1+y2)>>1;
			}else{ // if(alignH == 1 || alignH == 'right'){
				ox = x2;
				oy = y2;
			}
		}else if (alignV == 0 || alignV == 'center'){
			if(alignH === undefined || alignH == -1 || alignH == 'left'){
				ox = (x1+x4)>>1;
				oy = (y1+y4)>>1;
			}else if (alignH == 0 || alignH == 'center'){
				ox = (x1+x2+x3+x4)>>2;
				oy = (y1+y2+y3+y4)>>2;
			}else{ // if(alignH == 1 || alignH == 'right'){
				ox = (x2+x3)>>1;
				oy = (y2+y3)>>1;
			}
		}else{ // if(alignV == 1 || alignV == 'bottom'){
			if(alignH === undefined || alignH == -1 || alignH == 'left'){
				ox = x4;
				oy = y4;
			}else if (alignH == 0 || alignH == 'center'){
				ox = (x4+x3)>>1;
				oy = (y4+y3)>>1;
			}else{ // if(alignH == 1 || alignH == 'right'){
				ox = x3;
				oy = y3;
			}
			
		};

		// 2D Rotation http://en.wikipedia.org/wiki/Rotation_(mathematics) and ???
		// A point <x,y> can be rotated around the origin <0,0> by running it through the following equations to get the new point <x',y'> :
		// x' = cos(theta)*x - sin(theta)*y 
		// y' = sin(theta)*x + cos(theta)*y
		// where theta is the angle by which to rotate the point.
		// Why doesnt sphere have RotatePointX(origin_x, origin_y, x, y, angle) ?

		var Ca = Math.cos(print.angle);
		var Sa = Math.sin(print.angle);

		x1 = Ca*(x1-ox) - Sa*(y1-oy) + ox;
		y1 = Sa*(x1-ox) + Ca*(y1-oy) + oy;
		x2 = Ca*(x2-ox) - Sa*(y2-oy) + ox;
		y2 = Sa*(x2-ox) + Ca*(y2-oy) + oy;
		x3 = Ca*(x3-ox) - Sa*(y3-oy) + ox;
		y3 = Sa*(x3-ox) + Ca*(y3-oy) + oy;
		x4 = Ca*(x4-ox) - Sa*(y4-oy) + ox;
		y4 = Sa*(x4-ox) + Ca*(y4-oy) + oy;
	}
	// Print to screen
	print.surface.createImage().transformBlit( x1, y1, x2, y2, x3, y3, x4, y4);
	return print;
}

// Print Line. Same as printing the message with an "\n" at the end.
print.ln = function(x, y, msg, type, alignH, alignV){
	// You can skip x and y:
	if(typeof x == 'string'){
		alignV = type;
		alignH = msg;
		type = y;
		msg = x;
		y = undefined;
		x = undefined;
	}
	return print(x, y, (msg||"")+"\n", type, alignH, alignV);
}

print.oldcolor = new Array(); // used in print.ml()
print.oldfont = new Array(); // used in print.ml()
print.oldsituation = new Array(); // used in print.ml()
print.fonts = new Object(); // used in print.ml(), print.addFont() and print.setFont()


// Print Simple Markup Language. Caution: alignments not implemented correctly.
print.ml = function(x, y, msg, type, alignH, alignV){
	// You can skip x and y:
	if(typeof x == 'string'){
		alignV = type;
		alignH = msg;
		type = y;
		msg = x;
		y = undefined;
		x = undefined;
	}

	print(x, y, "");
	if(msg == "") return;
	msg = msg.replace("\n", "");
	var TYPE = type || 0;
	var msgArr = msg.split("<");
	var reverse = false;
	if(alignH==1){
		msgArr.reverse();
		reverse = true;
	}
	var tb, tbl, p, fx;
	for(tb=0, tbl = msgArr.length ; tb<tbl ; ++tb){
		if(msgArr[tb][0] == '/' && -1<(p=msgArr[tb].search(/>/)) ){
			fx = msgArr[tb].slice(1,p);
			if(print.fx[fx])
				TYPE ^= print.fx[fx];
			else if(fx == 'p')
				print(x, print.Y, "\n\n");
			else if(fx[0] == '#'){
				print.color.none = print.oldcolor.pop();
				print.font.setColorMask(print.color.none);
			}
			else if(fx[0] == '@'){
				var colormask = print.font.getColorMask();
				var oldfont = print.oldfont.pop();
				if(typeof(oldfont)=="object") print.font = oldfont;
				print.font.setColorMask(colormask);
			}
			else if(fx.match(/h[12345]/)){
				print.oldsituation.pop()();
				TYPE = print.TYPE;
				var h = parseInt(fx[1]) -1;
				if(h<3)
					print(x, print.Y, "\n\n");
				else
					print(x, print.Y, "\n");
			}
			else if(fx == "^" || fx == "_")
				print.oldsituation.pop()();

			msgArr[tb] = msgArr[tb].slice(p+1);
		}
		else if( -1<(p=msgArr[tb].search(/>/)) ){
			fx = msgArr[tb].slice(0,p);
			if(print.fx[fx])
				TYPE |= print.fx[fx];
			else if(fx == 'p')
				print(x, print.Y, "\n\n");
			else if(fx.match(/^br ?\/?/))
				print(x, print.Y, "\n");
			else if(fx[0] == '#'){
				print.oldcolor.push(print.font.getColorMask());
				print.font.setColorMask( CreateColor(
					parseInt(fx.slice(1,3), 16),
					parseInt(fx.slice(3,5), 16),
					parseInt(fx.slice(5,7), 16),
					fx.length>7 ? parseInt(fx.slice(7,9), 16) : 255
				));
				print.color.none = print.font.getColorMask();
			}
			else if(fx[0] == '@'){
				if(typeof(print.font)=="object") print.oldfont.push(print.font);
				print.setFont(fx.slice(1));
			}
			else if (fx.match(/h[12345]/)){
				print.oldsituation.push( Function("print.zoomX=" + print.zoomX + ";print.zoomY=" + print.zoomY + ";print.TYPE=" + TYPE));
				var h = parseInt(fx[1]) -1;
				print.zoomX = print.Hfx.zoomX[h];
				print.zoomY = print.Hfx.zoomY[h];
				TYPE = print.Hfx.TYPE[h];
				print(x, print.Y, "\n\n");
			}
			else if (fx.match(/\^[1-9]/)){
				print.oldsituation.push( Function("print.pos.y=" + print.pos.y) );
				print.pos.y -= parseInt(fx[1]) * (print.font.getHeight()>>2);
			}
			else if (fx.match(/\_[1-9]/)){
				print.oldsituation.push( Function("print.pos.y=" + print.pos.y) );
				print.pos.y += parseInt(fx[1]) * (print.font.getHeight()>>2);
			}
			else if (fx == 'li'){
				msgArr[tb] = msgArr[tb].replace(">", ">  *");
				msgArr[tb] += "\n";
			}


			msgArr[tb] = msgArr[tb].slice(p+1);	
		}

		// At least try fixing alignment a little...
		if(alignH == 1)
			print.pos.x = print.X;
		msgArr[tb] = msgArr[tb].replace("&gt;", ">").replace("&lt;", "<");
		print(msgArr[tb], TYPE, alignH, alignV);
	}
	return print;
}

// Font effects. MAYBE: OVERSCORE, 
print.fx = {
  SHADOW:1, OUTLINED:2, BOLD:4, UNDERLINE:8, ITALIC:16, ZOOMX:32, ZOOMY:64, ROTATE:128, SKEWX:256, SKEWY:512, STRIKETHROUGH:1024, DOUBLESTRIKETHROUGH:2048, NERVOUS:4096,
  S:1,      O:2,        B:4,    U:8,         I:16,      W:32,     H:64,     R:128,      X:256,     Y:512,    '-':1024,            '=':2048,                 N:4096,
  s:1,      o:2,        b:4,    u:8,         i:16,      w:32,     h:64,     r:128,      x:256,     y:512,                                                   n:4096,
}

// Html h1..h5 effects
print.Hfx = {
	zoomX:[2, 2, 1.5, 1.1, 1],
	zoomY:[2, 2, 1.5, 1.1, 1],
	TYPE:[
		print.fx.BOLD + print.fx.OUTLINED + print.fx.ZOOMX + print.fx.ZOOMY,
		print.fx.BOLD + print.fx.ZOOMX + print.fx.ZOOMY,
		print.fx.BOLD + print.fx.ZOOMX + print.fx.ZOOMY,
		print.fx.BOLD,
		print.fx.BOLD
	]
}

// How nervous is the text?
print.nervous = 3;

// Set the zoom factor
print.zoomX = 1;
print.zoomY = 1;
print.setZoom = function(zoomX, zoomY){
	if(zoomX!==undefined)
		print.zoomX = zoomX;
	if(zoomY!==undefined)
		print.zoomY = zoomY;
	return print;
}

// Internal function. Define our origin x,y depending on the alignments
print.calcXY = function(x, y, W, H , alignH, alignV, newline){
	// Define x and y
	if(x === undefined) x = print.pos.x;
	if(y === undefined) y = print.pos.y;

	// Save next x and y
	if(newline){
		print.pos.x = x;
		print.pos.y = y + H + print.vspace;
	}else{
		print.pos.x = x + (W||0); 
		print.pos.y = y;
	}

	// Prepare vertical and horizontal alignment
	if(alignH !== undefined){
		if(alignH == 1 || alignH == 'right')
			x -= W;
		else if (alignH == 0 || alignH == 'center')
			x -= W>>1;
	}
	if(alignV !== undefined){
		if(alignV == 1 || alignV == 'bottom')
			y -= H;
		else if (alignV == 0 || alignV == 'center')
			y -= H>>1;
	}
	print.X = x;
	print.Y = y;
	return print;
}

// Width and Height of our canvas (created in print.setup)
print.H = 0;
print.W = 0;

// Internal function. Create our surface, which we can blit.
print.setup = function(msg, type, alignH, alignV){																							
	// If we just calculated this same print statement, use the cached version
	if(print.previous == type+';'+msg)
		return;

	// calculate surface canvas size
	var cx = print.font.getStringWidth(msg);
	var cy = print.font.getHeight();
	var dx = 0;
	var dy = 0;

	if(type & print.fx.OUTLINED){
		cx+=2; cy+=2;
		dx+=2; dy+=2;
	}

	if(type & print.fx.SHADOW){
		cx+=2; cy+=2;
	}

	if(type & print.fx.BOLD){
		++cx;
		++dx;
	}

	// Create a canvas to print our font to
	print.createSurface(cx, cy);
	print.W = cx;
	print.H = cy;

	// Underlined
	if(type & print.fx.UNDERLINE)
		print.surface.line(dx, cy-1, dx+cx, cy-1, print.color.none);
	if(type & print.fx.UNDERLINE & print.fx.SHADOW)
		print.surface.line(dx+1, cy, dx+cx+1, cy, print.color.shadow);
	
	// Strikethrough
	if(type & print.fx.STRIKETHROUGH)
		print.surface.line(dx, dy+(cy>>1), dx+cx, dy+(cy>>1), print.color.none);

	if(type & print.fx.DOUBLESTRIKETHROUGH){
		print.surface.line(dx, dy+(cy>>1)-1, dx+cx, dy+(cy>>1)-1, print.color.none);
		print.surface.line(dx, dy+(cy>>1)+1, dx+cx, dy+(cy>>1)+1, print.color.none);
	}

	// Shadow
	if(type & print.fx.SHADOW){
		print.font.setColorMask(print.color.shadow);
		var i = dx+1;
		do{
			print.surface.drawText(print.font, i, dy+1, msg);
		}while(--i);
	}

	dx=0;
	dy=0;

	// Outline
	if(type & print.fx.OUTLINED){
		print.font.setColorMask(print.color.outline);
		var i,j;
		for(i=0;i<3;++i){
			for(j=0;j<3;++j){
				print.surface.drawText(print.font, i, j, msg);
			}
		}
		dx=1;
		dy=1;
	}

	// Draw our 'plain' text:
	print.font.setColorMask(print.color.none);
	print.surface.drawText(print.font, dx, dy, msg);

	// Bold
	if(type & print.fx.BOLD)
		print.surface.drawText(print.font, dx+1, dy, msg);

	// Set Cache
	print.previous = type+';'+msg;
	return print;
}

// Remember the cursor position
print.pos = { x:0, y:0 }

// Colors
print.color = {
	shadow: CreateColor(55,55,55, 155),
	outline: CreateColor(32,32,32, 200),
	mark: CreateColor(255,255,0, 255),
	none: CreateColor(255,255,255, 255),
	transparent: CreateColor(0,0,0, 0)
}

// Set your font colormask
print.tint = function(color){
	print.color.none = color;
	print.font.setColorMask(color);
	return print;
}

// Default font is defined for you
print.font = GetSystemFont();
print.fonts['systemfont'] = print.font;

// Change your font
print.setFont = function(fnt){
	if(fnt === undefined)
		fnt = 'systemfont';
	if(typeof fnt == 'string')
		fnt = print.fonts[fnt] || LoadFont(fnt);
	print.font = fnt;
	return print;
}

// Add fonts we can then set with print.ml() and print.setFont();
print.addFont = function(fnt,name,forcereload){
	if(typeof fnt == 'string' && (forcereload||!print.fonts[name||fnt]))
		print.fonts[name||fnt] = LoadFont(fnt);
	else if(typeof(fnt) == 'object')
		print.fonts[name||'_'] = fnt;
	return print;
}


// Space between two lines (in pixels)
print.vspace = 2;
print.setVSpace = function(vs){ 
	print.vspace = vs;
	return print;
}

// Internal function. Creates a canvas where we can print our text and font effects.
print.previous= "";
print.surface = undefined;
print.createSurface = function(w, h){
	return print.surface = CreateSurface(w, h, print.color.transparent);
}

// Angle for Italics, default:
print.italicAngle = Math.PI / 10;
print.setItalicAngle = function(italicAngle){ 
	print.italicAngle = italicAngle;
	return print;
}

// Internal Function. Skew for Italic angle (depends on font height)
print.skew = 0;
print.calcSkew = function(italicAngle){
	if(italicAngle)
		print.italicAngle = italicAngle;
	return print.skew = print.font.getHeight() * Math.tan(print.italicAngle);
}

// Angle for Rotation, default:
print.angle = 0;
print.setAngle = function(Angle){ 
	print.angle = Angle;
	return print;
}

// Skew pixels
print.skewX = 0;
print.skewY = 0;
print.setSkew = function(skewX, skewY){
	if(skewX !== undefined) print.skewX = skewX;
	if(skewY !== undefined) print.skewY = skewY;
	return print;
}
