/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2012 Sam Lantinga

    This library 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 library 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 library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/

#include "SDL.h"
#include "SDL_config.h"
#include "SDL_mouse.h"
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
#include "../../events/SDL_events_c.h"

#include "SDL_prizmvideo.h"
#include "SDL_prizmevents_c.h"
#include <gint/gint.h>
#include <gint/display.h>


//#include "SDL_tinspireevents_c.h"

static Uint16 prz_palette[256] = {0};

/* Initialization/Query functions */
static int PRZ_VideoInit(_THIS, SDL_PixelFormat *vformat);
static SDL_Rect **PRZ_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags);
static SDL_Surface *PRZ_SetVideoMode(_THIS, SDL_Surface *current, int width, int height, int bpp, Uint32 flags);
static int PRZ_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors);
static void PRZ_VideoQuit(_THIS);

/* etc. */
static void PRZ_UpdateRects(_THIS, int numrects, SDL_Rect *rects);

/* NSP driver bootstrap functions */
static int PRZ_Available(void)
{
	return(1);
}

static void PRZ_DeleteDevice(SDL_VideoDevice *device)
{
	SDL_free(device->hidden);
	SDL_free(device);
}

static SDL_VideoDevice *PRZ_CreateDevice(int devindex)
{
	SDL_VideoDevice *device;

	/* Initialize all variables that we clean on shutdown */
	device = (SDL_VideoDevice *)SDL_malloc(sizeof(SDL_VideoDevice));
	if ( device ) {
		SDL_memset(device, 0, (sizeof *device));
		device->hidden = (struct SDL_PrivateVideoData *)
				SDL_malloc((sizeof *device->hidden));
	}
	if ( (device == NULL) || (device->hidden == NULL) ) {
		SDL_OutOfMemory();
		if ( device ) {
			SDL_free(device);
		}
		return(0);
	}
	SDL_memset(device->hidden, 0, (sizeof *device->hidden));

	/* Set the function pointers */
	device->VideoInit = PRZ_VideoInit;
	device->ListModes = PRZ_ListModes;
	device->SetVideoMode = PRZ_SetVideoMode;
	device->SetColors = PRZ_SetColors;
	device->UpdateRects = PRZ_UpdateRects;
	device->VideoQuit = PRZ_VideoQuit;
	device->InitOSKeymap = PRZ_InitOSKeymap;
	device->PumpEvents = PRZ_PumpEvents;

	device->free = PRZ_DeleteDevice;

	return device;
}


VideoBootStrap PRZ_bootstrap = {
	"prizm", "SDL Casio PRIZM video driver",
	PRZ_Available, PRZ_CreateDevice
};


static int PRZ_VideoInit(_THIS, SDL_PixelFormat *vformat)
{
	this->hidden->cx = (int) true;
	this->info.current_w = DWIDTH;
	this->info.current_h = DHEIGHT;

	if ( this->hidden->cx ) {
		vformat->BitsPerPixel = 16;
		vformat->Rmask = PRZ_RMASK16;
		vformat->Gmask = PRZ_GMASK16;
		vformat->Bmask = PRZ_BMASK16;
	}

	return(0);
}

static SDL_Rect **PRZ_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags)
{
	return (SDL_Rect **) -1;
}

static SDL_Surface *PRZ_SetVideoMode(_THIS, SDL_Surface *current,
				     int width, int height, int bpp, Uint32 flags)
{
	Uint32 rmask, gmask, bmask;

	//NSP_DEBUG("Initializing display (%dx%dx%d)", width, height, bpp);

	//if ( flags != SDL_SWSURFACE )
	//	NSP_WARNING("Initializing with non-typical flags");

	if ( width < DWIDTH || height < DHEIGHT ) {
		int win_x = (DWIDTH - width) / 2;
		int win_y = (DHEIGHT - height) / 2;
		this->hidden->offset = (int)PRZ_PIXEL_ADDR(0, win_x, win_y, 2 * DWIDTH, 2);
		this->hidden->win_x = win_x;
	} else
		this->hidden->offset = this->hidden->win_x = 0;

	if ( bpp == 16 ) {
		rmask = PRZ_RMASK16;
		gmask = PRZ_GMASK16;
		bmask = PRZ_BMASK16;
	}

	if ( this->hidden->buffer ) {
		SDL_free( this->hidden->buffer );
	}


	this->hidden->buffer2 = SDL_malloc((bpp / 8) * DWIDTH * DHEIGHT);
	if ( ! this->hidden->buffer2 ) {
		//SDL_SetError("Couldn't allocate buffer2 for requested mode");
		return(NULL);
	}

	this->hidden->buffer = SDL_malloc((bpp / 8) * width * height);
	if ( ! this->hidden->buffer ) {
		SDL_free(this->hidden->buffer2);
		//SDL_SetError("Couldn't allocate buffer for requested mode");
		return(NULL);
	}

	memset(this->hidden->buffer2, 0, (bpp / 8) * DWIDTH * DHEIGHT);
	memset(this->hidden->buffer, 0, (bpp / 8) * width * height);

	/* Allocate the new pixel format for the screen */
	if ( ! SDL_ReallocFormat(current, bpp, rmask, gmask, bmask, 0) ) {
		SDL_free(this->hidden->buffer);
		this->hidden->buffer = NULL;
		//SDL_SetError("Couldn't allocate new pixel format for requested mode");
		return(NULL);
	}

	/* Set up the new mode framebuffer */
	current->flags = flags;
	this->hidden->w = this->info.current_w = current->w = width;
	this->hidden->h = this->info.current_h = current->h = height;
	current->pitch = (bpp / 8) * current->w;
	current->pixels = this->hidden->buffer;

	//NSP_DEBUG("Done (0x%p)", current);

	/* We're done */
	return(current);
}

#define PRZ_DRAW_LOOP(code) do { \
	while ( rows-- ) { \
		j = 0, k = 0; \
		code \
		src_addr += src_skip; \
		dst_addr += dst_skip; \
	} \
} while (0)

static void PRZ_UpdateRects(_THIS, int numrects, SDL_Rect *rects)
{
	int src_skip = SDL_VideoSurface->pitch;
	int dst_skip = (2 * DWIDTH);
	int i, j, k;

	if ( this->hidden->buffer2 == NULL )
		return;

	for ( i = 0; i < numrects; ++i ) {
		SDL_Rect *rect = &rects[i];
		Uint8 *src_addr, *dst_addr;
		SDL_bool odd_left, odd_right;
		int row_bytes, rows;

		if ( ! rect )
			continue;

		src_addr = PRZ_PIXEL_ADDR(SDL_VideoSurface->pixels, rect->x, rect->y,
					  SDL_VideoSurface->pitch, SDL_VideoSurface->format->BytesPerPixel);
		//dst_addr = PRZ_PIXEL_ADDR(this->hidden->buffer2, rect->x, rect->y, 2 * DWIDTH, 2);
		dst_addr = PRZ_PIXEL_ADDR(gint_vram, rect->x, rect->y, 2 * DWIDTH, 2);
		dst_addr += this->hidden->offset;

		odd_left = (this->hidden->win_x + rect->x) & 1;
		odd_right = (this->hidden->win_x + rect->x + rect->w) & 1;
		row_bytes = SDL_VideoSurface->format->BytesPerPixel * ((rect->x + rect->w > DWIDTH) ? DWIDTH : rect->w);
		rows = (rect->y + rect->h > DHEIGHT) ? DHEIGHT : rect->h;

		if ( ! this->hidden->cx && odd_right )
			--row_bytes;

		if ( this->hidden->cx ) {
			if ( SDL_VideoSurface->format->BitsPerPixel == 16 ) {
				/* 16 bpp SW, 16 bpp HW */
				PRZ_DRAW_LOOP(
					SDL_memcpy(dst_addr, src_addr, row_bytes);
				);
			}
			dupdate();
		}
	}
}

#define PRZ_MAP_RGB(r, g, b)	((((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)))

static int PRZ_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors)
{
	int i;
	for ( i = firstcolor; i < firstcolor + ncolors; ++i )
		prz_palette[i] = PRZ_MAP_RGB(colors[i].r, colors[i].g, colors[i].b);
	return(1);
}

/* Note:  If we are terminated, this could be called in the middle of
   another SDL video routine -- notably UpdateRects.
*/
static void PRZ_VideoQuit(_THIS)
{
	//NSP_DEBUG("Closing video");
}
