/*  Copyright (C) 2006 yopyop
    yopyop156@ifrance.com
    yopyop156.ifrance.com

    Copyright (C) 2006-2008 DeSmuME team

    This file is part of DeSmuME

    DeSmuME 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 2 of the License, or
    (at your option) any later version.

    DeSmuME 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 DeSmuME; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "GPU_osd.h"
#include "GPU.h"
#include "mem.h"
#include <string.h> //mem funcs
#include <stdarg.h> //va_start, etc
#include <sstream>
#include <stdio.h>
#include <time.h>
#include <glib.h>
#include "debug.h"

#include "aggdraw.h"
#include "movie.h"
#include "NDSSystem.h"
#include "mic.h"
#include "saves.h"

bool HudEditorMode = false;
OSDCLASS	*osd = NULL;
HudStruct Hud;

//contains a timer to be used for well-timed hud components
static s64 hudTimer;

static void SetHudDummy (HudCoordinates *hud)
{
	hud->x=666;
	hud->y=666;
}

static bool IsHudDummy (HudCoordinates *hud)
{
	return (hud->x == 666 && hud->y == 666);
}

template<typename T>
static T calcY(T y) // alters a GUI element y coordinate as necessary to obey swapScreens and singleScreen settings
{
	if(osd->singleScreen)
	{
		if(y >= 192)
			y -= 192;
		if(osd->swapScreens)
			y += 192;
	}
	else if(osd->swapScreens)
	{
		if(y >= 192)
			y -= 192;
		else
			y += 192;
	}
	return y;
}

void EditHud(s32 x, s32 y, HudStruct *hudstruct) {

	u32 i = 0;

	while (!IsHudDummy(&hudstruct->hud(i))) {
		HudCoordinates &hud = hudstruct->hud(i);

		//reset
		if(!hud.clicked) {
			hud.storedx=0;
			hud.storedy=0;
		}

		if((x >= hud.x && x <= hud.x + hud.xsize) && 
			(calcY(y) >= calcY(hud.y) && calcY(y) <= calcY(hud.y) + hud.ysize) && !hudstruct->clicked ) {

				hud.clicked=1;
				hud.storedx = x - hud.x;
				hud.storedy = y - hud.y;
		}

		if(hud.clicked) {
			hud.x = x - hud.storedx;
			hud.y = y - hud.storedy;
		}

		//sanity checks
		if(hud.x < 0)  hud.x = 0;
		if(hud.y < 0)  hud.y = 0;
		if(hud.x > 245)hud.x = 245; //margins
		if(hud.y > 384-16)hud.y = 384-16;

		if(hud.clicked)
		{
			hudstruct->clicked = true;
			break;//prevent items from grouping together
		}

		i++;
	}
}

void HudClickRelease(HudStruct *hudstruct) {

	u32 i = 0;

	while (!IsHudDummy(&hudstruct->hud(i))) {
		HudCoordinates &hud = hudstruct->hud(i);
		hud.clicked=0;
		i++;
	}

	hudstruct->clicked = false;
}

void HudStruct::reset()
{
	FpsDisplay.x=0;
	FpsDisplay.y=5;
	FpsDisplay.xsize=166;
	FpsDisplay.ysize=10;

	FrameCounter.x=0;
	FrameCounter.y=25;
	FrameCounter.xsize=60;
	FrameCounter.ysize=10;

	InputDisplay.x=0;
	InputDisplay.y=45;
	InputDisplay.xsize=220;
	InputDisplay.ysize=10;

	GraphicalInputDisplay.x=8;
	GraphicalInputDisplay.y=328;
	GraphicalInputDisplay.xsize=102;
	GraphicalInputDisplay.ysize=50;

	LagFrameCounter.x=0;
	LagFrameCounter.y=65;
	LagFrameCounter.xsize=30;
	LagFrameCounter.ysize=10;
	
	Microphone.x=0;
	Microphone.y=85;
	Microphone.xsize=20;
	Microphone.ysize=10;

	SavestateSlots.x = 8;
	SavestateSlots.y = 160;
	SavestateSlots.xsize = 240;
	SavestateSlots.ysize = 24;

	SetHudDummy(&Dummy);
	clicked = false;
}

static void joyFill(int n) {

	bool pressedForGame = NDS_getFinalUserInput().buttons.array[n];
	bool physicallyPressed = NDS_getRawUserInput().buttons.array[n];
	if(pressedForGame && physicallyPressed)
		aggDraw.hud->fillColor(0,0,0,255);
	else if(pressedForGame)
		aggDraw.hud->fillColor(255,0,0,255);
	else if(physicallyPressed)
		aggDraw.hud->fillColor(0,255,0,255);
	else
		aggDraw.hud->fillColor(255,255,255,255);
}

static void joyEllipse(double ex, double ey, int xc, int yc, int x, int y, double ratio, double rad, int button) {

	joyFill(button);
	aggDraw.hud->lineWidth(rad);
	aggDraw.hud->ellipse(x+((xc*ex)*ratio), y+((yc*ey)*ratio), rad*ratio, rad*ratio);
}

static void joyRoundedRect(double x1, double y1, int x2, int y2, int alpha1, int alpha2, int button)
{
	bool pressedForGame = NDS_getFinalUserInput().buttons.array[button];
	bool physicallyPressed = NDS_getRawUserInput().buttons.array[button];
	if(pressedForGame && physicallyPressed)
		aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(0,0,0,alpha1), agg::rgba8(0,0,0,alpha2));
	else if(pressedForGame)
		aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(255,0,0,alpha1), agg::rgba8(255,0,0,alpha2));
	else if(physicallyPressed)
		aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(0,255,0,alpha1), agg::rgba8(0,255,0,alpha2));
	else
		return; //aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(255,255,255,alpha1), agg::rgba8(255,255,255,alpha2));

	aggDraw.hud->roundedRect(x1,y1,x2,y2,1);
}


static void drawPad(double x, double y, double ratio) {

	// you might notice black/red/green colors used to show what buttons are pressed.
	// the logic is roughly:
	//    RED == PAST    (the button was held last frame)
	//  GREEN == FUTURE  (the button is physically held now)
	//  BLACK == PRESENT (the button was held last frame and is still physically held now)

	// aligning to odd half-pixel boundaries prevents agg2d from blurring thin straight lines
	x = floor(x) + 0.5;
	y = floor(calcY(y)) + 0.5;
	double xc = 41 - 0.5;
	double yc = 20 - 0.5;

	aggDraw.hud->lineColor(128,128,128,255);

	aggDraw.hud->fillLinearGradient(x, y, x+(xc*ratio), y+(yc*ratio), agg::rgba8(222,222,222,128), agg::rgba8(255,255,255,255));

	aggDraw.hud->roundedRect (x, y, floor(x+(xc*ratio))+0.5, floor(y+(yc*ratio))+0.5, 1);

	double screenLeft = x+(xc*.25*ratio);
	double screenTop = y+(yc*.1*ratio);
	double screenRight = x+(xc*.745*ratio);
	double screenBottom = y+(yc*.845*ratio);
	aggDraw.hud->fillLinearGradient(screenLeft, screenTop, screenRight, screenBottom, agg::rgba8(128,128,128,128), agg::rgba8(255,255,255,255));
	aggDraw.hud->roundedRect (screenLeft, screenTop, screenRight, screenBottom, 1);


	joyEllipse(.89,.45,xc,yc,x,y,ratio,1,6);//B
	joyEllipse(.89,.22,xc,yc,x,y,ratio,1,3);//X
	joyEllipse(.83,.34,xc,yc,x,y,ratio,1,4);//Y
	joyEllipse(.95,.34,xc,yc,x,y,ratio,1,5);//A
	joyEllipse(.82,.716,xc,yc,x,y,ratio,.5,7);//Start
	joyEllipse(.82,.842,xc,yc,x,y,ratio,.5,8);//Select


	double dpadPoints [][2] = {
		{.04,.33}, // top-left corner of left button
		{.08,.33},
		{.08,.24}, // top-left corner of up button
		{.13,.24}, // top-right corner of up button
		{.13,.33},
		{.17,.33}, // top-right corner of right button
		{.17,.43}, // bottom-right corner of right button
		{.13,.43},
		{.13,.516}, // bottom-right corner of down button
		{.08,.516}, // bottom-left corner of down button
		{.08,.43},
		{.04,.43}, // bottom-left corner of left button
	};
	static const int numdpadPoints = sizeof(dpadPoints)/sizeof(dpadPoints[0]);
	for(int i = 0; i < numdpadPoints; i++)
	{
		dpadPoints[i][0] = x+(xc*(dpadPoints[i][0]+.01)*ratio);
		dpadPoints[i][1] = y+(yc*(dpadPoints[i][1]+.00)*ratio);
	}

	// dpad outline
	aggDraw.hud->fillColor(255,255,255,200);
	aggDraw.hud->polygon((double*)dpadPoints, numdpadPoints);

	aggDraw.hud->noLine();

	// left
	joyRoundedRect(dpadPoints[0][0], dpadPoints[0][1], dpadPoints[7][0], dpadPoints[7][1], 255, 0, 11);
	
	// right
	joyRoundedRect(dpadPoints[1][0], dpadPoints[1][1], dpadPoints[6][0], dpadPoints[6][1], 0, 255, 12);

	// up
	joyRoundedRect(dpadPoints[2][0], dpadPoints[2][1], dpadPoints[7][0], dpadPoints[7][1], 255, 0, 9);
	
	// right
	joyRoundedRect(dpadPoints[1][0], dpadPoints[1][1], dpadPoints[8][0], dpadPoints[8][1], 0, 255, 10);

	// left shoulder
	joyRoundedRect(x+(xc*.00*ratio), y+(yc*.00*ratio), x+(xc*.15*ratio), y+(yc*.07*ratio), 255, 200, 2);

	// right shoulder
	joyRoundedRect(x+(xc*.85*ratio), y+(yc*.00*ratio), x+(xc*1.0*ratio), y+(yc*.07*ratio), 200, 255, 1);

	// lid...
	joyRoundedRect(x+(xc*.4*ratio), y+(yc*.96*ratio), x+(xc*0.6*ratio), y+(yc*1.0*ratio), 200, 200, 13);

	// touch pad
	{
		BOOL gameTouchOn = nds.isTouch;
		double gameTouchX = screenLeft+1 + (nds.touchX * 0.0625) * (screenRight - screenLeft - 2) / 256.0;
		double gameTouchY = screenTop+1 + (nds.touchY * 0.0625) * (screenBottom - screenTop - 2) / 192.0;
		bool physicalTouchOn = NDS_getRawUserInput().touch.isTouch;
		double physicalTouchX = screenLeft+1 + (NDS_getRawUserInput().touch.touchX * 0.0625) * (screenRight - screenLeft - 2) / 256.0;
		double physicalTouchY = screenTop+1 + (NDS_getRawUserInput().touch.touchY * 0.0625) * (screenBottom - screenTop - 2) / 192.0;
		if(gameTouchOn && physicalTouchOn && gameTouchX == physicalTouchX && gameTouchY == physicalTouchY)
		{
			aggDraw.hud->fillColor(0,0,0,255);
			aggDraw.hud->ellipse(gameTouchX, gameTouchY, ratio*0.37, ratio*0.37);
		}
		else
		{
			if(physicalTouchOn)
			{
				aggDraw.hud->fillColor(0,0,0,128);
				aggDraw.hud->ellipse(physicalTouchX, physicalTouchY, ratio*0.5, ratio*0.5);
				aggDraw.hud->fillColor(0,255,0,255);
				aggDraw.hud->ellipse(physicalTouchX, physicalTouchY, ratio*0.37, ratio*0.37);
			}
			if(gameTouchOn)
			{
				aggDraw.hud->fillColor(255,0,0,255);
				aggDraw.hud->ellipse(gameTouchX, gameTouchY, ratio*0.37, ratio*0.37);
			}
		}
	}
}


struct TouchInfo{
	u16 X;
	u16 Y;
};
static int touchalpha[8]= {31, 63, 95, 127, 159, 191, 223, 255};
static TouchInfo temptouch;
static const bool touchshadow = false;//true; // sorry, it's cool but also distracting and looks cleaner with it off. maybe if it drew line segments between touch points instead of isolated crosses...
static std::vector<TouchInfo> touch (8);

static void TextualInputDisplay() {

	// drawing the whole string at once looks ugly
	// (because of variable width font and the "shadow" appearing over blank space)
	// and can't give us the color-coded effects we want anyway (see drawPad for info)

	const UserButtons& gameButtons = NDS_getFinalUserInput().buttons;
	const UserButtons& physicalButtons = NDS_getRawUserInput().buttons;

	double x = Hud.InputDisplay.x;

	// from order FRLDUTSBAYXWEG where G is 0
	static const char* buttonChars = "<^>vABXYLRSsgf";
	static const int buttonIndex [14] = {11,9,12,10,5,6,3,4,2,1,7,8,0,13};
	for(int i = 0; i < 14; i++, x+=11.0)
	{
		bool pressedForGame = gameButtons.array[buttonIndex[i]];
		bool physicallyPressed = physicalButtons.array[buttonIndex[i]];
		if(pressedForGame && physicallyPressed)
			aggDraw.hud->lineColor(255,255,255,255);
		else if(pressedForGame)
			aggDraw.hud->lineColor(255,48,48,255);
		else if(physicallyPressed)
			aggDraw.hud->lineColor(0,192,0,255);
		else
			continue;

		// cast from char to std::string is a bit awkward
		std::string str(buttonChars+i, 2);
		str[1] = '\0';

		aggDraw.hud->renderTextDropshadowed(x, calcY(Hud.InputDisplay.y), str);
	}

	// touch pad
	{
		char str [32];
		bool gameTouchOn = nds.isTouch;
		int gameTouchX = nds.touchX >> 4;
		int gameTouchY = nds.touchY >> 4;
		bool physicalTouchOn = NDS_getRawUserInput().touch.isTouch;
		int physicalTouchX = NDS_getRawUserInput().touch.touchX >> 4;
		int physicalTouchY = NDS_getRawUserInput().touch.touchY >> 4;
		if(gameTouchOn && physicalTouchOn && gameTouchX == physicalTouchX && gameTouchY == physicalTouchY)
		{
			sprintf(str, "%d,%d", gameTouchX, gameTouchY);
			aggDraw.hud->lineColor(255,255,255,255);
			aggDraw.hud->renderTextDropshadowed(x, calcY(Hud.InputDisplay.y), str);
		}
		else
		{
			if(gameTouchOn)
			{
				sprintf(str, "%d,%d", gameTouchX, gameTouchY);
				aggDraw.hud->lineColor(255,48,48,255);
				aggDraw.hud->renderTextDropshadowed(x, calcY(Hud.InputDisplay.y)-(physicalTouchOn?8:0), str);
			}
			if(physicalTouchOn)
			{
				sprintf(str, "%d,%d", physicalTouchX, physicalTouchY);
				aggDraw.hud->lineColor(0,192,0,255);
				aggDraw.hud->renderTextDropshadowed(x, calcY(Hud.InputDisplay.y)+(gameTouchOn?8:0), str);
			}
		}
	}
}

static void TouchDisplay() {
	// note: calcY should not be used in this function.
	aggDraw.hud->lineWidth(1.0);

	temptouch.X = NDS_getRawUserInput().touch.touchX >> 4;
	temptouch.Y = NDS_getRawUserInput().touch.touchY >> 4;

	if(touchshadow) {

		touch.push_back(temptouch);
		if(touch.size() > 8) touch.erase(touch.begin());

		for (int i = 0; i < 8; i++) {
			temptouch = touch[i];
			if(temptouch.X != 0 || temptouch.Y != 0) {
				aggDraw.hud->lineColor(0, 255, 0, touchalpha[i]);
				aggDraw.hud->line(temptouch.X - 256, temptouch.Y + 192, temptouch.X + 256, temptouch.Y + 192); //horiz
				aggDraw.hud->line(temptouch.X, temptouch.Y - 256, temptouch.X, temptouch.Y + 384); //vert
				aggDraw.hud->fillColor(0, 0, 0, touchalpha[i]);
				aggDraw.hud->rectangle(temptouch.X-1, temptouch.Y + 192-1, temptouch.X+1, temptouch.Y + 192+1);
			}
		}
	}
	else
		if(NDS_getRawUserInput().touch.isTouch) {
			aggDraw.hud->lineColor(0, 255, 0, 128);
			aggDraw.hud->line(temptouch.X - 256, temptouch.Y + 192, temptouch.X + 256, temptouch.Y + 192); //horiz
			aggDraw.hud->line(temptouch.X, temptouch.Y - 256, temptouch.X, temptouch.Y + 384); //vert
		}

	if(nds.isTouch) {
		temptouch.X = nds.touchX >> 4;
		temptouch.Y = nds.touchY >> 4;
		aggDraw.hud->lineColor(255, 0, 0, 128);
		aggDraw.hud->line(temptouch.X - 256, temptouch.Y + 192, temptouch.X + 256, temptouch.Y + 192); //horiz
		aggDraw.hud->line(temptouch.X, temptouch.Y - 256, temptouch.X, temptouch.Y + 384); //vert
	}

}

static int previousslot = 0;
static char number[10];
static s64 slotTimer=0;

static void DrawStateSlots(){

	const int yloc = calcY(Hud.SavestateSlots.y); //160
	const int xloc = Hud.SavestateSlots.x; //8

	s64 fadecounter = 512 - (hudTimer-slotTimer)/4; //change constant to alter fade speed
	if(fadecounter < 1) fadecounter = 0;
	if(fadecounter>255) fadecounter = 255;

	int alpha = (int)fadecounter;
	if(HudEditorMode)
		alpha = 255;

	if(alpha!=0)
	{
		aggDraw.hud->lineWidth(1.0);
		aggDraw.hud->lineColor(0, 0, 0, alpha);
		aggDraw.hud->fillColor(255, 255, 255, alpha);

		for ( int i = 0, xpos=0; i < 10; xpos=xpos+24) {

			int yheight=0;

			aggDraw.hud->fillLinearGradient(xloc + xpos, yloc - yheight, xloc + 22 + xpos, yloc + 20 + yheight+20, agg::rgba8(100,200,255,alpha), agg::rgba8(255,255,255,0));

			if(lastSaveState == i) {
				yheight = 5;
				aggDraw.hud->fillLinearGradient(xloc + xpos, yloc - yheight, 22 + xloc + xpos, yloc + 20 + yheight+20, agg::rgba8(100,255,255,alpha), agg::rgba8(255,255,255,0));
			}

			aggDraw.hud->rectangle(xloc + xpos , yloc - yheight, xloc + 22 + xpos , yloc + 20 + yheight);
			snprintf(number, 10, "%d", i);
			aggDraw.hud->renderText(xloc + 1 + xpos + 4, yloc+4, std::string(number));
			i++;
		}
	}

	if(lastSaveState != previousslot)
		slotTimer = hudTimer;

	previousslot = lastSaveState;
}

static void DrawEditableElementIndicators()
{
	u32 i = 0;
	while (!IsHudDummy(&Hud.hud(i))) {
		HudCoordinates &hud = Hud.hud(i);
		aggDraw.hud->fillColor(0,0,0,0);
		aggDraw.hud->lineColor(0,0,0,64);
		aggDraw.hud->lineWidth(2.0);
		aggDraw.hud->rectangle(hud.x,calcY(hud.y),hud.x+hud.xsize+1.0,calcY(hud.y)+hud.ysize+1.0);
		aggDraw.hud->lineColor(255,hud.clicked?127:255,0,255);
		aggDraw.hud->lineWidth(1.0);
		aggDraw.hud->rectangle(hud.x-0.5,calcY(hud.y)-0.5,hud.x+hud.xsize+0.5,calcY(hud.y)+hud.ysize+0.5);
		i++;
	}
}


void DrawHUD()
{
	GTimeVal time;
	g_get_current_time(&time);
	hudTimer = ((s64)time.tv_sec * 1000) + ((s64)time.tv_usec/1000);

	if (HudEditorMode)
	{
		DrawEditableElementIndicators();
	}
	
	if (CommonSettings.hud.ShowInputDisplay) 
	{
		TextualInputDisplay();
		TouchDisplay();
	}

	if (CommonSettings.hud.FpsDisplay) 
	{
		osd->addFixed(Hud.FpsDisplay.x, Hud.FpsDisplay.y, "Fps:%02d/%02d (%02d%%)%s", Hud.fps, Hud.fps3d, Hud.arm9load, driver->EMU_IsEmulationPaused() ? " (paused)" : "");
	}

	if (CommonSettings.hud.FrameCounterDisplay) 
	{
		if(movieMode == MOVIEMODE_RECORD) 
			osd->addFixed(Hud.FrameCounter.x, Hud.FrameCounter.y, "%d",currFrameCounter);
		else if (movieMode == MOVIEMODE_PLAY)
			osd->addFixed(Hud.FrameCounter.x, Hud.FrameCounter.y, "%d/%d",currFrameCounter,currMovieData.records.size());
		else if (movieMode == MOVIEMODE_FINISHED)
			osd->addFixed(Hud.FrameCounter.x, Hud.FrameCounter.y, "%d/%d (finished)",currFrameCounter,currMovieData.records.size());
		else
			osd->addFixed(Hud.FrameCounter.x, Hud.FrameCounter.y, "%d (no movie)",currFrameCounter);
	}

	if (CommonSettings.hud.ShowLagFrameCounter) 
	{
		osd->addFixed(Hud.LagFrameCounter.x, Hud.LagFrameCounter.y, "%d",TotalLagFrames);
	}

	if (CommonSettings.hud.ShowGraphicalInputDisplay)
	{
		drawPad(Hud.GraphicalInputDisplay.x, Hud.GraphicalInputDisplay.y, 2.5);
	}

	#ifdef WIN32
	if (CommonSettings.hud.ShowMicrophone) 
	{
		osd->addFixed(Hud.Microphone.x, Hud.Microphone.y, "%03d [%07d]",MicDisplay, Hud.cpuloopIterationCount);
	}
	#endif

	DrawStateSlots();
}



OSDCLASS::OSDCLASS(u8 core)
{
	memset(name,0,7);

	mode=core;
	offset=0;

	lastLineText=0;
	lineText_x = 5;
	lineText_y = 120;
	lineText_color = AggColor(255, 255, 255);
	for (int i=0; i < OSD_MAX_LINES+1; i++)
	{
		lineText[i] = new char[1024];
		memset(lineText[i], 0, 1024);
		lineTimer[i] = 0;
		lineColor[i] = lineText_color;
	}

	rotAngle = 0;

	singleScreen = false;
	swapScreens = false;

	needUpdate = false;

	if (core==0) 
		strcpy(name,"Core A");
	else
	if (core==1)
		strcpy(name,"Core B");
	else
	{
		strcpy(name,"Main");
		mode=255;
	}

	//border(false);

	LOG("OSD_Init (%s)\n",name);
}

OSDCLASS::~OSDCLASS()
{
	LOG("OSD_Deinit (%s)\n",name);

	for (int i=0; i < OSD_MAX_LINES+1; i++)
	{
		if (lineText[i]) 
			delete [] lineText[i];
		lineText[i] = NULL;
	}
}

void OSDCLASS::setOffset(u16 ofs)
{
	offset=ofs;
}

void OSDCLASS::setRotate(u16 angle)
{
	rotAngle = angle;
}

void OSDCLASS::clear()
{
	needUpdate=false;
}

bool OSDCLASS::checkTimers()
{
	if (lastLineText == 0) return false;

	time_t	tmp_time = time(NULL);

	for (int i=0; i < lastLineText; i++)
	{
		if (tmp_time > (lineTimer[i] + OSD_TIMER_SECS) )
		{
			if (i < lastLineText)
			{
				for (int j=i; j < lastLineText; j++)
				{
					strcpy(lineText[j], lineText[j+1]);
					lineTimer[j] = lineTimer[j+1];
					lineColor[j] = lineColor[j+1];
				}
			}
			lineTimer[lastLineText] = 0;
			lastLineText--;
			if (lastLineText == 0) return false;
		}
	}
	return true;
}

void OSDCLASS::update()
{
	if ( (!needUpdate) && (!lastLineText) ) return;	// don't update if buffer empty (speed up)
	if (lastLineText)
	{
		if (checkTimers())
		{
			for (int i=0; i < lastLineText; i++)
			{
				aggDraw.hud->lineColor(lineColor[i]);
				aggDraw.hud->renderTextDropshadowed(lineText_x,lineText_y+(i*16),lineText[i]);
			}
		}
		else
		{
			if (!needUpdate) return;
		}
	}
}

void OSDCLASS::setListCoord(u16 x, u16 y)
{
	lineText_x = x;
	lineText_y = y;
}

void OSDCLASS::setLineColor(u8 r=255, u8 g=255, u8 b=255)
{
	lineText_color = AggColor(r,g,b);
}

void OSDCLASS::addLine(const char *fmt, ...)
{
	va_list list;

	if (lastLineText > OSD_MAX_LINES) lastLineText = OSD_MAX_LINES;
	if (lastLineText == OSD_MAX_LINES)	// full
	{
		lastLineText--;
		for (int j=0; j < lastLineText; j++)
		{
			strcpy(lineText[j], lineText[j+1]);
			lineTimer[j] = lineTimer[j+1];
			lineColor[j] = lineColor[j+1];
		}
	}

	va_start(list,fmt);
#if defined(_MSC_VER) || defined(__INTEL_COMPILER)
		_vsnprintf(lineText[lastLineText],1023,fmt,list);
#else
		vsnprintf(lineText[lastLineText],1023,fmt,list);
#endif
	va_end(list);
	lineColor[lastLineText] = lineText_color;
	lineTimer[lastLineText] = time(NULL);
	needUpdate = true;

	lastLineText++;
}

void OSDCLASS::addFixed(u16 x, u16 y, const char *fmt, ...)
{
	va_list list;
	char msg[1024];

	va_start(list,fmt);
	vsnprintf(msg,1023,fmt,list);
	va_end(list);

	aggDraw.hud->lineColor(255,255,255);
	aggDraw.hud->renderTextDropshadowed(x,calcY(y),msg);

	needUpdate = true;
}

void OSDCLASS::border(bool enabled)
{
	//render51.setTextBoxBorder(enabled);
}
