#ifdef BACKEND_SDL2


#include "sdl2.h"


#define BYTES_PER_PIXEL  4


static SDL_Window   *_window                   = NULL;
static SDL_Renderer *_renderer                 = NULL;
static SDL_Surface  *_surface                  = NULL;
static SDL_Texture  *_texture                  = NULL;
static uint16_t      _width                    = 0;
static uint16_t      _height                   = 0;
static uint8_t       _windowScale              = 1;
static uint32_t      _pixelFormat              = SDL_PIXELFORMAT_RGBA8888;
static SurfaceT     *_activeSurface            = NULL;


extern ColorT   *__guiBaseColors;


#define PUTPIXEL(s,x,y,c) s->buffer.bits32[(y) * s->width + (x)] = (c)


SurfaceT *imageLoad(char *filename) {
	SurfaceT    *image = NULL;
	SDL_Surface *i     = NULL;
	SDL_Surface *s     = IMG_Load(filename);

	if (s) {
		i = SDL_ConvertSurfaceFormat(s, _pixelFormat, 0);
		if (i) {
			image = videoSurfaceCreate(s->w, s->h);
			if (image) {
				SDL_LockSurface(i);
				memcpy(image->buffer.bits8, i->pixels, image->bytes);
				SDL_UnlockSurface(i);
			}
			SDL_FreeSurface(i);
		}
		SDL_FreeSurface(s);
	}

	return image;
}


void platformEventGet(EventT *event) {
	SDL_Event      e;
	static uint8_t ASCII      = 0;
	static uint8_t debounce   = 0;
	static uint8_t buttons    = 0;
	static int16_t x          = 0;
	static int16_t y          = 0;

	memset(event, 0, sizeof(EventT));

	while (SDL_PollEvent(&e)) {
		switch (e.type) {
			case SDL_MOUSEMOTION:
				x = e.motion.x;
				y = e.motion.y;
				break;

			case SDL_MOUSEBUTTONUP:
				if (e.button.button == SDL_BUTTON_LEFT) {
					buttons &= ~BUTTON_LEFT;
					event->flags |= EVENT_FLAG_LEFT_UP;
				}
				if (e.button.button == SDL_BUTTON_RIGHT) {
					buttons &= ~BUTTON_RIGHT;
					event->flags |= EVENT_FLAG_RIGHT_UP;
				}
				break;

			case SDL_MOUSEBUTTONDOWN:
				if (e.button.button == SDL_BUTTON_LEFT) {
					buttons |= BUTTON_LEFT;
					event->flags |= EVENT_FLAG_LEFT_DOWN;
				}
				if (e.button.button == SDL_BUTTON_RIGHT) {
					buttons |= BUTTON_RIGHT;
					event->flags |= EVENT_FLAG_RIGHT_DOWN;
				}
				break;

			case SDL_KEYUP:
				ASCII      = 0;
				debounce   = 0;
				break;

			case SDL_KEYDOWN:
				if (debounce == 0) {
					if (e.key.keysym.scancode != 0) {
						// Not a meta key
						debounce   = 1;
						ASCII = e.key.keysym.sym;
						//if (e.key.keysym.scancode == SDL_SCANCODE_ESCAPE) ASCII = 27;
						event->key = ASCII;
						event->flags |= EVENT_FLAG_KEYPRESS;
					}
				}
				break;
		}
	}

	event->x = x;
	event->y = y;
	event->buttons = buttons;
	if ((SDL_GetModState() & KMOD_ALT)   != 0) event->kbstat |= META_ALT;
	if ((SDL_GetModState() & KMOD_CTRL)  != 0) event->kbstat |= META_CTRL;
	if ((SDL_GetModState() & KMOD_SHIFT) != 0) event->kbstat |= META_SHIFT;
}


void platformShutdown(void) {

	free(__guiBaseColors);

	if (_texture) {
		SDL_DestroyTexture(_texture);
		_texture = NULL;
	}

	if (_renderer) {
		SDL_DestroyRenderer(_renderer);
		_renderer = NULL;
	}

	if (_window) {
		SDL_DestroyWindow(_window);
		_window = NULL;
	}

	IMG_Quit();
	SDL_Quit();
}


void platformStartup(int16_t width, int16_t height, int16_t depth) {
	uint8_t i;
	uint8_t EGA[16][3] = {
			{ 0,   0,   0   },      /* black */
			{ 0,   0,   170 },      /* blue */
			{ 0,   170, 0   },      /* green */
			{ 0,   170, 170 },      /* cyan */
			{ 170, 0,   0   },      /* red */
			{ 170, 0,   170 },      /* magenta */
			{ 170, 85,  0   },      /* brown */
			{ 170, 170, 170 },      /* light gray */
			{ 85,  85,  85  },      /* dark gray */
			{ 85,  85,  255 },      /* light blue */
			{ 85,  255, 85  },      /* light green */
			{ 85,  255, 255 },      /* light cyan */
			{ 255, 85,  85  },      /* light red */
			{ 255, 85,  255 },      /* light magenta */
			{ 255, 255, 85  },      /* yellow */
			{ 255, 255, 255 }       /* white */
	};

	(void)depth;

	SDL_Init(SDL_INIT_EVERYTHING);
	IMG_Init(IMG_INIT_PNG);

	_windowScale = 3;

	_window   = SDL_CreateWindow("GUI Debug", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_ALLOW_HIGHDPI);
	_surface  = SDL_GetWindowSurface(_window);
	_renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
	_texture  = SDL_CreateTexture(_renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, width, height);

	SDL_RenderSetLogicalSize(_renderer, width, height);
	SDL_SetWindowSize(_window, width * _windowScale, height * _windowScale);

	_width  = width;
	_height = height;

	__guiBaseColors = (ColorT *)malloc(sizeof(ColorT) * 16);
	for (i=0; i<16; i++) {
		__guiBaseColors[i] = COLOR(EGA[i][0], EGA[i][1], EGA[i][2]);
	}
}


uint16_t videoDisplayHeightGet(void) {
	return _height;
}


uint16_t videoDisplayWidthGet(void) {
	return _width;
}


void videoModesShow(void) {
	// Nada
}


void videoSurfaceBlit(SurfaceT *target, int16_t targetX, int16_t targetY, SurfaceT *source) {
	uint16_t  y1;
	size_t    offsetTarget;
	size_t    offsetSource;
	void     *pixels;
	int       temp;

	// HACK!  Are we copying to the screen?  This assumes a full screen update.
	if (!target) {
		SDL_LockTexture(_texture, NULL, &pixels, &temp);
		memcpy(pixels, source->buffer.bits8, source->bytes);
		SDL_UnlockTexture(_texture);
		SDL_RenderCopy(_renderer, _texture, NULL, NULL);
		SDL_RenderPresent(_renderer);

		// Throttle this to some sane frame rate.
		SDL_Delay(32);

		return;
	}

	if (targetX == 0 && targetY == 0 && target->width == source->width && target->height == source->height) {
		// Direct blit of entire surface.
		memcpy(target->buffer.bits8, source->buffer.bits8, source->bytes);
	} else {
		// Blit into larger surface.
		offsetTarget = targetY * _activeSurface->scanline + targetX * BYTES_PER_PIXEL;
		offsetSource = 0;
		for (y1=targetY; y1<targetY+source->height; y1++) {
			memcpy(&target->buffer.bits8[offsetTarget], &source->buffer.bits8[offsetSource], source->scanline);
			offsetTarget += target->scanline;
			offsetSource += source->scanline;
		}
	}
}


void videoSurfaceBlitWithTransparency(SurfaceT *target, int16_t targetX, int16_t targetY, SurfaceT *source, ColorT transparent) {
	uint16_t x1;
	uint16_t y1;
	uint16_t x2 = source->width;
	uint16_t y2 = source->height;
	ColorT   pixel;

	// Clip on right and bottom
	if (targetX + x2 > target->width)  x2 -= targetX + x2 - target->width;
	if (targetY + y2 > target->height) y2 -= targetY + y2 - target->height;

	for (y1=0; y1<y2; y1++) {
		for (x1=0; x1<x2; x1++) {
			pixel = source->buffer.bits32[y1 * source->width + x1];
			if (transparent != pixel) {
				PUTPIXEL(target, targetX + x1, targetY + y1, pixel);
			}
		}
	}

}


void videoSurfaceClear(ColorT color) {
	uint16_t x;
	size_t   offsetTarget;

	// Draw the top line.
	for (x=0; x<_activeSurface->width; x++) {
		PUTPIXEL(_activeSurface, x, 0, color);
	}

	// Copy it to the other lines.
	offsetTarget = _activeSurface->scanline;
	for (x=1; x<_activeSurface->height; x++) {
		memcpy(&_activeSurface->buffer.bits8[offsetTarget], &_activeSurface->buffer.bits8[0], _activeSurface->scanline);
		offsetTarget += _activeSurface->scanline;
	}
}


SurfaceT *videoSurfaceCreate(int16_t width, int16_t height) {
	SurfaceT *surface = (SurfaceT *)malloc(sizeof(SurfaceT));

	if (!surface) return NULL;

	surface->width    = width;
	surface->height   = height;
	surface->scanline = width * BYTES_PER_PIXEL;
	surface->bytes    = surface->scanline * height;

	surface->buffer.bits8 = malloc(surface->bytes);
	if (!surface->buffer.bits8) {
		free(surface);
		return NULL;
	}

	memset(surface->buffer.bits8, 0, surface->bytes);

	return surface;
}


void videoSurfaceDestroy(SurfaceT *surface) {
	free(surface->buffer.bits8);
	free(surface);
}


void videoSurfaceBox(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
	videoSurfaceLineH(x1, x2, y1, c);
	videoSurfaceLineH(x1, x2, y2, c);
	videoSurfaceLineV(x1, y1, y2, c);
	videoSurfaceLineV(x2, y1, y2, c);
}


void videoSurfaceBoxFilled(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
	int16_t  i;
	size_t   offsetTarget;
	size_t   offsetSource;
	uint16_t width;

	if (x1 > x2) {
		i  = x1;
		x1 = x2;
		x2 = i;
	}

	if (y1 > y2) {
		i  = y1;
		y1 = y2;
		y2 = i;
	}

	width = (x2 - x1 + 1) * BYTES_PER_PIXEL;

	// Draw the top line.
	for (i=x1; i<=x2; i++) {
		PUTPIXEL(_activeSurface, i, y1, c);
	}

	// Copy it to the other lines.
	offsetTarget = _activeSurface->scanline * (y1 + 1) + (x1 * BYTES_PER_PIXEL);
	offsetSource = _activeSurface->scanline * y1+ (x1 * BYTES_PER_PIXEL);
	for (i=y1 + 1; i<=y2; i++) {
		memcpy(&_activeSurface->buffer.bits8[offsetTarget], &_activeSurface->buffer.bits8[offsetSource], width);
		offsetTarget += _activeSurface->scanline;
	}
}


int16_t videoSurfaceHeightGet(SurfaceT *surface) {
	return surface->height;
}


void videoSurfaceLineH(int16_t x1, int16_t x2, int16_t y, ColorT c) {
	int16_t i;
	int16_t t;

	if (x1 > x2) {
		t  = x2;
		x2 = x1;
		x1 = t;
	}

	for (i=x1; i<=x2; i++) {
		PUTPIXEL(_activeSurface, i, y, c);
	}
}


void videoSurfaceLineV(int16_t x, int16_t y1, int16_t y2, ColorT c) {
	int16_t i;
	int16_t t;

	if (y1 > y2) {
		t  = y2;
		y2 = y1;
		y1 = t;
	}

	for (i=y1; i<=y2; i++) {
		PUTPIXEL(_activeSurface, x, i, c);
	}
}


ColorT videoSurfacePixelGet(SurfaceT *surface, int16_t x, int16_t y) {
	return surface->buffer.bits32[y * surface->width + x];
}


void videoSurfacePixelSet(int16_t x, int16_t y, ColorT c) {
	PUTPIXEL(_activeSurface, x, y, c);
}


void videoSurfaceSet(SurfaceT *surface) {
	_activeSurface = surface;
}


SurfaceT *videoSurfaceScreenGet(void) {
	return NULL;
}


int16_t videoSurfaceWidthGet(SurfaceT *surface) {
	return surface->width;
}


#endif  // BACKEND_SDL2
