/*(LGPL)
----------------------------------------------------------------------
	gfxengine.cpp - Graphics Engine
----------------------------------------------------------------------
 * Copyright (C) 2001-2003, 2006-2007 David Olofson
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 * 
 * This program 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define	DBG(x)	x

#include "logger.h"

#include <string.h>
#include <math.h>

#include "gfxengine.h"
#include "filters.h"
#include <SDL3_image/SDL_image.h>
#include "sdl_compat.h"
#include "sofont.h"
#include "window.h"

gfxengine_t *gfxengine;


gfxengine_t::gfxengine_t()
{
	gfxengine = this;	/* Uurgh! Kludge. */

	sdl_window = NULL;
	sdl_renderer = NULL;
	fb_texture = NULL;
	screen_surface = NULL;
	softbuf = NULL;
	fullwin = NULL;
	window = NULL;
	windows = NULL;
	wx = wy = 0;
	xs = ys = 256;		// 1.0
	sxs = sys = 256;	// 1.0
	sf1 = df = dsf = acf = NULL;
	gfx = NULL;
	csengine = NULL;
	_driver = GFX_DRIVER_SDL2D;
	_shadow = 1;
	_doublebuf = 1;
	_pages = -1;
	_vsync = 1;
	_fullscreen = 0;
	_centered = 0;
	use_interpolation = 1;
	_depth = 0;
	_title = "GfxEngine v0.4";
	_icontitle = "GfxEngine";
	_cursor = 1;
	_width = 320;
	_height = 240;
	_autoinvalidate = 1;
	_scalemode = GFX_SCALE_NEAREST;
	_clamping = 0;
	xflags = 0;

	_dither = 0;
	_dither_type = 0;
	broken_rgba8 = 0;
	alpha_threshold = 0;

	_brightness = 1.0;
	_contrast = 1.0;

	last_tick = -1000000;
	ticks_per_frame = 1000.0/60.0;

	is_running = 0;
	is_showing = 0;
	is_open = 0;

	memset(fonts, 0, sizeof(fonts));

	xscroll = yscroll = 0;
	for(int i = 0; i < CS_LAYERS ; ++i)
		xratio[i] = yratio[i] = 0.0;

	dirtyrects[0] = 0;
	dirtyrects[1] = 0;
	frontpage = 0;
	backpage = 1;
	screenshot_count = 0;
}


gfxengine_t::~gfxengine_t()
{
	stop();
	hide();
	close();
	window_t *w = windows;
	while(w)
	{
		w->engine = NULL;
		w = w->next;
	}
}


void gfxengine_t::output(window_t *outwin)
{
	window = outwin;
}


/*----------------------------------------------------------
	Initialization
----------------------------------------------------------*/

void gfxengine_t::size(int w, int h)
{
	int was_showing = is_showing;
	hide();

	_width = w;
	_height = h;
	if(csengine)
		cs_engine_set_size(csengine, w, h);

	if(was_showing)
		show();
}

void gfxengine_t::centered(int c)
{
	int was_showing = is_showing;
	hide();

	_centered = c;

	if(was_showing)
		show();
}

void gfxengine_t::scale(float x, float y)
{
	xs = (int)(x * 256.f);
	ys = (int)(y * 256.f);
	log_printf(DLOG, "gfxengine: Setting scale to %d:256 x %d:256.\n", xs, ys);
}

void gfxengine_t::mode(int bits, int fullscreen)
{
	int was_showing = is_showing;
	hide();

	int olddepth = _depth;
	_depth = bits;
	if(_depth != olddepth)
		reload();
	_fullscreen = fullscreen;

	if(was_showing)
		show();
}

void gfxengine_t::driver(gfx_drivers_t drv)
{
	int was_showing = is_showing;
	hide();

	_driver = drv;

	if(was_showing)
		show();
}

void gfxengine_t::doublebuffer(int use)
{
	if(_doublebuf == use)
		return;

	int was_showing = is_showing;
	hide();

	_doublebuf = use;

	if(was_showing)
		show();
}

void gfxengine_t::pages(int np)
{
	_pages = np;
}

void gfxengine_t::vsync(int use)
{
	if(_vsync == use)
		return;

	int was_showing = is_showing;
	hide();

	_vsync = use;

	if(was_showing)
		show();
}

void gfxengine_t::shadow(int use)
{
	if(_shadow == use)
		return;

	int was_showing = is_showing;
	hide();

	_shadow = use;

	if(was_showing)
		show();
}

void gfxengine_t::autoinvalidate(int use)
{
	_autoinvalidate = use;
}

void gfxengine_t::period(float frameduration)
{
	if(frameduration > 0)
	{
		ticks_per_frame = frameduration;
	}
	else
		log_printf(DLOG, "gfxengine: Time line reset.\n");
	if(csengine)
		cs_engine_advance(csengine, 0);
}

void gfxengine_t::wrap(int x, int y)
{
	wx = x;
	wy = y;
	if(csengine)
		cs_engine_set_wrap(csengine, x, y);
}


/*----------------------------------------------------------
	Data management
----------------------------------------------------------*/

void gfxengine_t::reset_filters()
{
	/* Remove all filters */
	s_remove_filter(NULL);
	sf1 = df = dsf = acf = NULL;

	/* Set up filter pipeline */
	s_remove_filter(NULL);

	s_add_filter(s_filter_rgba8);
#if 0
	s_filter_t *fi;
	fi = s_add_filter(s_filter_key2alpha);
	fi->args.max = 1;
#endif
	sf1 = s_add_filter(s_filter_scale);
	sf2 = s_add_filter(s_filter_scale);
	acf = s_add_filter(s_filter_cleanalpha);
	bcf = s_add_filter(s_filter_brightness);
	df = s_add_filter(s_filter_dither);
	dsf = s_add_filter(s_filter_displayformat);

	/* Set default parameters */
//	colorkey(0, 0, 0);
	clampcolor(0, 0, 0, 0);
	scalemode(GFX_SCALE_NEAREST);
	dither(0, 0);
	noalpha(0);
	brightness(1.0, 1.0);
	filterflags(0);
}


void gfxengine_t::filterflags(int fgs)
{
	s_filter_flags = fgs;
}


void gfxengine_t::scalemode(gfx_scalemodes_t sm, int clamping)
{
	_scalemode = sm;
	_clamping = clamping;
	if(!sf1 || !df)
		return;

	int rxs = (xs * 256 + 128) / sxs;
	int rys = (ys * 256 + 128) / sys;
#if 1
	// Filter 2 is off in most cases
	sf2->args.x = 0;
	sf2->args.fx = 0.0f;
	sf2->args.fy = 0.0f;
#endif
	if((rxs == 256) && (rys == 256))
	{
		sf1->args.x = SF_SCALE_NEAREST;
		sf1->args.fx = 1.0f;
		sf1->args.fy = 1.0f;
		return;
	}

	switch(clamping)
	{
	  default:
	  case 0:
		filterflags(0);
		break;
	  case 1:
		filterflags(SF_CLAMP_EXTEND);
		break;
	  case 2:
		filterflags(SF_CLAMP_SFONT);
		break;
	}

	sf1->args.fx = rxs * (1.0f/256.0f);
	sf1->args.fy = rys * (1.0f/256.0f);

	switch(_scalemode)
	{
	  case GFX_SCALE_NEAREST:	//Nearest
		sf1->args.x = SF_SCALE_NEAREST;
		break;
	  case GFX_SCALE_BILINEAR:	//Bilinear
		sf1->args.x = SF_SCALE_BILINEAR;
		break;
	  case GFX_SCALE_BILIN_OVER:	//Bilinear + Oversampling
#if 0
		sf1->args.x = SF_SCALE_BILINEAR;
		sf2->args.x = SF_SCALE_BILINEAR;
		sf1->args.fx = rxs * (1.5f/256.f);
		sf1->args.fy = rys * (1.5f/256.f);
		sf2->args.fx = 1.0f/1.5f;
		sf2->args.fy = 1.0f/1.5f;
		break;
		/* Hack for better bilinear [+ oversampling] scaling... */
		switch(clamping)
		{
		  case 2:
			sf1->args.x = SF_SCALE_NEAREST;
			sf2->args.x = SF_SCALE_BILINEAR;
			switch((rxs + 128) / 256)
			{
			  case 2:
				sf1->args.fx = 4.0f;
				sf1->args.fy = 4.0f;
				sf2->args.fx = 0.5f;
				sf2->args.fy = 0.5f;
				break;
			  case 3:
				sf1->args.fx = 2.0f;
				sf1->args.fy = 2.0f;
				sf2->args.fx = 1.5f;
				sf2->args.fy = 1.5f;
				break;
			  case 4:
				sf1->args.fx = 2.0f;
				sf1->args.fy = 2.0f;
				sf2->args.fx = 2.0f;
				sf2->args.fy = 2.0f;
				break;
			  default:
				sf1->args.fx = rxs * (2.f/256.f);
				sf1->args.fy = rys * (2.f/256.f);
				sf2->args.fx = 0.5f;
				sf2->args.fy = 0.5f;
				break;
			}
			break;
		  default:
			sf1->args.x = SF_SCALE_BILINEAR;
			break;
		}
#endif
		sf1->args.x = SF_SCALE_BILINEAR;
		break;
	  case GFX_SCALE_SCALE2X:	//Scale2x
		sf1->args.x = SF_SCALE_SCALE2X;
		break;
	  case GFX_SCALE_DIAMOND:	//Diamond2x
		sf1->args.x = SF_SCALE_DIAMOND;
		break;
	}
}


void gfxengine_t::source_scale(float x, float y)
{
	sxs = (int)(x * 256.f);
	sys = (int)(y * 256.f);
	scalemode(_scalemode, _clamping);
}

#if 0
void gfxengine_t::colorkey(Uint8 r, Uint8 g, Uint8 b)
{
	s_colorkey.r = r;
	s_colorkey.g = g;
	s_colorkey.b = b;
}
#endif

void gfxengine_t::clampcolor(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
{
	s_clampcolor.r = r;
	s_clampcolor.g = g;
	s_clampcolor.b = b;
	s_clampcolor.a = b;
}


void gfxengine_t::dither(int type, int _broken_rgba8)
{
	_dither = type >= 0;
	_dither_type = type;
	broken_rgba8 = _broken_rgba8;

	if(!df)
		return;
	if(!screen_surface)
		return;

	if(_dither)
	{
		// SDL 3: back buffer is always ARGB8888 (8 bits per channel,
		// no loss). The legacy formula was 1 << (Rloss - 1); for 8 bpc
		// the dithering amplitude collapses to 0.
		df->args.x = 0;
		df->args.r = 0;
		df->args.g = 0;
		df->args.b = 0;
	}
	else
		df->args.x = df->args.r = df->args.g = df->args.b = 0;
	df->args.y = type;
}


void gfxengine_t::noalpha(int threshold)
{
	alpha_threshold = threshold;
	if(!dsf || !acf)
		return;

	if(threshold > 0)
	{
		acf->args.min = 128;
		acf->args.max = 128;
		acf->args.fx = 1.0;
		acf->args.x = threshold;

		dsf->args.x = 1;
	}
	else
	{
		acf->args.min = 16;
		acf->args.max = 255-16;
		acf->args.fx = 1.3;
		acf->args.x = -16;

		dsf->args.x = 0;
	}
}


void gfxengine_t::brightness(float bright, float contr)
{
	_brightness = bright;
	_contrast = contr;
	if(!bcf)
		return;

	bcf->args.fx = _brightness;
	bcf->args.fy = _contrast;
	bcf->args.min = 0;
	bcf->args.max = 255;
}


int gfxengine_t::loadimage(int bank, const char *name)
{
	if(!csengine)
	{
		log_printf(ELOG, "loadimage: Engine must be open!\n");
		return -10;
	}
	s_blitmode = S_BLITMODE_AUTO;
	log_printf(DLOG, "Loading image %s (bank %d)...\n", name, bank);
	if(s_load_image(gfx, bank, name))
	{
		log_printf(ELOG, "  Failed to load %s!\n", name);
		return -1;
	}

	s_bank_t *b = s_get_bank(gfx, bank);
	if(!b)
	{ 
		log_printf(ELOG, "  gfxengine: Internal error 1!\n");
		return -15;
	}
	cs_engine_set_image_size(csengine, bank, b->w, b->h);

	log_printf(DLOG, "  Ok.\n");
	return 0;
}


int gfxengine_t::loadtiles(int bank, int w, int h, const char *name)
{
	if(!csengine)
	{
		log_printf(ELOG, "loadtiles: Engine must be open!\n");
		return -10;
	}
	s_blitmode = S_BLITMODE_AUTO;
	log_printf(DLOG, "Loading tiles %s (bank %d; %dx%d)...\n",
			name, bank, w, h);
	if(s_load_bank(gfx, bank, w, h, name))
	{
		log_printf(ELOG, "  Failed to load %s!\n", name);
		return -2;
	}

	cs_engine_set_image_size(csengine, bank, w, h);

	log_printf(DLOG, "  Ok. (%d frames)\n", s_get_bank(gfx, bank)->max+1);
	return 0;
}


int gfxengine_t::loadfont(int bank, const char *name)
{
	if(!csengine)
	{
		log_printf(ELOG, "loadfont: Engine must be open!\n");
		return -10;
	}
	s_blitmode = S_BLITMODE_AUTO;
	scalemode(_scalemode, 2);
	log_printf(DLOG, "Loading font %s (bank %d)...\n", name, bank);
	if(s_load_image(gfx, bank, name))
	{
		log_printf(ELOG, "  Failed to load %s!\n", name);
		return -2;
	}
	if(bank >= GFX_BANKS)
	{
		log_printf(ELOG, "  Too high bank #!\n");
		return -3;
	}

	if(!fonts[bank])
		fonts[bank] = new SoFont;
	if(!fonts[bank])
	{
		log_printf(ELOG, "  Failed to instantiate SoFont!\n");
		return -4;
	}

	fonts[bank]->ExtraSpace((xs + 127) / 256);
	if(fonts[bank]->load(s_get_sprite(gfx, bank, 0)->surface))
	{
		s_detach_sprite(gfx, bank, 0);
		log_printf(DLOG, "  Ok.\n");
		return 0;
	}
	else
	{
		log_printf(ELOG, "  SoFont::load() failed!\n");
		return -5;
	}
	return 0;
}


int gfxengine_t::copyrect(int bank, int sbank, int sframe, SDL_Rect *r)
{
	SDL_Rect sr = *r;
	if(!csengine)
	{
		log_printf(ELOG, "copyrect: Engine must be open!\n");
		return -10;
	}
	log_printf(DLOG, "Copying rect from %d:%d (bank %d)...\n",
			sbank, sframe, bank);
	int x2 = (int)((sr.x + sr.w) * xs + 128) >> 8;
	int y2 = (int)((sr.y + sr.h) * ys + 128) >> 8;
	sr.x = (int)(sr.x * xs + 128) >> 8;
	sr.y = (int)(sr.y * ys + 128) >> 8;
	sr.w = x2 - sr.x;
	sr.h = y2 - sr.y;
	if(s_copy_rect(gfx, bank, sbank, sframe, &sr) < 0)
	{
		log_printf(ELOG, "  s_copy_rect() failed!\n");
		return -1;
	}
	log_printf(DLOG, "  Ok.\n");
	return 0;
}


int gfxengine_t::is_loaded(int bank)
{
	if(!csengine)
		return 0;
	if(!s_get_bank(gfx, (unsigned)bank))
		return 0;
	return 1;
}


void gfxengine_t::reload()
{
	log_printf(DLOG, "Reloading all banks. (Not implemented!)\n");
//	s_reload_all_banks(gfx);
}


void gfxengine_t::unload(int bank)
{
	if(bank < 0)
	{
		log_printf(DLOG, "Unloading all banks.\n");
		for(int i = 0; i < GFX_BANKS; ++i)
		{
			delete fonts[i];
			fonts[i] = NULL;
		}
		if(gfx)
			s_delete_all_banks(gfx);
	}
	else
	{
		log_printf(DLOG, "Unloading bank %d.\n", bank);
		if(bank < GFX_BANKS)
		{
			delete fonts[bank];
			fonts[bank] = NULL;
		}
		if(gfx)
			s_delete_bank(gfx, bank);
	}
}


/*----------------------------------------------------------
	Engine open/close
----------------------------------------------------------*/

void gfxengine_t::on_frame(cs_engine_t *e)
{
	gfxengine->__frame();
	gfxengine->frame();
}


int gfxengine_t::open(int objects, int extraflags)
{
	xflags = extraflags;

	if(is_open)
		return show();

	log_printf(DLOG, "Opening engine...\n");
	csengine = cs_engine_create(_width, _height, objects);
	if(!csengine)
	{
		log_printf(ELOG, "Failed to set up control system engine!\n");
		return -1;
	}

	csengine->on_frame = on_frame;
	cs_engine_set_wrap(csengine, wx, wy);

	gfx = s_new_container(GFX_BANKS);
	if(!gfx)
	{
		log_printf(ELOG, "Failed to set up graphics container!\n");
		cs_engine_delete(csengine);
		return -2;
	}

	reset_filters();

	is_open = 1;
	return show();
}


void gfxengine_t::close()
{
	if(!is_open)
		return;

	log_printf(DLOG ,"Closing engine...\n");
	stop();
	hide();
	unload();
	cs_engine_delete(csengine);
	csengine = NULL;
	s_remove_filter(NULL);
	sf1 = df = dsf = acf = NULL;
	s_delete_container(gfx);
	gfx = NULL;
	is_open = 0;
}


/*----------------------------------------------------------
	Settings
----------------------------------------------------------*/

void gfxengine_t::title(const char *win, const char *icon)
{
	_title = win;
	_icontitle = icon;
	if(sdl_window)
		SDL_SetWindowTitle(sdl_window, _title);
}


/*----------------------------------------------------------
	Display show/hide
----------------------------------------------------------*/

int gfxengine_t::show()
{
	if(!is_open)
		return -1;

	if(is_showing)
		return 0;

	log_printf(DLOG, "Opening screen...\n");
	if(!SDL_WasInit(SDL_INIT_VIDEO))
		if(!SDL_InitSubSystem(SDL_INIT_VIDEO))
		{
			log_printf(ELOG, "Failed to initialize SDL: %s\n",
					SDL_GetError());
			return -2;
		}

	// SDL 3 window + renderer model.
	//
	// The kobo engine renders into a CPU-side back buffer (softbuf) via
	// SDL_BlitSurface/SDL_FillSurfaceRect, and uploads the result to a
	// streaming SDL_Texture once per frame. This preserves the entire
	// software-compositing code path inherited from SDL 1.2 (window.cpp,
	// sprite.c, filters.c, sofont.cpp), while letting the GPU handle
	// scaling and presentation. SDL_DOUBLEBUF / SDL_HWSURFACE / page-
	// flipping flags are obsolete and ignored.
	Uint64 win_flags = 0;
	if(_fullscreen)
		win_flags |= SDL_WINDOW_FULLSCREEN;

	sdl_window = SDL_CreateWindow(_title ? _title : "Kobo Deluxe",
			_width, _height, win_flags);
	if(!sdl_window)
	{
		log_printf(ELOG, "Failed to open window: %s\n", SDL_GetError());
		return -3;
	}

	sdl_renderer = SDL_CreateRenderer(sdl_window, NULL);
	if(!sdl_renderer)
	{
		log_printf(ELOG, "Failed to create renderer: %s\n",
				SDL_GetError());
		SDL_DestroyWindow(sdl_window);
		sdl_window = NULL;
		return -4;
	}

	// Letterbox the back buffer when the window is resized / fullscreen.
	SDL_SetRenderLogicalPresentation(sdl_renderer, _width, _height,
			SDL_LOGICAL_PRESENTATION_LETTERBOX);
	SDL_SetRenderVSync(sdl_renderer, _vsync ? 1 : 0);

	// The back buffer is always a CPU-side ARGB8888 surface.
	softbuf = SDL_CreateSurface(_width, _height, SDL_PIXELFORMAT_ARGB8888);
	if(!softbuf)
	{
		log_printf(ELOG, "Failed to create back buffer: %s\n",
				SDL_GetError());
		SDL_DestroyRenderer(sdl_renderer);
		SDL_DestroyWindow(sdl_window);
		sdl_renderer = NULL;
		sdl_window = NULL;
		return -5;
	}
	screen_surface = softbuf;	// alias for legacy code paths

	fb_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888,
			SDL_TEXTUREACCESS_STREAMING, _width, _height);
	if(!fb_texture)
	{
		log_printf(ELOG, "Failed to create framebuffer texture: %s\n",
				SDL_GetError());
		SDL_DestroySurface(softbuf);
		SDL_DestroyRenderer(sdl_renderer);
		SDL_DestroyWindow(sdl_window);
		softbuf = screen_surface = NULL;
		sdl_renderer = NULL;
		sdl_window = NULL;
		return -6;
	}
	SDL_SetTextureScaleMode(fb_texture,
			(_scalemode == GFX_SCALE_NEAREST) ?
				SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);

	if(_cursor)
		SDL_ShowCursor();
	else
		SDL_HideCursor();

	cs_engine_set_size(csengine, _width, _height);
	csengine->filter = use_interpolation;

	dither(_dither);
	noalpha(alpha_threshold);
	is_showing = 1;

	fullwin = new window_t;
	fullwin->init(this);
	fullwin->place(0, 0, (int)(_width / xscale()),
			(int)(_height / yscale()));

	clear();

	return 0;
}


void gfxengine_t::clear(Uint32 _color)
{
	if(!fullwin)
		return;

	fullwin->background(fullwin->map_rgb(_color));
	fullwin->clear();
	flip();
	fullwin->clear();
	flip();
	fullwin->clear();
}


void gfxengine_t::hide(void)
{
	if(!is_showing)
		return;

	log_printf(DLOG, "Closing screen...\n");
	stop();
	delete fullwin;
	fullwin = NULL;

	// Make sure no windows keep old surface pointers!
	window_t *w = windows;
	while(w)
	{
		if((w->surface == screen_surface) ||
				(w->surface == softbuf))
			w->surface = NULL;
		w = w->next;
	}

	if(fb_texture)
	{
		SDL_DestroyTexture(fb_texture);
		fb_texture = NULL;
	}
	if(softbuf)
	{
		SDL_DestroySurface(softbuf);
		softbuf = NULL;
	}
	screen_surface = NULL;
	if(sdl_renderer)
	{
		SDL_DestroyRenderer(sdl_renderer);
		sdl_renderer = NULL;
	}
	if(sdl_window)
	{
		SDL_DestroyWindow(sdl_window);
		sdl_window = NULL;
	}

	is_showing = 0;
}


void gfxengine_t::invalidate(SDL_Rect *rect, window_t *window)
{
	switch(_pages)
	{
	  case -1:
		if(_doublebuf)
			__invalidate(1, rect, window);
		__invalidate(0, rect, window);
		break;
	  case 0:
		__invalidate(0, NULL, NULL);
		break;
	  case 3:
		__invalidate(2, rect, window);
		// Fallthrough!
	  case 2:
		__invalidate(1, rect, window);
		// Fallthrough!
	  case 1:
		__invalidate(0, rect, window);
		break;
	}
}


void gfxengine_t::__invalidate(int page, SDL_Rect *rect, window_t *window)
{
	if(!screen_surface)
		return;

	if(!rect || (_pages == 0))
	{
		dirtyrects[page] = 1;
		dirtytable[page][0].x = 0;
		dirtytable[page][0].y = 0;
		dirtytable[page][0].w = screen_surface->w;
		dirtytable[page][0].h = screen_surface->h;
		dirtywtable[page][0] = NULL;
		return;
	}

	/* Clip to screen (stolen from SDL_surface.c) */
	SDL_Rect dr = *rect;
	SDL_Rect screen_clip;
	SDL_GetSurfaceClipRect(screen_surface, &screen_clip);
	int Amin, Amax, Bmin, Bmax;

	/* Horizontal intersection */
	Amin = dr.x;
	Amax = Amin + dr.w;
	Bmin = screen_clip.x;
	Bmax = Bmin + screen_clip.w;
	if(Bmin > Amin)
	        Amin = Bmin;
	dr.x = Amin;
	if(Bmax < Amax)
	        Amax = Bmax;
	dr.w = Amax - Amin > 0 ? Amax - Amin : 0;

	/* Vertical intersection */
	Amin = dr.y;
	Amax = Amin + dr.h;
	Bmin = screen_clip.y;
	Bmax = Bmin + screen_clip.h;
	if(Bmin > Amin)
	        Amin = Bmin;
	dr.y = Amin;
	if(Bmax < Amax)
	        Amax = Bmax;
	dr.h = Amax - Amin > 0 ? Amax - Amin : 0;

	if(!dr.w || !dr.h)
		return;

	for(int i = 0; i < dirtyrects[page]; ++i)
		if(memcmp(&dirtytable[page][i], &dr, sizeof(dr)) == 0)
			return;

	if(dirtyrects[page] < MAX_DIRTYRECTS - 1)
	{
		dirtytable[page][dirtyrects[page]] = dr;
		dirtywtable[page][dirtyrects[page]] = window;
		++dirtyrects[page];
	}
	else
		log_printf(ELOG, "gfxengine: Page %d out of dirtyrects!\n",
				page);
}


/*----------------------------------------------------------
	Engine start/stop
----------------------------------------------------------*/

void gfxengine_t::start_engine()
{
	last_tick = -1000000;
}


void gfxengine_t::stop_engine()
{
}


/*----------------------------------------------------------
	Control
----------------------------------------------------------*/

/*
 * Run engine until stop() is called.
 *
 * The virtual member frame() will be called once for
 * each control system frame. pre_render() and
 * post_render() will be called before/after the engine
 * renders each video frame.
 */
void gfxengine_t::run()
{
	open();
	show();
	start_engine();
	is_running = 1;
	double toframe = 0.0f;
	double fdt = 1.0f;
	cs_engine_advance(csengine, 0);
	while(is_running)
	{
		int t = (int)SDL_GetTicks();
		int dt = t - last_tick;
		last_tick = t;
		if(abs(dt) > 500)
			last_tick = t;
		if(dt > 250)
			dt = (int)ticks_per_frame;
		if(_timefilter)
			fdt += (dt - fdt) * _timefilter;
		else
			fdt = ticks_per_frame;
		toframe += fdt / ticks_per_frame;
		cs_engine_advance(csengine, toframe);
		pre_render();
		window->select();
		cs_engine_render(csengine);
		post_render();
		if(_autoinvalidate)
			window->invalidate();
		flip();
	}
	stop_engine();
}


void gfxengine_t::stop()
{
	if(!is_running)
		return;

	log_printf(DLOG, "Stopping engine...\n");
	is_running = 0;
}


void gfxengine_t::cursor(int csr)
{
	_cursor = csr;
	if(screen_surface)
	{
		if(csr)
			SDL_ShowCursor();
		else
			SDL_HideCursor();
	}
}


void gfxengine_t::interpolation(int inter)
{
	use_interpolation = inter;
	if(!csengine)
		return;

	csengine->filter = use_interpolation;
}


void gfxengine_t::timefilter(float coeff)
{
	if(coeff < 0.001f)
		_timefilter = 0.0f;
	else if(coeff > 1.0f)
		_timefilter = 1.0f;
	else
		_timefilter = coeff;
}


void gfxengine_t::scroll_ratio(int layer, float xr, float yr)
{
	if(layer < 0)
		return;
	if(layer >= CS_LAYERS)
		return;

	xratio[layer] = xr;
	yratio[layer] = yr;
}


void gfxengine_t::scroll(int xscr, int yscr)
{
	xscroll = xscr;
	yscroll = yscr;

	/* Apply current scroll pos to layers */
	for(int i = 0; i < CS_LAYERS ; ++i)
	{
		csengine->offsets[i].v.x = (int)floor(xscroll * xratio[i]);
		csengine->offsets[i].v.y = (int)floor(yscroll * yratio[i]);
	}
}


void gfxengine_t::force_scroll()
{
	if(csengine)
		for(int i = 0; i < CS_LAYERS ; ++i)
			cs_point_force(&csengine->offsets[i]);
}


int gfxengine_t::xoffs(int layer)
{
	if(layer < 0)
		return 0;
	if(layer >= CS_LAYERS)
		return 0;
	return csengine->offsets[layer].gx;
}


int gfxengine_t::yoffs(int layer)
{
	if(layer < 0)
		return 0;
	if(layer >= CS_LAYERS)
		return 0;
	return csengine->offsets[layer].gy;
}


SDL_Surface *gfxengine_t::surface()
{
	if(softbuf)
		return softbuf;
	else
		return screen_surface;
}


void gfxengine_t::screenshot()
{
	char filename[1024];
	snprintf(filename, sizeof(filename), "screen%d.bmp", screenshot_count++);
	SDL_SaveBMP(screen_surface, filename);
}


/*----------------------------------------------------------
	Internal stuff
----------------------------------------------------------*/

/* Default frame handler */
void gfxengine_t::frame()
{
	SDL_Event ev;
	while(SDL_PollEvent(&ev))
	{
		if(ev.type == SDL_EVENT_KEY_DOWN)
			if(ev.key.key == SDLK_ESCAPE)
				stop();
	}
}


/* Internal frame handler */
void gfxengine_t::__frame()
{
}


void gfxengine_t::pre_render()
{
}


void gfxengine_t::post_render()
{
}


void gfxengine_t::refresh_rect(SDL_Rect *r)
{
	window_t *w = windows;
	for(w = windows; w; w = w->next)
	{
		if(!w->visible())
			continue;

		SDL_Rect dr = *r;

		/* Clip to window */
		int Amin, Amax, Bmin, Bmax;

		/* Horizontal intersection */
		Amin = dr.x;
		Amax = Amin + dr.w;
		Bmin = w->phys_rect.x;
		Bmax = Bmin + w->phys_rect.w;
		if(Bmin > Amin)
			Amin = Bmin;
		dr.x = Amin;
		if(Bmax < Amax)
			Amax = Bmax;
		dr.w = Amax - Amin > 0 ? Amax - Amin : 0;

		/* Vertical intersection */
		Amin = dr.y;
		Amax = Amin + dr.h;
		Bmin = w->phys_rect.y;
		Bmax = Bmin + w->phys_rect.h;
		if(Bmin > Amin)
			Amin = Bmin;
		dr.y = Amin;
		if(Bmax < Amax)
			Amax = Bmax;
		dr.h = Amax - Amin > 0 ? Amax - Amin : 0;

		if(dr.w && dr.h)
			w->phys_refresh(&dr);
	}
}


void gfxengine_t::flip()
{
	if(!screen_surface || !sdl_renderer || !fb_texture)
		return;

	// SDL 3 has no hardware page flipping; we always present one back
	// buffer per frame. Keep the dirtyrect iteration only to drive the
	// phys_refresh() callbacks that window widgets rely on; the upload
	// is a single full-surface copy regardless.
	frontpage = backpage = 0;
	for(int i = 0; i < dirtyrects[backpage]; ++i)
	{
		if(dirtywtable[backpage][i])
		{
			if(!dirtywtable[backpage][i]->visible())
				continue;
			SDL_Rect dr = dirtytable[backpage][i];
			dirtywtable[backpage][i]->phys_refresh(&dr);
		}
		else
			refresh_rect(&dirtytable[backpage][i]);
	}
	dirtyrects[backpage] = 0;

	SDL_UpdateTexture(fb_texture, NULL, softbuf->pixels, softbuf->pitch);
	SDL_RenderClear(sdl_renderer);
	SDL_RenderTexture(sdl_renderer, fb_texture, NULL, NULL);
	SDL_RenderPresent(sdl_renderer);
}


/*
 * Generic render() callback for sprites and tiles.
 */
void gfxengine_t::render_sprite(cs_obj_t *o)
{
	SDL_Rect dest_rect;
	s_sprite_t *s = gfxengine->get_sprite(o->anim.bank, o->anim.frame);
	if(!s || !s->surface)
		return;

	int x = o->point.gx - (s->x << 8);
	int y = o->point.gy - (s->y << 8);
	dest_rect.x = CS2PIXEL((x * gfxengine->xs + 128) >> 8);
	dest_rect.y = CS2PIXEL((y * gfxengine->ys + 128) >> 8);
	dest_rect.x += (gfxengine->window->x() * gfxengine->xs + 128) >> 8;
	dest_rect.y += (gfxengine->window->y() * gfxengine->xs + 128) >> 8;
	SDL_BlitSurface(s->surface, NULL, gfxengine->surface(), &dest_rect);

	if(!gfxengine->_autoinvalidate)
	{
		dest_rect.w = s->surface->w;
		dest_rect.h = s->surface->h;
		gfxengine->invalidate(&dest_rect);
	}
}


cs_obj_t *gfxengine_t::get_obj(int layer)
{
	cs_obj_t *o = cs_engine_get_obj(csengine);
	if(o)
	{
		o->render = render_sprite;
		cs_obj_layer(o, layer);
		cs_obj_activate(o);
	}
	return o;
}


void gfxengine_t::free_obj(cs_obj_t *obj)
{
	cs_obj_free(obj);
}


int gfxengine_t::objects_in_use()
{
	if(!csengine)
		return 0;

	return csengine->pool_total - csengine->pool_free;
}


SoFont *gfxengine_t::get_font(unsigned int f)
{
	if(f < GFX_BANKS)
		return fonts[f];
	else
		return NULL;
}
