/*
  Hatari - statusbar.c

  This file is distributed under the GNU General Public License, version 2
  or at your option any later version. Read the file gpl.txt for details.

  Code to draw statusbar area, floppy leds etc.

  Use like this:
  - Before screen surface is (re-)created Statusbar_SetHeight()
    has to be called with the new screen height. Add the returned
    value to screen height (zero means no statusbar).  After this,
    Statusbar_GetHeight() can be used to retrieve the statusbar size
  - After screen surface is (re-)created, call Statusbar_Init()
    to re-initialize / re-draw the statusbar
  - Call Statusbar_SetFloppyLed() to set floppy drive led ON/OFF,
    or call Statusbar_EnableHDLed() to enabled HD led for a while
  - Whenever screen is redrawn, call Statusbar_Update() to update
    statusbar contents and find out whether and what screen area
    needs to be updated (outside of screen locking)
  - If screen redraws can be partial, Statusbar_OverlayRestore()
    needs to be called before locking the screen for drawing and
    Statusbar_OverlayBackup() needs to be called after screen unlocking,
    but before calling Statusbar_Update().  These are needed for
    hiding the overlay drive led (= restoring the area that was below
    them before LED was shown) when drive leds are turned OFF.
  - If other information shown by Statusbar (TOS version etc) changes,
    call Statusbar_UpdateInfo()

  TODO:
  - re-calculate colors on each update to make sure they're
    correct in Falcon & TT 8-bit palette modes?
  - call Statusbar_AddMessage() from log.c?
*/
const char Statusbar_fileid[] = "Hatari statusbar.c";

#include <assert.h>
#include "main.h"
#include "configuration.h"
#include "screenSnapShot.h"
#include "sdlgui.h"
#include "statusbar.h"
#include "tos.h"
#include "screen.h"
#include "video.h"
#include "wavFormat.h"
#include "ymFormat.h"
#include "avi_record.h"
#include "vdi.h"
#include "fdc.h"
#include "stMemory.h"
#include "blitter.h"
#include "str.h"
#include "lilo.h"

#define DEBUG 0
#if DEBUG
# include <execinfo.h>
# define DEBUGPRINT(x) printf x
#else
# define DEBUGPRINT(x)
#endif

/* space needed for FDC information */
#define FDC_MSG_MAX_LEN 20

#define STATUSBAR_LINES 2
#define MAX_DRIVE_LEDS (DRIVE_LED_HD + 1)

/* whole statusbar area, for full updates */
static SDL_Rect FullRect;

/* whether drive leds should be ON and their previous shown state */
static struct {
	drive_led_t state;
	drive_led_t oldstate;
	Uint32 expire;	/* when to disable led, valid only if >0 && state=TRUE */
	int offset;	/* led x-pos on screen */
} Led[MAX_DRIVE_LEDS];


/* drive leds size & y-pos */
static SDL_Rect LedRect;

/* overlay led size & pos */
static SDL_Rect OverlayLedRect;

/* screen contents left under overlay led */
static SDL_Surface *OverlayUnderside;

static enum {
	OVERLAY_NONE,
	OVERLAY_DRAWN,
	OVERLAY_RESTORED
} nOverlayState;

static SDL_Rect RecLedRect;
static bool bOldRecording;

static SDL_Rect BltLedRect;
static int BltLed_lines_on;

/* led colors */
static Uint32 LedColor[ MAX_LED_STATE ];
static Uint32 BltColorOn, BltColorOff;
static Uint32 RecColorOn, RecColorOff;
static Uint32 GrayBg, LedColorBg;

/* needs to be enough for all messages, but <= MessageRect width / font width */
#define MAX_MESSAGE_LEN 63
typedef struct msg_item {
	struct msg_item *next;
	char msg[MAX_MESSAGE_LEN+1];
	Uint32 timeout;	/* msecs, zero=no timeout */
	Uint32 expire;  /* when to expire message */
	bool shown;
} msg_item_t;

static msg_item_t DefaultMessage;
static msg_item_t *MessageList = &DefaultMessage;
static SDL_Rect MessageRect;

/* rect for both frame skip value and fast forward indicator */
static SDL_Rect FrameSkipsRect;
static int nOldFrameSkips;
static int bOldFastForward;

static SDL_Rect FDCTextRect;

static SDL_Rect JoysticksTextRect;

/* screen height above statusbar and height of statusbar below screen */
static int ScreenHeight;
static int StatusbarHeight;


/*-----------------------------------------------------------------------*/
/**
 * Return statusbar height for given width and height
 */
int Statusbar_GetHeightForSize(int width, int height)
{
	int h = 0;
	/* Must arrive at same conclusion about font size as SDLGui_SetScreen(),
	 * and max size returned by this must correspond to STATUSBAR_MAX_HEIGHT
	 */
	if (ConfigureParams.Screen.bShowStatusbar)
	{
		/* smaller SDL GUI font height = 8, larger = 16 */
		h = 8;
		if (width >= 640 && height >= (400-2*h))
		{
			h *= 2;
		}
		h += 1+1;
		h *= STATUSBAR_LINES;
	}
	DEBUGPRINT(("Statusbar_GetHeightForSize(%d, %d) -> %d\n", width, height, h));
	return h;
}

/*-----------------------------------------------------------------------*/
/**
 * Set screen height used for statusbar height calculation.
 *
 * Return height of statusbar that should be added to the screen
 * height when screen is (re-)created, or zero if statusbar will
 * not be shown
 */
int Statusbar_SetHeight(int width, int height)
{
#if DEBUG
	/* find out from where the set height is called */
	void *addr[8];
	int count = backtrace(addr, sizeof(addr)/sizeof(*addr));
	backtrace_symbols_fd(addr, count, fileno(stderr));
#endif
	ScreenHeight = height;
	StatusbarHeight = Statusbar_GetHeightForSize(width, height);
	DEBUGPRINT(("Statusbar_SetHeight(%d, %d) -> %d\n", width, height, StatusbarHeight));
	return StatusbarHeight;
}

/*-----------------------------------------------------------------------*/
/**
 * Return height of statusbar set with Statusbar_SetHeight()
 */
int Statusbar_GetHeight(void)
{
	return StatusbarHeight;
}


/*-----------------------------------------------------------------------*/
/**
 * Enable HD drive led, it will be automatically disabled after a while.
 */
void Statusbar_EnableHDLed(drive_led_t state)
{
	/* leds are shown for 1/2 sec after enabling */
	Led[DRIVE_LED_HD].expire = SDL_GetTicks() + 1000/2;
	Led[DRIVE_LED_HD].state = state;
}

/*-----------------------------------------------------------------------*/
/**
 * Set given floppy drive led state, anything enabling led with this
 * needs also to take care of disabling it.
 */
void Statusbar_SetFloppyLed(drive_index_t drive, drive_led_t state)
{
	assert(drive == DRIVE_LED_A || drive == DRIVE_LED_B);
	Led[drive].state = state;
}


/*-----------------------------------------------------------------------*/
/**
 * Compute the number of lines to fill with color BltColorOn, depending
 * on the blitter's activity per VBL
 * return a value between 0 and max_h (included)
 */
static int Statusbar_BlitterGetLinesOn ( int max_h )
{
	double	res;

	res = ( max_h * Blitter_StatsGetRate() ) / 100.0;
	return ceil(res);
}


/*-----------------------------------------------------------------------*/
/**
 * Return a text corresponding to the status of each emulated joystick
 * - : off   J : real joystick   K : keyboard emulation
 */
static void Statusbar_JoysticksGetText ( char *buf )
{
	int i;

	for (i = 0; i < JOYSTICK_COUNT; i++)
	{
		switch (ConfigureParams.Joysticks.Joy[i].nJoystickMode)
		{
		case JOYSTICK_DISABLED:
			*buf++ = '-';
			break;
		case JOYSTICK_REALSTICK:
			*buf++ = 'J';
			break;
		case JOYSTICK_KEYBOARD:
			*buf++ = 'K';
			break;
		}
	}
	*buf = '\0';
}



/*-----------------------------------------------------------------------*/
/**
 * Set overlay led size/pos on given screen to internal Rect
 * and free previous resources.
 */
static void Statusbar_OverlayInit(const SDL_Surface *surf)
{
	int h;
	/* led size/pos needs to be re-calculated in case screen changed */
	h = surf->h / 50;
	OverlayLedRect.w = 2*h;
	OverlayLedRect.h = h;
	OverlayLedRect.x = surf->w - 5*h/2;
	OverlayLedRect.y = h/2;
	/* free previous restore surface if it's incompatible */
	if (OverlayUnderside && (
	    OverlayUnderside->w != OverlayLedRect.w ||
	    OverlayUnderside->h != OverlayLedRect.h ||
	    OverlayUnderside->format->BitsPerPixel != surf->format->BitsPerPixel))
	{
		SDL_FreeSurface(OverlayUnderside);
		OverlayUnderside = NULL;
	}
	nOverlayState = OVERLAY_NONE;
}

/*-----------------------------------------------------------------------*/
/**
 * (re-)initialize statusbar internal variables for given screen surface
 * (sizes&colors may need to be re-calculated for the new SDL surface)
 * and draw the statusbar background.
 */
void Statusbar_Init(SDL_Surface *surf)
{
	msg_item_t *item;
	SDL_Rect ledbox;
	int i, fontw, fonth, lineh, xoffset, yoffset;
	const char *text[MAX_DRIVE_LEDS] = { "A:", "B:", "HD:" };
	char FdcText[FDC_MSG_MAX_LEN];
	int FdcTextLen;
	char JoysticksText[JOYSTICK_COUNT+1];

	DEBUGPRINT(("Statusbar_Init()\n"));
	assert(surf);

	/* dark green and light green for leds themselves */
	LedColor[ LED_STATE_OFF ]	= SDL_MapRGB(surf->format, 0x00, 0x40, 0x00);
	LedColor[ LED_STATE_ON ]	= SDL_MapRGB(surf->format, 0x00, 0xc0, 0x00);
	LedColor[ LED_STATE_ON_BUSY ]	= SDL_MapRGB(surf->format, 0x00, 0xe0, 0x00);
	LedColorBg  = SDL_MapRGB(surf->format, 0x00, 0x00, 0x00);
	BltColorOff = SDL_MapRGB(surf->format, 0x40, 0x00, 0x00);
	BltColorOn  = SDL_MapRGB(surf->format, 0xe0, 0x00, 0x00);
	RecColorOff = SDL_MapRGB(surf->format, 0x40, 0x00, 0x00);
	RecColorOn  = SDL_MapRGB(surf->format, 0xe0, 0x00, 0x00);
	GrayBg      = SDL_MapRGB(surf->format, 0xc0, 0xc0, 0xc0);

	/* disable leds */
	for (i = 0; i < MAX_DRIVE_LEDS; i++)
	{
		Led[i].state = Led[i].oldstate = LED_STATE_OFF;
		Led[i].expire = 0;
	}
	Statusbar_OverlayInit(surf);
	
	/* disable statusbar if it doesn't fit to video mode */
	if (surf->h < ScreenHeight + StatusbarHeight)
	{
		StatusbarHeight = 0;
	}
	if (!StatusbarHeight)
	{
		DEBUGPRINT(("Doesn't fit <- Statusbar_Init()\n"));
		return;
	}

	/* prepare fonts */
	SDLGui_Init();
	SDLGui_SetScreen(surf);
	SDLGui_GetFontSize(&fontw, &fonth);

	/* video mode didn't match, need to recalculate sizes */
	lineh = 1+fonth+1;
	if (surf->h > ScreenHeight + StatusbarHeight)
	{
		StatusbarHeight = STATUSBAR_LINES*lineh;
		/* actually statusbar vertical offset */
		ScreenHeight = surf->h - StatusbarHeight;
	}
	else
	{
		assert(STATUSBAR_LINES*lineh <= StatusbarHeight);
	}

	/* draw statusbar background gray so that text shows */
	FullRect.x = 0;
	FullRect.y = surf->h - StatusbarHeight;
	FullRect.w = surf->w;
	FullRect.h = StatusbarHeight;
	SDL_FillRect(surf, &FullRect, GrayBg);

	/* initialize messages (first row) */
	MessageRect.x = fontw;
	MessageRect.y = ScreenHeight + lineh/2 - fonth/2;
	MessageRect.w = surf->w - fontw;
	MessageRect.h = fonth;
	for (item = MessageList; item; item = item->next)
	{
		item->shown = false;
	}

	/* indicator leds size (second row) */
	LedRect.w = fonth/2;
	LedRect.h = fonth - 4;
	LedRect.y = ScreenHeight + lineh + lineh/2 - LedRect.h/2;

	/* black box for the leds */
	ledbox = LedRect;
	ledbox.y -= 1;
	ledbox.w += 2;
	ledbox.h += 2;

	xoffset = fontw;
	yoffset = ScreenHeight + lineh + lineh/2 - fonth/2;

	/* draw led texts and boxes + calculate box offsets */
	for (i = 0; i < MAX_DRIVE_LEDS; i++)
	{
		SDLGui_Text(xoffset, yoffset, text[i]);
		xoffset += strlen(text[i]) * fontw;
		xoffset += fontw/2;

		ledbox.x = xoffset - 1;
		SDL_FillRect(surf, &ledbox, LedColorBg);

		LedRect.x = xoffset;
		SDL_FillRect(surf, &LedRect, LedColor[ LED_STATE_OFF ]);

		Led[i].offset = xoffset;
		xoffset += LedRect.w + fontw;
	}

	/* print FDC's info */
	FDCTextRect.x = xoffset;
	FDCTextRect.y = yoffset;
	FdcTextLen = FDC_Get_Statusbar_Text(FdcText, sizeof(FdcText));
	SDLGui_Text(FDCTextRect.x, FDCTextRect.y, FdcText);
	FDCTextRect.w = FdcTextLen * fontw + fontw/2;
	FDCTextRect.h = fonth;
	xoffset += FDCTextRect.w;

	/* print joysticks' info */
	xoffset += 2*fontw;
	JoysticksTextRect.x = xoffset;
	JoysticksTextRect.y = yoffset;
	Statusbar_JoysticksGetText(JoysticksText);
	SDLGui_Text(JoysticksTextRect.x, JoysticksTextRect.y, JoysticksText);
	JoysticksTextRect.w = JOYSTICK_COUNT * fontw + fontw/2;
	JoysticksTextRect.h = fonth;
	// xoffset += JoysticksTextRect.w;

	/* draw frameskip on the right */
	FrameSkipsRect.x = surf->w - 21*fontw;
	FrameSkipsRect.y = yoffset;
	SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, "FS:");
	FrameSkipsRect.x += 3 * fontw + fontw/2;
	FrameSkipsRect.w = 4 * fontw;
	FrameSkipsRect.h = fonth;

	if(ConfigureParams.System.bFastForward)
	{
		SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, "0 >>");
	}
	else
	{
		SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, "0");
	}

	nOldFrameSkips = 0;

	/* draw blitter led box on the right */
	BltLedRect = LedRect;
	BltLedRect.x = surf->w - 7*fontw - BltLedRect.w;
	ledbox.x = BltLedRect.x - 1;
	SDLGui_Text(ledbox.x - 4*fontw - fontw/2, yoffset, "BLT:");
	SDL_FillRect(surf, &ledbox, LedColorBg);
	SDL_FillRect(surf, &BltLedRect, BltColorOff);
	BltLed_lines_on = 0;

	/* draw recording led box on the right */
	RecLedRect = LedRect;
	RecLedRect.x = surf->w - fontw - RecLedRect.w;
	ledbox.x = RecLedRect.x - 1;
	SDLGui_Text(ledbox.x - 4*fontw - fontw/2, yoffset, "REC:");
	SDL_FillRect(surf, &ledbox, LedColorBg);
	SDL_FillRect(surf, &RecLedRect, RecColorOff);
	bOldRecording = false;

	/* and blit statusbar on screen */
	Screen_UpdateRects(surf, 1, &FullRect);
	DEBUGPRINT(("Drawn <- Statusbar_Init()\n"));
}


/*-----------------------------------------------------------------------*/
/**
 * Queue new statusbar message 'msg' to be shown for 'msecs' milliseconds
 */
void Statusbar_AddMessage(const char *msg, Uint32 msecs)
{
	msg_item_t *item;

	if (!ConfigureParams.Screen.bShowStatusbar)
	{
		/* no sense in queuing messages that aren't shown */
		return;
	}
	item = calloc(1, sizeof(msg_item_t));
	assert(item);

	item->next = MessageList;
	MessageList = item;

	Str_Copy(item->msg, msg, sizeof(item->msg));
	DEBUGPRINT(("Add message: '%s'\n", item->msg));

	if (msecs)
	{
		item->timeout = msecs;
	}
	else
	{
		/* show items by default for 2.5 secs */
		item->timeout = 2500;
	}
	item->shown = false;
}

/*-----------------------------------------------------------------------*/
/**
 * Write given 'more' string to 'buffer' and return new end of 'buffer'
 */
static char *Statusbar_AddString(char *buffer, const char *more)
{
	if (!more)
		return buffer;
	while (*more)
		*buffer++ = *more++;
	return buffer;
}

/*-----------------------------------------------------------------------*/
/**
 * Retrieve/update default statusbar information
 */
void Statusbar_UpdateInfo(void)
{
	int size;
	char buffer[200];				/* large enough for any message */
	char *end;

	end = buffer;

	/* CPU MHz */
	if (ConfigureParams.System.nCpuFreq > 9)
	{
		*end++ = '0' + ConfigureParams.System.nCpuFreq / 10;
	}
	*end++ = '0' + ConfigureParams.System.nCpuFreq % 10;
	end = Statusbar_AddString(end, "MHz");

	/* CPU type */
	if(ConfigureParams.System.nCpuLevel > 0)
	{
		*end++ = '/';
		*end++ = '0';
		if ( ConfigureParams.System.nCpuLevel == 5 )	/* Special case : 68060 has nCpuLevel=5 */
			*end++ = '0' + 6;
		else
			*end++ = '0' + ConfigureParams.System.nCpuLevel % 10;
		*end++ = '0';
	}

	const char *mode = NULL;
	bool cache = ConfigureParams.System.bCpuDataCache && ConfigureParams.System.nCpuLevel > 2;
	/* Prefetch / cycle exact mode and data cache? */
	if ( ConfigureParams.System.bCycleExactCpu )
		mode = cache ? "(CED)" : "(CE)";
	else if ( ConfigureParams.System.bCompatibleCpu )
		mode = cache ? "(PFD)" : "(PF)";
	else if (cache)
		mode = "(D)";
	end = Statusbar_AddString(end, mode);

	/* additional WinUAE CPU/FPU info */
	*end++ = '/';
	switch (ConfigureParams.System.n_FPUType)
	{
	case FPU_68881:
		end = Statusbar_AddString(end, "68881");
		break;
	case FPU_68882:
		end = Statusbar_AddString(end, "68882");
		break;
	case FPU_CPU:
		end = ( ConfigureParams.System.nCpuLevel == 5 ? Statusbar_AddString(end, "060") : Statusbar_AddString(end, "040") );
		break;
	default:
		*end++ = '-';
	}
	if (ConfigureParams.System.bSoftFloatFPU && ConfigureParams.System.n_FPUType != FPU_NONE)
	{
		end = Statusbar_AddString(end, "(SF)");
	}
	if (ConfigureParams.System.bMMU)
	{
		end = Statusbar_AddString(end, "/MMU");
	}

	/* amount of memory in MB */
	*end++ = ' ';
	size = ConfigureParams.Memory.STRamSize_KB;
	end += sprintf(end, "%d", size / 1024);
	if ( size % 1024 == 256 )
		end += sprintf(end, ".25");
	else if ( size % 1024 == 512 )
		end += sprintf(end, ".5");

	if (TTmemory && ConfigureParams.Memory.TTRamSize_KB)
	{
		end += sprintf(end, "/%i", ConfigureParams.Memory.TTRamSize_KB/1024);
	}
	end = Statusbar_AddString(end, "MB ");

	/* machine type */
	switch (ConfigureParams.System.nMachineType)
	{
	case MACHINE_ST:
		end = Statusbar_AddString(end, "ST(");
		end = Statusbar_AddString(end, Video_GetTimings_Name());
		*end++ = ')';
		break;
	case MACHINE_MEGA_ST:
		end = Statusbar_AddString(end, "MegaST");
		break;
	case MACHINE_STE:
		end = Statusbar_AddString(end, "STE");
		break;
	case MACHINE_MEGA_STE:
		end = Statusbar_AddString(end, "MegaSTE");
		break;
	case MACHINE_TT:
		end = Statusbar_AddString(end, "TT");
		break;
	case MACHINE_FALCON:
		end = Statusbar_AddString(end, "Falcon");
		break;
	default:
		end = Statusbar_AddString(end, "???");
	}

	/* TOS type/version */
	end = Statusbar_AddString(end, ", ");
	if (bIsEmuTOS)
	{
		if (EmuTosVersion > 0)
		{
			char str[20];
			snprintf(str, sizeof(str), "EmuTOS %d.%d.%d",
			         EmuTosVersion >> 24, (EmuTosVersion >> 16) & 0xff,
			         (EmuTosVersion >> 8) & 0xff);
			end = Statusbar_AddString(end, str);
			if (EmuTosVersion & 0xff)
				end = Statusbar_AddString(end, "+");
		}
		else
		{
			end = Statusbar_AddString(end, "EmuTOS");
		}
	}
	else if (bUseLilo)
	{
		end = Statusbar_AddString(end, "Linux");
	}
	else
	{
		end = Statusbar_AddString(end, "TOS ");
		*end++ = '0' + ((TosVersion & 0xf00) >> 8);
		*end++ = '.';
		*end++ = '0' + ((TosVersion & 0xf0) >> 4);
		*end++ = '0' + (TosVersion & 0xf);
	}

	/* monitor type */
	end = Statusbar_AddString(end, ", ");
	if (bUseVDIRes)
	{
		end = Statusbar_AddString(end, "VDI");
	}
	else
	{
		switch (ConfigureParams.Screen.nMonitorType)
		{
		case MONITOR_TYPE_MONO:
			end = Statusbar_AddString(end, "MONO");
			break;
		case MONITOR_TYPE_RGB:
			end = Statusbar_AddString(end, "RGB");
			break;
		case MONITOR_TYPE_VGA:
			/* There were no VGA monitors for the ST/STE */
			if (Config_IsMachineST() || Config_IsMachineSTE())
				end = Statusbar_AddString(end, "RGB");
			else
				end = Statusbar_AddString(end, "VGA");
			break;
		case MONITOR_TYPE_TV:
			end = Statusbar_AddString(end, "TV");
			break;
		default:
			*end++ = '?';
		}
		end += sprintf(end, " %d Hz" , nScreenRefreshRate);
	}

	*end = '\0';

	Str_Copy(DefaultMessage.msg, buffer, MAX_MESSAGE_LEN);
	DEBUGPRINT(("Set default message: '%s'\n", DefaultMessage.msg));
	/* make sure default message gets (re-)drawn when next checked */
	DefaultMessage.shown = false;
}

/*-----------------------------------------------------------------------*/
/**
 * Draw 'msg' centered to the message area
 */
static SDL_Rect* Statusbar_DrawMessage(SDL_Surface *surf, const char *msg)
{
	int fontw, fonth, offset;
	SDL_FillRect(surf, &MessageRect, GrayBg);
	if (*msg)
	{
		SDLGui_GetFontSize(&fontw, &fonth);
		offset = (MessageRect.w - strlen(msg) * fontw) / 2;
		SDLGui_Text(MessageRect.x + offset, MessageRect.y, msg);
	}
	DEBUGPRINT(("Draw message: '%s'\n", msg));
	return &MessageRect;
}

/*-----------------------------------------------------------------------*/
/**
 * If message's not shown, show it.  If message's timed out,
 * remove it and show next one.
 * 
 * Return updated area, or NULL if nothing drawn
 */
static SDL_Rect* Statusbar_ShowMessage(SDL_Surface *surf, Uint32 ticks)
{
	msg_item_t *next;

	if (MessageList->shown)
	{
		if (!MessageList->expire)
		{
			/* last/default message newer expires */
			return NULL;
		}
		if (MessageList->expire > ticks)
		{
			/* not timed out yet */
			return NULL;
		}
		assert(MessageList->next); /* last message shouldn't end here */
		next = MessageList->next;
		free(MessageList);
		/* show next */
		MessageList = next;
	}
	/* not shown yet, show */
	MessageList->shown = true;
	if (MessageList->timeout && !MessageList->expire)
	{
		MessageList->expire = ticks + MessageList->timeout;
	}
	return Statusbar_DrawMessage(surf, MessageList->msg);
}


/*-----------------------------------------------------------------------*/
/**
 * Save the area that will be left under overlay led
 */
void Statusbar_OverlayBackup(SDL_Surface *surf)
{
	if ((StatusbarHeight && ConfigureParams.Screen.bShowStatusbar)
	    || !ConfigureParams.Screen.bShowDriveLed)
	{
		/* overlay not used with statusbar */
		return;
	}
	assert(surf);
	if (!OverlayUnderside)
	{
		SDL_Surface *bak;
		SDL_PixelFormat *fmt = surf->format;
		bak = SDL_CreateRGBSurface(surf->flags,
					   OverlayLedRect.w, OverlayLedRect.h,
					   fmt->BitsPerPixel,
					   fmt->Rmask, fmt->Gmask, fmt->Bmask,
					   fmt->Amask);
		assert(bak);
		OverlayUnderside = bak;
	}
	SDL_BlitSurface(surf, &OverlayLedRect, OverlayUnderside, NULL);
}

/*-----------------------------------------------------------------------*/
/**
 * Restore the area left under overlay led
 * 
 * State machine for overlay led handling will return from
 * Statusbar_Update() call the area that is restored (if any)
 */
void Statusbar_OverlayRestore(SDL_Surface *surf)
{
	if ((StatusbarHeight && ConfigureParams.Screen.bShowStatusbar)
	    || !ConfigureParams.Screen.bShowDriveLed)
	{
		/* overlay not used with statusbar */
		return;
	}
	if (nOverlayState == OVERLAY_DRAWN && OverlayUnderside)
	{
		assert(surf);
		SDL_BlitSurface(OverlayUnderside, NULL, surf, &OverlayLedRect);
		/* this will make the draw function to update this the screen */
		nOverlayState = OVERLAY_RESTORED;
	}
}

/*-----------------------------------------------------------------------*/
/**
 * Draw overlay led
 */
static void Statusbar_OverlayDrawLed(SDL_Surface *surf, Uint32 color)
{
	SDL_Rect rect;
	if (nOverlayState == OVERLAY_DRAWN)
	{
		/* some led already drawn */
		return;
	}
	nOverlayState = OVERLAY_DRAWN;

	/* enabled led with border */
	rect = OverlayLedRect;
	rect.x += 1;
	rect.y += 1;
	rect.w -= 2;
	rect.h -= 2;
	SDL_FillRect(surf, &OverlayLedRect, LedColorBg);
	SDL_FillRect(surf, &rect, color);
}

/*-----------------------------------------------------------------------*/
/**
 * Draw overlay led onto screen surface if any drives are enabled.
 * 
 * Return updated area, or NULL if nothing drawn
 */
static SDL_Rect* Statusbar_OverlayDraw(SDL_Surface *surf)
{
	Uint32 currentticks = SDL_GetTicks();
	int i;

	if (bRecordingYM || bRecordingWav || Avi_AreWeRecording())
	{
		Statusbar_OverlayDrawLed(surf, RecColorOn);
	}
	for (i = 0; i < MAX_DRIVE_LEDS; i++)
	{
		if (Led[i].state)
		{
			if (Led[i].expire && Led[i].expire < currentticks)
			{
				Led[i].state = LED_STATE_OFF;
				continue;
			}
			Statusbar_OverlayDrawLed(surf, LedColor[ Led[i].state ]);
			break;
		}
	}
	/* possible state transitions:
	 *   NONE -> DRAWN -> RESTORED -> DRAWN -> RESTORED -> NONE
	 * Other than NONE state needs to be updated on screen
	 */
	switch (nOverlayState)
	{
	case OVERLAY_RESTORED:
		nOverlayState = OVERLAY_NONE;
		/* fall through */
	case OVERLAY_DRAWN:
		DEBUGPRINT(("Overlay LED = %s\n", nOverlayState==OVERLAY_DRAWN?"ON":"OFF"));
		return &OverlayLedRect;
	case OVERLAY_NONE:
		break;
	}
	return NULL;
}


/*-----------------------------------------------------------------------*/
/**
 * Update statusbar information (leds etc) if/when needed.
 * 
 * May not be called when screen is locked (SDL limitation).
 * 
 * Return updated area, or NULL if nothing is drawn.
 */
SDL_Rect* Statusbar_Update(SDL_Surface *surf, bool do_update)
{
	static char FdcOld[FDC_MSG_MAX_LEN] = "";
	char FdcNew[FDC_MSG_MAX_LEN];
	static char JoysticksOld[JOYSTICK_COUNT+1] = "";
	char JoysticksNew[JOYSTICK_COUNT+1];
	Uint32 color, currentticks;
	static SDL_Rect rect;
	SDL_Rect *last_rect;
	int i, updates;
	int BltLed_lines_on_new;

	/* Don't update anything on screen if video output is disabled */
	if ( ConfigureParams.Screen.DisableVideo )
		return NULL;

	assert(surf);
	if (!(StatusbarHeight && ConfigureParams.Screen.bShowStatusbar))
	{
		last_rect = NULL;
		/* not enabled (anymore), show overlay led instead? */
		if (ConfigureParams.Screen.bShowDriveLed)
		{
			last_rect = Statusbar_OverlayDraw(surf);
			if (do_update && last_rect)
			{
				Screen_UpdateRects(surf, 1, last_rect);
				last_rect = NULL;
			}
		}
		return last_rect;
	}

	/* Statusbar_Init() not called before this? */
#if DEBUG
	if (surf->h != ScreenHeight + StatusbarHeight)
	{
		printf("DEBUG: %d != %d + %d\n", surf->h, ScreenHeight, StatusbarHeight);
	}
#endif
	assert(surf->h == ScreenHeight + StatusbarHeight);

	currentticks = SDL_GetTicks();
	last_rect = Statusbar_ShowMessage(surf, currentticks);
	updates = last_rect ? 1 : 0;

	rect = LedRect;
	for (i = 0; i < MAX_DRIVE_LEDS; i++)
	{
		if (Led[i].expire && Led[i].expire < currentticks)
		{
			Led[i].state = LED_STATE_OFF;
		}
		if (Led[i].state == Led[i].oldstate)
		{
			continue;
		}
		Led[i].oldstate = Led[i].state;
		color = LedColor[ Led[i].state ];
		rect.x = Led[i].offset;
		SDL_FillRect(surf, &rect, color);
		DEBUGPRINT(("LED[%d] = %d\n", i, Led[i].state));
		last_rect = &rect;
		updates++;
	}

	FDC_Get_Statusbar_Text(FdcNew, sizeof(FdcNew));
	if (strcmp(FdcNew, FdcOld))
	{
		strcpy(FdcOld, FdcNew);
		SDL_FillRect(surf, &FDCTextRect, GrayBg);
		SDLGui_Text(FDCTextRect.x, FDCTextRect.y, FdcNew);
		last_rect = &FDCTextRect;
		updates++;
	}

	/* joysticks' type */
	Statusbar_JoysticksGetText(JoysticksNew);
	if (strcmp(JoysticksNew, JoysticksOld))
	{
		strcpy(JoysticksOld, JoysticksNew);
		SDL_FillRect(surf, &JoysticksTextRect, GrayBg);
		SDLGui_Text(JoysticksTextRect.x, JoysticksTextRect.y, JoysticksNew);
		last_rect = &JoysticksTextRect;
		updates++;
	}

	if (nOldFrameSkips != nFrameSkips ||
	    bOldFastForward != ConfigureParams.System.bFastForward)
	{
		char fscount[5];
		int end = 2;

		nOldFrameSkips = nFrameSkips;
		bOldFastForward = ConfigureParams.System.bFastForward;

		if (nFrameSkips < 10)
			fscount[0] = '0' + nFrameSkips;
		else
			fscount[0] = 'X';
		fscount[1] = ' ';
		if(ConfigureParams.System.bFastForward)
		{
			fscount[2] = '>';
			fscount[3] = '>';
			end = 4;
		}
		fscount[end] = '\0';

		SDL_FillRect(surf, &FrameSkipsRect, GrayBg);
		SDLGui_Text(FrameSkipsRect.x, FrameSkipsRect.y, fscount);
		DEBUGPRINT(("FS = %s\n", fscount));
		last_rect = &FrameSkipsRect;
		updates++;
	}

	/* Blitter : draw 'BltLed_lines_on_new' lines with color BltColorOn */
	/* and the rest with color BltColorOff. This gives some kind of vu-meter */
	BltLed_lines_on_new = Statusbar_BlitterGetLinesOn( BltLedRect.h );
	if (BltLed_lines_on_new != BltLed_lines_on)
	{
		BltLed_lines_on = BltLed_lines_on_new;
		if ( BltLed_lines_on > 0 )
		{
			rect = BltLedRect;
			rect.y += rect.h - BltLed_lines_on;
			rect.h = BltLed_lines_on;
			SDL_FillRect(surf, &rect, BltColorOn);
			last_rect = &rect;
			updates++;
		}
		if ( BltLed_lines_on < BltLedRect.h )
		{
			rect = BltLedRect;
			rect.h -= BltLed_lines_on;
			SDL_FillRect(surf, &rect, BltColorOff);
			last_rect = &rect;
			updates++;
		}
	}

	if ((bRecordingYM || bRecordingWav || Avi_AreWeRecording()) != bOldRecording)
	{
		bOldRecording = !bOldRecording;
		if (bOldRecording)
		{
			color = RecColorOn;
		}
		else
		{
			color = RecColorOff;
		}
		SDL_FillRect(surf, &RecLedRect, color);
		DEBUGPRINT(("REC = ON\n"));
		last_rect = &RecLedRect;
		updates++;
	}

	if (updates > 1)
	{
		/* multiple items updated -> update whole statusbar */
		last_rect = &FullRect;
	}
	if (do_update && last_rect)
	{
		Screen_UpdateRects(surf, 1, last_rect);
		last_rect = NULL;
	}
	return last_rect;
}
