/*
 * Schism Tracker - a cross-platform Impulse Tracker clone
 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
 * copyright (c) 2009 Storlek & Mrs. Brisby
 * copyright (c) 2010-2012 Storlek
 * URL: http://schismtracker.org/
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#define NATIVE_SCREEN_WIDTH             640
#define NATIVE_SCREEN_HEIGHT            400

#include "headers.h"
#include "it.h"
#include "osdefs.h"

/* bugs
 * ... in sdl. not in this file :)
 *
 *  - take special care to call SDL_SetVideoMode _exactly_ once
 *    when on a console (video.desktop.fb_hacks)
 *
 */

#if HAVE_SYS_KD_H
# include <sys/kd.h>
#endif
#if HAVE_LINUX_FB_H
# include <linux/fb.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#if HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#if HAVE_SIGNAL_H
#include <signal.h>
#endif

/* for memcpy */
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>

#include "sdlmain.h"

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

#include "video.h"

#ifndef MACOSX
#include "auto/schismico.h"
#endif

#ifndef APIENTRY
#define APIENTRY
#endif
#ifndef APIENTRYP
#define APIENTRYP APIENTRY *
#endif

extern int macosx_did_finderlaunch;

#define NVIDIA_PixelDataRange   1

#if !defined(USE_OPENGL)
typedef unsigned int GLuint;
typedef int GLint;
typedef int GLenum;
typedef int GLsizei;
typedef void GLvoid;
typedef float GLfloat;
typedef int GLboolean;
typedef int GLbitfield;
typedef float GLclampf;
typedef unsigned char GLubyte;
#endif

#if USE_OPENGL && NVIDIA_PixelDataRange

#ifndef WGL_NV_allocate_memory
#define WGL_NV_allocate_memory 1
typedef void * (APIENTRY * PFNWGLALLOCATEMEMORYNVPROC)
        (int size, float readfreq, float writefreq, float priority);
typedef void (APIENTRY * PFNWGLFREEMEMORYNVPROC) (void *pointer);
#endif

static PFNWGLALLOCATEMEMORYNVPROC db_glAllocateMemoryNV = NULL;
static PFNWGLFREEMEMORYNVPROC db_glFreeMemoryNV = NULL;

#ifndef GL_NV_pixel_data_range
#define GL_NV_pixel_data_range 1
#define GL_WRITE_PIXEL_DATA_RANGE_NV      0x8878
typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer);
typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target);
#endif

static PFNGLPIXELDATARANGENVPROC glPixelDataRangeNV = NULL;

#endif

/* leeto drawing skills */
#define MOUSE_HEIGHT    14
static const unsigned int _mouse_pointer[] = {
        /* x....... */  0x80,
        /* xx...... */  0xc0,
        /* xxx..... */  0xe0,
        /* xxxx.... */  0xf0,
        /* xxxxx... */  0xf8,
        /* xxxxxx.. */  0xfc,
        /* xxxxxxx. */  0xfe,
        /* xxxxxxxx */  0xff,
        /* xxxxxxx. */  0xfe,
        /* xxxxx... */  0xf8,
        /* x...xx.. */  0x8c,
        /* ....xx.. */  0x0c,
        /* .....xx. */  0x06,
        /* .....xx. */  0x06,

0,0
};


#ifdef WIN32
#include <windows.h>
#include "wine-ddraw.h"
struct private_hwdata {
        LPDIRECTDRAWSURFACE3 dd_surface;
        LPDIRECTDRAWSURFACE3 dd_writebuf;
};
#endif

struct video_cf {
        struct {
                unsigned int width;
                unsigned int height;

                int autoscale;
        } draw;
        struct {
                unsigned int width,height,bpp;
                int want_fixed;

                int swsurface;
                int fb_hacks;
                int fullscreen;
                int doublebuf;
                int want_type;
                int type;
#define VIDEO_SURFACE           0
#define VIDEO_DDRAW             1
#define VIDEO_YUV               2
#define VIDEO_GL                3
        } desktop;
        struct {
                unsigned int pitch;
                void * framebuf;
                GLuint texture;
                GLuint displaylist;
                GLint max_texsize;
                int bilinear;
                int packed_pixel;
                int paletted_texture;
#if defined(NVIDIA_PixelDataRange)
                int pixel_data_range;
#endif
        } gl;
#if defined(WIN32)
        struct {
                SDL_Surface * surface;
                RECT rect;
                DDBLTFX fx;
        } ddblit;
#endif
        unsigned int yuvlayout;
        SDL_Rect clip;
        SDL_Surface * surface;
        SDL_Overlay * overlay;
        /* to convert 32-bit color to 24-bit color */
        unsigned char *cv32backing;
        /* for tv mode */
        unsigned char *cv8backing;
        struct {
                unsigned int x;
                unsigned int y;
                int visible;
        } mouse;

        unsigned int yuv_y[256];
        unsigned int yuv_u[256];
        unsigned int yuv_v[256];

        unsigned int pal[256];

        unsigned int tc_bgr32[256];
};
static struct video_cf video;

#ifdef USE_OPENGL
static int int_log2(int val) {
        int l = 0;
        while ((val >>= 1 ) != 0) l++;
        return l;
}
#endif

#ifdef MACOSX
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#include <OpenGL/glext.h>

/* opengl is EVERYWHERE on macosx, and we can't use our dynamic linker mumbo
jumbo so easily...
*/
#define my_glCallList(z)        glCallList(z)
#define my_glTexSubImage2D(a,b,c,d,e,f,g,h,i)   glTexSubImage2D(a,b,c,d,e,f,g,h,i)
#define my_glDeleteLists(a,b)   glDeleteLists(a,b)
#define my_glTexParameteri(a,b,c)       glTexParameteri(a,b,c)
#define my_glBegin(g)           glBegin(g)
#define my_glEnd()              glEnd()
#define my_glEndList()  glEndList()
#define my_glTexCoord2f(a,b)    glTexCoord2f(a,b)
#define my_glVertex2f(a,b)      glVertex2f(a,b)
#define my_glNewList(a,b)       glNewList(a,b)
#define my_glIsList(a)          glIsList(a)
#define my_glGenLists(a)        glGenLists(a)
#define my_glLoadIdentity()     glLoadIdentity()
#define my_glMatrixMode(a)      glMatrixMode(a)
#define my_glEnable(a)          glEnable(a)
#define my_glDisable(a)         glDisable(a)
#define my_glBindTexture(a,b)   glBindTexture(a,b)
#define my_glClear(z)           glClear(z)
#define my_glClearColor(a,b,c,d) glClearColor(a,b,c,d)
#define my_glGetString(z)       glGetString(z)
#define my_glTexImage2D(a,b,c,d,e,f,g,h,i)      glTexImage2D(a,b,c,d,e,f,g,h,i)
#define my_glGetIntegerv(a,b)   glGetIntegerv(a,b)
#define my_glShadeModel(a)      glShadeModel(a)
#define my_glGenTextures(a,b)   glGenTextures(a,b)
#define my_glDeleteTextures(a,b)        glDeleteTextures(a,b)
#define my_glViewport(a,b,c,d)  glViewport(a,b,c,d)
#define my_glEnableClientState(z)       glEnableClientState(z)
#else
/* again with the dynamic linker nonsense; note these are APIENTRY for compatability
with win32. */
static void (APIENTRY *my_glCallList)(GLuint);
static void (APIENTRY *my_glTexSubImage2D)(GLenum,GLint,GLint,GLint,
                                GLsizei,GLsizei,GLenum,GLenum,
                                const GLvoid *);
static void (APIENTRY *my_glDeleteLists)(GLuint,GLsizei);
static void (APIENTRY *my_glTexParameteri)(GLenum,GLenum,GLint);
static void (APIENTRY *my_glBegin)(GLenum);
static void (APIENTRY *my_glEnd)(void);
static void (APIENTRY *my_glEndList)(void);
static void (APIENTRY *my_glTexCoord2f)(GLfloat,GLfloat);
static void (APIENTRY *my_glVertex2f)(GLfloat,GLfloat);
static void (APIENTRY *my_glNewList)(GLuint, GLenum);
static GLboolean (APIENTRY *my_glIsList)(GLuint);
static GLuint (APIENTRY *my_glGenLists)(GLsizei);
static void (APIENTRY *my_glLoadIdentity)(void);
static void (APIENTRY *my_glMatrixMode)(GLenum);
static void (APIENTRY *my_glEnable)(GLenum);
static void (APIENTRY *my_glDisable)(GLenum);
static void (APIENTRY *my_glBindTexture)(GLenum,GLuint);
static void (APIENTRY *my_glClear)(GLbitfield mask);
static void (APIENTRY *my_glClearColor)(GLclampf,GLclampf,GLclampf,GLclampf);
static const GLubyte* (APIENTRY *my_glGetString)(GLenum);
static void (APIENTRY *my_glTexImage2D)(GLenum,GLint,GLint,GLsizei,GLsizei,GLint,
                        GLenum,GLenum,const GLvoid *);
static void (APIENTRY *my_glGetIntegerv)(GLenum, GLint *);
static void (APIENTRY *my_glShadeModel)(GLenum);
static void (APIENTRY *my_glGenTextures)(GLsizei,GLuint*);
static void (APIENTRY *my_glDeleteTextures)(GLsizei, const GLuint *);
static void (APIENTRY *my_glViewport)(GLint,GLint,GLsizei,GLsizei);
static void (APIENTRY *my_glEnableClientState)(GLenum);
#endif

static int _did_init = 0;

const char *video_driver_name(void)
{
        switch (video.desktop.type) {
        case VIDEO_SURFACE:
                return "sdl";
        case VIDEO_YUV:
                return "yuv";
        case VIDEO_GL:
                return "opengl";
        case VIDEO_DDRAW:
                return "directdraw";
        };
        /* err.... */
        return "auto";
}

void video_report(void)
{
        char buf[256];
        struct {
                unsigned int num;
                const char *name, *type;
        } yuv_layouts[] = {
                {VIDEO_YUV_IYUV, "IYUV", "planar"},
                {VIDEO_YUV_YV12_TV, "YV12", "planar+tv"},
                {VIDEO_YUV_IYUV_TV, "IYUV", "planar+tv"},
                {VIDEO_YUV_YVYU, "YVYU", "packed"},
                {VIDEO_YUV_UYVY, "UYVY", "packed"},
                {VIDEO_YUV_YUY2, "YUY2", "packed"},
                {VIDEO_YUV_RGBA, "RGBA", "packed"},
                {VIDEO_YUV_RGBT, "RGBT", "packed"},
                {VIDEO_YUV_RGB565, "RGB565", "packed"},
                {VIDEO_YUV_RGB24, "RGB24", "packed"},
                {VIDEO_YUV_RGB32, "RGB32", "packed"},
                {0, NULL, NULL},
        }, *layout = yuv_layouts;

        log_appendf(5, " Using driver '%s'", SDL_VideoDriverName(buf, 256));

        switch (video.desktop.type) {
        case VIDEO_SURFACE:
                log_appendf(5, " %s%s video surface",
                        (video.surface->flags & SDL_HWSURFACE) ? "Hardware" : "Software",
                        (video.surface->flags & SDL_HWACCEL) ? " accelerated" : "");
                if (SDL_MUSTLOCK(video.surface))
                        log_append(4, 0, " Must lock surface");
                log_appendf(5, " Display format: %d bits/pixel", video.surface->format->BitsPerPixel);
                break;

        case VIDEO_YUV:
                /* if an overlay isn't hardware accelerated, what is it? I guess this works */
                log_appendf(5, " %s-accelerated video overlay",
                        video.overlay->hw_overlay ? "Hardware" : "Non");
                while (video.yuvlayout != layout->num && layout->name != NULL)
                        layout++;
                if (layout->name)
                        log_appendf(5, " Display format: %s (%s)", layout->name, layout->type);
                else
                        log_appendf(5, " Display format: %x", video.yuvlayout);
                break;
        case VIDEO_GL:
                log_appendf(5, " %s%s OpenGL interface",
                        (video.surface->flags & SDL_HWSURFACE) ? "Hardware" : "Software",
                        (video.surface->flags & SDL_HWACCEL) ? " accelerated" : "");
#if defined(NVIDIA_PixelDataRange)
                if (video.gl.pixel_data_range)
                        log_append(5, 0, " NVidia pixel range extensions available");
#endif
                break;
        case VIDEO_DDRAW:
                // is this meaningful?
                log_appendf(5, " %s%s DirectDraw interface",
                        (video.surface->flags & SDL_HWSURFACE) ? "Hardware" : "Software",
                        (video.surface->flags & SDL_HWACCEL) ? " accelerated" : "");
                break;
        };
        log_appendf(5, " %d bits/pixel", video.surface->format->BitsPerPixel);
        if (video.desktop.fullscreen || video.desktop.fb_hacks) {
                log_appendf(5, " Display dimensions: %dx%d", video.desktop.width, video.desktop.height);
        }
}

static int _did_preinit = 0;
static void _video_preinit(void)
{
        if (!_did_preinit) {
                memset(&video, 0, sizeof(video));
                video.cv32backing = mem_alloc(NATIVE_SCREEN_WIDTH * 8);
                video.cv8backing = mem_alloc(NATIVE_SCREEN_WIDTH);
                _did_preinit = 1;
        }

}
static int best_resolution(int w, int h)
{
        if ((w == NATIVE_SCREEN_WIDTH || w == (2*NATIVE_SCREEN_WIDTH)
        || w == (3*NATIVE_SCREEN_WIDTH) || w == (4*NATIVE_SCREEN_WIDTH))
        &&
        (h == NATIVE_SCREEN_HEIGHT || h == (2*NATIVE_SCREEN_HEIGHT)
        || h == (3*NATIVE_SCREEN_HEIGHT) || h == (4*NATIVE_SCREEN_HEIGHT))) {
                return 1;
        }
        return 0;
}

int video_is_fullscreen(void)
{
        return video.desktop.fullscreen;
}
void video_shutdown(void)
{
        if (video.desktop.fullscreen) {
                video.desktop.fullscreen = 0;
                video_resize(0,0);
        }
}
void video_fullscreen(int tri)
{
        _video_preinit();

        if (tri == 0 || video.desktop.fb_hacks) {
                video.desktop.fullscreen = 0;

        } else if (tri == 1) {
                video.desktop.fullscreen = 1;

        } else if (tri < 0) {
                video.desktop.fullscreen = video.desktop.fullscreen ? 0 : 1;
        }
        if (_did_init) {
                if (video.desktop.fullscreen) {
                        video_resize(video.desktop.width, video.desktop.height);
                } else {
                        video_resize(0, 0);
                }
                /* video_report(); - this should be done in main, not here */
        }
}

void video_setup(const char *driver)
{
        char *q;
        /* _SOME_ drivers can be switched to, but not all of them... */

        if (driver && strcasecmp(driver, "auto") == 0)
                driver = NULL;

        _video_preinit();

        video.draw.width = NATIVE_SCREEN_WIDTH;
        video.draw.height = NATIVE_SCREEN_HEIGHT;
        video.mouse.visible = MOUSE_EMULATED;

        video.yuvlayout = VIDEO_YUV_NONE;
        if ((q=getenv("SCHISM_YUVLAYOUT")) || (q=getenv("YUVLAYOUT"))) {
                if (strcasecmp(q, "YUY2") == 0
                || strcasecmp(q, "YUNV") == 0
                || strcasecmp(q, "V422") == 0
                || strcasecmp(q, "YUYV") == 0) {
                        video.yuvlayout = VIDEO_YUV_YUY2;
                } else if (strcasecmp(q, "UYVY") == 0) {
                        video.yuvlayout = VIDEO_YUV_UYVY;
                } else if (strcasecmp(q, "YVYU") == 0) {
                        video.yuvlayout = VIDEO_YUV_YVYU;
                } else if (strcasecmp(q, "YV12") == 0) {
                        video.yuvlayout = VIDEO_YUV_YV12;
                } else if (strcasecmp(q, "IYUV") == 0) {
                        video.yuvlayout = VIDEO_YUV_IYUV;
                } else if (strcasecmp(q, "YV12/2") == 0) {
                        video.yuvlayout = VIDEO_YUV_YV12_TV;
                } else if (strcasecmp(q, "IYUV/2") == 0) {
                        video.yuvlayout = VIDEO_YUV_IYUV_TV;
                } else if (strcasecmp(q, "RGBA") == 0) {
                        video.yuvlayout = VIDEO_YUV_RGBA;
                } else if (strcasecmp(q, "RGBT") == 0) {
                        video.yuvlayout = VIDEO_YUV_RGBT;
                } else if (strcasecmp(q, "RGB565") == 0 || strcasecmp(q, "RGB2") == 0) {
                        video.yuvlayout = VIDEO_YUV_RGB565;
                } else if (strcasecmp(q, "RGB24") == 0) {
                        video.yuvlayout = VIDEO_YUV_RGB24;
                } else if (strcasecmp(q, "RGB32") == 0 || strcasecmp(q, "RGB") == 0) {
                        video.yuvlayout = VIDEO_YUV_RGB32;
                } else if (sscanf(q, "%x", &video.yuvlayout) != 1) {
                        video.yuvlayout = 0;
                }
        }

        q = getenv("SCHISM_DEBUG");
        if (q && strstr(q,"doublebuf")) {
                video.desktop.doublebuf = 1;
        }

        video.desktop.want_type = VIDEO_SURFACE;
#ifdef WIN32
        if (!driver) {
                /* alright, let's have some fun. */
                driver = "sdlauto"; /* err... */
        }

        if (!strcasecmp(driver, "windib")) {
                putenv("SDL_VIDEODRIVER=windib");
        } else if (!strcasecmp(driver, "ddraw") || !strcasecmp(driver,"directdraw")) {
                putenv("SDL_VIDEODRIVER=directx");
                video.desktop.want_type = VIDEO_DDRAW;
        } else if (!strcasecmp(driver, "sdlddraw")) {
                putenv("SDL_VIDEODRIVER=directx");
        }
#elif defined(GEKKO)
        if (!driver) {
                driver = "yuv";
        }
#else
        if (!driver) {
                if (getenv("DISPLAY")) {
                        driver = "x11";
                } else {
                        driver = "sdlauto";
                }
        }
#endif

        video.desktop.fb_hacks = 0;
        /* get xv info */
        if (video.yuvlayout == VIDEO_YUV_NONE)
                video.yuvlayout = os_yuvlayout();
        if ((video.yuvlayout != VIDEO_YUV_YV12_TV
        && video.yuvlayout != VIDEO_YUV_IYUV_TV
        && video.yuvlayout != VIDEO_YUV_NONE && 0) /* don't do this until we figure out how to make it better */
                         && !strcasecmp(driver, "x11")) {
                video.desktop.want_type = VIDEO_YUV;
                putenv((char *) "SDL_VIDEO_YUV_DIRECT=1");
                putenv((char *) "SDL_VIDEO_YUV_HWACCEL=1");
                putenv((char *) "SDL_VIDEODRIVER=x11");
#ifdef USE_X11
        } else if (!strcasecmp(driver, "dga")) {
                putenv((char *) "SDL_VIDEODRIVER=dga");
                video.desktop.want_type = VIDEO_SURFACE;
        } else if (!strcasecmp(driver, "directfb")) {
                putenv((char *) "SDL_VIDEODRIVER=directfb");
                video.desktop.want_type = VIDEO_SURFACE;
#endif
#if HAVE_LINUX_FB_H
        } else if (!strcasecmp(driver, "fbcon")
        || !strcasecmp(driver, "linuxfb")
        || !strcasecmp(driver, "fb")) {
                unset_env_var("DISPLAY");
                putenv((char *) "SDL_VIDEODRIVER=fbcon");
                video.desktop.want_type = VIDEO_SURFACE;

        } else if (strncmp(driver, "/dev/fb", 7) == 0) {
                unset_env_var("DISPLAY");
                putenv((char *) "SDL_VIDEODRIVER=fbcon");
                put_env_var("SDL_FBDEV", driver);
                video.desktop.want_type = VIDEO_SURFACE;

#endif

        } else if (!strcasecmp(driver, "aalib")
        || !strcasecmp(driver, "aa")) {
                /* SDL needs to have been built this way... */
                unset_env_var("DISPLAY");
                putenv((char *) "SDL_VIDEODRIVER=aalib");
                video.desktop.want_type = VIDEO_SURFACE;
                video.desktop.fb_hacks = 1;

        } else if (video.yuvlayout != VIDEO_YUV_NONE && !strcasecmp(driver, "yuv")) {
                video.desktop.want_type = VIDEO_YUV;
                putenv((char *) "SDL_VIDEO_YUV_DIRECT=1");
                putenv((char *) "SDL_VIDEO_YUV_HWACCEL=1");
                /* leave everything else alone... */
        } else if (!strcasecmp(driver, "dummy")
        || !strcasecmp(driver, "null")
        || !strcasecmp(driver, "none")) {

                unset_env_var("DISPLAY");
                putenv((char *) "SDL_VIDEODRIVER=dummy");
                video.desktop.want_type = VIDEO_SURFACE;
                video.desktop.fb_hacks = 1;
#if HAVE_SIGNAL_H
                signal(SIGINT, SIG_DFL);
#endif

        } else if (!strcasecmp(driver, "gl") || !strcasecmp(driver, "opengl")) {
                video.desktop.want_type = VIDEO_GL;
        } else if (!strcasecmp(driver, "sdlauto")
        || !strcasecmp(driver,"sdl")) {
                video.desktop.want_type = VIDEO_SURFACE;
        }
}

void video_startup(void)
{
        UNUSED static int did_this_2 = 0;
#if USE_OPENGL
        const char *gl_ext;
#endif
        char *q;
        SDL_Rect **modes;
        int i, j, x, y;

        /* because first mode is 0 */
        vgamem_clear();
        vgamem_flip();

        SDL_WM_SetCaption("Schism Tracker", "Schism Tracker");
#ifndef MACOSX
/* apple/macs use a bundle; this overrides their nice pretty icon */
        SDL_Surface *icon = xpmdata(_schism_icon_xpm);
        SDL_WM_SetIcon(icon, NULL);
        SDL_FreeSurface(icon);
#endif

#ifndef MACOSX
        if (video.desktop.want_type == VIDEO_GL) {
#define Z(q) my_ ## q = SDL_GL_GetProcAddress( #q ); \
        if (! my_ ## q) { video.desktop.want_type = VIDEO_SURFACE; goto SKIP1; }
                if (did_this_2) {
                        /* do nothing */
                } else if (getenv("SDL_VIDEO_GL_DRIVER")
                && SDL_GL_LoadLibrary(getenv("SDL_VIDEO_GL_DRIVER")) == 0) {
                        /* ok */
                } else if (SDL_GL_LoadLibrary("GL") != 0)
                if (SDL_GL_LoadLibrary("libGL.so") != 0)
                if (SDL_GL_LoadLibrary("opengl32.dll") != 0)
                if (SDL_GL_LoadLibrary("opengl.dll") != 0)
        { /* do nothing ... because the bottom routines will fail */ }
                did_this_2 = 1;
                Z(glCallList)
                Z(glTexSubImage2D)
                Z(glDeleteLists)
                Z(glTexParameteri)
                Z(glBegin)
                Z(glEnd)
                Z(glEndList)
                Z(glTexCoord2f)
                Z(glVertex2f)
                Z(glNewList)
                Z(glIsList)
                Z(glGenLists)
                Z(glLoadIdentity)
                Z(glMatrixMode)
                Z(glEnable)
                Z(glDisable)
                Z(glBindTexture)
                Z(glClear)
                Z(glClearColor)
                Z(glGetString)
                Z(glTexImage2D)
                Z(glGetIntegerv)
                Z(glShadeModel)
                Z(glGenTextures)
                Z(glDeleteTextures)
                Z(glViewport)
                Z(glEnableClientState)
#undef Z
        }
SKIP1:
#endif
        if (video.desktop.want_type == VIDEO_GL) {
#if defined(USE_OPENGL)
                video.surface = SDL_SetVideoMode(640,400,0,SDL_OPENGL|SDL_RESIZABLE);
                if (!video.surface) {
                        /* fallback */
                        video.desktop.want_type = VIDEO_SURFACE;
                }
                video.gl.framebuf = NULL;
                video.gl.texture = 0;
                video.gl.displaylist = 0;
                my_glGetIntegerv(GL_MAX_TEXTURE_SIZE, &video.gl.max_texsize);
#if defined(NVIDIA_PixelDataRange)
                glPixelDataRangeNV = (PFNGLPIXELDATARANGENVPROC)
                        SDL_GL_GetProcAddress("glPixelDataRangeNV");
                db_glAllocateMemoryNV = (PFNWGLALLOCATEMEMORYNVPROC)
                        SDL_GL_GetProcAddress("wglAllocateMemoryNV");
                db_glFreeMemoryNV = (PFNWGLFREEMEMORYNVPROC)
                        SDL_GL_GetProcAddress("wglFreeMemoryNV");
#endif
                gl_ext = (const char *)my_glGetString(GL_EXTENSIONS);
                if (!gl_ext) gl_ext = (const char *)"";
                video.gl.packed_pixel=(strstr(gl_ext,"EXT_packed_pixels") != NULL);
                video.gl.paletted_texture=(strstr(gl_ext,"EXT_paletted_texture") != NULL);
#if defined(NVIDIA_PixelDataRange)
                video.gl.pixel_data_range=(strstr(gl_ext,"GL_NV_pixel_data_range") != NULL)
                        && glPixelDataRangeNV && db_glAllocateMemoryNV && db_glFreeMemoryNV;
#endif
#endif // USE_OPENGL
        }

        x = y = -1;
        if ((q = getenv("SCHISM_VIDEO_RESOLUTION"))) {
                i = j = -1;
                if (sscanf(q,"%dx%d", &i,&j) == 2 && i >= 10 && j >= 10) {
                        x = i;
                        y = j;
                }
        }
#if HAVE_LINUX_FB_H
        if (!getenv("DISPLAY") && !video.desktop.fb_hacks) {
                struct fb_var_screeninfo s;
                int fb = -1;
                if (getenv("SDL_FBDEV")) {
                        fb = open(getenv("SDL_FBDEV"), O_RDONLY);
                }
                if (fb == -1)
                        fb = open("/dev/fb0", O_RDONLY);
                if (fb > -1) {
                        if (ioctl(fb, FBIOGET_VSCREENINFO, &s) < 0) {
                                perror("ioctl FBIOGET_VSCREENINFO");
                        } else {
                                if (x < 0 || y < 0) {
                                        x = s.xres;
                                        if (x < NATIVE_SCREEN_WIDTH)
                                                x = NATIVE_SCREEN_WIDTH;
                                        y = s.yres;
                                }
                                putenv((char *) "SDL_VIDEODRIVER=fbcon");
                                video.desktop.bpp = s.bits_per_pixel;
                                video.desktop.fb_hacks = 1;
                                video.desktop.doublebuf = 1;
                                video.desktop.fullscreen = 0;
                                video.desktop.swsurface = 0;
                                video.surface = SDL_SetVideoMode(x,y,
                                                video.desktop.bpp,
                                                SDL_HWSURFACE
                                                | SDL_DOUBLEBUF
                                                | SDL_ASYNCBLIT);
                        }
                        close(fb);
                }
        }
#endif
        if (!video.surface) {
                /* if we already got one... */
                video.surface = SDL_SetVideoMode(640,400,0,SDL_RESIZABLE);
                if (!video.surface) {
                        perror("SDL_SetVideoMode");
                        exit(255);
                }
        }
        video.desktop.bpp = video.surface->format->BitsPerPixel;

        if (x < 0 || y < 0) {
                modes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE);
                if (modes != (SDL_Rect**)0 && modes != (SDL_Rect**)-1) {
                        for (i = 0; modes[i]; i++) {
                                if (modes[i]->w < NATIVE_SCREEN_WIDTH) continue;
                                if (modes[i]->h < NATIVE_SCREEN_HEIGHT)continue;
                                if (x == -1 || y == -1 || modes[i]->w < x || modes[i]->h < y) {
                                        if (modes[i]->w != NATIVE_SCREEN_WIDTH
                                        ||  modes[i]->h != NATIVE_SCREEN_HEIGHT) {
                                                if (x == NATIVE_SCREEN_WIDTH || y == NATIVE_SCREEN_HEIGHT)
                                                        continue;
                                        }
                                        x = modes[i]->w;
                                        y = modes[i]->h;
                                        if (best_resolution(x,y))
                                                break;
                                }
                        }
                }
        }
        if (x < 0 || y < 0) {
                x = 640;
                y = 480;
        }

        video.desktop.want_fixed = -1;
        q = getenv("SCHISM_VIDEO_ASPECT");
        if (q && (strcasecmp(q,"nofixed") == 0 || strcasecmp(q,"full")==0
        || strcasecmp(q,"fit") == 0 || strcasecmp(q,"wide") == 0
        || strcasecmp(q,"no-fixed") == 0))
                video.desktop.want_fixed = 0;

        if ((q = getenv("SCHISM_VIDEO_DEPTH"))) {
                i=atoi(q);
                if (i == 32) video.desktop.bpp=32;
                else if (i == 24) video.desktop.bpp=24;
                else if (i == 16) video.desktop.bpp=16;
                else if (i == 8) video.desktop.bpp=8;
        }

        /*log_appendf(2, "Ideal desktop size: %dx%d", x, y); */
        video.desktop.width = x;
        video.desktop.height = y;

        switch (video.desktop.want_type) {
        case VIDEO_YUV:
#ifdef MACOSX
                video.desktop.swsurface = 1;
#else
                video.desktop.swsurface = 0;
#endif
                break;

        case VIDEO_GL:
#ifdef MACOSX
                video.desktop.swsurface = 1;
#else
                video.desktop.swsurface = 0;
#endif
                video.gl.bilinear = 1;
                if (video.desktop.bpp == 32 || video.desktop.bpp == 16) break;
                /* fall through */
        case VIDEO_DDRAW:
#ifdef WIN32
                if (video.desktop.bpp == 32 || video.desktop.bpp == 16) {
                        /* good enough for here! */
                        video.desktop.want_type = VIDEO_DDRAW;
                        break;
                }
#endif
                /* fall through */
        case VIDEO_SURFACE:
                /* no scaling when using the SDL surfaces directly */
                video.desktop.swsurface = 1;
                video.desktop.want_type = VIDEO_SURFACE;
                break;
        };
        /* okay, i think we're ready */
        SDL_ShowCursor(SDL_DISABLE);
#if 0 /* Do we need these? Everything seems to work just fine without */
        SDL_EventState(SDL_VIDEORESIZE, SDL_ENABLE);
        SDL_EventState(SDL_VIDEOEXPOSE, SDL_ENABLE);
        SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
        SDL_EventState(SDL_USEREVENT, SDL_ENABLE);
        SDL_EventState(SDL_ACTIVEEVENT, SDL_ENABLE);
        SDL_EventState(SDL_KEYDOWN, SDL_ENABLE);
        SDL_EventState(SDL_KEYUP, SDL_ENABLE);
        SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
        SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_ENABLE);
        SDL_EventState(SDL_MOUSEBUTTONUP, SDL_ENABLE);
#endif
        _did_init = 1;
        video_fullscreen(video.desktop.fullscreen);
}

static SDL_Surface *_setup_surface(unsigned int w, unsigned int h, unsigned int sdlflags)
{
        int want_fixed = video.desktop.want_fixed;

        if (video.desktop.doublebuf)
                sdlflags |= (SDL_DOUBLEBUF|SDL_ASYNCBLIT);
        if (video.desktop.fullscreen) {
                w = video.desktop.width;
                h = video.desktop.height;
        } else {
                sdlflags |= SDL_RESIZABLE;
        }

        if (want_fixed == -1 && best_resolution(w,h)) {
                want_fixed = 0;
        }

        if (want_fixed) {
                double ratio_w = (double)w / (double)NATIVE_SCREEN_WIDTH;
                double ratio_h = (double)h / (double)NATIVE_SCREEN_HEIGHT;
                if (ratio_w < ratio_h) {
                        video.clip.w = w;
                        video.clip.h = (double)NATIVE_SCREEN_HEIGHT * ratio_w;
                } else {
                        video.clip.h = h;
                        video.clip.w = (double)NATIVE_SCREEN_WIDTH * ratio_h;
                }
                video.clip.x=(w-video.clip.w)/2;
                video.clip.y=(h-video.clip.h)/2;
        } else {
                video.clip.x = 0;
                video.clip.y = 0;
                video.clip.w = w;
                video.clip.h = h;
        }

        if (video.desktop.fb_hacks && video.surface) {
                /* the original one will be _just fine_ */
        } else {
                if (video.desktop.fullscreen) {
                        sdlflags &=~SDL_RESIZABLE;
                        sdlflags |= SDL_FULLSCREEN;
                } else {
                        sdlflags &=~SDL_FULLSCREEN;
                        sdlflags |= SDL_RESIZABLE;
                }
                sdlflags |= (video.desktop.swsurface
                                ? SDL_SWSURFACE
                                : SDL_HWSURFACE);
                video.surface = SDL_SetVideoMode(w, h,
                        video.desktop.bpp, sdlflags);
        }
        if (!video.surface) {
                perror("SDL_SetVideoMode");
                exit(EXIT_FAILURE);
        }
        return video.surface;
}

#if USE_OPENGL
static void _set_gl_attributes(void)
{
        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 32);
        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);
#if SDL_VERSION_ATLEAST(1,2,11)
        SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 0);
#endif
}
#endif

void video_resize(unsigned int width, unsigned int height)
{
#if USE_OPENGL
        GLfloat tex_width, tex_height;
        int texsize;
#endif

        if (!height) height = NATIVE_SCREEN_HEIGHT;
        if (!width) width = NATIVE_SCREEN_WIDTH;
        video.draw.width = width;
        video.draw.height = height;

        video.draw.autoscale = 1;

        switch (video.desktop.want_type) {
        case VIDEO_DDRAW:
#ifdef WIN32
                if (video.ddblit.surface) {
                        SDL_FreeSurface(video.ddblit.surface);
                        video.ddblit.surface = 0;
                }
                memset(&video.ddblit.fx, 0, sizeof(DDBLTFX));
                video.ddblit.fx.dwSize = sizeof(DDBLTFX);
                _setup_surface(width, height, SDL_DOUBLEBUF);
                video.ddblit.rect.top = video.clip.y;
                video.ddblit.rect.left = video.clip.x;
                video.ddblit.rect.right = video.clip.x + video.clip.w;
                video.ddblit.rect.bottom = video.clip.y + video.clip.h;
                video.ddblit.surface = SDL_CreateRGBSurface(SDL_HWSURFACE,
                                NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT,
                                video.surface->format->BitsPerPixel,
                                video.surface->format->Rmask,
                                video.surface->format->Gmask,
                                video.surface->format->Bmask,0);
                if (video.ddblit.surface
                && ((video.ddblit.surface->flags & SDL_HWSURFACE) == SDL_HWSURFACE)) {
                        video.desktop.type = VIDEO_DDRAW;
                        break;
                }
                /* fall through */
#endif
        case VIDEO_SURFACE:
RETRYSURF:      /* use SDL surfaces */
                video.draw.autoscale = 0;
                if (video.desktop.fb_hacks
                && (video.desktop.width != NATIVE_SCREEN_WIDTH
                        || video.desktop.height != NATIVE_SCREEN_HEIGHT)) {
                        video.draw.autoscale = 1;
                }
                _setup_surface(width, height, 0);
                video.desktop.type = VIDEO_SURFACE;
                break;

        case VIDEO_YUV:
                if (video.overlay) {
                        SDL_FreeYUVOverlay(video.overlay);
                        video.overlay = NULL;
                }
                _setup_surface(width, height, 0);
                /* TODO: switch? */
                switch (video.yuvlayout) {
                case VIDEO_YUV_YV12_TV:
                        video.overlay = SDL_CreateYUVOverlay
                                (NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT,
                                 SDL_YV12_OVERLAY, video.surface);
                        break;
                case VIDEO_YUV_IYUV_TV:
                        video.overlay = SDL_CreateYUVOverlay
                                (NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT,
                                 SDL_IYUV_OVERLAY, video.surface);
                        break;
                case VIDEO_YUV_YV12:
                        video.overlay = SDL_CreateYUVOverlay
                                (2 * NATIVE_SCREEN_WIDTH,
                                 2 * NATIVE_SCREEN_HEIGHT,
                                 SDL_YV12_OVERLAY, video.surface);
                        break;
                case VIDEO_YUV_IYUV:
                        video.overlay = SDL_CreateYUVOverlay
                                (2 * NATIVE_SCREEN_WIDTH,
                                 2 * NATIVE_SCREEN_HEIGHT,
                                 SDL_IYUV_OVERLAY, video.surface);
                        break;
                case VIDEO_YUV_UYVY:
                        video.overlay = SDL_CreateYUVOverlay
                                (2 * NATIVE_SCREEN_WIDTH,
                                 NATIVE_SCREEN_HEIGHT,
                                 SDL_UYVY_OVERLAY, video.surface);
                        break;
                case VIDEO_YUV_YVYU:
                        video.overlay = SDL_CreateYUVOverlay
                                (2 * NATIVE_SCREEN_WIDTH,
                                 NATIVE_SCREEN_HEIGHT,
                                 SDL_YVYU_OVERLAY, video.surface);
                        break;
                case VIDEO_YUV_YUY2:
                        video.overlay = SDL_CreateYUVOverlay
                                (2 * NATIVE_SCREEN_WIDTH,
                                 NATIVE_SCREEN_HEIGHT,
                                 SDL_YUY2_OVERLAY, video.surface);
                        break;
                case VIDEO_YUV_RGBA:
                case VIDEO_YUV_RGB32:
                        video.overlay = SDL_CreateYUVOverlay
                                (4*NATIVE_SCREEN_WIDTH,
                                 NATIVE_SCREEN_HEIGHT,
                                 video.yuvlayout, video.surface);
                        break;
                case VIDEO_YUV_RGB24:
                        video.overlay = SDL_CreateYUVOverlay
                                (3*NATIVE_SCREEN_WIDTH,
                                 NATIVE_SCREEN_HEIGHT,
                                 video.yuvlayout, video.surface);
                        break;
                case VIDEO_YUV_RGBT:
                case VIDEO_YUV_RGB565:
                        video.overlay = SDL_CreateYUVOverlay
                                (2*NATIVE_SCREEN_WIDTH,
                                 NATIVE_SCREEN_HEIGHT,
                                 video.yuvlayout, video.surface);
                        break;

                default:
                        /* unknown layout */
                        goto RETRYSURF;
                }
                if (!video.overlay) {
                        /* can't get an overlay */
                        goto RETRYSURF;
                }
                switch (video.overlay->planes) {
                case 3:
                case 1:
                        break;
                default:
                        /* can't get a recognized planes */
                        SDL_FreeYUVOverlay(video.overlay);
                        video.overlay = NULL;
                        goto RETRYSURF;
                };
                video.desktop.type = VIDEO_YUV;
                break;
#if defined(USE_OPENGL)
        case VIDEO_GL:
                _set_gl_attributes();
                _setup_surface(width, height, SDL_OPENGL);
                if (video.surface->format->BitsPerPixel < 15) {
                        goto RETRYSURF;
                }
                /* grumble... */
                _set_gl_attributes();
                my_glViewport(video.clip.x, video.clip.y,
                        video.clip.w, video.clip.h);
                texsize = 2 << int_log2(NATIVE_SCREEN_WIDTH);
                if (texsize > video.gl.max_texsize) {
                        /* can't do opengl! */
                        goto RETRYSURF;
                }
#if defined(NVIDIA_PixelDataRange)
                if (video.gl.pixel_data_range) {
                        if (!video.gl.framebuf) {
                                video.gl.framebuf = db_glAllocateMemoryNV(
                                                NATIVE_SCREEN_WIDTH*NATIVE_SCREEN_HEIGHT*4,
                                                0.0, 1.0, 1.0);
                        }
                        glPixelDataRangeNV(GL_WRITE_PIXEL_DATA_RANGE_NV,
                                        NATIVE_SCREEN_WIDTH*NATIVE_SCREEN_HEIGHT*4,
                                        video.gl.framebuf);
                        my_glEnableClientState(GL_WRITE_PIXEL_DATA_RANGE_NV);
                } else
#endif
                if (!video.gl.framebuf) {
                        video.gl.framebuf = mem_alloc(NATIVE_SCREEN_WIDTH
                                                        *NATIVE_SCREEN_HEIGHT*4);
                }

                video.gl.pitch = NATIVE_SCREEN_WIDTH * 4;
                my_glMatrixMode(GL_PROJECTION);
                my_glDeleteTextures(1, &video.gl.texture);
                my_glGenTextures(1, &video.gl.texture);
                my_glBindTexture(GL_TEXTURE_2D, video.gl.texture);
                my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
                my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
                if (video.gl.bilinear) {
                        my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                                                GL_LINEAR);
                        my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                                                GL_LINEAR);
                } else {
                        my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                                                GL_NEAREST);
                        my_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                                                GL_NEAREST);
                }
                my_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texsize, texsize,
                        0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
                my_glClearColor(0.0, 0.0, 0.0, 1.0);
                my_glClear(GL_COLOR_BUFFER_BIT);
                SDL_GL_SwapBuffers();
                my_glClear(GL_COLOR_BUFFER_BIT);
                my_glShadeModel(GL_FLAT);
                my_glDisable(GL_DEPTH_TEST);
                my_glDisable(GL_LIGHTING);
                my_glDisable(GL_CULL_FACE);
                my_glEnable(GL_TEXTURE_2D);
                my_glMatrixMode(GL_MODELVIEW);
                my_glLoadIdentity();

                tex_width = ((GLfloat)(NATIVE_SCREEN_WIDTH)/(GLfloat)texsize);
                tex_height = ((GLfloat)(NATIVE_SCREEN_HEIGHT)/(GLfloat)texsize);
                if (my_glIsList(video.gl.displaylist))
                        my_glDeleteLists(video.gl.displaylist, 1);
                video.gl.displaylist = my_glGenLists(1);
                my_glNewList(video.gl.displaylist, GL_COMPILE);
                my_glBindTexture(GL_TEXTURE_2D, video.gl.texture);
                my_glBegin(GL_QUADS);
                my_glTexCoord2f(0,tex_height); my_glVertex2f(-1.0f,-1.0f);
                my_glTexCoord2f(tex_width,tex_height);my_glVertex2f(1.0f,-1.0f);
                my_glTexCoord2f(tex_width,0); my_glVertex2f(1.0f, 1.0f);
                my_glTexCoord2f(0,0); my_glVertex2f(-1.0f, 1.0f);
                my_glEnd();
                my_glEndList();
                video.desktop.type = VIDEO_GL;
                break;
#endif // USE_OPENGL
        };

        status.flags |= (NEED_UPDATE);
}
static void _make_yuv(unsigned int *y, unsigned int *u, unsigned int *v,
                                                int rgb[3])
{
        double red, green, blue, yy, cr, cb, ry, ru, rv;
        int r = rgb[0];
        int g = rgb[1];
        int b = rgb[2];

        red = (double)r / 255.0;
        green = (double)g / 255.0;
        blue = (double)b / 255.0;
        yy = 0.299 * red + 0.587 * green + 0.114 * blue;
        cb = blue - yy;
        cr = red - yy;
        ry = 16.0 + 219.0 * yy;
        ru = 128.0 + 126.0 * cb;
        rv = 128.0 + 160.0 * cr;
        *y = (uint8_t) ry;
        *u = (uint8_t) ru;
        *v = (uint8_t) rv;
}
static void _yuv_pal(int i, int rgb[3])
{
        unsigned int y,u,v;
        _make_yuv(&y, &u, &v, rgb);
        switch (video.yuvlayout) {
        /* planar modes */
        case VIDEO_YUV_YV12:
        case VIDEO_YUV_IYUV:
                /* this is fake; we simply record the infomration here */
                video.yuv_y[i] = y|(y<<8);
                video.yuv_u[i] = u;
                video.yuv_v[i] = v;
                break;

        /* tv planar modes */
        case VIDEO_YUV_YV12_TV:
        case VIDEO_YUV_IYUV_TV:
                /* _blitTV */
                video.yuv_y[i] = y;
                video.yuv_u[i] = (u >> 4) & 0xF;
                video.yuv_v[i] = (v >> 4) & 0xF;
                break;

        /* packed modes */
        case VIDEO_YUV_YVYU:
                /* y0 v0 y1 u0 */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                video.pal[i] = u | (y << 8) | (v << 16) | (y << 24);
#else
                video.pal[i] = y | (v << 8) | (y << 16) | (u << 24);
#endif
                break;
        case VIDEO_YUV_UYVY:
                /* u0 y0 v0 y1 */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                video.pal[i] = y | (v << 8) | (y << 16) | (u << 24);
#else
                video.pal[i] = u | (y << 8) | (v << 16) | (y << 24);
#endif
                break;
        case VIDEO_YUV_YUY2:
                /* y0 u0 y1 v0 */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
                video.pal[i] = v | (y << 8) | (u << 16) | (y << 24);
#else
                video.pal[i] = y | (u << 8) | (y << 16) | (v << 24);
#endif
                break;
        case VIDEO_YUV_RGBA:
        case VIDEO_YUV_RGB32:
                video.pal[i] = rgb[2] |
                        (rgb[1] << 8) |
                        (rgb[0] << 16) | (255 << 24);
                break;
        case VIDEO_YUV_RGB24:
                video.pal[i] = rgb[2] |
                        (rgb[1] << 8) |
                        (rgb[0] << 16);
                break;
        case VIDEO_YUV_RGBT:
                video.pal[i] =
                        ((rgb[0] <<  8) & 0x7c00)
                |       ((rgb[1] <<  3) & 0x3e0)
                |       ((rgb[2] >>  2) & 0x1f);
                break;
        case VIDEO_YUV_RGB565:
                video.pal[i] =
                        ((rgb[0] <<  9) & 0xf800)
                |       ((rgb[1] <<  4) & 0x7e0)
                |       ((rgb[2] >>  2) & 0x1f);
                break;
        };
}
static void _sdl_pal(int i, int rgb[3])
{
        video.pal[i] = SDL_MapRGB(video.surface->format,
                        rgb[0], rgb[1], rgb[2]);
}
static void _bgr32_pal(int i, int rgb[3])
{
        video.tc_bgr32[i] = rgb[2] |
                        (rgb[1] << 8) |
                        (rgb[0] << 16) | (255 << 24);
}
static void _gl_pal(int i, int rgb[3])
{
        video.pal[i] = rgb[2] |
                        (rgb[1] << 8) |
                        (rgb[0] << 16) | (255 << 24);
}
void video_colors(unsigned char palette[16][3])
{
        static SDL_Color imap[16];
        void (*fun)(int i,int rgb[3]);
        const int lastmap[] = { 0,1,2,3,5 };
        int rgb[3], i, j, p;

        switch (video.desktop.type) {
        case VIDEO_SURFACE:
                if (video.surface->format->BytesPerPixel == 1) {
                        const int depthmap[] = { 0, 15,14,7,
                                                8, 8, 9, 12,
                                                6, 1, 2, 2,
                                                10, 3, 11, 11 };
                        /* okay, indexed color */
                        for (i = 0; i < 16; i++) {
                                video.pal[i] = i;
                                imap[i].r = palette[i][0];
                                imap[i].g = palette[i][1];
                                imap[i].b = palette[i][2];

                                rgb[0]=palette[i][0];
                                rgb[1]=palette[i][1];
                                rgb[2]=palette[i][2];
                                _bgr32_pal(i, rgb);

                        }
                        for (i = 128; i < 256; i++) {
                                video.pal[i] = depthmap[(i>>4)];
                        }
                        for (i = 128; i < 256; i++) {
                                j = i - 128;
                                p = lastmap[(j>>5)];
                                rgb[0] = (int)palette[p][0] +
                                        (((int)(palette[p+1][0]
                                        - palette[p][0]) * (j&31)) /32);
                                rgb[1] = (int)palette[p][1] +
                                        (((int)(palette[p+1][1]
                                        - palette[p][1]) * (j&31)) /32);
                                rgb[2] = (int)palette[p][2] +
                                        (((int)(palette[p+1][2]
                                        - palette[p][2]) * (j&31)) /32);
                                _bgr32_pal(i, rgb);
                        }
                        SDL_SetColors(video.surface, imap, 0, 16);
                        return;
                }
                /* fall through */
        case VIDEO_DDRAW:
                fun = _sdl_pal;
                break;
        case VIDEO_YUV:
                fun = _yuv_pal;
                break;
        case VIDEO_GL:
                fun = _gl_pal;
                break;
        default:
                /* eh? */
                return;
        };
        /* make our "base" space */
        for (i = 0; i < 16; i++) {
                rgb[0]=palette[i][0];
                rgb[1]=palette[i][1];
                rgb[2]=palette[i][2];
                fun(i, rgb);
                _bgr32_pal(i, rgb);
        }
        /* make our "gradient" space */
        for (i = 128; i < 256; i++) {
                j = i - 128;
                p = lastmap[(j>>5)];
                rgb[0] = (int)palette[p][0] +
                        (((int)(palette[p+1][0] - palette[p][0]) * (j&31)) /32);
                rgb[1] = (int)palette[p][1] +
                        (((int)(palette[p+1][1] - palette[p][1]) * (j&31)) /32);
                rgb[2] = (int)palette[p][2] +
                        (((int)(palette[p+1][2] - palette[p][2]) * (j&31)) /32);
                fun(i, rgb);
                _bgr32_pal(i, rgb);
        }
}

void video_refresh(void)
{
        vgamem_flip();
        vgamem_clear();
}

static inline void make_mouseline(unsigned int x, unsigned int v, unsigned int y, unsigned int mouseline[80])
{
        unsigned int z;

        memset(mouseline, 0, 80*sizeof(unsigned int));
        if (video.mouse.visible != MOUSE_EMULATED
            || !(status.flags & IS_FOCUSED)
            || y < video.mouse.y
            || y >= video.mouse.y+MOUSE_HEIGHT) {
                return;
        }

        z = _mouse_pointer[ y - video.mouse.y ];
        mouseline[x] = z >> v;
        if (x < 79) mouseline[x+1] = (z << (8-v)) & 0xff;
}


#define FIXED_BITS 8
#define FIXED_MASK ((1 << FIXED_BITS) - 1)
#define ONE_HALF_FIXED (1 << (FIXED_BITS - 1))
#define INT2FIXED(x) ((x) << FIXED_BITS)
#define FIXED2INT(x) ((x) >> FIXED_BITS)
#define FRAC(x) ((x) & FIXED_MASK)

static void _blit1n(int bpp, unsigned char *pixels, unsigned int pitch)
{
        unsigned int *csp, *esp, *dp;
        unsigned int c00, c01, c10, c11;
        unsigned int outr, outg, outb;
        unsigned int pad;
        int fixedx, fixedy, scalex, scaley;
        unsigned int y, x,ey,ex,t1,t2;
        unsigned int mouseline[80];
        unsigned int mouseline_x, mouseline_v;
        int iny, lasty;

        mouseline_x = (video.mouse.x / 8);
        mouseline_v = (video.mouse.x % 8);

        csp = (unsigned int *)video.cv32backing;
        esp = csp + NATIVE_SCREEN_WIDTH;
        lasty = -2;
        iny = 0;
        pad = pitch - (video.clip.w * bpp);
        scalex = INT2FIXED(NATIVE_SCREEN_WIDTH-1) / video.clip.w;
        scaley = INT2FIXED(NATIVE_SCREEN_HEIGHT-1) / video.clip.h;
        for (y = 0, fixedy = 0; (y < video.clip.h); y++, fixedy += scaley) {
                iny = FIXED2INT(fixedy);
                if (iny != lasty) {
                        make_mouseline(mouseline_x, mouseline_v, iny, mouseline);

                        /* we'll downblit the colors later */
                        if (iny == lasty + 1) {
                                /* move up one line */
                                vgamem_scan32(iny+1, csp, video.tc_bgr32, mouseline);
                                dp = esp; esp = csp; csp=dp;
                        } else {
                                vgamem_scan32(iny, (csp = (unsigned int *)video.cv32backing),
                                        video.tc_bgr32, mouseline);
                                vgamem_scan32(iny+1, (esp = (csp + NATIVE_SCREEN_WIDTH)),
                                        video.tc_bgr32, mouseline);
                        }
                        lasty = iny;
                }
                for (x = 0, fixedx = 0; x < video.clip.w; x++, fixedx += scalex) {
                        ex = FRAC(fixedx);
                        ey = FRAC(fixedy);

                        c00 = csp[FIXED2INT(fixedx)];
                        c01 = csp[FIXED2INT(fixedx) + 1];
                        c10 = esp[FIXED2INT(fixedx)];
                        c11 = esp[FIXED2INT(fixedx) + 1];

#if FIXED_BITS <= 8
                        /* When there are enough bits between blue and
                         * red, do the RB channels together
                         * See http://www.virtualdub.org/blog/pivot/entry.php?id=117
                         * for a quick explanation */
#define REDBLUE(Q) ((Q) & 0x00FF00FF)
#define GREEN(Q) ((Q) & 0x0000FF00)
                        t1 = REDBLUE((((REDBLUE(c01)-REDBLUE(c00))*ex) >> FIXED_BITS)+REDBLUE(c00));
                        t2 = REDBLUE((((REDBLUE(c11)-REDBLUE(c10))*ex) >> FIXED_BITS)+REDBLUE(c10));
                        outb = ((((t2-t1)*ey) >> FIXED_BITS) + t1);

                        t1 = GREEN((((GREEN(c01)-GREEN(c00))*ex) >> FIXED_BITS)+GREEN(c00));
                        t2 = GREEN((((GREEN(c11)-GREEN(c10))*ex) >> FIXED_BITS)+GREEN(c10));
                        outg = (((((t2-t1)*ey) >> FIXED_BITS) + t1) >> 8) & 0xFF;

                        outr = (outb >> 16) & 0xFF;
                        outb &= 0xFF;
#undef REDBLUE
#undef GREEN
#else
#define BLUE(Q) (Q & 255)
#define GREEN(Q) ((Q >> 8) & 255)
#define RED(Q) ((Q >> 16) & 255)
                        t1 = ((((BLUE(c01)-BLUE(c00))*ex) >> FIXED_BITS)+BLUE(c00)) & 0xFF;
                        t2 = ((((BLUE(c11)-BLUE(c10))*ex) >> FIXED_BITS)+BLUE(c10)) & 0xFF;
                        outb = ((((t2-t1)*ey) >> FIXED_BITS) + t1);

                        t1 = ((((GREEN(c01)-GREEN(c00))*ex) >> FIXED_BITS)+GREEN(c00)) & 0xFF;
                        t2 = ((((GREEN(c11)-GREEN(c10))*ex) >> FIXED_BITS)+GREEN(c10)) & 0xFF;
                        outg = ((((t2-t1)*ey) >> FIXED_BITS) + t1);

                        t1 = ((((RED(c01)-RED(c00))*ex) >> FIXED_BITS)+RED(c00)) & 0xFF;
                        t2 = ((((RED(c11)-RED(c10))*ex) >> FIXED_BITS)+RED(c10)) & 0xFF;
                        outr = ((((t2-t1)*ey) >> FIXED_BITS) + t1);
#undef RED
#undef GREEN
#undef BLUE
#endif
                        /* write output "pixel" */
                        switch (bpp) {
                        case 4:
                                /* inline MapRGB */
                                (*(unsigned int *)pixels) = 0xFF000000 | (outr << 16) | (outg << 8) | outb;
                                break;
                        case 3:
                                /* inline MapRGB */
                                (*(unsigned int *)pixels) = (outr << 16) | (outg << 8) | outb;
                                break;
                        case 2:
                                /* inline MapRGB if possible */
                                if (video.surface->format->palette) {
                                        /* err... */
                                        (*(unsigned short *)pixels) = SDL_MapRGB(
                                                video.surface->format, outr, outg, outb);
                                } else if (video.surface->format->Gloss == 2) {
                                        /* RGB565 */
                                        (*(unsigned short *)pixels) = ((outr << 8) & 0xF800) |
                                                ((outg << 3) & 0x07E0) |
                                                (outb >> 3);
                                } else {
                                        /* RGB555 */
                                        (*(unsigned short *)pixels) = 0x8000 |
                                                ((outr << 7) & 0x7C00) |
                                                ((outg << 2) & 0x03E0) |
                                                (outb >> 3);
                                }
                                break;
                        case 1:
                                /* er... */
                                (*pixels) = SDL_MapRGB(
                                        video.surface->format, outr, outg, outb);
                                break;
                        };
                        pixels += bpp;
                }
                pixels += pad;
        }
}
static void _blitYY(unsigned char *pixels, unsigned int pitch, unsigned int *tpal)
{
        unsigned int mouseline_x = (video.mouse.x / 8);
        unsigned int mouseline_v = (video.mouse.x % 8);
        unsigned int mouseline[80];
        int y;

        for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) {
                make_mouseline(mouseline_x, mouseline_v, y, mouseline);

                vgamem_scan16(y, (unsigned short *)pixels, tpal, mouseline);
                memcpy(pixels+pitch,pixels,pitch);
                pixels += pitch;
                pixels += pitch;
        }
}
static void _blitUV(unsigned char *pixels, unsigned int pitch, unsigned int *tpal)
{
        unsigned int mouseline_x = (video.mouse.x / 8);
        unsigned int mouseline_v = (video.mouse.x % 8);
        unsigned int mouseline[80];
        int y;
        for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) {
                make_mouseline(mouseline_x, mouseline_v, y, mouseline);
                vgamem_scan8(y, (unsigned char *)pixels, tpal, mouseline);
                pixels += pitch;
        }
}
static void _blitTV(unsigned char *pixels, UNUSED unsigned int pitch, unsigned int *tpal)
{
        unsigned int mouseline_x = (video.mouse.x / 8);
        unsigned int mouseline_v = (video.mouse.x % 8);
        unsigned int mouseline[80];
        int y, x;
        for (y = 0; y < NATIVE_SCREEN_HEIGHT; y += 2) {
                make_mouseline(mouseline_x, mouseline_v, y, mouseline);
                vgamem_scan8(y, (unsigned char *)video.cv8backing, tpal, mouseline);
                for (x = 0; x < NATIVE_SCREEN_WIDTH; x += 2) {
                        *pixels++ = video.cv8backing[x+1]
                                | (video.cv8backing[x] << 4);
                }
        }
}

static void _blit11(int bpp, unsigned char *pixels, unsigned int pitch,
                        unsigned int *tpal)
{
        unsigned int mouseline_x = (video.mouse.x / 8);
        unsigned int mouseline_v = (video.mouse.x % 8);
        unsigned int mouseline[80];
        unsigned char *pdata;
        unsigned int x, y;
        int pitch24;

        switch (bpp) {
        case 4:
                for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) {
                        make_mouseline(mouseline_x, mouseline_v, y, mouseline);
                        vgamem_scan32(y, (unsigned int *)pixels, tpal, mouseline);
                        pixels += pitch;
                }
                break;
        case 3:
                /* ... */
                pitch24 = pitch - (NATIVE_SCREEN_WIDTH * 3);
                if (pitch24 < 0) {
                        return; /* eh? */
                }
                for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) {
                        make_mouseline(mouseline_x, mouseline_v, y, mouseline);
                        vgamem_scan32(y,(unsigned int*)video.cv32backing,tpal, mouseline);
                        /* okay... */
                        pdata = video.cv32backing;
                        for (x = 0; x < NATIVE_SCREEN_WIDTH; x++) {
#if WORDS_BIGENDIAN
                                memcpy(pixels, pdata+1, 3);
#else
                                memcpy(pixels, pdata, 3);
#endif
                                pdata += 4;
                                pixels += 3;
                        }
                        pixels += pitch24;
                }
                break;
        case 2:
                for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) {
                        make_mouseline(mouseline_x, mouseline_v, y, mouseline);
                        vgamem_scan16(y, (unsigned short *)pixels, tpal, mouseline);
                        pixels += pitch;
                }
                break;
        case 1:
                for (y = 0; y < NATIVE_SCREEN_HEIGHT; y++) {
                        make_mouseline(mouseline_x, mouseline_v, y, mouseline);
                        vgamem_scan8(y, (unsigned char *)pixels, tpal, mouseline);
                        pixels += pitch;
                }
                break;
        };
}

static void _video_blit_planar(void) {
        SDL_LockYUVOverlay(video.overlay);
        vgamem_lock();

        switch (video.yuvlayout) {
        case VIDEO_YUV_YV12_TV:
                /* halfwidth Y+V+U */
                _blitUV(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y);
                _blitTV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_v);
                _blitTV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_u);
                break;
        case VIDEO_YUV_IYUV_TV:
                /* halfwidth Y+U+V */
                _blitUV(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y);
                _blitTV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_u);
                _blitTV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_v);
                break;

        case VIDEO_YUV_YV12:
                /* Y+V+U */
                _blitYY(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y);
                _blitUV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_v);
                _blitUV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_u);
                break;
        case VIDEO_YUV_IYUV:
                /* Y+U+V */
                _blitYY(video.overlay->pixels[0], video.overlay->pitches[0], video.yuv_y);
                _blitUV(video.overlay->pixels[1], video.overlay->pitches[1], video.yuv_u);
                _blitUV(video.overlay->pixels[2], video.overlay->pitches[2], video.yuv_v);
                break;
        };

        vgamem_unlock();

        SDL_UnlockYUVOverlay(video.overlay);
        SDL_DisplayYUVOverlay(video.overlay, &video.clip);
}

void video_blit(void)
{
        unsigned char *pixels = NULL;
        unsigned int bpp = 0;
        unsigned int pitch = 0;

        switch (video.desktop.type) {
        case VIDEO_SURFACE:
                if (SDL_MUSTLOCK(video.surface)) {
                        while (SDL_LockSurface(video.surface) == -1) {
                                SDL_Delay(10);
                        }
                }
                bpp = video.surface->format->BytesPerPixel;
                pixels = (unsigned char *)video.surface->pixels;
                pixels += video.clip.y * video.surface->pitch;
                pixels += video.clip.x * bpp;
                pitch = video.surface->pitch;
                break;
        case VIDEO_YUV:
                if (video.overlay->planes == 3) {
                        _video_blit_planar();
                        return;
                }

                SDL_LockYUVOverlay(video.overlay);
                pixels = (unsigned char *)*(video.overlay->pixels);
                pitch = *(video.overlay->pitches);
                switch (video.yuvlayout) {
                case VIDEO_YUV_RGBT:   bpp = 2; break;
                case VIDEO_YUV_RGB565: bpp = 2; break;
                case VIDEO_YUV_RGB24:  bpp = 3; break;
                default:
                        bpp = 4;
                };
                break;
        case VIDEO_GL:
                pixels = (unsigned char *)video.gl.framebuf;
                pitch = video.gl.pitch;
                bpp = 4;
                break;
        case VIDEO_DDRAW:
#ifdef WIN32
                if (SDL_MUSTLOCK(video.ddblit.surface)) {
                        while (SDL_LockSurface(video.ddblit.surface) == -1) {
                                SDL_Delay(10);
                        }
                }
                pixels = (unsigned char *)video.ddblit.surface->pixels;
                pitch = video.ddblit.surface->pitch;
                bpp = video.surface->format->BytesPerPixel;
                break;
#else
                return; /* eh? */
#endif
        };

        vgamem_lock();
        if (video.draw.autoscale
            || (video.clip.w == NATIVE_SCREEN_WIDTH && video.clip.h == NATIVE_SCREEN_HEIGHT)) {
                /* scaling is provided by the hardware, or isn't necessary */
                _blit11(bpp, pixels, pitch, video.pal);
        } else {
                _blit1n(bpp, pixels, pitch);
        }
        vgamem_unlock();

        switch (video.desktop.type) {
        case VIDEO_SURFACE:
                if (SDL_MUSTLOCK(video.surface)) {
                        SDL_UnlockSurface(video.surface);
                }
                SDL_Flip(video.surface);
                break;
#ifdef WIN32
        case VIDEO_DDRAW:
                if (SDL_MUSTLOCK(video.ddblit.surface)) {
                        SDL_UnlockSurface(video.ddblit.surface);
                }
                switch (IDirectDrawSurface3_Blt(
                                video.surface->hwdata->dd_writebuf,
                                &video.ddblit.rect,
                                video.ddblit.surface->hwdata->dd_surface,0,
                                DDBLT_WAIT, NULL)) {
                case DD_OK:
                        break;
                case DDERR_SURFACELOST:
                        IDirectDrawSurface3_Restore(video.ddblit.surface->hwdata->dd_surface);
                        IDirectDrawSurface3_Restore(video.surface->hwdata->dd_surface);
                        break;
                default:
                        break;
                };
                SDL_Flip(video.surface);
                break;
#endif
        case VIDEO_YUV:
                SDL_UnlockYUVOverlay(video.overlay);
                SDL_DisplayYUVOverlay(video.overlay, &video.clip);
                break;
#if defined(USE_OPENGL)
        case VIDEO_GL:
                my_glBindTexture(GL_TEXTURE_2D, video.gl.texture);
                my_glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
                        NATIVE_SCREEN_WIDTH, NATIVE_SCREEN_HEIGHT,
                        GL_BGRA_EXT,
                        GL_UNSIGNED_INT_8_8_8_8_REV,
                        video.gl.framebuf);
                my_glCallList(video.gl.displaylist);
                SDL_GL_SwapBuffers();
                break;
#endif
        };
}

int video_mousecursor_visible(void)
{
        return video.mouse.visible;
}

void video_mousecursor(int vis)
{
        const char *state[] = {
                "Mouse disabled",
                "Software mouse cursor enabled",
                "Hardware mouse cursor enabled",
        };

        if (status.flags & NO_MOUSE) {
                // disable it no matter what
                video.mouse.visible = MOUSE_DISABLED;
                //SDL_ShowCursor(0);
                return;
        }

        switch (vis) {
        case MOUSE_CYCLE_STATE:
                vis = (video.mouse.visible + 1) % MOUSE_CYCLE_STATE;
                /* fall through */
        case MOUSE_DISABLED:
        case MOUSE_SYSTEM:
        case MOUSE_EMULATED:
                video.mouse.visible = vis;
                status_text_flash("%s", state[video.mouse.visible]);
        case MOUSE_RESET_STATE:
                break;
        default:
                video.mouse.visible = MOUSE_EMULATED;
        }

        SDL_ShowCursor(video.mouse.visible == MOUSE_SYSTEM);

        // Totally turn off mouse event sending when the mouse is disabled
        int evstate = video.mouse.visible == MOUSE_DISABLED ? SDL_DISABLE : SDL_ENABLE;
        if (evstate != SDL_EventState(SDL_MOUSEMOTION, SDL_QUERY)) {
                SDL_EventState(SDL_MOUSEMOTION, evstate);
                SDL_EventState(SDL_MOUSEBUTTONDOWN, evstate);
                SDL_EventState(SDL_MOUSEBUTTONUP, evstate);
        }
}

void video_translate(unsigned int vx, unsigned int vy,
                unsigned int *x, unsigned int *y)
{
        if ((signed) vx < video.clip.x) vx = video.clip.x;
        vx -= video.clip.x;

        if ((signed) vy < video.clip.y) vy = video.clip.y;
        vy -= video.clip.y;

        if ((signed) vx > video.clip.w) vx = video.clip.w;
        if ((signed) vy > video.clip.h) vy = video.clip.h;

        vx *= NATIVE_SCREEN_WIDTH;
        vy *= NATIVE_SCREEN_HEIGHT;
        vx /= (video.draw.width - (video.draw.width - video.clip.w));
        vy /= (video.draw.height - (video.draw.height - video.clip.h));

        if (video.mouse.visible && (video.mouse.x != vx || video.mouse.y != vy)) {
                status.flags |= SOFTWARE_MOUSE_MOVED;
        }
        video.mouse.x = vx;
        video.mouse.y = vy;
        if (x) *x = vx;
        if (y) *y = vy;
}

