/*
 * UAE - The Un*x Amiga Emulator
 *
 * Win32 interface
 *
 * Copyright 1997 Mathias Ortmann
 */

/* No idea what this is for, but it doesn't link without it if we use
   QueryInterface.  */
#define INITGUID

/* Uncomment this line if you want the logs time-stamped
#define TIMESTAMP_LOGS
*/

#include "config.h"
#include "sysconfig.h"

#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>

#include <windows.h>
#ifdef _MSC_VER
#include <zmouse.h>
#include <ddraw.h>
#include <commctrl.h>
#include <commdlg.h>
#include <shellapi.h>
#else
#include "winstuff.h"
#endif
#include <io.h>

#include "sysdeps.h"
#include "options.h"
#include "gensound.h"
#include "sounddep/sound.h"
#include "uae.h"
#include "memory.h"
#include "custom.h"
#include "events.h"
#include "xwin.h"
#include "keyboard.h"
#include "drawing.h"
#include "picasso96.h"
#include "bsdsocket.h"
#include "osdep/win32.h"
#include "osdep/win32gui.h"
#include "resource.h"
#include "drawing.h"
#include "gui.h"

#ifdef __GNUC__
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
        EXTERN_C const GUID name \
                = { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }

DEFINE_GUID( IID_IDirectDraw2,        0xB3A6F3E0,0x2B43,0x11CF,0xA2,0xDE,0x00,0xAA,0x00,0xB9,0x33,0x56 );
DEFINE_GUID( IID_IDirectDrawSurface2, 0x57805885,0x6eec,0x11cf,0x94,0x41,0xa8,0x23,0x03,0xc1,0x0e,0x27 );
DEFINE_GUID( IID_IDirectDrawSurface3, 0xDA044E00,0x69B2,0x11D0,0xA1,0xD5,0x00,0xAA,0x00,0xB8,0xDF,0xBB );
#endif

static FILE *debugfile = NULL;

#define IHF_WINDOWHIDDEN 6
#define NORMAL_WINDOW_STYLE (WS_VISIBLE | WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU )

HINSTANCE hInst;

static BOOL (WINAPI * pGetOpenFileNameA) (LPOPENFILENAME);
static HRESULT (WINAPI * pDirectDrawCreate) (GUID FAR *, LPDIRECTDRAW FAR *, IUnknown FAR *);
static HRESULT CALLBACK modesCallback (LPDDSURFACEDESC modeDesc, LPVOID context);

static int display_change_requested = 0;

/* For the DX_Invalidate() and gfx_unlock_picasso() functions */
static int p96_double_buffer_first, p96_double_buffer_last;

HWND hAmigaWnd, hMainWnd, hStatusWnd;
/*DWORD Keys; */
static RECT amigawin_rect;

char *start_path;

static LPDIRECTDRAW         lpDD   = NULL;

static LPDIRECTDRAWSURFACE  lpDDS_primary   = NULL;
static LPDIRECTDRAWSURFACE  lpDDS_secondary = NULL;
static LPDIRECTDRAWSURFACE  lockable_surface = NULL;
static DDSURFACEDESC        current_surface;
static DDSURFACEDESC        primary_ddsd, secondary_ddsd;
static LPDDSURFACEDESC      lockable_surfacedesc = NULL;

static LPDIRECTDRAWCLIPPER  lpDDC  = NULL;
static LPDIRECTDRAWPALETTE  lpDDP  = NULL;

typedef enum
{
    invalid_surface,
    primary_surface,
    secondary_surface
} surface_type_e;

surface_type_e surface_type = primary_surface;

char VersionStr[256];

static int current_width, current_height, current_depth;
static int fullscreen, needs_fullscreen;
static int current_pixbytes;
static LPPALETTEENTRY current_palette = NULL;

static int default_freq = 0;

static int in_sizemove;

int screen_is_picasso = 0;

int customsize = 0;

int bActive;
int toggle_sound;

#ifdef __GNUC__
BOOL useqpc = 0;
#else
BOOL useqpc = 1;
#endif

BOOL viewing_child = FALSE;
BOOL running_winnt = FALSE;

HKEY hWinUAEKey    = NULL;

static char scrlinebuf[4096];	/* this is too large, but let's rather play on the safe side here */
static int scrindirect;

static void set_linemem (void)
{
    if (scrindirect)
    {
	    gfxvidinfo.linemem  = scrlinebuf;
        gfxvidinfo.emergmem = 0;
    }
    else
    {
	    gfxvidinfo.linemem  = 0;
        if( fullscreen )
            gfxvidinfo.emergmem = scrlinebuf;
        else
            gfxvidinfo.emergmem = scrlinebuf; /* this was 0 */
    }
}

/* Keyboard emulation, Win32 helper routines. */
static LPARAM keysdown[256];
static short numkeysdown;
int checkkey (int vkey, LPARAM lParam)
{
    switch (vkey) {
    case VK_LWIN:
    case VK_RWIN:
     case VK_SHIFT:
     case VK_LSHIFT:
     case VK_RSHIFT:
     case VK_CONTROL:
     case VK_LCONTROL:
     case VK_RCONTROL:
     case VK_MENU:
     case VK_LMENU:
     case VK_RMENU:
	return GetKeyState (vkey) & 0x8000;
    }
    return GetAsyncKeyState (vkey) & 0x8000;
}

/* Mouse emulation, Win32 interface */
static int mousecx = 160, mousecy = 100, mousedx = 160, mousedy = 100;
static int mousecl = MAKELONG (160, 100);
int mouseactive;

void setmouseactive (int active)
{
    mousedx = (amigawin_rect.right - amigawin_rect.left) / 2;
    mousedy = (amigawin_rect.bottom - amigawin_rect.top) / 2;
    mousecl = MAKELONG (mousedx, mousedy);
    mousecx = amigawin_rect.left + mousedx;
    mousecy = amigawin_rect.top + mousedy;

    if (active == mouseactive)
	return;
    mouseactive = active;

    if (active) {
	ShowCursor (FALSE);
	SetCursorPos (mousecx, mousecy);
    if( currprefs.win32_middle_mouse )
    	SetWindowText (hMainWnd ? hMainWnd : hAmigaWnd, "WinUAE - [Mouse active - press Alt-Tab or middle-button to cancel]");
    else
    	SetWindowText (hMainWnd ? hMainWnd : hAmigaWnd, "WinUAE - [Mouse active - press Alt-Tab to cancel]");
	ClipCursor (&amigawin_rect);
    } else {
	ShowCursor (TRUE);
	SetWindowText (hMainWnd ? hMainWnd : hAmigaWnd, "WinUAE" );
	ClipCursor (NULL);
    }
}

static int hascapture = 0;

static void setcapture (void)
{
    if (hascapture)
	return;
    hascapture++;
    SetCapture (hAmigaWnd);
}

static __inline__ void releasecapture (void)
{
    if (!hascapture)
	return;
    hascapture--;
    ReleaseCapture ();
}

__inline__ frame_time_t read_processor_time_cyrix (void)
{
    LARGE_INTEGER counter;
    QueryPerformanceCounter( &counter );
    return (frame_time_t)(counter.LowPart);
}

#ifdef __GNUC__
__inline__ frame_time_t read_processor_time(void)
{
    if( useqpc )
    {
        return read_processor_time_cyrix();
    }
    else
    {
#ifdef X86_ASSEMBLY
        frame_time_t result;
        __asm__ __volatile__ (".byte 0x0f,0x31" : "=a" (result) :);
        return result;
#else /* use the Cyrix-method here too... */
        return read_processor_time_cyrix();
#endif
    }
}
#endif

static void figure_processor_speed (void)
{
    LARGE_INTEGER perfreq = {0,0};
    extern frame_time_t syncbase, vsynctime;
    frame_time_t clockrate;

#if defined( __GNUC__ ) && defined( X86_ASSEMBLY )
    extern int rdtsc_check( void ) __asm__ ("rdtsc_check");
    if( rdtsc_check() )
    {
        useqpc = 0;
    }
    else
    {
        useqpc = 1;
    }
#endif
    rpt_available = 1; /* ALWAYS available on Win32, because we've either got RDTSC or QueryPerformanceCounter() */

    if( useqpc )
    {
        QueryPerformanceFrequency( &perfreq );
        if( perfreq.LowPart < 66000000 )
            write_log( "You appear to have a strange machine regarding the QueryPerformanceFrequency() API.  "
                       "Please report this bug to Microsoft, with a frequency-value of %u\n",perfreq.LowPart );
    }
	write_log( "Measuring Clock Rate... " );
	SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS );
	clockrate = read_processor_time();
	Sleep( 1000 );
	clockrate = read_processor_time() - clockrate;
	SetPriorityClass( GetCurrentProcess(), NORMAL_PRIORITY_CLASS );

	write_log( "%dMHz\n", clockrate / 1000000 );
    syncbase = clockrate;
    vsynctime = syncbase / VBLANK_HZ_PAL; /* default to 50Hz */
}

/* DirectDraw stuff */
static char *getddrname (HRESULT ddrval)
{
    switch (ddrval) 
    {
    case DDERR_ALREADYINITIALIZED:
	    return "This object is already initialized.";
    case DDERR_CANNOTATTACHSURFACE:
	    return "Cannot attach surface.";
    case DDERR_CANNOTDETACHSURFACE:
	    return "Cannot detach surface.";
    case DDERR_CURRENTLYNOTAVAIL:
	    return "Support unavailable.";
    case DDERR_EXCEPTION:
	    return "Unexpected exception.";
    case DDERR_GENERIC:
	    return "Undefined";	/* THIS MAKES SENSE!  FUCKING M$ */
    case DDERR_HEIGHTALIGN:
	    return "Height needs to be aligned.";
    case DDERR_INCOMPATIBLEPRIMARY:
	    return "New params doesn't match existing primary surface.";
    case DDERR_INVALIDCAPS:
	    return "Device doesn't have requested capabilities.";
    case DDERR_INVALIDCLIPLIST:
	    return "Provided clip-list not supported.";
    case DDERR_INVALIDMODE:
	    return "Mode not supported.";
    case DDERR_INVALIDOBJECT:
	    return "Invalid object.";
    case DDERR_INVALIDPARAMS:
	    return "Invalid params.";
    case DDERR_INVALIDPIXELFORMAT:
	    return "Device doesn't support requested pixel format.";
    case DDERR_INVALIDRECT:
	    return "Invalid RECT.";
    case DDERR_LOCKEDSURFACES:
	    return "Surface locked.";
    case DDERR_NO3D:
	    return "No 3d capabilities.";
    case DDERR_NOALPHAHW:
	    return "No alpha h/w.";
    case DDERR_NOCLIPLIST:
	    return "No clip-list.";
    case DDERR_NOCOLORCONVHW:
	    return "No colour-conversion h/w.";
    case DDERR_NOCOOPERATIVELEVELSET:
        return "No cooperative-level set.";
    case DDERR_NOCOLORKEY:
	    return "No colour-key.";
    case DDERR_NOCOLORKEYHW:
        return "No colour-key hardware.";
    case DDERR_NODIRECTDRAWSUPPORT:
        return "No DirectDraw support with this display driver!";
    case DDERR_NOEXCLUSIVEMODE:
        return "Exlusive-mode needed but not set yet.";
    case DDERR_NOFLIPHW:
        return "No flipping hardware.";
    case DDERR_NOGDI:
        return "No GDI present.";
    case DDERR_NOMIRRORHW:
        return "No mirror hardware.";
    case DDERR_NOTFOUND:
        return "Requested item not found.";
    case DDERR_NOOVERLAYHW:
        return "No overlay hardware.";
    case DDERR_NORASTEROPHW:
        return "No raster-op hardware.";
    case DDERR_NOROTATIONHW:
        return "No rotation hardware.";
    case DDERR_NOSTRETCHHW:
        return "No stretch hardware.";
    case DDERR_NOT4BITCOLOR:
        return "Not a 4-bit colour palette.";
    case DDERR_NOT4BITCOLORINDEX:
        return "Not a 4-bit colour-index.";
    case DDERR_NOT8BITCOLOR:
        return "Not an 8-bit colour palette.";
    case DDERR_NOTEXTUREHW:
        return "No texture hardware.";
    case DDERR_NOVSYNCHW:
        return "No vsync hardware.";
    case DDERR_NOZBUFFERHW:
        return "No z-buffer hardware.";
    case DDERR_NOZOVERLAYHW:
        return "No z-overlay hardware.";
    case DDERR_OUTOFCAPS:
    	return "out of caps";
    case DDERR_OUTOFMEMORY:
	    return "Out of memory.";
    case DDERR_OUTOFVIDEOMEMORY:
	    return "out of video memory.";
    case DDERR_OVERLAYCANTCLIP:
        return "no clipped overlay support in h/w.";
    case DDERR_OVERLAYCOLORKEYONLYONEACTIVE:
        return "can only have one color-key active at one time for overlays.";
    case DDERR_PALETTEBUSY:
    	return "Palette busy.";
    case DDERR_COLORKEYNOTSET:
    	return "No source colour-key provided.";
    case DDERR_SURFACEALREADYATTACHED:
        return "Surface already attached.";
    case DDERR_SURFACEALREADYDEPENDENT:
        return"Surface already dependent.";
    case DDERR_SURFACEBUSY:
    	return "Surface busy.";
    case DDERR_CANTLOCKSURFACE:
    	return "Access to surface refused (no DCI Provider).";
    case DDERR_SURFACEISOBSCURED:
        return "Surface is obscured.";
    case DDERR_SURFACELOST:
    	return "Surface lost.";
    case DDERR_SURFACENOTATTACHED:
        return "Surface not attached.";
    case DDERR_TOOBIGHEIGHT:
        return "Height requested is too large.";
    case DDERR_TOOBIGSIZE:
        return "Size requested is too large.";
    case DDERR_TOOBIGWIDTH:
        return "Width requested is too large.";
    case DDERR_UNSUPPORTED:
        return "Unsupported.";
    case DDERR_UNSUPPORTEDFORMAT:
    	return "Unsupported format.";
    case DDERR_UNSUPPORTEDMASK:
        return "Unsupported bitmask in pixel-format request.";
    case DDERR_VERTICALBLANKINPROGRESS:
        return "Vertical blank in progress.";
    case DDERR_WASSTILLDRAWING:
    	return "Was still drawing.";
    case DDERR_XALIGN:
        return "rectangle needs to be x-aligned.";
    case DDERR_INVALIDDIRECTDRAWGUID:
        return "invalid GUID for direct-draw.";
    case DDERR_DIRECTDRAWALREADYCREATED:
        return "DirectDraw object already created for this process.";
    case DDERR_NODIRECTDRAWHW:
        return "driver does not support h/w DirectDraw.";
    case DDERR_PRIMARYSURFACEALREADYEXISTS:
        return "Primary surface already exists for this process.";
    case DDERR_NOEMULATION:
        return "DirectDraw s/w emulation not available.";
    case DDERR_REGIONTOOSMALL:
        return "GetClipList() region is too small.";
    case DDERR_CLIPPERISUSINGHWND:
        return "Trying to set a clip-list for a clipper object which is already attached to an HWND.";
    case DDERR_NOCLIPPERATTACHED:
        return "No clipper object attached to surface object.";
    case DDERR_NOHWND:
        return "No HWND provided for clipper notification, or for CooperativeLevel.";
    case DDERR_HWNDSUBCLASSED:
        return "CooperativeLevel HWND has been subclassed, preventing state restoration.";
    case DDERR_HWNDALREADYSET:
        return "CooperativeLevel HWND already set - can't be reset while surfaces or palettes exist.";
    case DDERR_NOPALETTEATTACHED:
        return "No palette object attached to this surface.";
    case DDERR_NOPALETTEHW:
        return "No h/w support for 16 or 256 colour palettes.";
    case DDERR_BLTFASTCANTCLIP:
    	return "Cannot use BLTFAST with Clipper attached to surface.";
    case DDERR_NOBLTHW:
        return "No blitter.";
    case DDERR_NODDROPSHW:
        return "No DirectDraw ROP h/w.";
    case DDERR_OVERLAYNOTVISIBLE:
        return "GetOverlayPosition called on a hidden overlay.";
    case DDERR_NOOVERLAYDEST:
        return "GetOverlayPosition called on an overlay that UpdateOverlay has never been called on.";
    case DDERR_INVALIDPOSITION:
        return "Position of overlay on destination is no longer legal.";
    case DDERR_NOTAOVERLAYSURFACE:
        return "Overlay member is called for a non-overlay surface.";
    case DDERR_EXCLUSIVEMODEALREADYSET:
        return "Attempt to set CooperativeLevel when already set to exclusive.";
    case DDERR_NOTFLIPPABLE:
        return "Surface not flippable.";
    case DDERR_CANTDUPLICATE:
        return "Can't duplicate primary or 3d surfaces, or surfaces that are implicitly created.";
    case DDERR_NOTLOCKED:
        return "Surface not locked.";
    case DDERR_CANTCREATEDC:
        return "Cannot create any more DCs.";
    case DDERR_NODC:
        return "No DC ever created for this surface.";
    case DDERR_WRONGMODE:
        return "Surface cannot be restored, because it was created in a different mode.";
    case DDERR_IMPLICITLYCREATED:
        return "Surface cannot be restored, because it is an implicitly created surface.";
    case DDERR_NOTPALETTIZED:
        return "Surface being used is not a palette-based surface.";
    case DDERR_UNSUPPORTEDMODE:
        return "The display is currently in an unsupported mode.";
    case DDERR_NOMIPMAPHW:
        return "Operation could not be carried out because there is no mip-map texture mapping hardware present or available.";
    case DDERR_INVALIDSURFACETYPE:
        return "The requested action could not be performed because the surface was of the wrong type.";
    case DDERR_NOOPTIMIZEHW:
        return "Device does not support optimized surfaces, therefore no video memory optimized surfaces.";
    case DDERR_NOTLOADED:
        return "Surface is an optimized surface, but has not yet been allocated any memory.";
    case DDERR_NOFOCUSWINDOW:
        return "Attempt was made to create or set a device window without first setting the focus window.";
    case DDERR_DCALREADYCREATED:
        return "A DC has already been returned for this surface. Only one DC can be retrieved per surface.";
    case DDERR_NONONLOCALVIDMEM:
        return "An attempt was made to allocate non-local video memory from a device that does not support non-local video memory.";
    case DDERR_CANTPAGELOCK:
    	return "Attempt to page-lock a surface failed.";
    case DDERR_CANTPAGEUNLOCK:
	    return "Attempt to page-unlock a surface failed.";
    case DDERR_NOTPAGELOCKED:
        return "An attempt was made to page unlock a surface with no outstanding page locks.";
    case DDERR_MOREDATA:
        return "There is more data available than the specified buffer size could hold.";
    case DDERR_VIDEONOTACTIVE:
        return "The video port is not active.";
    case DDERR_DEVICEDOESNTOWNSURFACE:
        return "Surfaces created by one direct draw device cannot be used directly by another direct draw device.";
    case DDERR_NOTINITIALIZED:
        return "An attempt was made to invoke an interface member of a DirectDraw object created by CoCreateInstance() before it was initialized.";
    }
    return "";
}

static int lockcnt = 0;

static int do_surfacelock( LPDIRECTDRAWSURFACE surface, LPDDSURFACEDESC surfacedesc )
{
    int result = 1;
    HRESULT ddrval;
    /*write_log( "do_surfacelock(0x%x, 0x%x)\n", surface, surfacedesc );*/
    if( surface && surfacedesc )
    {
        surfacedesc->dwSize = sizeof( DDSURFACEDESC );
        while ( (ddrval = IDirectDrawSurface_Lock( surface, NULL, surfacedesc, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,
                                                    NULL ) ) != DD_OK )
        {
            if (ddrval == DDERR_SURFACELOST) {
    	        ddrval = IDirectDrawSurface_Restore( surface );
                if (ddrval != DD_OK)
                {
                    result = 0;
                    break;
                }
            }
            else if (ddrval != DDERR_SURFACEBUSY) 
            {
	            write_log ("lpDDS->Lock() failed - %s (%d)\n", getddrname (ddrval), (unsigned short) ddrval);
                result = 0;
                break;
            }
        }
        if( result )
            lockcnt++;
    }
    else
    {
        result = 0;
    }
    return result;
}

surface_type_e try_surface_locks( int wantfull )
{
    surface_type_e result = invalid_surface;
#ifdef TEST_SECONDARY_SURFACE
    if( do_surfacelock( lpDDS_secondary, &secondary_ddsd ) )
        result = secondary_surface;
#else
    if( (!wantfull && screen_is_picasso) || !do_surfacelock( lpDDS_primary, &primary_ddsd ) )
    {
        /* For windowed-mode Picasso screens, or for a failure in locking the primary-surface */
        /* lock the secondary surface, and draw to it.  A blit is then required from secondary to */
        /* primary surface. */
        if( lpDDS_secondary && do_surfacelock( lpDDS_secondary, &secondary_ddsd ) )
        {
            result = secondary_surface;
        }
    }
    else
    {
        result = primary_surface;
    }
#endif
    return result;
}

void unlockscr( void )
{
    lockcnt--;
    IDirectDrawSurface_Unlock( lockable_surface, lockable_surfacedesc->lpSurface );
}

/* Return one of the pixel formats declared in picasso96.h if the surface
 * is usable for us, or RGBFB_NONE if it is not usable.  */
static RGBFTYPE surface_pixelformat (DDSURFACEDESC *surface)
{
    DDPIXELFORMAT *pfp = &surface->ddpfPixelFormat;
    DWORD r, g, b;

    if ((surface->dwFlags & (DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_REFRESHRATE)) 
	!= (DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_REFRESHRATE))
	return RGBFB_NONE;

    if ((pfp->dwFlags & DDPF_RGB) == 0)
	return RGBFB_NONE;

    r = pfp->dwRBitMask;
    g = pfp->dwGBitMask;
    b = pfp->dwBBitMask;
    switch (pfp->dwRGBBitCount) {
     case 8:
	if ((pfp->dwFlags & DDPF_PALETTEINDEXED8) != 0)
	    return RGBFB_CHUNKY;
	break;

     case 16:
	if (r == 0xF800 && g == 0x07E0 && b == 0x001F)
	    return RGBFB_R5G6B5PC;
	if (r == 0x7C00 && g == 0x03E0 && b == 0x001F)
	    return RGBFB_R5G5B5PC;
	if (b == 0xF800 && g == 0x07E0 && r == 0x001F)
	    return RGBFB_B5G6R5PC;
	if (b == 0x7C00 && g == 0x03E0 && r == 0x001F)
	    return RGBFB_B5G5R5PC;
	/* This happens under NT - with r == b == g == 0 !!! */
	write_log ("Unknown 16 bit format %d %d %d\n", r, g, b);
	break;

     case 24:
	if (r == 0xFF0000 && g == 0x00FF00 && b == 0x0000FF)
	    return RGBFB_B8G8R8;
	if (r == 0x0000FF && g == 0x00FF00 && b == 0xFF0000)
	    return RGBFB_R8G8B8;
	break;

     case 32:
	if (r == 0x00FF0000 && g == 0x0000FF00 && b == 0x000000FF)
	    return RGBFB_B8G8R8A8;
	if (r == 0x000000FF && g == 0x0000FF00 && b == 0x00FF0000)
	    return RGBFB_R8G8B8A8;
	if (r == 0xFF000000 && g == 0x00FF0000 && b == 0x0000FF00)
	    return RGBFB_A8B8G8R8;
	if (r == 0x0000FF00 && g == 0x00FF0000 && b == 0xFF000000)
	    return RGBFB_A8R8G8B8;
	break;
	
     default:
	gui_message("Unknown %d bit format %d %d %d\n", pfp->dwRGBBitCount, r, g, b); /* %%% - BERND, and here too... */
	break;
    }
    return RGBFB_NONE;
}

static int rgbformat_bits (RGBFTYPE t)
{
    unsigned long f = 1 << t;
    return ((f & RGBMASK_8BIT) != 0 ? 8
	    : (f & RGBMASK_15BIT) != 0 ? 15
	    : (f & RGBMASK_16BIT) != 0 ? 16
	    : (f & RGBMASK_24BIT) != 0 ? 24
	    : (f & RGBMASK_32BIT) != 0 ? 32
	    : 0);
}

void release_ddraw (void)
{
    if( lpDD )
    {
	    IDirectDraw_RestoreDisplayMode( lpDD );
	    IDirectDraw_SetCooperativeLevel( lpDD, hAmigaWnd, DDSCL_NORMAL);
    }

    if (lpDDC)
	IDirectDrawClipper_Release (lpDDC);
    if (lpDDS_primary)
	    IDirectDrawSurface_Release( lpDDS_primary );
    if (lpDDS_secondary)
	    IDirectDrawSurface_Release( lpDDS_secondary );
    if (lpDDP)
	IDirectDrawPalette_Release (lpDDP);
    if (lpDD)
	    IDirectDraw_Release (lpDD);
    lpDDC  = NULL;
    lockable_surface = NULL;
    lockable_surfacedesc = NULL;
    surface_type = invalid_surface;
    lpDDS_primary    = NULL;
    lpDDS_secondary  = NULL;
    lpDDP  = NULL;
    lpDD   = NULL;
}


int start_ddraw (void)
{
    HRESULT ddrval;

    ddrval = (*pDirectDrawCreate) (NULL, &lpDD, NULL);
    if (ddrval != DD_OK)
	goto oops;

    current_surface.dwSize = sizeof current_surface;
    ddrval = IDirectDraw_GetDisplayMode (lpDD, &current_surface);
    if (ddrval != DD_OK)
	goto oops;

    return 1;

  oops:
    gui_message("start_ddraw(): DirectDraw initialization failed with %s/%d\n", getddrname (ddrval), ddrval);
    release_ddraw ();
    return 0;
}

static int set_ddraw (int width, int height, int wantfull, int bits,
		      LPPALETTEENTRY pal)
{
    HRESULT ddrval;

    bits = (bits + 7) & ~7;
    
    ddrval = IDirectDraw_SetCooperativeLevel( lpDD, hAmigaWnd,
					      (wantfull
					       ? DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN
					       : DDSCL_NORMAL));
    if (ddrval != DD_OK)
	goto oops;

    ddrval = IDirectDraw_CreateClipper( lpDD, 0, &lpDDC, NULL );
    if (ddrval != DD_OK)
    {
        write_log( "set_ddraw: No clipping support, so we must lock the front-buffer.  P96 in a window will not work\n" );
        lpDDC = NULL; /* Just to make sure */
    }

    if (wantfull) 
    {
        write_log( "set_ddraw: Trying %dx%d, %d bits\n", width, height, bits );
        ddrval = IDirectDraw_SetDisplayMode( lpDD, width, height, bits );
	    if (ddrval != DD_OK)
        {
            write_log( "set_ddraw: Couldn't SetDisplayMode()\n" );
	        goto oops;
        }

	    current_surface.dwSize = sizeof current_surface;
	    ddrval = IDirectDraw_GetDisplayMode( lpDD, &current_surface );
	    if (ddrval != DD_OK)
        {
            write_log( "set_ddraw: Couldn't GetDisplayMode()\n" );
	        goto oops;
        }
    }

    ZeroMemory( &primary_ddsd, sizeof( primary_ddsd ) );
    primary_ddsd.dwSize            = sizeof( DDSURFACEDESC );
    primary_ddsd.dwFlags           = DDSD_CAPS;
    primary_ddsd.ddsCaps.dwCaps    = DDSCAPS_PRIMARYSURFACE | DDSCAPS_VIDEOMEMORY;
    ddrval = IDirectDraw_CreateSurface( lpDD, &primary_ddsd, &lpDDS_primary, NULL );
    if( ddrval != DD_OK )
    {
        primary_ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
        ddrval = IDirectDraw_CreateSurface( lpDD, &primary_ddsd, &lpDDS_primary, NULL );
    }
    if( ddrval != DD_OK )
    {
        write_log( "set_ddraw: Couldn't CreateSurface() for primary.\n" );
	    goto oops;
    }

    if( lpDDC ) /* Only create the secondary surface if we have clipping, and can blit from offscreen to
                   primary surface. */
    {
        ZeroMemory( &secondary_ddsd, sizeof( secondary_ddsd ) );
        secondary_ddsd.dwSize = sizeof( DDSURFACEDESC );
        secondary_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
        secondary_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;
        secondary_ddsd.dwWidth  = width;
        secondary_ddsd.dwHeight = height;
        ddrval = IDirectDraw_CreateSurface( lpDD, &secondary_ddsd, &lpDDS_secondary, NULL );
        if( ddrval != DD_OK )
        {
            secondary_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
            ddrval = IDirectDraw_CreateSurface( lpDD, &secondary_ddsd, &lpDDS_secondary, NULL );
        }
        if( ddrval != DD_OK )
        {
            write_log( "set_ddraw: Couldn't CreateSurface() for secondary.\n" );
            goto oops;
        }
    }

    switch( surface_type = try_surface_locks( wantfull ) )
    {
    case invalid_surface:
        lockable_surfacedesc = NULL;
        lockable_surface = NULL;
        write_log( "set_ddraw: Couldn't lock primary, and no secondary available.\n" );
        goto oops;
        break;
    case primary_surface:
        lockable_surfacedesc = &primary_ddsd;
        lockable_surface     = lpDDS_primary;
        break;
    case secondary_surface:
        lockable_surfacedesc = &secondary_ddsd;
        lockable_surface     = lpDDS_secondary;
        write_log( "set_ddraw: Couldn't lock primary, so offscreen secondary buffer is being used.\n" );
        break;
    }

    if( lockable_surface )
        unlockscr();

    if( lpDDC )
    {
        ddrval = IDirectDrawSurface_SetClipper( lpDDS_primary, lpDDC );
        if( ddrval != DD_OK )
        {
            write_log( "set_ddraw: Couldn't SetClipper()\n" );
            goto oops;
        }

        ddrval = IDirectDrawClipper_SetHWnd( lpDDC, 0, hAmigaWnd );
        if (ddrval != DD_OK)
        {
            write_log( "set_ddraw: Couldn't SetHWnd()\n" );
	        goto oops;
        }
    }

    current_pixbytes = (lockable_surfacedesc->ddpfPixelFormat.dwRGBBitCount + 7) >> 3;

    write_log( "set_ddraw() called, and is %dx%d@%d-bytes\n", width, height, current_pixbytes );

    if (current_pixbytes == 1) {
        current_palette = pal;
        ddrval = IDirectDraw_CreatePalette( lpDD, DDPCAPS_8BIT | DDPCAPS_ALLOW256, pal, &lpDDP, NULL);
	    if (ddrval != DD_OK)
        {
            write_log( "set_ddraw: Couldn't CreatePalette()\n" );
	        goto oops;
        }

	    ddrval = IDirectDrawSurface_SetPalette (lpDDS_primary, lpDDP);
	    if (ddrval != DD_OK)
        {
            write_log( "set_ddraw: Couldn't SetPalette()\n" );
	        goto oops;
        }
    }

    return 1;

  oops:
    gui_message("set_ddraw(): DirectDraw initialization failed with %s/%d\n", getddrname (ddrval), ddrval);
    return 0;
}

/* Color management */

static xcolnr xcol8[4096];
static PALETTEENTRY colors256[256];
static int ncols256 = 0;

static int red_bits, green_bits, blue_bits;
static int red_shift, green_shift, blue_shift;

static int get_color (int r, int g, int b, xcolnr * cnp)
{
    if (ncols256 == 256)
	return 0;
    colors256[ncols256].peRed = r * 0x11;
    colors256[ncols256].peGreen = g * 0x11;
    colors256[ncols256].peBlue = b * 0x11;
    colors256[ncols256].peFlags = 0;
    *cnp = ncols256;
    ncols256++;
    return 1;
}

static void init_colors (void)
{
    int i;

    if (ncols256 == 0) {
	alloc_colors256 (get_color);
	memcpy (xcol8, xcolors, sizeof xcol8);
    }

    /* init colors */

    switch (current_pixbytes) {
    case 1:
	memcpy (xcolors, xcol8, sizeof xcolors);
	if (lpDDP != 0) {
	    HRESULT ddrval = IDirectDrawPalette_SetEntries (lpDDP, 0, 0, 256, colors256);
	    if (ddrval != DD_OK)
		gui_message ("DX_SetPalette() failed with %s/%d\n", getddrname (ddrval), ddrval);
	}
	break;

    case 2:
    case 3:
    case 4:
	red_bits = bits_in_mask (lockable_surfacedesc->ddpfPixelFormat.dwRBitMask);
	green_bits = bits_in_mask (lockable_surfacedesc->ddpfPixelFormat.dwGBitMask);
	blue_bits = bits_in_mask (lockable_surfacedesc->ddpfPixelFormat.dwBBitMask);
	red_shift = mask_shift (lockable_surfacedesc->ddpfPixelFormat.dwRBitMask);
	green_shift = mask_shift (lockable_surfacedesc->ddpfPixelFormat.dwGBitMask);
	blue_shift = mask_shift (lockable_surfacedesc->ddpfPixelFormat.dwBBitMask);

	alloc_colors64k (red_bits, green_bits, blue_bits, red_shift,
			 green_shift, blue_shift);
	break;
    }
    switch (gfxvidinfo.pixbytes) {
     case 2:
	for (i = 0; i < 4096; i++)
	    xcolors[i] = xcolors[i] * 0x00010001;
	gfxvidinfo.can_double = 1;
	break;
     case 1:
	for (i = 0; i < 4096; i++)
	    xcolors[i] = xcolors[i] * 0x01010101;
	gfxvidinfo.can_double = 1;
	break;
     default:
	gfxvidinfo.can_double = 0;
	break;
    }
}

static void close_windows (void)
{
    gfxvidinfo.bufmem = 0;
    gfxvidinfo.linemem = 0;

    releasecapture ();
    setmouseactive (0);
    ClipCursor (NULL);
    release_ddraw ();
    dsound_newwindow (0);

    if (hStatusWnd)
    	DestroyWindow (hStatusWnd);
    if (hAmigaWnd)
	    DestroyWindow (hAmigaWnd);
    if (hMainWnd)
	    DestroyWindow (hMainWnd);

    hMainWnd = 0;
    hStatusWnd = 0;
    hAmigaWnd = 0;
}

void toggle_fullscreen (void)
{
    if (needs_fullscreen)
	return;

    display_change_requested = 1;
    if (screen_is_picasso)
	currprefs.gfx_pfullscreen ^= 1;
    else
	currprefs.gfx_afullscreen ^= 1;    
}

static long FAR PASCAL AmigaWindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hDC;
    short wheeldelta;
    static int store_xy = 0;
    BOOL minimized;

    switch (message) {

    case WM_PALETTECHANGED:
        if( (HWND)wParam != hWnd )
        {
            write_log( "WM_PALETTECHANGED Request\n" );
            if( lpDDP && lpDDS_primary )
            {
                IDirectDrawSurface_SetPalette( lpDDS_primary, NULL );
            	IDirectDrawSurface_SetPalette( lpDDS_primary, lpDDP );
            }
        }
        break;

    case WM_ACTIVATEAPP:
        if (bActive = wParam) 
        {
	        if (fullscreen) 
            {
		        SetCursor (NULL);
		        SetCursorPos (mousecx, mousecy);
	        }
    	    my_kbd_handler (VK_CAPITAL, 0x3a, TRUE);
	    }
        else
        {
            if (!fullscreen)
		        setmouseactive (0);
            else
            {
                if( screen_is_picasso )
                {
                    picasso_requested_on = 0;
                    picasso_on = 0;
                }
            }
	    }
	break;

    case WM_ACTIVATE:
    minimized = HIWORD( wParam );
    if (LOWORD (wParam) != WA_INACTIVE) 
    {
        write_log( "WinUAE now active via WM_ACTIVATE\n" );
        if( !minimized )
        {
            if( currprefs.win32_iconified_nospeed )
            {
                SetPriorityClass( GetCurrentProcess(), NORMAL_PRIORITY_CLASS );
            }
            resume_sound();
        }
	    ShowWindow (hWnd, SW_RESTORE);
        if( screen_is_picasso )
        {
            picasso_requested_on = 1;
        }
	}
    else
    {
        write_log( "WinUAE now inactive via WM_ACTIVATE\n" );
        if( minimized && !quit_program )
        {
            if( currprefs.win32_iconified_nospeed )
            {
                SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS );
            }
            if( currprefs.win32_iconified_nosound )
            {
                pause_sound();
            }
        }
    }
	break;

     case WM_SETCURSOR:
	if (fullscreen) {
	    SetCursor (NULL);
	    return TRUE;
	}
	break;

    case WM_SYSCOMMAND:
        switch( wParam & 0xFFF0 )
        {
        case SC_MAXIMIZE:
            toggle_fullscreen ();
	        return 0;
            break;
        case SC_MINIMIZE:
            {
            picasso_on = 0;
            picasso_requested_on = 0;
            break;
            }
        }
	break;

     case WM_KEYUP:
     case WM_SYSKEYUP:
	numkeysdown--;
	keysdown[wParam] = 0;
	my_kbd_handler (wParam, (lParam >> 16) & 0x1ff, FALSE);
    return 0;
	break;

     case WM_KEYDOWN:
     case WM_SYSKEYDOWN:
	if (LOWORD (lParam) == 1) 
    {
	    if (numkeysdown) 
        {
		    int key;
		    numkeysdown = 0;

		    for (key = 256; key--;) 
            {
		        if (keysdown[key]) 
                {
			        if (checkkey (key, lParam))
			            numkeysdown++;
			        else 
                    {
			            my_kbd_handler (key, (keysdown[key] >> 16) & 0x1ff, FALSE);
			            keysdown[key] = 0;
			        }
		        }
		    }
	    }
	    if (!keysdown[wParam]) 
        {
		    keysdown[wParam] = lParam;
		    numkeysdown++;
	    }
	    numkeysdown++;
	    my_kbd_handler (wParam, (lParam >> 16) & 0x1ff, TRUE);
	}
	break;

     case WM_LBUTTONDOWN:
	if (ievent_alive) {
	    setcapture ();
	    buttonstate[0] = 1;
	} else if (!fullscreen && !mouseactive)
	    setmouseactive (1);
	else
	    buttonstate[0] = 1;
	break;

     case WM_LBUTTONUP:
	releasecapture ();
	buttonstate[0] = 0;
	break;

     case WM_MBUTTONDOWN:
	if (ievent_alive)
	    setcapture ();
	buttonstate[1] = 1;
	break;

     case WM_MBUTTONUP:
	releasecapture ();
	buttonstate[1] = 0;
    /* Do some fun stuff to handle Julian's request for the middle-mouse-button to act like ALT-TAB */
    if( !fullscreen && mouseactive && currprefs.win32_middle_mouse )
        setmouseactive(0);
	break;

     case WM_RBUTTONDOWN:
	if (ievent_alive)
	    setcapture ();
	buttonstate[2] = 1;
	break;

     case WM_RBUTTONUP:
	releasecapture ();
	buttonstate[2] = 0;
	break;

     case WM_VSCROLL:
         write_log( "WM_VSCROLL\n" );
         if( LOWORD( wParam ) == SB_LINEDOWN )
             record_key(0x7A<<1);
         else if( LOWORD( wParam ) == SB_LINEUP )
             record_key(0x7B<<1);
         break;

     case WM_MOUSEWHEEL:
         write_log( "WM_MOUSEWHEEL\n" );
         wheeldelta = HIWORD(wParam);
         if( wheeldelta > 0 )
             record_key(0x7A<<1);
         else
             record_key(0x7B<<1);
         break;

     case WM_MOUSEMOVE:
	if ((mouseactive && !ievent_alive) || fullscreen) {
	    /*
	     * In this mode, the mouse pointer is always centered in the window,
	     * this is ensured by the SetCursorPos call below.
	     * We don't want to handle messages that result from such a SetCursorPos
	     * call (recursion!), so exit early if we see one.
	     */
	    if (lParam == mousecl)
		break;
	    lastmx += (signed short) LOWORD (lParam) - mousedx;
	    lastmy += (signed short) HIWORD (lParam) - mousedy;
	    if (ievent_alive) {
		if (lastmx < 0)
		    lastmx = 0;
		if (lastmx > current_width)
		    lastmx = current_width;
		if (lastmy < 0)
		    lastmy = 0;
		if (lastmy > current_height)
		    lastmy = current_height;

	    }
	    SetCursorPos (mousecx, mousecy);
	    break;
	}
	lastmx = (signed short) LOWORD (lParam);
	lastmy = (signed short) HIWORD (lParam);
	break;

     case WM_PAINT:
	clear_inhibit_frame (IHF_WINDOWHIDDEN);
	hDC = BeginPaint (hWnd, &ps);
	EndPaint (hWnd, &ps);
	notice_screen_contents_lost ();
	break;

     case WM_DROPFILES:
	if (DragQueryFile ((HDROP) wParam, (UINT) - 1, NULL, 0)) {
	    if (DragQueryFile ((HDROP) wParam, 0, NULL, 0) < 255)
		DragQueryFile ((HDROP) wParam, 0, changed_prefs.df[0], sizeof (changed_prefs.df[0]));
	}
	DragFinish ((HDROP) wParam);
	break;

     case WM_CAPTURECHANGED:
	if ((HWND)lParam != hAmigaWnd)
	    buttonstate[0] = buttonstate[1] = buttonstate[2] = 0;
	break;

     case WM_TIMER:
	finishjob ();
	break;

     case WM_USER + 0x200:
	DoSomeWeirdPrintingStuff (wParam);
	break;

     case WM_CREATE:
	DragAcceptFiles (hWnd, TRUE);
	break;

     case WM_CLOSE:
	uae_quit ();
	return 0;

     case WM_WINDOWPOSCHANGED:
	GetWindowRect (hAmigaWnd, &amigawin_rect);
    if( hMainWnd )
    {
        if( hWinUAEKey && store_xy++)
        {
            RegSetValueEx( hWinUAEKey, "xPos", 0, REG_DWORD, (LPBYTE)&amigawin_rect.left, sizeof( LONG ) );
            RegSetValueEx( hWinUAEKey, "yPos", 0, REG_DWORD, (LPBYTE)&amigawin_rect.top, sizeof( LONG ) );
        }
    }
	break;

    default:
#ifdef BSDSOCKET_SUPPORTED
        if( message >= 0xB000 && message < 0xB000+MAXPENDINGASYNC*2 )
        {
#if DEBUG_SOCKETS
            write_log( "sockmsg(0x%x, 0x%x, 0x%x)\n", message, wParam, lParam );
#endif
            sockmsg( message, wParam, lParam );
            return 0;
        }
#endif
    break;
    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}

static long FAR PASCAL MainWindowProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    RECT rc;
    HDC hDC;

    switch (message) {
     case WM_LBUTTONDOWN:
     case WM_MOUSEWHEEL:
     case WM_MOUSEMOVE:
     case WM_ACTIVATEAPP:
     case WM_ACTIVATE:
     case WM_SETCURSOR:
     case WM_SYSCOMMAND:
     case WM_KEYUP:
     case WM_SYSKEYUP:
     case WM_KEYDOWN:
     case WM_SYSKEYDOWN:
     case WM_LBUTTONUP:
     case WM_MBUTTONDOWN:
     case WM_MBUTTONUP:
     case WM_RBUTTONDOWN:
     case WM_RBUTTONUP:
     case WM_DROPFILES:
     case WM_CREATE:
     case WM_DESTROY:
     case WM_USER + 0x200:
     case WM_CLOSE:
     case WM_HELP:
	return AmigaWindowProc (hWnd, message, wParam, lParam);

     case WM_DISPLAYCHANGE:
	if (!fullscreen && (wParam + 7) / 8 != current_pixbytes)
	    display_change_requested = 1;
	break;
	
     case WM_ENTERSIZEMOVE:
	in_sizemove++;
	break;

     case WM_EXITSIZEMOVE:
	in_sizemove--;

	/* fall through */

     case WM_WINDOWPOSCHANGED:
	GetWindowRect (hAmigaWnd, &amigawin_rect);

	if (in_sizemove > 0)
	    break;

	if (!fullscreen && hAmigaWnd) {
	    if (amigawin_rect.left & 3) {
		RECT rc2;
		GetWindowRect (hMainWnd, &rc2);
		if (1 /*!mon || rc2.left + 4 < GetSystemMetrics (SM_CXSCREEN) */ ) {
		    MoveWindow (hMainWnd, rc2.left + 4 - amigawin_rect.left % 4, rc2.top,
				rc2.right - rc2.left, rc2.bottom - rc2.top, TRUE);
		}
	    }

	    setmouseactive (0);
	    return 0;
	}

	break;

     case WM_PAINT:
	hDC = BeginPaint (hWnd, &ps);
	GetClientRect (hWnd, &rc);
	DrawEdge (hDC, &rc, EDGE_SUNKEN, BF_RECT);

	EndPaint (hWnd, &ps);
	break;

     case WM_NCLBUTTONDBLCLK:
	if (wParam == HTCAPTION) {
	    toggle_fullscreen ();
	    return 0;
	}
	break;

    default:
#ifdef BSDSOCKET_SUPPORTED
        if( message >= 0xB000 && message < 0xB000+MAXPENDINGASYNC*2 )
        {
#if _DEBUG
            write_log( "sockmsg(0x%x, 0x%x, 0x%x)\n", message, wParam, lParam );
#endif
            sockmsg( message, wParam, lParam );
            return 0;
        }
#endif
    break;

    }

    return DefWindowProc (hWnd, message, wParam, lParam);
}

/* Console Win32 helper routines */
void activate_debugger ();

/* We're not a console-app anymore! */
void setup_brkhandler (void)
{
}

void remove_brkhandler (void)
{
}

static int register_classes (void)
{
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = AmigaWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = 0;
    wc.hIcon = LoadIcon (GetModuleHandle (NULL), IDI_APPICON);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject (BLACK_BRUSH);
    wc.lpszMenuName = 0;
    wc.lpszClassName = "AmigaPowah";
    if (!RegisterClass (&wc))
	return 0;

    wc.style = 0;
    wc.lpfnWndProc = MainWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = 0;
    wc.hIcon = LoadIcon (GetModuleHandle (NULL), IDI_APPICON);
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = GetStockObject (BLACK_BRUSH);
    wc.lpszMenuName = 0;
    wc.lpszClassName = "PCsuxRox";
    if (!RegisterClass (&wc))
	return 0;
    return 1;
}

struct win32_displaymode *win32_displaymode_list;

static HRESULT CALLBACK modesCallback (LPDDSURFACEDESC modeDesc, LPVOID context)
{
    struct win32_displaymode **dmpp;
    RGBFTYPE colortype;

    colortype = surface_pixelformat (modeDesc);
    if (colortype == RGBFB_NONE)
	return DDENUMRET_OK;

    dmpp = &win32_displaymode_list;
    while (*dmpp != 0) {
	if ((*dmpp)->width == modeDesc->dwWidth
	    && (*dmpp)->height == modeDesc->dwHeight
	    && (*dmpp)->refreshrate == modeDesc->dwRefreshRate)
	    break;
	dmpp = &(*dmpp)->next;
    }

    if (*dmpp == 0) {
	*dmpp = (struct win32_displaymode *)malloc (sizeof **dmpp);
	(*dmpp)->next = 0;
	(*dmpp)->width = modeDesc->dwWidth;
	(*dmpp)->height = modeDesc->dwHeight;
	(*dmpp)->refreshrate = modeDesc->dwRefreshRate;
	(*dmpp)->colormodes = 0;
    }
    (*dmpp)->colormodes |= 1 << colortype;
    return DDENUMRET_OK;
}

static int our_possible_depths[] = { 8, 15, 16, 24, 32 };

RGBFTYPE figure_pixel_formats( RGBFTYPE colortype )
{
    HWND tmpw;
    HRESULT ddrval;
    struct win32_displaymode *dm;
    int got_16bit_mode = 0;
    int window_created = 0;

    if( colortype == 0 ) /* Need to query a 16-bit display mode for its pixel-format.  Do this by opening such a screen */
    {
        tmpw = CreateWindowEx (WS_EX_TOPMOST,
			       "AmigaPowah", PROGNAME,
			       WS_VISIBLE | WS_POPUP,
			       CW_USEDEFAULT, CW_USEDEFAULT,
			       1,//GetSystemMetrics (SM_CXSCREEN),
			       1,//GetSystemMetrics (SM_CYSCREEN),
			       0, NULL, 0, NULL);
        if( tmpw )
        {
            window_created = 1;
            ddrval = IDirectDraw_SetCooperativeLevel (lpDD, tmpw, DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
            if( ddrval != DD_OK )
            {
	            gui_message( "figure_pixel_formats: error %s\n", getddrname(ddrval) );
	            goto out;
            }
        }
        else
        {
            gui_message( "figure_pixel_formats: test-window could not be created.\n" );
        }
    }
    else
    {
        got_16bit_mode = 1;
    }

    for (dm = win32_displaymode_list; dm != 0; dm = dm->next) {

        if (!got_16bit_mode)
        {
    	    write_log ("figure_pixel_formats: Attempting %dx%d: ", dm->width, dm->height);

            ddrval = IDirectDraw_SetDisplayMode (lpDD, dm->width, dm->height, 16);
	        if (ddrval != DD_OK)
		    continue;

	        current_surface.dwSize = sizeof current_surface;
	        ddrval = IDirectDraw_GetDisplayMode (lpDD, &current_surface);
	        if (ddrval != DD_OK)
		    continue;
	        colortype = surface_pixelformat (&current_surface);
	        if (colortype != RGBFB_NONE) {
                write_log ("%d ", our_possible_depths[2]);
                
                /* Clear the 16-bit information, and get the real stuff! */
                dm->colormodes &= ~(RGBFF_R5G6B5PC|RGBFF_R5G5B5PC|RGBFF_R5G6B5|RGBFF_R5G5B5|RGBFF_B5G6R5PC|RGBFF_B5G5R5PC);
                dm->colormodes |= 1 << colortype;
                got_16bit_mode = 1;
                write_log( "Got real 16-bit colour-depth information: 0x%x\n", colortype );
            }
        }
        else if (dm->colormodes & (RGBFF_R5G6B5PC|RGBFF_R5G5B5PC|RGBFF_R5G6B5|RGBFF_R5G5B5|RGBFF_B5G6R5PC|RGBFF_B5G5R5PC) ) 
        {
            /* Clear the 16-bit information, and set the real stuff! */
            dm->colormodes &= ~(RGBFF_R5G6B5PC|RGBFF_R5G5B5PC|RGBFF_R5G6B5|RGBFF_R5G5B5|RGBFF_B5G6R5PC|RGBFF_B5G5R5PC);
            dm->colormodes |= 1 << colortype;
        }
    }
    out:
    if( window_created )
    {
        Sleep(2000);
        DestroyWindow (tmpw);
    }
    return colortype;
}

static int create_windows (void)
{
    if (!fullscreen) 
    {
        RECT rc;
        LONG stored_x = 1, stored_y = GetSystemMetrics( SM_CYMENU ) + GetSystemMetrics( SM_CYBORDER );
        DWORD regkeytype;
        DWORD regkeysize = sizeof(LONG);
        HLOCAL hloc;
	    LPINT lpParts;

        RegQueryValueEx( hWinUAEKey, "xPos", 0, &regkeytype, (LPBYTE)&stored_x, &regkeysize );
        RegQueryValueEx( hWinUAEKey, "yPos", 0, &regkeytype, (LPBYTE)&stored_y, &regkeysize );
        if( stored_x < 1 )
            stored_x = 1;
        if( stored_y < ( GetSystemMetrics( SM_CYMENU ) + GetSystemMetrics( SM_CYBORDER ) ) )
            stored_y = GetSystemMetrics( SM_CYMENU ) + GetSystemMetrics( SM_CYBORDER );

        rc.left = stored_x;
	    rc.top = stored_y;
        rc.right = stored_x + current_width;
	    //rc.right = stored_x + current_width + GetSystemMetrics( SM_CXBORDER )*2;
        rc.bottom = stored_y + current_height + GetSystemMetrics( SM_CYMENU ) + GetSystemMetrics( SM_CYBORDER )*3;

	    AdjustWindowRect (&rc, NORMAL_WINDOW_STYLE, FALSE);

        hMainWnd = CreateWindowEx( picasso_on ? WS_EX_ACCEPTFILES : WS_EX_ACCEPTFILES, "PCsuxRox", "WinUAE",
				       NORMAL_WINDOW_STYLE, rc.left, rc.top,
				       rc.right - rc.left, rc.bottom - rc.top,
				       NULL, NULL, 0, NULL);

	    if (! hMainWnd)
	        return 0;
	    hStatusWnd = CreateStatusWindow (WS_CHILD | WS_VISIBLE, "", hMainWnd, 1);
	    if (hStatusWnd) 
        {
	        GetClientRect (hMainWnd, &rc);
	        /* Allocate an array for holding the right edge coordinates. */
	        hloc = LocalAlloc (LHND, sizeof (int) * NUM_PARTS);
	        if (hloc) 
            {
		        lpParts = LocalLock (hloc);

		        /* Calculate the right edge coordinate for each part, and copy the coords
		         * to the array.  */
		        lpParts[0] = rc.right - (DRIVE_WIDTH * 4) - LED_WIDTH - FPS_WIDTH - 2;
                lpParts[1] = lpParts[0] + FPS_WIDTH;
		        lpParts[2] = lpParts[1] + LED_WIDTH;
		        lpParts[3] = lpParts[2] + DRIVE_WIDTH;
		        lpParts[4] = lpParts[3] + DRIVE_WIDTH;
		        lpParts[5] = lpParts[4] + DRIVE_WIDTH;
		        lpParts[6] = lpParts[5] + DRIVE_WIDTH;

		        /* Create the seven parts */
		        SendMessage (hStatusWnd, SB_SETPARTS, (WPARAM) NUM_PARTS, (LPARAM) lpParts);

		        LocalUnlock (hloc);
		        LocalFree (hloc);
	        }
	    }
    }
    else
	    hMainWnd = NULL;

    hAmigaWnd = CreateWindowEx (fullscreen ? WS_EX_TOPMOST : WS_EX_ACCEPTFILES,
				"AmigaPowah", "WinUAE",
				hMainWnd ? WS_VISIBLE | WS_CHILD : WS_VISIBLE | WS_POPUP,
				hMainWnd ? 0 : CW_USEDEFAULT, hMainWnd ? 0 : CW_USEDEFAULT,
				fullscreen ? GetSystemMetrics (SM_CXSCREEN) : current_width,
				fullscreen ? GetSystemMetrics (SM_CYSCREEN) : current_height,
				hMainWnd, NULL, 0, NULL);
    
    if (! hAmigaWnd) 
    {
	    if (hMainWnd)
	        DestroyWindow (hMainWnd);
	    return 0;
    }


    if (hMainWnd)
        UpdateWindow( hMainWnd );
    if (hAmigaWnd)
    	UpdateWindow (hAmigaWnd);

    return 1;
}

/* DirectX will fail with "Mode not supported" if we try to switch to a full
 * screen mode that doesn't match one of the dimensions we got during enumeration.
 * So try to find a best match for the given resolution in our list.  */
int adjust_screenmode (int *pwidth, int *pheight, int *ppixbits)
{
    struct win32_displaymode *best;
    uae_u32 selected_mask = (*ppixbits == 8 ? RGBMASK_8BIT
			     : *ppixbits == 15 ? RGBMASK_15BIT
			     : *ppixbits == 16 ? RGBMASK_16BIT
			     : *ppixbits == 24 ? RGBMASK_24BIT
			     : RGBMASK_32BIT);
    int pass, i = 0, index = 0;
    
    for (pass = 0; pass < 2; pass++) 
    {
	    struct win32_displaymode *dm;
	    uae_u32 mask = (pass == 0
			    ? selected_mask
			    : RGBMASK_8BIT | RGBMASK_15BIT | RGBMASK_16BIT | RGBMASK_24BIT | RGBMASK_32BIT); /* %%% - BERND, were you missing 15-bit here??? */
        i = 0;
        index = 0;

	    best = win32_displaymode_list;
	    dm = best->next;

	    while (dm != 0) 
        {
	        if ((dm->colormodes & mask) != 0) 
            {
		        if (dm->width <= best->width && dm->height <= best->height
		            && dm->width >= *pwidth && dm->height >= *pheight)
                {
		            best = dm;
                    index = i;
                }
		        if (dm->width >= best->width && dm->height >= best->height
		            && dm->width <= *pwidth && dm->height <= *pheight)
                {
		            best = dm;
                    index = i;
                }
	        }
	        dm = dm->next;
            i++;
	    }
	    if (best->width == *pwidth && best->height == *pheight)
        {
            selected_mask = mask; /* %%% - BERND, I added this - does it make sense?  Otherwise, I'd specify a 16-bit display-mode for my
                                           Workbench (using -H 2, but SHOULD have been -H 1), and end up with an 8-bit mode instead*/
	        break;
        }
    }
    *pwidth = best->width;
    *pheight = best->height;
    if( best->colormodes & selected_mask )
	return index;

    /* Ordering here is done such that 16-bit is preferred, followed by 15-bit, 8-bit, 32-bit and 24-bit */
    if (best->colormodes & RGBMASK_16BIT)
	*ppixbits = 16;
    else if (best->colormodes & RGBMASK_15BIT) /* %%% - BERND, this possibility was missing? */
    *ppixbits = 15;
    else if (best->colormodes & RGBMASK_8BIT)
	*ppixbits = 8;
    else if (best->colormodes & RGBMASK_32BIT)
	*ppixbits = 32;
    else if (best->colormodes & RGBMASK_24BIT)
	*ppixbits = 24;
    else
        index = -1;

    return index;
}

static BOOL doInit (void)
{
    if (! create_windows ())
	    goto oops;

    if( screen_is_picasso ) 
    {
	    if (! set_ddraw (current_width, current_height, fullscreen, current_depth,
			    (LPPALETTEENTRY) & picasso96_state.CLUT))
	        goto oops;
        picasso_vidinfo.rowbytes = lockable_surfacedesc->lPitch;
	    picasso_vidinfo.pixbytes = current_pixbytes;
	    picasso_vidinfo.rgbformat = surface_pixelformat (&current_surface);
    }
    else
    {
	    if (fullscreen)
        {
            write_log( "Calling adjust_screenmode with %d,%d,%d\n", current_width, current_height, current_depth );
	        if( adjust_screenmode (&current_width, &current_height, &current_depth) < 0 )
		        abort ();
            write_log( "Finished adjust_screenmode with %d,%d,%d\n", current_width, current_height, current_depth );
        }
	    if (! set_ddraw (current_width, current_height, fullscreen, current_depth, colors256))
	        goto oops;
	    gfxvidinfo.bufmem = 0;
	    gfxvidinfo.linemem = 0;
        if( surface_type == primary_surface )
	        gfxvidinfo.maxblocklines = 0;
        else
            gfxvidinfo.maxblocklines = current_height;
	    gfxvidinfo.pixbytes = current_pixbytes;
	    gfxvidinfo.width = current_width;
	    gfxvidinfo.height = current_height;
	    gfxvidinfo.rowbytes = lockable_surfacedesc->lPitch;
        gfxvidinfo.emergmem = 0;
    }

    if( fullscreen ) 
    {
	    scrindirect = 0;
	    gfxvidinfo.linemem = 0;
        gfxvidinfo.emergmem = scrlinebuf;
	    mousecx = 160, mousecy = 100, mousedx = 160, mousedy = 100, mousecl = MAKELONG (160, 100);
    }
    if( !do_surfacelock( lockable_surface, lockable_surfacedesc ) )
	    goto oops;
    unlockscr();

    if( ( lockable_surfacedesc->ddpfPixelFormat.dwFlags & (DDPF_RGB | DDPF_PALETTEINDEXED8 | DDPF_RGBTOYUV ) ) ) 
    {
	    write_log( "%s mode (bits: %d, pixbytes: %d)\n", fullscreen ? "Full screen" : "Window",
		           lockable_surfacedesc->ddpfPixelFormat.dwRGBBitCount, current_pixbytes);
    }
    else
    {
	    gui_message("Error: Unsupported pixel format - use a different screen mode\n");
	    goto oops;
    }

    init_colors ();

    if (! fullscreen)
    	MainWindowProc (0, WM_WINDOWPOSCHANGED, 0, 0);
    dsound_newwindow (hAmigaWnd);
    return 1;

oops:
    release_ddraw ();
    if (hMainWnd)
	    DestroyWindow (hMainWnd);
    if (hAmigaWnd)
	    DestroyWindow (hAmigaWnd);
    return 0;
}

struct myRGNDATA {
    RGNDATAHEADER rdh;
    RECT rects[640];		/* fixed buffers suck, but this is _very_ unlikely to overflow */
} ClipList = { { sizeof (ClipList), RDH_RECTANGLES, 0, 640 * sizeof (RECT) } };

/* this is the way the display line is put to screen
 * if the display is not 16 bits deep or the window is not fully visible */
static void clipped_linetoscr (char *dst, char *src, int y)
{
    LPRECT lpRect = ClipList.rects;
    int i;

    switch (current_pixbytes) {
     case 1:
	for (i = ClipList.rdh.nCount; i--; lpRect++) {
	    if (y >= lpRect->top && y < lpRect->bottom)
		memcpy (dst + lpRect->left, src + lpRect->left, lpRect->right);
	}
	break;

     case 2:
	for (i = ClipList.rdh.nCount; i--; lpRect++) {
	    if (y >= lpRect->top && y < lpRect->bottom)
		memcpy (dst + lpRect->left * 2, src + lpRect->left * 2, lpRect->right * 2);
	}
	break;

     case 3:
	for (i = ClipList.rdh.nCount; i--; lpRect++) {
	    if (y >= lpRect->top && y < lpRect->bottom)
		memcpy (dst + lpRect->left * 3, src + lpRect->left * 3, lpRect->right * 3);
	}
	break;

     case 4:
	for (i = ClipList.rdh.nCount; i--; lpRect++) {
	    if (y >= lpRect->top && y < lpRect->bottom)
		memcpy (dst + lpRect->left * 4, src + lpRect->left * 4, lpRect->right * 4);
	}
	break;
    }
}

void flush_line (int lineno)
{
    if (scrindirect)
	clipped_linetoscr (gfxvidinfo.bufmem + lineno * gfxvidinfo.rowbytes,
			   scrlinebuf, lineno);
}

void flush_block (int a, int b)
{
    HRESULT ddrval;
    RECT dstrect = { amigawin_rect.left, amigawin_rect.top+a, amigawin_rect.right, amigawin_rect.top+b+1};
    RECT srcrect = { 0, a, secondary_ddsd.dwWidth-1, b+1 };

    if( surface_type == secondary_surface )
    {
        while( ( ddrval = IDirectDrawSurface_Blt( lpDDS_primary, &dstrect, lpDDS_secondary, &srcrect, DDBLT_WAIT, NULL ) ) != DD_OK )
        {
            if (ddrval == DDERR_SURFACELOST) {
    	        ddrval = IDirectDrawSurface_Restore( lpDDS_primary );
                if (ddrval != DD_OK)
                {
                    break;
                }
            }
            else if (ddrval != DDERR_SURFACEBUSY) 
            {
	            write_log ("Blt() failed - %s (%d)\n", getddrname (ddrval), (unsigned short) ddrval);
                break;
            }
            else
            {
                write_log ("Blt() failed - busy\n" );
                break;
            }
        }
    }
}

void flush_double_buffer( int a, int b )
{
    if( surface_type == secondary_surface )
        flush_block( a, b );
}

void flush_screen (int a, int b)
{
}

static uae_u8 *dolock (void)
{
    char *surface = NULL, *oldsurface;
    DWORD tmp;
    LPRECT lpRect;

    if( !do_surfacelock( lockable_surface, lockable_surfacedesc ) )
    	return 0;

    surface = lockable_surfacedesc->lpSurface;
    oldsurface = gfxvidinfo.bufmem;
    if (! fullscreen) 
    {
        if( surface_type == primary_surface )
	        surface += amigawin_rect.top * lockable_surfacedesc->lPitch + current_pixbytes * amigawin_rect.left;
    }
    gfxvidinfo.bufmem = surface;
    if (surface != oldsurface && ! screen_is_picasso) 
    {
	    write_log ("Need to init_row_map\n");
	    init_row_map ();
    }
    scrindirect = 0;

    if( fullscreen ) 
    {
	    set_linemem ();
	    clear_inhibit_frame (IHF_WINDOWHIDDEN);
	    return surface;
    }

    if( surface_type == primary_surface )
    {
        tmp = sizeof (ClipList.rects);

        /* This is the VERY instruction that drags other threads (input/file system) down when in windowed
         * mode - WHY can't Microsoft implement the IsClipListChanged() method as documented? ARGH! */
        if (IDirectDrawClipper_GetClipList (lpDDC, NULL, (LPRGNDATA) & ClipList, &tmp) == DD_OK) 
        {
	        lpRect = ClipList.rects;

	        if (!ClipList.rdh.nCount) 
            {
	            set_inhibit_frame (IHF_WINDOWHIDDEN);
	            unlockscr ();
	            return 0;
	        }
	        if (ClipList.rdh.nCount != 1
	            || lpRect->right - lpRect->left != current_width
	            || lpRect->bottom - lpRect->top != current_height)
	        {
	            scrindirect = 1;
	            for (tmp = ClipList.rdh.nCount; tmp--; lpRect++) 
                {
		            lpRect->left -= amigawin_rect.left;
		            lpRect->right -= amigawin_rect.left;
		            lpRect->top -= amigawin_rect.top;
		            lpRect->bottom -= amigawin_rect.top;

		            lpRect->right -= lpRect->left;
	            }
	        }
        }
        set_linemem ();
    }
    clear_inhibit_frame (IHF_WINDOWHIDDEN);
    return surface;
}

int lockscr (void)
{
    return dolock () != 0;
}

uae_u8 *gfx_lock_picasso (void)
{
    return dolock ();
}

void gfx_unlock_picasso (void)
{
    unlockscr ();
    if( surface_type == secondary_surface )
        flush_block( p96_double_buffer_first, p96_double_buffer_last ); /* these values are from the DX_Invalidate() function */
}

static int open_windows (void)
{
    char *fs_warning = 0;
    RGBFTYPE colortype;

    current_pixbytes = 0;

    in_sizemove = 0;
    fixup_prefs_dimensions (&currprefs);

    if (! start_ddraw ())
	return 0;

    colortype = surface_pixelformat (&current_surface);
    write_log ("Ct: %08lx, picasso_vidinfo.selected_rgbformat %08lx\n", (unsigned long)colortype,
	    picasso_vidinfo.selected_rgbformat);

    if (screen_is_picasso) {
	fullscreen = currprefs.gfx_pfullscreen;
	current_width = picasso_vidinfo.width;
	current_height = picasso_vidinfo.height;
	current_depth = rgbformat_bits (picasso_vidinfo.selected_rgbformat);
    } else {
	fullscreen = currprefs.gfx_afullscreen;
	current_width = currprefs.gfx_width;
	current_height = currprefs.gfx_height;
	current_depth = (currprefs.color_mode == 0 ? 8
			 : currprefs.color_mode == 1 ? 15
			 : currprefs.color_mode == 2 ? 16
			 : currprefs.color_mode == 3 ? 8
			 : currprefs.color_mode == 4 ? 8 : 32);
    }

    needs_fullscreen = 0;
    if (colortype == RGBFB_NONE) {
	needs_fullscreen = 1;
	fs_warning = "the desktop is running in an unknown color mode.";
    } else if (colortype == RGBFB_CLUT) {
	needs_fullscreen = 1;
	fs_warning = "the desktop is running in 8 bit color depth, which UAE can't use in windowed mode.";
    } else if (current_width > current_surface.dwWidth || current_height > current_surface.dwHeight) {
	needs_fullscreen = 1;
	fs_warning = "the desktop is too small for the specified window size.";
    } else if (screen_is_picasso
	       && picasso_vidinfo.selected_rgbformat != RGBFB_CHUNKY
	       && picasso_vidinfo.selected_rgbformat != colortype)
    {
	needs_fullscreen = 1;
	fs_warning = "you selected a Picasso display with a color depth different from that of the desktop.";
    }

    if (needs_fullscreen && ! fullscreen) {
	char tmpstr[300];
	fullscreen = needs_fullscreen;
	/* Temporarily drop the DirectDraw stuff.  This is necessary, otherwise
	 * WinNT will just return 1 for the message box without ever displaying 
	 * it on the screen.  */
	release_ddraw ();
	sprintf (tmpstr, "The selected screen mode can't be displayed in a window, because %s\n"
		 "Switching to full-screen display.", fs_warning);
	MessageBox (0, tmpstr, "UAE", MB_ICONEXCLAMATION | MB_OK);
	start_ddraw ();
    }
    
    if (! fullscreen)
	current_depth = rgbformat_bits (colortype);
    
    if (! doInit ())
	return 0;

    return 1;
}

void handle_events (void)
{
    MSG msg;

    while (PeekMessage (&msg, 0, 0, 0, PM_REMOVE)) {
	TranslateMessage (&msg);
	DispatchMessage (&msg);
    }
}

int check_prefs_changed_gfx (void)
{
    if (display_change_requested || 
        ( currprefs.gfx_afullscreen != changed_prefs.gfx_afullscreen ) ||
        ( currprefs.gfx_pfullscreen != changed_prefs.gfx_pfullscreen ) )
    {
	    display_change_requested = 0;
        currprefs.gfx_afullscreen = changed_prefs.gfx_afullscreen;
        currprefs.gfx_pfullscreen = changed_prefs.gfx_pfullscreen;
        close_windows ();
	    open_windows ();
	    return 1;
    }
    return 0;
}

/* Mathias says: "this truly sucks, I'll include a native gunzip() routine soon" */

static char *uncompress_error[2] = { "Error: You need gzip.exe (32 bit) to use .adz/.roz files!\n", 
                                    "Error: You need xdms.exe (32 bit) to use .dms files!\n" };

int uncompress_hack( int type, const char *src, const char *dst)
{
    char fullname[1024];
    char buf[1024];
    char cmd[256];
    STARTUPINFO si = {sizeof si};
    PROCESS_INFORMATION pi;

    strcpy( fullname, dst );

    if( type == 1 )
    {
        strcpy( cmd, "gzip.exe -f -d" );
        strcat( fullname, ".gz" );
    }
    else
    {
        strcpy( cmd, "xdms.exe u" );
        strcat( fullname, ".dms" );
    }

    if( CopyFile( src, fullname, FALSE ) ) 
    {
	    sprintf (buf, "%s \"%s\"", cmd, fullname );
	    si.dwFlags = STARTF_USESTDHANDLES;
	    if( CreateProcess( NULL, buf, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi ) ) 
        {
	        WaitForSingleObject( pi.hProcess, INFINITE );
	        return -1;
    	}
        else
        {
            gui_message( uncompress_error[type-1] );
        }
    }
    return 0;
}

/* if drive is -1, show the full GUI, otherwise file-requester for DF[drive] */
void DisplayGUI( int drive )
{
    int flipflop = 0;

    if( ( !screen_is_picasso && currprefs.gfx_afullscreen && ( currprefs.gfx_width < 640 ) ) || 
        ( screen_is_picasso && currprefs.gfx_pfullscreen && ( picasso96_state.Width < 640 ) ) )
        flipflop = 1;

    if (pGetOpenFileNameA) {
	if (lpDDP)
	    IDirectDrawSurface_SetPalette( lpDDS_primary, NULL );

	if (!fullscreen)
	    setmouseactive (FALSE);

    pause_sound();
    if( drive == -1 )
    {
        if( flipflop )
            ShowWindow( hAmigaWnd, SW_MINIMIZE );
	    GetSettings(0);
        if( flipflop )
            ShowWindow( hAmigaWnd, SW_RESTORE );
    }
    else
    {
        DiskSelection( hAmigaWnd, IDC_DF0+drive, 0, &changed_prefs );
    }
    resume_sound();

	if (lpDDP)
	    IDirectDrawSurface_SetPalette( lpDDS_primary, lpDDP );

	if (!fullscreen || !mouseactive)
	    SetCursor (NULL);
    }
}

#ifdef __GNUC__
#undef WINAPI
#define WINAPI
#endif

static HINSTANCE hDDraw = NULL, hComDlg32 = NULL, hRichEdit = NULL;

static int cleanuplibs (void)
{
    if (hRichEdit)
	FreeLibrary (hRichEdit);
    if (hDDraw)
	FreeLibrary (hDDraw);
    if (hComDlg32)
	FreeLibrary (hComDlg32);
    return 1;
}

/* try to load COMDLG32 and DDRAW, initialize csDraw */
static int initlibs (void)
{
    OSVERSIONINFO osinfo;
    /* Figure out which Win32 OS we're running under */
    osinfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
    if( GetVersionEx( &osinfo ) )
    {
        if( osinfo.dwPlatformId == VER_PLATFORM_WIN32_NT )
            running_winnt = TRUE;
        else
            running_winnt = FALSE;
    }

    figure_processor_speed ();
    /* Make sure we do an InitCommonControls() to get some advanced controls */
    InitCommonControls ();

    if (hComDlg32 = LoadLibrary ("COMDLG32.DLL")) 
    {
	    pGetOpenFileNameA = (BOOL (WINAPI *) (LPOPENFILENAME)) GetProcAddress (hComDlg32, "GetOpenFileNameA");
    }
    else
    {
	    /* System administrator? ROFL! -- Bernd */
        gui_message("COMDLG32.DLL not available. Please contact your system administrator.\n");
    }

    /* LoadLibrary the richedit control stuff */
    if ((hRichEdit = LoadLibrary ("RICHED32.DLL")) == NULL) 
    {
        gui_message("RICHED32.DLL not available. Please contact your system administrator.\n");
    }

    hDDraw = LoadLibrary ("DDRAW.DLL");
    if (hDDraw == 0) 
    {
        gui_message("You have to install DirectX on your system before you can use UAE.\n"
		            "Refer to the documentation for further details.\n");
	    return 0;
    }
    pDirectDrawCreate = (HRESULT (WINAPI *) (GUID FAR *, LPDIRECTDRAW FAR *, IUnknown FAR *)) GetProcAddress (hDDraw, "DirectDrawCreate");

    return 1;
}

/* console window for debugging messages */
/* Brian: disable for release version if you want (TW) */

#define WRITE_LOG_BUF_SIZE 1024

static int consoleopen;
HANDLE stdinput,stdoutput;

static void openconsole(void)
{
    if(consoleopen) return;
    AllocConsole();
    stdinput=GetStdHandle(STD_INPUT_HANDLE);
    stdoutput=GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleMode(stdinput,ENABLE_PROCESSED_INPUT|ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_OUTPUT);
}

/* console functions for debugger */

void console_out (const char *format,...)
{
    va_list parms;
    char buffer[WRITE_LOG_BUF_SIZE];
    DWORD temp;

    va_start (parms, format);
    _vsnprintf( buffer, WRITE_LOG_BUF_SIZE-1, format, parms );
    va_end (parms);
    openconsole();
    WriteConsole(stdoutput,buffer,strlen(buffer),&temp,0);
}

int console_get (char *out, int maxlen)
{
    DWORD len,totallen;

    totallen=0;
    while(maxlen>0) 
    {
	    ReadConsole(stdinput,out,1,&len,0);
	    if(*out == 13) break;
	    out++;
	    maxlen--;
	    totallen++;
    }
    *out=0;
    return totallen;
}

void console_flush (void)
{
}

/* GCC/EGCS wants this write_log in order to work from socket-land and to do traces */
#ifdef __GNUC__
void write_log (const char *format,...)
{
    DWORD numwritten;
    char buffer[12];
    va_list parms;
    int count = 0;
    int *blah = (int *)0xdeadbeef;

    if( debugfile )
    {
#if defined HAVE_GETTICKCOUNT && defined TIMESTAMP_LOGS
        {
            sprintf( buffer, "%7d - ", GetTickCount() );
            fprintf( debugfile, buffer );
        }
#endif
        va_start (parms, format);
        count = vfprintf( debugfile, format, parms );
        fflush( debugfile );
        if( count >= WRITE_LOG_BUF_SIZE-1 )
        {
            fprintf( debugfile, "SHIT in write_log()\n" );
            fflush( debugfile );
            *blah = 0; /* Access Violation here! */
            abort();
        }
        va_end (parms);
    }
}
#else /* MSVC likes this one, and so do I */
void write_log (const char *format,...)
{
    DWORD numwritten;
    char buffer[WRITE_LOG_BUF_SIZE];
    va_list parms;
    int count = 0;
    int *blah = (int *)0xdeadbeef;

    va_start (parms, format);
    count = _vsnprintf( buffer, WRITE_LOG_BUF_SIZE-1, format, parms );
#if defined HAVE_GETTICKCOUNT && defined TIMESTAMP_LOGS
    {
        char buffme[WRITE_LOG_BUF_SIZE];
        sprintf( buffme, "%7d - %s", GetTickCount(), buffer );
        OutputDebugString( buffme );
        if( debugfile )
            fprintf( debugfile, buffme );
    }
#else
    OutputDebugString( buffer );
    if( debugfile )
        fprintf( debugfile, buffer );
#endif
    openconsole();
    WriteConsole(stdoutput,buffer,strlen(buffer),&numwritten,0);
    va_end (parms);
}
#endif

int debuggable (void)
{
    return 0;
}

int needmousehack (void)
{
    return 1;
}

void LED (int a)
{
}

int DX_FillResolutions (uae_u16 * ppixel_format)
{
    struct win32_displaymode *dm;
    int count = 0;

    *ppixel_format = 0;
    for (dm = win32_displaymode_list; dm != 0; dm = dm->next) {
	*ppixel_format |= dm->colormodes;
	if (dm->colormodes & RGBMASK_8BIT) {
	    DisplayModes[count].res.width = dm->width;
	    DisplayModes[count].res.height = dm->height;
	    DisplayModes[count].refresh = default_freq >= 70 ? 70:60;
	    DisplayModes[count].depth = 1;
	    count++;
	}
	if (count >= MAX_PICASSO_MODES)
	    break;
	if (dm->colormodes & (RGBMASK_16BIT | RGBMASK_15BIT)) {
	    DisplayModes[count].res.width = dm->width;
	    DisplayModes[count].res.height = dm->height;
	    DisplayModes[count].refresh = default_freq >= 70 ? 70:60;
	    DisplayModes[count].depth = 2;
	    count++;
	}
	if (count >= MAX_PICASSO_MODES)
	    break;
	if (dm->colormodes & RGBMASK_24BIT) {
	    DisplayModes[count].res.width = dm->width;
	    DisplayModes[count].res.height = dm->height;
	    DisplayModes[count].refresh = default_freq >= 70 ? 70:60;
	    DisplayModes[count].depth = 3;
	    count++;
	}
	if (count >= MAX_PICASSO_MODES)
	    break;
	if (dm->colormodes & RGBMASK_32BIT) {
	    DisplayModes[count].res.width = dm->width;
	    DisplayModes[count].res.height = dm->height;
	    DisplayModes[count].refresh = default_freq >= 70 ? 70:60;
	    DisplayModes[count].depth = 4;
	    count++;
	}
	if (count >= MAX_PICASSO_MODES)
	    break;
    }
    return count;
}

void DX_SetPalette (int start, int count)
{
    HRESULT ddrval;

    if (! screen_is_picasso || picasso96_state.RGBFormat != RGBFB_CHUNKY)
	    return;

    if (picasso_vidinfo.pixbytes != 1) 
    {
	    write_log ("DX Setpalette emulation\n");
	    /* This is the case when we're emulating a 256 color display.  */
	    while (count-- > 0) 
        {
	        int r = picasso96_state.CLUT[start].Red;
	        int g = picasso96_state.CLUT[start].Green;
	        int b = picasso96_state.CLUT[start].Blue;
	        picasso_vidinfo.clut[start++] = (doMask256 (r, red_bits, red_shift)
					         | doMask256 (g, green_bits, green_shift)
					         | doMask256 (b, blue_bits, blue_shift));
	    }
	    return;
    }

    /* Set our DirectX palette here */
    if (lpDDP && ( current_pixbytes == 1 ) )
    {
	    /* For now, until I figure this out, just set the entire range of CLUT values */
	    ddrval = IDirectDrawPalette_SetEntries (lpDDP, 0, start, count, (LPPALETTEENTRY) & (picasso96_state.CLUT[start]));
	    if (ddrval != DD_OK)
	        gui_message("DX_SetPalette() failed with %s/%d\n", getddrname (ddrval), ddrval);
    }
    else
    {
	    write_log ("ERROR - DX_SetPalette() palette 0x%x, pixbytes %d\n", lpDDP, current_pixbytes );
    }
}

void DX_Invalidate (int first, int last)
{
    p96_double_buffer_first = first;
    p96_double_buffer_last  = last;
}

int DX_BitsPerCannon (void)
{
    return 8;
}

int DX_FillRect( int X, int Y, int Width, int Height, uae_u32 Pen, uae_u8 Bpp)
{
    int result = 0;
    HRESULT ddrval;
    DDBLTFX ddbltfx;
    RECT dstrect = { amigawin_rect.left+X, amigawin_rect.top+Y, amigawin_rect.left+X+Width, amigawin_rect.top+Y+Height };
#if 0
    RECT srcrect = { amigawin_rect.left+X, amigawin_rect.top+Y, amigawin_rect.left+X+Width, amigawin_rect.top+Y+Height };
#else
    RECT srcrect = { amigawin_rect.left, amigawin_rect.top, amigawin_rect.left+1, amigawin_rect.top+1 };
#endif

    write_log( "DX_FillRect(%d,%d,%d,%d,0x%x,%d)\n", X, Y, Width, Height, Pen, Bpp );
#ifdef __GNUC__
    ddbltfx.DUMMYUNIONNAME5.dwFillColor = Pen;
#else
    ddbltfx.dwFillColor = Pen;
#endif
    ddbltfx.dwSize = sizeof( DDBLTFX );
    while( ( ddrval = IDirectDrawSurface_Blt( lpDDS_primary, &dstrect, lockable_surface, &srcrect, DDBLT_WAIT|DDBLT_COLORFILL, &ddbltfx ) ) != DD_OK )
    {
        if (ddrval == DDERR_SURFACELOST) 
        {
    	    ddrval = IDirectDrawSurface_Restore( lpDDS_primary );
            if (ddrval != DD_OK)
            {
                break;
            }
        }
        else if (ddrval != DDERR_SURFACEBUSY) 
        {
	        write_log ("Blt() failed - %s (%d)\n", getddrname (ddrval), (unsigned short) ddrval);
            break;
        }
    }
    if( ddrval == DD_OK )
        result = 1;
    return result;
}

int DX_Blit( int srcx, int srcy, int dstx, int dsty, int width, int height )
{
    int result = 0;
    HRESULT ddrval;
    RECT dstrect = { amigawin_rect.left+dstx, amigawin_rect.top+dsty, amigawin_rect.left+dstx+width, amigawin_rect.top+dsty+height };
    RECT srcrect = { amigawin_rect.left+srcx, amigawin_rect.top+srcy, amigawin_rect.left+srcx+width, amigawin_rect.top+srcy+height };
    write_log( "DX_Blit(%d,%d,%d,%d,%d,%d)\n", srcx, srcy, dstx, dsty, width, height );
    while( ( ddrval = IDirectDrawSurface_Blt( lpDDS_primary, &dstrect, lockable_surface, &srcrect, DDBLT_WAIT, NULL ) ) != DD_OK )
    {
        if (ddrval == DDERR_SURFACELOST) 
        {
    	    ddrval = IDirectDrawSurface_Restore( lpDDS_primary );
            if (ddrval != DD_OK)
            {
                break;
            }
        }
        else if (ddrval != DDERR_SURFACEBUSY) 
        {
	        write_log ("Blt() failed - %s (%d)\n", getddrname (ddrval), (unsigned short) ddrval);
            break;
        }
    }
    if( ddrval == DD_OK )
        result = 1;
    return result;
}

void DX_WaitVerticalSync( void )
{
    IDirectDraw_WaitForVerticalBlank( lpDD, DDWAITVB_BLOCKBEGIN, NULL );
}

void gfx_set_picasso_state (int on)
{
    if (screen_is_picasso == on)
	return;

    screen_is_picasso = on;
    close_windows ();
    open_windows ();
    DX_SetPalette (0, 256);
}

void gfx_set_picasso_modeinfo (int w, int h, int depth, int rgbfmt)
{
    depth >>= 3;
    if (picasso_vidinfo.width == w
	&& picasso_vidinfo.height == h
	&& picasso_vidinfo.depth == depth
	&& picasso_vidinfo.selected_rgbformat == rgbfmt)
	return;

    picasso_vidinfo.selected_rgbformat = rgbfmt;
    picasso_vidinfo.width = w;
    picasso_vidinfo.height = h;
    picasso_vidinfo.depth = depth;
    picasso_vidinfo.extra_mem = 1;

    if (screen_is_picasso) {
	close_windows ();
	open_windows ();
	DX_SetPalette (0, 256);
    }
}

void logging_init( void )
{
    char debugfilename[MAX_PATH];
    if( currprefs.win32_logfile )
    {
        sprintf( debugfilename, "%s\\winuaelog.txt", start_path );
        if( !debugfile )
            debugfile = fopen( debugfilename, "wt" );
    }
    write_log (PROGNAME"\n");
    write_log ("\n(c) 1995-1999 Bernd Schmidt   - Core UAE concept and implementation."
	       "\n(c) 1996-1999 Mathias Ortmann - Win32 port and bsdsocket support."
	       "\n(c) 1996-1999 Brian King      - Win32 port, Picasso96 RTG, and GUI."
           "\n(c) 1998-1999 Toni Wilen      - AGA chipset, NTSC/PAL modes.\n"
	       "\nPress F12 to show the Settings Dialog (GUI), Alt-F4 to quit."
           "\nEnd+F1 changes floppy 0, End+F2 changes floppy 1, etc.\n"
	       "\nhttp://www.codepoet.com/UAE/\n\n");
}

void logging_cleanup( void )
{
    if( debugfile )
        fclose( debugfile );
}

int graphics_init (void)
{
    return open_windows ();
}

int graphics_setup (void)
{
    char *posn;
    RGBFTYPE colortype      = RGBFB_NONE;
    DWORD dwType            = REG_DWORD;
    DWORD dwDisplayInfoSize = sizeof( colortype );
    DWORD disposition;
    DEVMODE devmode;
    LONG result;
    DWORD i = 0;

    /* Get our executable's root-path */
    if( ( start_path = xmalloc( MAX_PATH ) ) )
    {
        GetModuleFileName( 0, start_path, MAX_PATH );
        if( posn = strrchr( start_path, '\\' ) )
    	    *posn = 0;
    }

    strcpy (VersionStr, PROGNAME);

    if (! initlibs ())
	return 0;

    if (! register_classes ())
	return 0;

    if (! start_ddraw ())
	return 0;
    IDirectDraw_EnumDisplayModes (lpDD, 0, NULL, NULL, modesCallback);

    memset( &devmode, 0, sizeof(DEVMODE) );
    devmode.dmSize = sizeof(DEVMODE);
    devmode.dmDriverExtra = 0;
#if 1
    while( EnumDisplaySettings( NULL, i++, &devmode ) )
    {
        write_log( "Found display-setting of %dx%d, %d-Bpp @ %dHz\n", devmode.dmPelsWidth, devmode.dmPelsHeight, devmode.dmBitsPerPel, devmode.dmDisplayFrequency );
        memset( &devmode, 0, sizeof(DEVMODE) );
        devmode.dmSize = sizeof(DEVMODE);
        devmode.dmDriverExtra = 0;
    }
#else
    if( EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &devmode ) )
    {
        default_freq = devmode.dmDisplayFrequency;
        write_log( "Your Windows desktop refresh frequency is %d Hz\n", default_freq );
    }
#endif
    if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, "Software\\CodePoet Computing\\WinUAE", 0, KEY_ALL_ACCESS, &hWinUAEKey ) == ERROR_SUCCESS )
    {
        RegQueryValueEx( hWinUAEKey, "DisplayInfo", 0, &dwType, (LPBYTE)&colortype, &dwDisplayInfoSize );
        if( colortype == 0 ) /* No color information stored in the registry yet */
        {
            if( MessageBox( NULL, "WinUAE will now determine your graphics-card 16-bit pixel format.  "
                                  "Your screen will go black for two seconds, with a resolution of 640x480 @ 60Hz.  "
                                  "This procedure is necessary to properly display your Amiga software on 16-bit "
                                  "display-modes, and will only be done this one time.  Proceed with this test?",
                                  "Pixel Format Detection", MB_YESNO | MB_ICONWARNING | MB_TASKMODAL | MB_SETFOREGROUND ) == IDYES )
            {
                colortype = figure_pixel_formats(0);
                RegSetValueEx( hWinUAEKey, "DisplayInfo", 0, REG_DWORD, (CONST BYTE *)&colortype, sizeof( colortype ) );
            }
        }
        else /* Set the 16-bit pixel format for the appropriate modes */
        {
            figure_pixel_formats( colortype );
        }
        RegCloseKey( hWinUAEKey );
    }
    else
    {
        gui_message( "WinUAE is missing HKEY_LOCAL_MACHINE Registry keys!  Please run the WinUAE Setup program again.\n" );
    }

    result = RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\CodePoet Computing\\WinUAE", 0, KEY_ALL_ACCESS, &hWinUAEKey );
    if( result != ERROR_SUCCESS )
    {
        result = RegCreateKeyEx( HKEY_CURRENT_USER, "Software\\CodePoet Computing\\WinUAE", 0, "REG_SZ",
                            REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hWinUAEKey, &disposition );
    }
    if( result != ERROR_SUCCESS )
    {
        hWinUAEKey = NULL;
    }
    
    release_ddraw ();

    InitPicasso96();
    
    return 1;
}

void graphics_leave (void)
{
    close_windows ();
    dumpcustom ();
    cleanuplibs ();
    _fcloseall();
    if( hWinUAEKey )
        RegCloseKey( hWinUAEKey );
}

static const char *sound_styles[] = { "waveout_looping", "waveout_dblbuff", "dsound_looping", "dsound_dblbuff", 0 };

void target_save_options (FILE *f, struct uae_prefs *p)
{
    fprintf (f, "win32.middle_mouse=%s\n", p->win32_middle_mouse ? "true" : "false");
    fprintf (f, "win32.logfile=%s\n", p->win32_logfile ? "true" : "false");
    fprintf (f, "win32.sound_style=%s\n", sound_styles[ p->win32_sound_style ] );
    fprintf (f, "win32.map_drives=%s\n", p->automount_drives ? "true" : "false" );
    fprintf (f, "win32.serial_port=%s\n", p->use_serial ? p->sername : "none" );
    fprintf (f, "win32.parallel_port=%s\n", p->prtname[0] ? p->prtname : "none" );
    fprintf (f, "win32.iconified_nospeed=%s\n", p->win32_iconified_nospeed ? "true" : "false");
    fprintf (f, "win32.iconified_nosound=%s\n", p->win32_iconified_nosound ? "true" : "false");
}

int target_parse_option (struct uae_prefs *p, char *option, char *value)
{
    int result = (cfgfile_yesno (option, value, "middle_mouse", &p->win32_middle_mouse)
        || cfgfile_yesno  (option, value, "logfile", &p->win32_logfile)
        || cfgfile_yesno  (option, value, "networking", &p->socket_emu)
        || cfgfile_yesno  (option, value, "map_drives", &p->automount_drives)
        || cfgfile_yesno  (option, value, "iconified_nospeed", &p->win32_iconified_nospeed)
        || cfgfile_yesno  (option, value, "iconified_nosound", &p->win32_iconified_nosound)
        || cfgfile_strval (option, value, "sound_style", &p->win32_sound_style, sound_styles, 1)
        || cfgfile_intval (option, value, "sound_tweak", &p->win32_sound_tweak, 1) 
        || cfgfile_string (option, value, "serial_port", &p->sername[0], 256)
        || cfgfile_string (option, value, "parallel_port", &p->prtname[0], 256));

    if( p->sername[0] == 'n' )
        p->use_serial = 0;
    else
        p->use_serial = 1;

    if( p->prtname[0] == 'n' )
        p->prtname[0] = '\0';

    return result;
}

uae_u32 OSDEP_minimize_uae( void )
{
    return ShowWindow( hAmigaWnd, SW_MINIMIZE );
}

void machdep_init (void)
{
}

#if defined NO_MAIN_IN_MAIN_C
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
		    int nCmdShow)
{
    hInst = hInstance;

    /*initgfxspecs(); */
#ifdef __CYGWIN32__
    {
	char *token, *tmpbuf;
	int my_argc, i;
	char ** my_argv;

	tmpbuf = my_strdup (lpCmdLine);
	my_argc = 1;
	if (strtok (tmpbuf, "\n ") != NULL) {
	    do {
		my_argc++;
	    } while (strtok (NULL, "\n ") != NULL);
	}
	free (tmpbuf);

	tmpbuf = my_strdup (lpCmdLine);

	my_argv = (char **)malloc ((1 + my_argc) * sizeof (char **));
	my_argv[0] = "uae.exe";

	token = strtok (tmpbuf, "\n ");
	i = 1;
	while (token != NULL) {
	    my_argv[i++] = my_strdup (token);
	    token = strtok (NULL, "\n ");
	}
	my_argv[my_argc] = NULL;

	real_main (my_argc, my_argv);
    }
#elif defined __MINGW32__
    real_main (_argc, _argv);
#else
    real_main (__argc, __argv);
#endif
    return FALSE;
}
#endif
