/* vi: set sw=4 ts=4: */
/*
 * CCE - Console Chinese Environment -
 * Copyright (C) 1998-2003 Rui He (rhe@3eti.com)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE TERRENCE R. LAMBERT BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<fcntl.h>

#include	"defs.h"
#include	"errors.h"
#include	"vc.h"
#include	"vga.h"
#include	"term.h"
#include	"mouse.h"

#if defined(SUPPORT_SDLLIB)

#include	"ccesdl.h"

SDL_Surface *sdlScreen;
static Uint32    sdlPix[16];

static int sdlPixBytes;  /* How many bytes per pixel? */
static int sdlMustLock;  /* Do we need to lock the surface? Not necessary for fb and X11 */

#define SDL_TITLE_STR	"CCE/SDL"
#define SDL_ICON_STR	"CCE/SDL"

#ifdef USE_DYNAMIC_SDLLIB

 int  (*p_SDL_Init)(Uint32 flags);
 void (*p_SDL_Quit)(void);
 char * (*p_SDL_VideoDriverName)(char *namebuf, int maxlen);

 const SDL_VideoInfo * (*p_SDL_GetVideoInfo)(void);
 SDL_Rect ** (*p_SDL_ListModes)(SDL_PixelFormat *format, Uint32 flags);
 SDL_Surface * (*p_SDL_SetVideoMode)(int width, int height, int bpp, Uint32 flags);
 void (*p_SDL_WM_SetCaption)(const char *title, const char *icon);

 int (*p_SDL_FillRect)(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color);
 Uint32 (*p_SDL_MapRGB)(SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b);
 int (*p_SDL_SetColors)(SDL_Surface *surface, SDL_Color *colors, int firstcolor, int ncolors);

 int (*p_SDL_LockSurface)(SDL_Surface *surface);
 void (*p_SDL_UnlockSurface)(SDL_Surface *surface);
 void (*p_SDL_UpdateRect)(SDL_Surface *screen, Sint32 x, Sint32 y, Uint32 w, Uint32 h);
 int (*p_SDL_Flip)(SDL_Surface *screen);

 void (*p_SDL_WarpMouse)(Uint16 x, Uint16 y);
 int  (*p_SDL_PollEvent)(SDL_Event *event);
 int  (*p_SDL_EnableKeyRepeat)(int delay, int interval);
 int  (*p_SDL_EnableUNICODE)(int enable);

static void *sdlLibHandle;

static char *sdlLibSearchPath[] = {
#if defined(__WINDOWS__)
	"SDL.dll",
#else
	"libSDL.so",
	"libSDL-1.2.so.0",  /* sometimes there is no libSDL.so symbolic link ?*/
	"/usr/local/lib/libSDL.so",
	"/usr/local/lib/libSDL-1.2.so.0",
#endif
	NULL,
};

static int LoadDynamicSDLLibrary(void)
{
	int i;

	for(i = 0; sdlLibSearchPath[i]; i++)
	{
		sdlLibHandle = dlopen(sdlLibSearchPath[i], RTLD_LAZY);
		if (sdlLibHandle) break;
	}

	if (!sdlLibHandle) 
	{
		message("Failed to load SDL library %s.\r\n", sdlLibSearchPath[0]);
		return FAILURE;
	}

	p_SDL_Init = dlsym(sdlLibHandle, SYMBOL_SDL_INIT);
    p_SDL_Quit = dlsym(sdlLibHandle, SYMBOL_SDL_QUIT);
	p_SDL_VideoDriverName = dlsym(sdlLibHandle, SYMBOL_SDL_VIDEODRIVERNAME);
    p_SDL_GetVideoInfo = dlsym(sdlLibHandle, SYMBOL_SDL_GETVIDEOINFO);
    p_SDL_ListModes = dlsym(sdlLibHandle, SYMBOL_SDL_LISTMODES);
    p_SDL_SetVideoMode = dlsym(sdlLibHandle, SYMBOL_SDL_SETVIDEOMODE);
	p_SDL_WM_SetCaption = dlsym(sdlLibHandle, SYMBOL_SDL_WM_SETCAPTION);

    p_SDL_FillRect = dlsym(sdlLibHandle, SYMBOL_SDL_FILLRECT);
    p_SDL_MapRGB = dlsym(sdlLibHandle, SYMBOL_SDL_MAPRGB);
	p_SDL_SetColors = dlsym(sdlLibHandle, SYMBOL_SDL_SETCOLORS);
    p_SDL_LockSurface = dlsym(sdlLibHandle, SYMBOL_SDL_LOCKSURFACE);
    p_SDL_UnlockSurface = dlsym(sdlLibHandle, SYMBOL_SDL_UNLOCKSURFACE);
    p_SDL_UpdateRect = dlsym(sdlLibHandle, SYMBOL_SDL_UPDATERECT);
    p_SDL_Flip = dlsym(sdlLibHandle, SYMBOL_SDL_FLIP);
	p_SDL_WarpMouse = dlsym(sdlLibHandle, SYMBOL_SDL_WARPMOUSE);
	p_SDL_PollEvent = dlsym(sdlLibHandle, SYMBOL_SDL_POLLEVENT);
	p_SDL_EnableKeyRepeat = dlsym(sdlLibHandle, SYMBOL_SDL_ENABLEKEYREPEAT);
	p_SDL_EnableUNICODE = dlsym(sdlLibHandle, SYMBOL_SDL_ENABLEUNICODE);

	if (!p_SDL_Init || !p_SDL_Quit || !p_SDL_GetVideoInfo || !p_SDL_ListModes 
        || !p_SDL_SetVideoMode || !p_SDL_FillRect || !p_SDL_MapRGB || !p_SDL_Flip 
        || !p_SDL_LockSurface || !p_SDL_UnlockSurface || !p_SDL_UpdateRect
		|| !p_SDL_VideoDriverName || !p_SDL_WarpMouse || !p_SDL_PollEvent 
		|| !p_SDL_EnableKeyRepeat || !p_SDL_EnableUNICODE || !p_SDL_SetColors
		|| !p_SDL_WM_SetCaption)
	{
		warn("Can't resolve all the required SDL routines.\r\n");
		dlclose(sdlLibHandle);
		sdlLibHandle = NULL;
		return FAILURE;
	}

	return SUCCESS;
}

static void UnloadDynamicSDLLibrary(void)
{
	if (sdlLibHandle) 
	{
		dlclose(sdlLibHandle);
		sdlLibHandle = NULL;
	}
}

#endif  /* USE_DYNAMIC_SDLLIB */

int IsSDLAvailable(void)
{
	const SDL_VideoInfo *info;
	SDL_Rect **modes;
	char driver[64];
	int i;

#ifdef USE_DYNAMIC_SDLLIB
	if (LoadDynamicSDLLibrary() != SUCCESS) return 0;
#endif
	
  	if (SDL_INIT(SDL_INIT_VIDEO) != 0) 
	{
		warn("Failed to initialize SDL library.\r\n");
		goto err2;
	}

	if (SDL_VIDEODRIVERNAME(driver, sizeof(driver)) ) 
	{
		message("SDL library using video driver: %s.\r\n", driver);
	}
	
	info = SDL_GETVIDEOINFO();

#ifdef DEBUG
    fprintf(stderr,
"Current display: %d bits-per-pixel\n",info->vfmt->BitsPerPixel);
    if ( info->vfmt->palette == NULL ) 
	{
        fprintf(stderr, "    Red Mask = 0x%.8x\n", info->vfmt->Rmask);
        fprintf(stderr, "    Green Mask = 0x%.8x\n", info->vfmt->Gmask);
        fprintf(stderr, "    Blue Mask = 0x%.8x\n", info->vfmt->Bmask);
    }
#endif

    modes = SDL_LISTMODES(NULL, SDL_FULLSCREEN);

    if ( modes == (SDL_Rect **)0 || modes == (SDL_Rect **)-1) 
	{
        fprintf(stderr, "No available fullscreen video modes\n");
		goto err;
    } 
#ifdef DEBUG
	else 
	{
        fprintf(stderr, "Fullscreen video modes:\n");
        for ( i=0; modes[i]; ++i ) 
		{
            fprintf(stderr, "\t%dx%dx%d\n", modes[i]->w, modes[i]->h, info->vfmt->BitsPerPixel);
        }
    }
#endif

	if (!strcasecmp(driver, "ps2gs") && StartFromConsole) /* PS2 Graphics Synthesizer ?*/
	{  

		dispInfo.gxdim = 640;
		dispInfo.gydim = 480;
	}
	else
	{

		dispInfo.gxdim = modes[0]->w;
		dispInfo.gydim = modes[0]->h; 
		
		if (dispInfo.gxdim > 1280)  /* too big? */
		{
			for(i = 0; modes[i]; i++)
				if (modes[i]->w <= 1280)  /* try to get one smaller than 1280x1024? */
				{
					dispInfo.gxdim = modes[i]->w;
					dispInfo.gydim = modes[i]->h;
					break;
				}
		}
	}

	dispInfo.type = TYPE_SDL;
	
	return 1;

err:
	SDL_QUIT();
err2:
#ifdef USE_DYNAMIC_SDLLIB
	UnloadDynamicSDLLibrary();
#endif
	return 0;
}

static void SDLMapColors(u_char *red, u_char *green, u_char *blue)
{
	int i;

	for(i = 0; i < 16; i++)
	{
		sdlPix[i] = SDL_MAPRGB(sdlScreen->format, red[i], green[i], blue[i]); 
	}
}

static void SDLSetPels(Uint8 *red, Uint8 *green, Uint8 *blue)
{
	int i;
	SDL_Color colors[16];

	for(i = 0; i < 16; i++)
	{
		colors[i].r = red[i]; 
		colors[i].g = green[i];
		colors[i].b = blue[i];
	}
	SDL_SETCOLORS(sdlScreen, colors, 0, 16);

}

int SDLInit(void)
{
	int res;
	Uint32 flags = SDL_HWSURFACE | SDL_ANYFORMAT; 

	if (SDLAvail && dispInfo.type != TYPE_SDL)  /* haven't test it yet, do it now */
		SDLAvail = IsSDLAvailable();
	if (!SDLAvail) goto err;

#if defined(__Darwin__)
	if (1)  /* MacOSX/SDL will be faster if using full screen mode, otherwise too slow? */
#else
	if (StartFromConsole) 
#endif
		flags |= SDL_FULLSCREEN;
	else
	{

		if (dispInfo.gxdim >= 1024 && fontwidth > 10) /* 12*24 font? */
		{
			dispInfo.gxdim = 1024;
			dispInfo.gydim = 768;
		}
		else if (dispInfo.gxdim >= 1024 || (fontwidth > 10 && dispInfo.gxdim >= 800))
		{
			dispInfo.gxdim = 800; 
			dispInfo.gydim = 600; 
		}
		else /* otherwise 640x480 */
		{
			dispInfo.gxdim = 640; dispInfo.gydim = 480;
		}
	}

	sdlScreen = SDL_SETVIDEOMODE(dispInfo.gxdim, dispInfo.gydim, 0/* dispInfo.bpp */, flags);
	if (!sdlScreen)
	{
		warn("Failed to set default SDL graphics mode.\n");
		goto err;
	}

#ifdef DEBUG
	fprintf(stderr, "SDL_Surface: flags=0x%x width=%d height=%d pitch=%d bpp=%d BytesPerPixel=%d\n", 
		sdlScreen->flags, sdlScreen->w, sdlScreen->h, sdlScreen->pitch, sdlScreen->format->BitsPerPixel, sdlScreen->format->BytesPerPixel);
#endif

	sdlPixBytes = sdlScreen->format->BytesPerPixel;
	sdlMustLock = SDL_MUSTLOCK(sdlScreen);

	dispInfo.gxdim = sdlScreen->w;
	dispInfo.gydim = sdlScreen->h;
	dispInfo.bpp = sdlScreen->format->BitsPerPixel;
	dispInfo.type = TYPE_SDL;
	dispInfo.glineByte = sdlScreen->pitch; /* not used ?  */

	if (dispInfo.bpp <= 8) SDLSetPels(red8, green8, blue8);
	SDLMapColors(red8, green8, blue8);

    InitDispInfo();

	message("Using %dx%d %d-bpp SDL, %dx%d terminal.\n",
		dispInfo.gxdim, dispInfo.gydim, dispInfo.bpp, dispInfo.txmax, dispInfo.tymax);

	mInfo.has_mouse = TRUE;  /* use SDL's mouse support */
	cursorInfo.interval *= 2; /* increase the cursor interval since SDL select() use
								 a shorter time */
	SDL_WARPMOUSE(0,0);  /* move the cursor to (0,0) */
	
	SDL_ENABLEKEYREPEAT(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	SDL_ENABLEUNICODE(1);  /* enable translation of keysym to printable format */
	
	SDL_WM_SETCAPTION(SDL_TITLE_STR, SDL_ICON_STR);

	return SUCCESS;

err:
	if (sdlScreen) sdlScreen = NULL;
	SDL_QUIT();

#ifdef USE_DYNAMIC_SDLLIB
	UnloadDynamicSDLLibrary();
#endif
	
	return FAILURE;
	
}

void SDLDetach(void)
{
	if (sdlScreen)
	{
		SDL_QUIT();
		sdlScreen = NULL;
	}

#ifdef USE_DYNAMIC_SDLLIB
	UnloadDynamicSDLLibrary();
#endif
}

void SDLOpDraw(int xpos, int ypos, u_char *code, int fc, int bc, int width, int height)
{
	int i,j;
	u_char bits;
	Uint8 *dest, *line;
	Uint32 pix;
	
	if (sdlMustLock) SDL_LOCKSURFACE(sdlScreen);  /* for direct surface access */

	line = ((Uint8 *)sdlScreen->pixels) + ypos * sdlScreen->pitch + sdlPixBytes * xpos;

	for(i = 0; i < height; i++)
	{
		dest = line;
		for(j = 0; j < width; j++)
		{
			if ((j & 0x7) == 0) bits = *code++;   /* j % 8 == 0 */
			pix = sdlPix[ bits & (0x80 >> (j & 0x7)) ? fc : bc ];
			switch(sdlPixBytes)
			{
			case 1:
				*dest++ = (Uint8)(pix);
				break;
			case 2:
				*((Uint16 *)dest)++ = (Uint16)(pix); 
				break;
			case 3:
				*dest++ = (Uint8)(pix); pix >>= 8;
				*dest++ = (Uint8)(pix); pix >>= 8;
				*dest++ = (Uint8)(pix);
				break;
			case 4:
				*((Uint32 *)dest)++ = (Uint32)(pix);
				break;
			}
		}
		line += sdlScreen->pitch;
	}

	if (sdlMustLock) SDL_UNLOCKSURFACE(sdlScreen);

	SDL_UPDATERECT(sdlScreen, xpos, ypos, width, height);
}

void SDLOpClear(int y, int height, int color)
{    
	SDL_Rect rect;

	rect.x = 0;
	rect.y = y;
	rect.w = dispInfo.gxdim;
	rect.h = height;

	SDL_FILLRECT(sdlScreen, &rect, sdlPix[color]);
	SDL_FLIP(sdlScreen);
}

void SDLOpCursor(CursorInfo *ci, int xpos, int ypos)
{
	int width = fontwidth;
	int height = cursorBtm - cursorTop;
    int i,j;
	Uint8 *dest;

	if (kanjiCursor && ci->kanji)
		width *= 2;

	if (sdlMustLock) SDL_LOCKSURFACE(sdlScreen);  /* for direct surface access */

	dest = ((Uint8 *)sdlScreen->pixels) + ypos * sdlScreen->pitch + sdlPixBytes * xpos;

	for(i = 0; i < height; i++)
	{
		for(j = 0; j < width * sdlPixBytes; j++)
		{

				dest[j] ^= 0xFF;
		}
		dest += sdlScreen->pitch;	
	}

	if (sdlMustLock) SDL_UNLOCKSURFACE(sdlScreen);

	SDL_UPDATERECT(sdlScreen, xpos, ypos, width, height);
}

#ifdef SUPPORT_FREETYPE_FONT

#include "ftfont.h"
 
void SDLDrawFreeTypeChar(int xpos, int ypos, FT_GlyphSlot glyph, int fc, int bc, int width, int height)
{	int i,j, k;
	u_char bits, *code;
	Uint8 *dest, *line;
	Uint32 pix;
    int top, left, row, col, pitch;

	if (glyph == NULL)
	{
		left=top=row=col=pitch=0;
		code = NULL;
	}
	else
	{
		left = ftXOrig + glyph->bitmap_left;  /* bitmap_left should be >= 0 ? */
		top = ftYOrig - glyph->bitmap_top;
		if (top < 0) top = 0;
		row = glyph->bitmap.rows;  /* rows/width >= 0 */
		col = glyph->bitmap.width;
		code = glyph->bitmap.buffer;
		pitch = glyph->bitmap.pitch; /* bitmap scan line bytes */
	}

	if (sdlMustLock) SDL_LOCKSURFACE(sdlScreen);  /* for direct surface access */

	line = ((Uint8 *)sdlScreen->pixels) + ypos * sdlScreen->pitch + sdlPixBytes * xpos;

	for(i = 0; i < height; i++)
	{
		dest = line;
		k = 0;
		for(j = 0; j < width; j++)
		{
			if (i < top || i >= top+row || j < left || j >= left+col)
				pix = sdlPix[bc];
			else  /* in freetype font region */
			{
				if (glyph->bitmap.pixel_mode == ft_pixel_mode_mono) /* bit oriented */
				{
					if ((k & 0x7) == 0) bits = code[k/8];
				    pix = sdlPix[ bits & (0x80 >> (k & 0x7)) ? fc : bc ];
				}
				else
				{
					bits = code[k]; /* 0-255 gray scale */

					if (DoAntiAlias && sdlPixBytes > 1) /*  TrueColor, will do anti-alias */
					{
						pix = SDL_MAPRGB(sdlScreen->format, 
                    		(Uint8)(red8[bc] + ((red8[fc]-red8[bc]) * bits)/255),
                    	 	(Uint8)(green8[bc] + ((green8[fc]-green8[bc]) * bits)/255),
                         	(Uint8)(blue8[bc] + ((blue8[fc]-blue8[bc]) * bits)/255));
					}
					else pix = sdlPix[ bits >= 128? fc : bc];  /* won't do anti-alias */
				}
				k++;
			}

			switch(sdlPixBytes)
			{
			case 1:
				*dest++ = (Uint8)(pix);
				break;
			case 2:
				*((Uint16 *)dest)++ = (Uint16)(pix); 
				break;
			case 3:
				*dest++ = (Uint8)(pix); pix >>= 8;
				*dest++ = (Uint8)(pix); pix >>= 8;
				*dest++ = (Uint8)(pix);
				break;
			case 4:
				*((Uint32 *)dest)++ = (Uint32)(pix);
				break;
			}
		}

		line += sdlScreen->pitch;
		if (k > 0) /* k>0 means code is used somehow,  code could be NULL */
            code += pitch;

	}

	if (sdlMustLock) SDL_UNLOCKSURFACE(sdlScreen);

	SDL_UPDATERECT(sdlScreen, xpos, ypos, width, height);
}

#endif /* SUPPORT_FREETYPE_FONT */

void SDLScreenSaver(bool blank)
{
}
 
void SDLTextMode(void)
{
}

void SDLGraphMode(void)
{

	SDLOpClear(0, dispInfo.gydim - dispInfo.input, DEF_BGCOLOR);

}

void SDLStart(void)
{
	cursorTop = fontheight - 2;
	cursorBtm = fontheight - 1;
}

VideoInfo SDLInfo =
{
    SDLInit,
    SDLStart,
    SDLTextMode,
    SDLGraphMode,
    CCEWput,
    CCEInputWput,
    CCESput,
    CCEInputSput,
	CCEInputDrawIcon,
    CCECursor,
    CCEClear,
    SDLScreenSaver,
    SDLDetach
};

#endif

