//Copyright Paul Reiche, Fred Ford. 1992-2002
/*
 *  This program 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.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "pure.h"
#include "libs/graphics/bbox.h"
#include "libs/log.h"
#include "scalers.h"
#include "uqmversion.h"

#if SDL_MAJOR_VERSION > 1

typedef struct tfb_sdl2_screeninfo_s {
	SDL_Surface *scaled;
	SDL_Texture *texture;
	BOOLEAN dirty, active;
	SDL_Rect updated;
} TFB_SDL2_SCREENINFO;

static TFB_SDL2_SCREENINFO SDL2_Screens[TFB_GFX_NUMSCREENS];

static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static const char *rendererBackend = NULL;

static int ScreenFilterMode;

static TFB_ScaleFunc scaler = NULL;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define A_MASK 0xff000000
#define B_MASK 0x00ff0000
#define G_MASK 0x0000ff00
#define R_MASK 0x000000ff
#else
#define A_MASK 0x000000ff
#define B_MASK 0x0000ff00
#define G_MASK 0x00ff0000
#define R_MASK 0xff000000
#endif

static void TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount);
static void TFB_SDL2_Postprocess (void);
static void TFB_SDL2_UploadTransitionScreen (void);
static void TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect);
static void TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect);
static void TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect);

static TFB_GRAPHICS_BACKEND sdl2_scaled_backend = {
        TFB_SDL2_Preprocess,
        TFB_SDL2_Postprocess,
	TFB_SDL2_UploadTransitionScreen,
        TFB_SDL2_Scaled_ScreenLayer,
        TFB_SDL2_ColorLayer };

static TFB_GRAPHICS_BACKEND sdl2_unscaled_backend = {
        TFB_SDL2_Preprocess,
        TFB_SDL2_Postprocess,
	TFB_SDL2_UploadTransitionScreen,
        TFB_SDL2_Unscaled_ScreenLayer,
        TFB_SDL2_ColorLayer };

static SDL_Surface *
Create_Screen (int w, int h)
{
        SDL_Surface *newsurf = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h,
                        32, R_MASK, G_MASK, B_MASK, 0);
        if (newsurf == 0)
	{
                log_add (log_Error, "Couldn't create screen buffers: %s",
                                SDL_GetError());
        }
        return newsurf;
}

static int
ReInit_Screen (SDL_Surface **screen, int w, int h)
{
        if (*screen)
                SDL_FreeSurface (*screen);
        *screen = Create_Screen (w, h);
        
        return *screen == 0 ? -1 : 0;
}

static int
FindBestRenderDriver (void)
{
	int i, n;
	if (!rendererBackend) {
		/* If the user has no preference, just let SDL2 choose */
		return -1;
	}
	n = SDL_GetNumRenderDrivers ();
	log_add (log_Info, "Searching for render driver \"%s\".", rendererBackend);

	for (i = 0; i < n; i++) {
		SDL_RendererInfo info;
		if (SDL_GetRenderDriverInfo (i, &info) < 0) {
			continue;
		}
		if (!strcmp(info.name, rendererBackend)) {
			return i;
		}
		log_add (log_Info, "Skipping render driver \"%s\"", info.name);
	}
	/* We did not find any accelerated drivers that weren't D3D9.
	 * Return -1 to ask SDL2 to do its best. */
	log_add (log_Info, "Render driver \"%s\" not available, using system default", rendererBackend);
	return -1;
}

int
TFB_Pure_ConfigureVideo (int driver, int flags, int width, int height, int togglefullscreen)
{
	int i;
	GraphicsDriver = driver;
	(void) togglefullscreen;
	if (window == NULL)
	{
		SDL_RendererInfo info;
		char caption[200];

		sprintf (caption, "The Ur-Quan Masters v%d.%d.%d%s",
				UQM_MAJOR_VERSION, UQM_MINOR_VERSION,
				UQM_PATCH_VERSION, UQM_EXTRA_VERSION);
		window = SDL_CreateWindow (caption,
				SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
				width, height, 0);
		if (flags & TFB_GFXFLAGS_FULLSCREEN)
		{
			/* If we create the window fullscreen, it will have
			 * no icon if and when it becomes windowed. */
			SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP);
		}
		if (!window) 
		{
			return -1;
		}
		renderer = SDL_CreateRenderer (window, FindBestRenderDriver (), 0);
		if (!renderer)
		{
			return -1;
		}
		if (SDL_GetRendererInfo (renderer, &info) == 0)
		{
			log_add (log_Info, "SDL2 renderer '%s' selected.\n", info.name);
		}
		else
		{
			log_add (log_Info, "SDL2 renderer had no name.");
		}
		SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight);
		for (i = 0; i < TFB_GFX_NUMSCREENS; i++)
		{
			SDL2_Screens[i].scaled = NULL;
			SDL2_Screens[i].texture = NULL;
			SDL2_Screens[i].dirty = TRUE;
			SDL2_Screens[i].active = TRUE;
			if (0 != ReInit_Screen (&SDL_Screens[i], ScreenWidth, ScreenHeight))
			{
				return -1;
			}
		}
		SDL2_Screens[1].active = FALSE;
		SDL_Screen = SDL_Screens[0];
		TransitionScreen = SDL_Screens[2];
		format_conv_surf = SDL_CreateRGBSurface(SDL_SWSURFACE, 0, 0,
				32, R_MASK, G_MASK, B_MASK, A_MASK);
		if (!format_conv_surf)
		{
			return -1;
		}
	}
	else
	{
		if (flags & TFB_GFXFLAGS_FULLSCREEN)
		{
			SDL_SetWindowFullscreen (window, SDL_WINDOW_FULLSCREEN_DESKTOP);
		}
		else
		{
			SDL_SetWindowFullscreen (window, 0);
			SDL_SetWindowSize (window, width, height);
		}
	}

	if (GfxFlags & TFB_GFXFLAGS_SCALE_ANY)
	{
		/* Linear scaling */
		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
	}
	else
	{
		/* Nearest-neighbor scaling */
		SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
	}

	if (GfxFlags & TFB_GFXFLAGS_SCALE_SOFT_ONLY)
	{
		for (i = 0; i < TFB_GFX_NUMSCREENS; i++)
		{
			if (!SDL2_Screens[i].active)
			{
				continue;
			}
			if (0 != ReInit_Screen(&SDL2_Screens[i].scaled,
					ScreenWidth * 2, ScreenHeight * 2))
			{
				return -1;
			}
			if (SDL2_Screens[i].texture)
			{
				SDL_DestroyTexture (SDL2_Screens[i].texture);
				SDL2_Screens[i].texture = NULL;
			}
			SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth * 2, ScreenHeight * 2);
			SDL_LockSurface (SDL2_Screens[i].scaled);
			SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL2_Screens[i].scaled->pixels, SDL2_Screens[i].scaled->pitch);
			SDL_UnlockSurface (SDL2_Screens[i].scaled);
		}
		scaler = Scale_PrepPlatform (flags, SDL2_Screens[0].scaled->format);
		graphics_backend = &sdl2_scaled_backend;
	}
	else
	{
		for (i = 0; i < TFB_GFX_NUMSCREENS; i++)
		{
			if (SDL2_Screens[i].scaled)
			{
				SDL_FreeSurface (SDL2_Screens[i].scaled);
				SDL2_Screens[i].scaled = NULL;
			}
			if (SDL2_Screens[i].texture)
			{
				SDL_DestroyTexture (SDL2_Screens[i].texture);
				SDL2_Screens[i].texture = NULL;
			}
			SDL2_Screens[i].texture = SDL_CreateTexture (renderer, SDL_PIXELFORMAT_RGBX8888, SDL_TEXTUREACCESS_STREAMING, ScreenWidth, ScreenHeight);
			SDL_LockSurface (SDL_Screens[i]);
			SDL_UpdateTexture (SDL2_Screens[i].texture, NULL, SDL_Screens[i]->pixels, SDL_Screens[i]->pitch);
			SDL_UnlockSurface (SDL_Screens[i]);
		}
		scaler = NULL;
		graphics_backend = &sdl2_unscaled_backend;
	}

	/* We succeeded, so alter the screen size to our new sizes */
	ScreenWidthActual = width;
	ScreenHeightActual = height;

	return 0;
}

int
TFB_Pure_InitGraphics (int driver, int flags, const char *renderer, int width, int height)
{
	log_add (log_Info, "Initializing SDL.");
	log_add (log_Info, "SDL initialized.");
	log_add (log_Info, "Initializing Screen.");

	ScreenWidth = 320;
	ScreenHeight = 240;
	rendererBackend = renderer;

	if (TFB_Pure_ConfigureVideo (driver, flags, width, height, 0))
	{
		log_add (log_Fatal, "Could not initialize video: %s",
				SDL_GetError ());
		exit (EXIT_FAILURE);
	}

	/* Initialize scalers (let them precompute whatever) */
	Scale_Init ();

	return 0;
}

void
TFB_Pure_UninitGraphics (void)
{
	if (renderer) {
		SDL_DestroyRenderer (renderer);
	}
	if (window) {
		SDL_DestroyWindow (window);
	}
}

static void
TFB_SDL2_UploadTransitionScreen (void)
{
	SDL2_Screens[TFB_SCREEN_TRANSITION].updated.x = 0;
	SDL2_Screens[TFB_SCREEN_TRANSITION].updated.y = 0;
	SDL2_Screens[TFB_SCREEN_TRANSITION].updated.w = ScreenWidth;
	SDL2_Screens[TFB_SCREEN_TRANSITION].updated.h = ScreenHeight;
	SDL2_Screens[TFB_SCREEN_TRANSITION].dirty = TRUE;
}

static void
TFB_SDL2_UpdateTexture (SDL_Texture *dest, SDL_Surface *src, SDL_Rect *rect)
{
	char *srcBytes;
	SDL_LockSurface (src);
	srcBytes = src->pixels;
	if (rect)
	{
		/* SDL2 screen surfaces are always 32bpp */
		srcBytes += (src->pitch * rect->y) + (rect->x * 4);
	}
	/* 2020-08-02: At time of writing, the documentation for
	 * SDL_UpdateTexture states this: "If the texture is intended to be
	 * updated often, it is preferred to create the texture as streaming
	 * and use [SDL_LockTexture and SDL_UnlockTexture]." Unfortunately,
	 * SDL_LockTexture will corrupt driver-space memory in the 32-bit
	 * Direct3D 9 driver on Intel Integrated graphics chips, resulting
	 * in an immediate crash with no detectable errors from the API up
	 * to that point.
	 *
	 * We also cannot simply forbid the Direct3D driver outright, because
	 * pre-Windows 10 machines appear to fail to initialize D3D11 even
	 * while claiming to support it.
	 *
	 * These bugs may be fixed in the future, but in the meantime we
	 * rely on this allegedly slower but definitely more reliable
	 * function. */
	SDL_UpdateTexture (dest, rect, srcBytes, src->pitch);
	SDL_UnlockSurface (src);
}

static void
TFB_SDL2_ScanLines (void)
{
	int y;
	SDL_SetRenderDrawColor (renderer, 0, 0, 0, 64);
	SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_BLEND);
	SDL_RenderSetLogicalSize (renderer, ScreenWidth * 2, ScreenHeight * 2);
	for (y = 0; y < ScreenHeight * 2; y += 2)
	{
		SDL_RenderDrawLine (renderer, 0, y, ScreenWidth * 2 - 1, y);
	}
	SDL_RenderSetLogicalSize (renderer, ScreenWidth, ScreenHeight);
}

static void
TFB_SDL2_Preprocess (int force_full_redraw, int transition_amount, int fade_amount)
{
	(void) transition_amount;
	(void) fade_amount;

	if (force_full_redraw == TFB_REDRAW_YES)
	{
		SDL2_Screens[TFB_SCREEN_MAIN].updated.x = 0;
		SDL2_Screens[TFB_SCREEN_MAIN].updated.y = 0;
		SDL2_Screens[TFB_SCREEN_MAIN].updated.w = ScreenWidth;
		SDL2_Screens[TFB_SCREEN_MAIN].updated.h = ScreenHeight;
		SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE;
	}
	else if (TFB_BBox.valid)
	{
		SDL2_Screens[TFB_SCREEN_MAIN].updated.x = TFB_BBox.region.corner.x;
		SDL2_Screens[TFB_SCREEN_MAIN].updated.y = TFB_BBox.region.corner.y;
		SDL2_Screens[TFB_SCREEN_MAIN].updated.w = TFB_BBox.region.extent.width;
		SDL2_Screens[TFB_SCREEN_MAIN].updated.h = TFB_BBox.region.extent.height;
		SDL2_Screens[TFB_SCREEN_MAIN].dirty = TRUE;
	}

	SDL_SetRenderDrawBlendMode (renderer, SDL_BLENDMODE_NONE);
	SDL_SetRenderDrawColor (renderer, 0, 0, 0, 255);
	SDL_RenderClear (renderer);
}

static void
TFB_SDL2_Unscaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect)
{
	SDL_Texture *texture = SDL2_Screens[screen].texture;
	if (SDL2_Screens[screen].dirty)
	{
		TFB_SDL2_UpdateTexture (texture, SDL_Screens[screen], &SDL2_Screens[screen].updated);
	}
	if (a == 255)
	{
		SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE);
	}
	else
	{
		SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND);
		SDL_SetTextureAlphaMod (texture, a);
	}
	SDL_RenderCopy (renderer, texture, rect, rect);
}

static void
TFB_SDL2_Scaled_ScreenLayer (SCREEN screen, Uint8 a, SDL_Rect *rect)
{
	SDL_Texture *texture = SDL2_Screens[screen].texture;
	SDL_Rect srcRect, *pSrcRect = NULL;
	if (SDL2_Screens[screen].dirty)
	{
		SDL_Surface *src = SDL2_Screens[screen].scaled;
		SDL_Rect scaled_update = SDL2_Screens[screen].updated;
		scaler (SDL_Screens[screen], src, &SDL2_Screens[screen].updated);
		scaled_update.x *= 2;
		scaled_update.y *= 2;
		scaled_update.w *= 2;
		scaled_update.h *= 2;
		TFB_SDL2_UpdateTexture (texture, src, &scaled_update);
	}
	if (a == 255)
	{
		SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_NONE);
	}
	else
	{
		SDL_SetTextureBlendMode (texture, SDL_BLENDMODE_BLEND);
		SDL_SetTextureAlphaMod (texture, a);
	}
	/* The texture has twice the resolution when scaled, but the
	 * screen's logical resolution has not changed, so the clip
	 * rectangle does not need to be scaled. The *source* clip
	 * rect, however, must be scaled to match. */
	if (rect)
	{
		srcRect = *rect;
		srcRect.x *= 2;
		srcRect.y *= 2;
		srcRect.w *= 2;
		srcRect.h *= 2;
		pSrcRect = &srcRect;
	}
	SDL_RenderCopy (renderer, texture, pSrcRect, rect);
}

static void
TFB_SDL2_ColorLayer (Uint8 r, Uint8 g, Uint8 b, Uint8 a, SDL_Rect *rect)
{
	SDL_SetRenderDrawBlendMode (renderer, a == 255 ? SDL_BLENDMODE_NONE 
			: SDL_BLENDMODE_BLEND);
	SDL_SetRenderDrawColor (renderer, r, g, b, a);
	SDL_RenderFillRect (renderer, rect);
}

static void
TFB_SDL2_Postprocess (void)
{
	if (GfxFlags & TFB_GFXFLAGS_SCANLINES)
		TFB_SDL2_ScanLines ();

	SDL_RenderPresent (renderer);
}

#endif
