/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2004
 *					All rights reserved
 *
 *  This file is part of GPAC / SDL audio and video plugin
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *   
 *  GPAC 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *		
 */

 /*
   Note on threading: the plugin handles both 2D and 3D output. 
   The 2D output model is a direct one: sending a "resize" event to the user results in a synchronous
   call to the plugin Resize function
   The 3D output model is an asynchrone one: due to openGL context in threading environments, all calls to the plugins Resize and SetFullScreen are handled through the main rendering thread (the one accessing opengl context) to ensure the GL context is always setup and used in a single thread. Therefore sending a "resize" event to the user will postpone actual resize.
 */

#include "SDL_out.h"

#ifdef WIN32
#include <windows.h>
#endif


/*cursors data*/
static char hand_data[] = 
{
	0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
	0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,1,2,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,1,2,2,1,2,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,1,2,2,1,2,2,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	1,1,1,0,1,2,2,1,2,2,1,2,2,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	1,2,2,1,1,2,2,2,2,2,2,2,2,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	1,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,1,2,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,1,2,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,2,2,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,1,2,2,2,2,2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};


static char collide_data[] = 
{
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
	0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,1,1,1,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,1,1,0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,1,1,0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,1,1,1,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};




#define SDLVID()	SDLVidCtx *ctx = (SDLVidCtx *)dr->opaque

#if defined(__linux__)
#define HAVE_X11
#endif
#ifdef HAVE_X11
#include <X11/Xlib.h>
#endif

static u32 video_modes[] = 
{
	320, 200,
	320, 240,
	400, 300,
	600, 400,
	800, 600,
	1024, 768,
	1152, 864,
	1280, 1024
};
static u32 nb_video_modes = 8;

void SDLVid_SetCaption()
{
	char szName[100];
	if (SDL_VideoDriverName(szName, 100)) {
		char szCap[1024];
		sprintf(szCap, "SDL Video Output (%s)", szName);
		SDL_WM_SetCaption(szCap, NULL);
	} else {
		SDL_WM_SetCaption("SDL Video Output", NULL);
	}
}

SDL_Cursor *SDLVid_LoadCursor(char *maskdata)
{
	s32 ind, i, j;
	u8 data[4*32];
	u8 mask[4*32];

	ind = -1;
	for (i=0; i<32; i++) {
		for (j=0; j<32; j++) {
			if (j%8) {
				data[ind] <<= 1;
				mask[ind] <<= 1;
			} else {
				ind++;
				data[ind] = mask[ind] = 0;
			}
			switch (maskdata[j+32*i]) {
			/*black*/
			case 1:
				data[ind] |= 0x01;
			/*white*/
			case 2:
				mask[ind] |= 0x01;
				break;
			}
		}
	}
	return SDL_CreateCursor(data, mask, 32, 32, 0, 0);
}


static u32 SDLVid_TranslateActionKey(u32 VirtKey) 
{
	switch (VirtKey) {
	case SDLK_HOME: return M4VK_HOME;
	case SDLK_END: return M4VK_END;
	case SDLK_PAGEUP: return M4VK_PRIOR;
	case SDLK_PAGEDOWN: return M4VK_NEXT;
	case SDLK_UP: return M4VK_UP;
	case SDLK_DOWN: return M4VK_DOWN;
	case SDLK_LEFT: return M4VK_LEFT;
	case SDLK_RIGHT: return M4VK_RIGHT;
	case SDLK_F1: return M4VK_F1;
	case SDLK_F2: return M4VK_F2;
	case SDLK_F3: return M4VK_F3;
	case SDLK_F4: return M4VK_F4;
	case SDLK_F5: return M4VK_F5;
	case SDLK_F6: return M4VK_F6;
	case SDLK_F7: return M4VK_F7;
	case SDLK_F8: return M4VK_F8;
	case SDLK_F9: return M4VK_F9;
	case SDLK_F10: return M4VK_F10;
	case SDLK_F11: return M4VK_F11;
	case SDLK_F12: return M4VK_F12;
	case SDLK_RETURN: return M4VK_RETURN;
	case SDLK_ESCAPE: return M4VK_ESCAPE;
	case SDLK_LSHIFT:
	case SDLK_RSHIFT:
		return M4VK_SHIFT;
	case SDLK_LCTRL: 
	case SDLK_RCTRL: 
		return M4VK_CONTROL;
	case SDLK_LALT:
	case SDLK_RALT:
		return M4VK_MENU;
	default: return 0;
	}
}

static void SDLVid_DestroyObjects(SDLVidCtx *ctx)
{
	while (ChainGetCount(ctx->surfaces)) {
		SDLWrapSurface *ptr = ChainGetEntry(ctx->surfaces, 0);
		ChainDeleteEntry(ctx->surfaces, 0);
		if (ptr->surface) SDL_FreeSurface(ptr->surface);
		free(ptr);
	}
	if (ctx->back_buffer) SDL_FreeSurface(ctx->back_buffer);
	ctx->back_buffer = NULL;
}

static void SDLVid_MapBIFSCoordinate(SDLVidCtx *ctx, SDL_Event *sdl_evt, M4Event *m4_evt)
{
	if (ctx->fullscreen && ctx->is_3D_out) {
		m4_evt->mouse.x = sdl_evt->motion.x - ctx->fs_width/2;
		m4_evt->mouse.y = ctx->fs_height/2 - sdl_evt->motion.y;
	} else {
		m4_evt->mouse.x = sdl_evt->motion.x - ctx->width/2;
		m4_evt->mouse.y = ctx->height/2 - sdl_evt->motion.y;
	}
}


#define SDL_WINDOW_FLAGS			SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL
#define SDL_FULLSCREEN_FLAGS		SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_FULLSCREEN
#define SDL_GL_WINDOW_FLAGS			SDL_HWSURFACE | SDL_OPENGL | SDL_HWACCEL | SDL_RESIZABLE
#define SDL_GL_FULLSCREEN_FLAGS		SDL_HWSURFACE | SDL_OPENGL | SDL_HWACCEL | SDL_FULLSCREEN

void SDL_ResizeWindow(VideoOutput *dr, u32 width, u32 height) 
{
	SDLVID();
	M4Event evt;

	/*lock X mutex to make sure the event queue is not being processed*/
	MX_P(ctx->evt_mx);
	if (ctx->is_3D_out) {
		u32 flags = SDL_GL_WINDOW_FLAGS;
		if (ctx->os_handle) flags |= SDL_RESIZABLE;
		if (!ctx->screen) ctx->screen = SDL_SetVideoMode(width, height, 0, flags);
		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, ctx->screen->format->BitsPerPixel);
		SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
		SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 0);
		SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 0);
		SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0);
		SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 0);

		assert(width);
		assert(height);
		ctx->screen = SDL_SetVideoMode(width, height, 0, flags);
		assert(ctx->screen);
		ctx->width = width;
		ctx->height = height;
		evt.type = M4E_GL_CHANGED;
		dr->on_event(dr->evt_cbk_hdl, &evt);		
	} else {
		u32 flags = SDL_WINDOW_FLAGS;
		if (ctx->os_handle) flags |= SDL_RESIZABLE;
		ctx->screen = SDL_SetVideoMode(width, height, 0, flags);
		assert(ctx->screen);
	}
	
	if (!ctx->os_handle) SDLVid_SetCaption();

	MX_V(ctx->evt_mx);
}

/*SDL hack for window reuse is not really stable nor working on Win32...*/
static void SDL_SetHack(void *os_handle, Bool set_on)
{
#ifndef WIN32

	unsetenv("SDL_WINDOWID=");
	if (!os_handle) return;
	if (set_on) {
		char buf[16];
		snprintf(buf, sizeof(buf), "%u", (u32) os_handle);
		setenv("SDL_WINDOWID", buf, 1);
	}
	fprintf(stdout, "SDLout: env %s\n", getenv("SDL_WINDOWID"));
#endif
}

u32 SDL_EventProc(void *par)
{
	u32 flags, last_mouse_move;
	s32 wheel_delta;
	Bool cursor_on;
	SDL_Event sdl_evt;
	M4Event m4_evt;
	VideoOutput *dr = (VideoOutput *)par;
	SDLVID();

	SDL_SetHack(ctx->os_handle, 1);

	flags = SDL_WasInit(SDL_INIT_VIDEO);
	if (!(flags & SDL_INIT_VIDEO)) {
		if (SDL_InitSubSystem(SDL_INIT_VIDEO)<0) {
			ctx->sdl_th_state = 3;
			return 0;
		}
	}

	/*note we create the window on the first resize. This is the only way to keep synchronous with X and cope with GL threading*/
	
	ctx->sdl_th_state = 1;
	ctx->curs_def = SDL_GetCursor();
	ctx->curs_hand = SDLVid_LoadCursor(hand_data);
	ctx->curs_collide = SDLVid_LoadCursor(collide_data);
	SDL_EnableUNICODE(1);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

	wheel_delta = 0;
	last_mouse_move = SDL_GetTicks();
	cursor_on = 1;

	/*save display resolution (SDL doesn't give acees to that)*/
	ctx->display_width = ctx->display_height = 0;
#ifdef HAVE_X11
    {
    Display *dpy = XOpenDisplay(NULL);
    if (dpy) {
        ctx->display_width = DisplayWidth(dpy, DefaultScreen(dpy));
        ctx->display_height = DisplayHeight(dpy, DefaultScreen(dpy));
		XCloseDisplay(dpy);
    }
    }
#endif
#ifdef WIN32
    ctx->display_width = GetSystemMetrics(SM_CXSCREEN);
    ctx->display_height = GetSystemMetrics(SM_CYSCREEN);
#endif

	while (ctx->sdl_th_state==1) {
		/*after much testing: we must ensure nothing is using the event queue when resizing window.
		-- under X, it throws Xlib "unexpected async reply" under linux, therefore we don't wait events,
		we check for events and execute them if any
		-- under Win32, the SDL_SetVideoMode deadlocks, so we don't force exclusive access to events
		*/
#ifndef WIN32
		MX_P(ctx->evt_mx);
#endif
		while (SDL_PollEvent(&sdl_evt)) {
			switch (sdl_evt.type) {
			case SDL_VIDEORESIZE:
				m4_evt.type = M4E_NEEDRESIZE;
				m4_evt.size.width = sdl_evt.resize.w;
				m4_evt.size.height = sdl_evt.resize.h;
				dr->on_event(dr->evt_cbk_hdl, &m4_evt);
				break;
			case SDL_QUIT:
				if (ctx->sdl_th_state==1) {
					m4_evt.type = M4E_QUIT;
					dr->on_event(dr->evt_cbk_hdl, &m4_evt);
				} else {
					goto exit;
				}
				break;
			case SDL_VIDEOEXPOSE:
				m4_evt.type = M4E_REFRESH;
				dr->on_event(dr->evt_cbk_hdl, &m4_evt);
				break;

			/*keyboard*/
			case SDL_KEYDOWN:
			case SDL_KEYUP:
				m4_evt.key.m4_vk_code = SDLVid_TranslateActionKey(sdl_evt.key.keysym.sym);
				m4_evt.key.virtual_code = sdl_evt.key.keysym.sym;
				if (m4_evt.key.m4_vk_code) {
					m4_evt.type = (sdl_evt.key.type==SDL_KEYDOWN) ? M4E_VKEYDOWN : M4E_VKEYUP;
					if (m4_evt.key.m4_vk_code<=M4VK_RIGHT) m4_evt.key.virtual_code = 0;
					dr->on_event(dr->evt_cbk_hdl, &m4_evt);
					/*also send a normal key for non-key-sensors*/
					if (m4_evt.key.m4_vk_code>M4VK_RIGHT) goto send_key;
				} else {
send_key:
					m4_evt.type = (sdl_evt.key.type==SDL_KEYDOWN) ? M4E_KEYDOWN : M4E_KEYUP;
					dr->on_event(dr->evt_cbk_hdl, &m4_evt);
					if ((sdl_evt.key.type==SDL_KEYDOWN) && sdl_evt.key.keysym.unicode) {
						m4_evt.character.unicode_char = sdl_evt.key.keysym.unicode;
						m4_evt.type = M4E_CHAR;
						dr->on_event(dr->evt_cbk_hdl, &m4_evt);
					}
				}
				break;

			/*mouse*/
			case SDL_MOUSEMOTION:
				last_mouse_move = SDL_GetTicks();
				m4_evt.type = M4E_MOUSEMOVE;
				SDLVid_MapBIFSCoordinate(ctx, &sdl_evt, &m4_evt);
				dr->on_event(dr->evt_cbk_hdl, &m4_evt);
				break;
			case SDL_MOUSEBUTTONDOWN:
			case SDL_MOUSEBUTTONUP:
				last_mouse_move = SDL_GetTicks();
				SDLVid_MapBIFSCoordinate(ctx, &sdl_evt, &m4_evt);
				switch (sdl_evt.button.button) {
				case SDL_BUTTON_LEFT:
					m4_evt.type = (sdl_evt.type==SDL_MOUSEBUTTONUP) ? M4E_LEFTUP : M4E_LEFTDOWN;
					dr->on_event(dr->evt_cbk_hdl, &m4_evt);
					break;
				case SDL_BUTTON_MIDDLE:
					m4_evt.type = (sdl_evt.type==SDL_MOUSEBUTTONUP) ? M4E_MIDDLEUP : M4E_MIDDLEDOWN;
					dr->on_event(dr->evt_cbk_hdl, &m4_evt);
					break;
				case SDL_BUTTON_RIGHT:
					m4_evt.type = (sdl_evt.type==SDL_MOUSEBUTTONUP) ? M4E_RIGHTUP : M4E_RIGHTDOWN;
					dr->on_event(dr->evt_cbk_hdl, &m4_evt);
					break;
#ifdef SDL_BUTTON_WHEELUP
				case SDL_BUTTON_WHEELUP:
				case SDL_BUTTON_WHEELDOWN:
					/*SDL handling is not perfect there, it just says up/down but no info on how much
					the wheel was rotated...*/
					wheel_delta += (sdl_evt.button.button==SDL_BUTTON_WHEELUP) ? 1 : -1;
					m4_evt.type = M4E_MOUSEWHEEL;
					m4_evt.mouse.wheel_pos = FLT2FIX(wheel_delta);
					dr->on_event(dr->evt_cbk_hdl, &m4_evt);
					break;
#endif
				}
				break;
			}
		}

#ifndef WIN32
		MX_V(ctx->evt_mx);
#endif

		/*looks like this hides the cursor for ever when switching back from FS*/
#if 0
		if (ctx->fullscreen && (last_mouse_move + 2000 < SDL_GetTicks()) ) {
			if (cursor_on) SDL_ShowCursor(0);
			cursor_on = 0;
		} else if (!cursor_on) {
			SDL_ShowCursor(1);
			cursor_on = 1;
		}
#endif
		
		M4_Sleep(5);
	}

exit:
	SDLVid_DestroyObjects(ctx);
	SDL_FreeCursor(ctx->curs_hand);
	SDL_FreeCursor(ctx->curs_collide);
	SDL_QuitSubSystem(SDL_INIT_VIDEO);
	ctx->sdl_th_state = 3;
	return 0;
}


M4Err SDLVid_SetupHardware(struct _video_out *dr, void *os_handle, u32 no_proc_override, M4GLConfig *cfg)
{
	SDLVID();
	/*SDL hack for window reuse is not really stable nor working on Win32...*/
#ifdef WIN32
	if (os_handle) return M4NotSupported;
#endif

	ctx->os_handle = os_handle;
	ctx->is_init = 0;
	ctx->is_3D_out = cfg ? 1 : 0;
	if (!SDLOUT_InitSDL()) return M4IOErr;
	ctx->sdl_th_state = 0;
	TH_Run(ctx->sdl_th, SDL_EventProc, dr);
	while (!ctx->sdl_th_state) M4_Sleep(10);
	if (ctx->sdl_th_state==3) {
		SDLOUT_CloseSDL();
		ctx->sdl_th_state = 0;
		return M4IOErr;
	}
	ctx->is_init = 1;
	return M4OK;
}

static void SDLVid_Shutdown(VideoOutput *dr)
{
	SDLVID();
	/*remove all surfaces*/

	if (!ctx->is_init) return;
	if (ctx->sdl_th_state==1) {
		SDL_Event evt;
		ctx->sdl_th_state = 2;
		evt.type = SDL_QUIT;
		SDL_PushEvent(&evt);
		while (ctx->sdl_th_state != 3) M4_Sleep(100);
	}
	SDLOUT_CloseSDL();
	ctx->is_init = 0;
}

M4Err SDLVid_Resize(VideoOutput *dr, u32 newWidth, u32 newHeight)
{
	SDLVID();

	/*ignored in 3D*/
	if (ctx->is_3D_out) return M4OK;

	if (ctx->back_buffer && ((u32) ctx->back_buffer->w==newWidth) && ((u32) ctx->back_buffer->h==newHeight)) {
		return M4OK;
	}
	if (ctx->back_buffer) SDL_FreeSurface(ctx->back_buffer);
	ctx->back_buffer = SDL_CreateRGBSurface(0L, newWidth, newHeight, ctx->screen->format->BitsPerPixel, ctx->screen->format->Rmask, ctx->screen->format->Gmask, ctx->screen->format->Bmask, 0);
	ctx->width = newWidth;
	ctx->height = newHeight;
	if (!ctx->back_buffer) return M4IOErr;
	return M4OK;
}

M4Err SDLVid_SetFullScreen(VideoOutput *dr, u32 bFullScreenOn, u32 *screen_width, u32 *screen_height)
{
	u32 bpp, pref_bpp;
	SDLVID();

	if (ctx->fullscreen==bFullScreenOn) return M4OK;

	/*lock to get sure the event queue is not processed under X*/
	MX_P(ctx->evt_mx);
	ctx->fullscreen = bFullScreenOn;

	pref_bpp = bpp = ctx->screen->format->BitsPerPixel;

	if (ctx->fullscreen) {
		u32 flags;
		Bool switch_res = 0;
		char *sOpt = PMI_GetOpt(dr, "Video", "SwitchResolution");
		if (sOpt && !stricmp(sOpt, "yes")) switch_res = 1;
		if (!ctx->display_width || !ctx->display_height) switch_res = 1;

		SDL_SetHack(ctx->os_handle, 0);

		flags = ctx->is_3D_out ? SDL_GL_FULLSCREEN_FLAGS : SDL_FULLSCREEN_FLAGS;
		ctx->store_width = *screen_width;
		ctx->store_height = *screen_height;
		if (switch_res) {
			u32 i;
			ctx->fs_width = *screen_width;
			ctx->fs_height = *screen_height;
			for(i=0; i<nb_video_modes; i++) {
				if (ctx->fs_width<=video_modes[2*i] && ctx->fs_height<=video_modes[2*i + 1]) {
					if ((pref_bpp = SDL_VideoModeOK(video_modes[2*i], video_modes[2*i+1], bpp, flags))) {
						ctx->fs_width = video_modes[2*i];
						ctx->fs_height = video_modes[2*i + 1];
						break;
					}
				}
			}
		} else {
			ctx->fs_width = ctx->display_width;
			ctx->fs_height = ctx->display_height;
		}
		ctx->screen = SDL_SetVideoMode(ctx->fs_width, ctx->fs_height, pref_bpp, flags);
		/*we switched bpp, clean all objects*/
		if (bpp != pref_bpp) SDLVid_DestroyObjects(ctx);
		*screen_width = ctx->fs_width;
		*screen_height = ctx->fs_height;
		/*GL has changed*/
		if (ctx->is_3D_out) {
			M4Event evt;
			evt.type = M4E_GL_CHANGED;
			dr->on_event(dr->evt_cbk_hdl, &evt);
		}
	} else {
		SDL_SetHack(ctx->os_handle, 1);

		SDL_ResizeWindow(dr, ctx->store_width, ctx->store_height);
		*screen_width = ctx->store_width;
		*screen_height = ctx->store_height;
	}
	MX_V(ctx->evt_mx);
	if (!ctx->screen) return M4IOErr;
	return M4OK;
}



static M4Err SDLVid_FlushVideo(VideoOutput *dr, M4Window *dest)
{
	SDL_Rect rc;
	SDLVID();
	/*if resizing don't process otherwise we may deadlock*/
	if (!ctx->screen) return M4OK;

	if (ctx->is_3D_out) {
		SDL_GL_SwapBuffers();
		return M4OK;
	}
	if (!ctx->back_buffer) return M4BadParam;

	if ((dest->w != (u32) ctx->back_buffer->w) || (dest->h != (u32) ctx->back_buffer->h)) {
		SDLVid_Blit(dr, 0, (u32) -1, NULL, dest);
	} else {
		rc.x = dest->x; rc.y = dest->y; rc.w = dest->w; rc.h = dest->h;
		SDL_BlitSurface(ctx->back_buffer, NULL, ctx->screen, &rc);
	}
	SDL_Flip(ctx->screen);
	return M4OK;
}

static void SDLVid_SetCursor(VideoOutput *dr, u32 cursor_type)
{
	SDLVID();
	switch (cursor_type) {
	case M4CursorAnchor:
	case M4CursorTouch:
	case M4CursorRotate:
	case M4CursorProximity:
	case M4CursorPlane:
		SDL_SetCursor(ctx->curs_hand);
		break;
	case M4CursorCollide:
		SDL_SetCursor(ctx->curs_collide);
		break;
	default:
		SDL_SetCursor(ctx->curs_def);
		break;
	}
}

static M4Err SDLVid_PushEvent(VideoOutput *dr, M4Event *evt)
{
	switch (evt->type) {
	case M4E_SET_CURSOR:
		SDLVid_SetCursor(dr, evt->cursor.cursor_type);
		break;
	case M4E_SET_CAPTION:
		SDL_WM_SetCaption(evt->caption.caption, NULL);
		break;
	case M4E_SHOWHIDE:
		/*the only way to have proper show/hide with SDL is to shutdown the video system and reset it up
		which we don't want to do since the setup MUST occur in the rendering thread for some configs (openGL)*/
		return M4NotSupported;
	case M4E_NEEDRESIZE:
		SDL_ResizeWindow(dr, evt->size.width, evt->size.height);
		break;
	}
	return M4OK;
}

void *SDL_NewVideo()
{
	SDLVidCtx *ctx;
	VideoOutput *driv;
	
	driv = malloc(sizeof(VideoOutput));
	memset(driv, 0, sizeof(VideoOutput));
	M4_REG_PLUG(driv, M4_VIDEO_OUTPUT_INTERFACE, "SDL Video Output", "gpac distribution", 0);

	ctx = malloc(sizeof(SDLVidCtx));
	memset(ctx, 0, sizeof(SDLVidCtx));
	ctx->surfaces = NewChain();
	ctx->sdl_th = NewThread();
	ctx->evt_mx = NewMutex();
	
	driv->opaque = ctx;
	driv->SetupHardware = SDLVid_SetupHardware;
	driv->Shutdown = SDLVid_Shutdown;
	driv->Resize = SDLVid_Resize;
	driv->SetFullScreen = SDLVid_SetFullScreen;
	driv->FlushVideo = SDLVid_FlushVideo;
	driv->PushEvent = SDLVid_PushEvent;
	driv->bHas3DSupport = 1;
	SDL_SetupVideo2D(driv);
	return driv;
}

void SDL_DeleteVideo(void *ifce)
{
	VideoOutput *dr = (VideoOutput *)ifce;
	SDLVID();

	DeleteChain(ctx->surfaces);
	TH_Delete(ctx->sdl_th);
	MX_Delete(ctx->evt_mx);
	free(ctx);
	free(dr);
}

