/*
 * Kangaroo Punch MultiPlayer Game Server Mark II
 * Copyright (C) 2020-2021  Scott Duensing
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 *
 */


#include "surface.h"
#include "vesa.h"


static SurfaceT *_offScreenSurface;
static SurfaceT *_activeSurface;


static void surfacePutPixel8(uint16_t x, uint16_t y, ColorT color);
static void surfacePutPixel16(uint16_t x, uint16_t y, ColorT color);
static void surfacePutPixel32(uint16_t x, uint16_t y, ColorT color);


void (*surfacePutPixel)(uint16_t x, uint16_t y, ColorT color);



static void surfacePutPixel8(uint16_t x, uint16_t y, ColorT color) {
	_activeSurface->buffer.bits8[y * _activeSurface->width + x] = (uint8_t)color;
}


static void surfacePutPixel16(uint16_t x, uint16_t y, ColorT color) {
	_activeSurface->buffer.bits16[y * _activeSurface->width + x] = (uint16_t)color;
}


static void surfacePutPixel32(uint16_t x, uint16_t y, ColorT color) {
	_activeSurface->buffer.bits32[y * _activeSurface->width + x] = color;
}


void surfaceBlit(SurfaceT *source, uint16_t x, uint16_t y) {
	uint16_t y1;
	size_t   offsetTarget;
	size_t   offsetSource;

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


void surfaceClear(ColorT color) {

	uint16_t x;
	size_t   offsetTarget;

	// Draw the top line.
	for (x=0; x<_activeSurface->width; x++) {
		surfacePutPixel(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 *surfaceCreate(uint16_t width, uint16_t height) {
	SurfaceT *surface = (SurfaceT *)malloc(sizeof(SurfaceT));

	if (!surface) return NULL;

	surface->width    = width;
	surface->height   = height;
	surface->scanline = width * vbeDisplayDepthBytesGet();
	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 surfaceDestroy(SurfaceT **surface) {
	SurfaceT *s = *surface;

	free(s->buffer.bits8);
	free(s);
	s = NULL;
}


uint16_t surfaceHeightGet(void) {
	return _activeSurface->height;
}


void surfaceHighlightFrameDraw(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, ColorT upperLeft, ColorT lowerRight) {
	surfaceLineDraw(x1, y1, x2, y1, upperLeft);
	surfaceLineDraw(x1, y1, x1, y2, upperLeft);
	surfaceLineDraw(x1, y2, x2, y2, lowerRight);
	surfaceLineDraw(x2, y1, x2, y2, lowerRight);
}


void surfaceLineDraw(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT color) {
	int16_t x;
	int16_t y;
	int16_t dx;
	int16_t dy;
	int16_t incX;
	int16_t incY;
	int16_t balance;

	if (x2 >= x1) {
		dx = x2 - x1;
		incX = 1;
	} else {
		dx = x1 - x2;
		incX = -1;
	}

	if (y2 >= y1) {
		dy = y2 - y1;
		incY = 1;
	} else {
		dy = y1 - y2;
		incY = -1;
	}

	x = x1;
	y = y1;

	if (dx >= dy) {
		dy <<= 1;
		balance = dy - dx;
		dx <<= 1;
		while (x != x2) {
			surfacePutPixel(x, y, color);
			if (balance >= 0) {
				y += incY;
				balance -= dx;
			}
			balance += dy;
			x += incX;
		}
		surfacePutPixel(x, y, color);
	} else {
		dx <<= 1;
		balance = dx - dy;
		dy <<= 1;
		while (y != y2) {
			surfacePutPixel(x, y, color);
			if (balance >= 0) {
				x += incX;
				balance -= dy;
			}
			balance += dx;
			y += incY;
		}
		surfacePutPixel(x, y, color);
	}
}


SurfaceT *surfaceOffScreenGet(void) {
	return _offScreenSurface;
}


void surfaceRectangleDraw(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT color) {
	surfaceLineDraw(x1, y1, x2, y1, color);
	surfaceLineDraw(x2, y1, x2, y2, color);
	surfaceLineDraw(x1, y2, x2, y2, color);
	surfaceLineDraw(x1, y1, x1, y2, color);
}


void surfaceRectangleFilledDraw(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT color) {
	uint16_t x;
	uint16_t y;

	// This could be optimized similar to surfaceClear

	for (y=y1; y<=y2; y++) {
		for (x=x1; x<=x2; x++) {
			surfacePutPixel(x, y, color);
		}
	}
}


void surfaceSet(SurfaceT *surface) {
	if (surface) {
		_activeSurface = surface;
	} else {
		_activeSurface = _offScreenSurface;
	}
}


void surfaceShutdown(void) {
	surfaceDestroy(&_offScreenSurface);
}


uint8_t surfaceStartup(void) {
	_offScreenSurface = surfaceCreate(vbeDisplayWidthGet(), vbeDisplayHeightGet());

	if (vbeDisplayDepthBitsGet() ==  8) surfacePutPixel = surfacePutPixel8;
	if (vbeDisplayDepthBitsGet() == 16) surfacePutPixel = surfacePutPixel16;
	if (vbeDisplayDepthBitsGet() == 15) surfacePutPixel = surfacePutPixel16;
	if (vbeDisplayDepthBitsGet() == 32) surfacePutPixel = surfacePutPixel32;

	return (_offScreenSurface == NULL ? 1 : 0);
}


uint16_t surfaceWidthGet(void) {
	return _activeSurface->width;
}
