// vid_next.m -- NEXTSTEP video driver

#define	INTERCEPTOR

#import <appkit/appkit.h>
#import <string.h>
#import "intercep.h"
#include "quakedef.h"
#include "d_local.h"

int	BASEWIDTH = 320;
int BASEHEIGHT = 200;

void SetupBitmap (void);
void SetupFramebuffer (void);
void UpdateBitmap (void);
void UpdateFramebuffer (vrect_t *vrect);
void SetVideoEncoding (char *encoding);
void Update8_1 (pixel_t *src, byte *dest, int width,
		int height, int destrowbytes);
void Update16_1 (pixel_t *src, unsigned short *dest, int width,
		int height, int destrowbytes);
void Update32_1 (pixel_t *src, unsigned *dest, int width,
		int height, int destrowbytes);


@interface QuakeView : View
@end

@interface FrameWindow:Window
@end

unsigned short	d_8to16table[256];	// not used in 8 bpp mode
unsigned	d_8to24table[256];	// not used in 8 bpp mode


/*
==========================================================================

						API FUNCTIONS

==========================================================================
*/

typedef enum {disp_bitmap, disp_framebuffer}	display_t;

pixel_t		*vid_buffer;
pixel_t		*buffernative;
unsigned	pcolormap[4][256];	// map from quake pixels to native pixels
unsigned	pixbytesnative;
unsigned	rowbytesnative;
int			dither;

int			drawdirect = 0;

int			d_con_indirect = 0;

display_t		vid_display;

byte			vid_palette[768];	// saved for restarting vid system

id				vid_window_i;
id				vid_view_i;
#ifdef INTERCEPTOR
NXDirectBitmap	*vid_dbitmap_i;
NXFramebuffer	*vid_framebuffer_i;
#endif

NXRect   		screenBounds;		// only valid in framebuffer mode

int				vid_scale;

char			*vid_encodingstring;

int				vid_fullscreen;
int				vid_screen;

int				vid_high_hunk_mark;

typedef enum
{
	enc_24_rgba,
	enc_24_0rgb,
	enc_24_rgb0,
	enc_12_rgba,
	enc_12_rgb0,
	enc_15_0rgb,
	enc_564,
	enc_8_gray,
	enc_8_rgb
} vid_encoding_t;

typedef struct
{
	char			*string;
	int				pixelbytes;
	void			(*colormap) (void);
	vid_encoding_t	name;
} vidtype_t;

vid_encoding_t	vid_encoding;
 
void	Table8 (void);
void	Table15 (void);
void	Table12 (void);
void	Table12Swap (void);
void	Table24 (void);
void	Table24Swap (void);

vidtype_t vid_encodingtable[]=
{
{"RRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA",4, Table24Swap, enc_24_rgba},
{"--------RRRRRRRRGGGGGGGGBBBBBBBB",4, Table24, enc_24_0rgb},
{"RRRRRRRRGGGGGGGGBBBBBBBB--------",4, Table24Swap, enc_24_rgb0},
{"RRRRGGGGBBBBAAAA",2, Table12Swap, enc_12_rgba},
{"RRRRGGGGBBBB----",2, Table12, enc_12_rgb0},
{"-RRRRRGGGGGBBBBB",2, Table15, enc_15_0rgb},
{"WWWWWWWW",1, Table8, enc_8_gray},
{"PPPPPPPP",1, Table8, enc_8_rgb},
{NULL,0, 0, 0}
};

vidtype_t	*vid_type;
void	InitNS8Bit (void);

/*
================
D_BeginDirectRect
================
*/
void D_BeginDirectRect (int x, int y, byte *pbitmap, int width, int height)
{
// direct drawing of the "accessing disk" icon isn't supported under Nextstep
}


/*
================
D_EndDirectRect
================
*/
void D_EndDirectRect (int x, int y, int width, int height)
{
// direct drawing of the "accessing disk" icon isn't supported under Nextstep
}


/*
==============
VID_Restart

internal call only
===============
*/
void VID_Restart (display_t mode, int scale)
{
	vid_display = mode;
	vid_scale = scale;

	[NXApp activateSelf:YES];

	if (vid_display == disp_framebuffer)
		SetupFramebuffer ();
	else
		SetupBitmap ();

	vid.recalc_refdef = 1;
}


/*
=================
VID_Scale_f

Keybinding command
=================
*/
void VID_Scale_f (void)
{
	int		scale;
	
	if (Cmd_Argc () != 2)
		return;
		
	scale = atoi (Cmd_Argv(1));
	if (scale != 1 && scale != 2)
	{
		Con_Printf ("scale must be 1 or 2\n");
		return;
	}
	VID_Shutdown ();
	VID_Restart (vid_display, scale);
}

/*
=================
VID_Mode_f

Keybinding command
=================
*/
void VID_Mode_f (void)
{
	int		mode;

	if (Cmd_Argc () != 2)
		return;

	mode = atoi (Cmd_Argv(1));

	VID_Shutdown ();
	if (mode == 0)
	{
		drawdirect = 0;
		VID_Restart (disp_bitmap, vid_scale);
	}
	else if (mode == 1)
	{
		drawdirect = 0;
		VID_Restart (disp_framebuffer, vid_scale);
	}
	else
	{
		drawdirect = 1;
		VID_Restart (disp_framebuffer, vid_scale);
	}
}

/*
=================
VID_Size_f

Keybinding command
=================
*/
void VID_Size_f (void)
{	
	if (Cmd_Argc () != 3)
		return;

	VID_Shutdown ();

	BASEWIDTH = atoi (Cmd_Argv(1));
	BASEHEIGHT = atoi (Cmd_Argv(2));

	VID_Restart (vid_display, vid_scale);
}

/*
================
VID_Init
================
*/
void	VID_Init (unsigned char *palette)
{
	InitNS8Bit ();			// fixed palette lookups
	
	Q_memcpy (vid_palette, palette, sizeof(vid_palette));

	if (COM_CheckParm ("-bitmap"))
		vid_display = disp_bitmap;
	else
		vid_display = disp_framebuffer;

	if (COM_CheckParm ("-screen2"))
		vid_screen = 1;
	else
		vid_screen = 0;

	if (COM_CheckParm ("-direct"))
		drawdirect = 1;
	
	Cmd_AddCommand ("vid_scale", VID_Scale_f);
	Cmd_AddCommand ("vid_mode", VID_Mode_f);
	Cmd_AddCommand ("vid_size", VID_Size_f);

	vid.width = BASEWIDTH;
	vid.height = BASEHEIGHT;
	vid.aspect = 1.0;
	vid.numpages = 1;
	vid.colormap = host_colormap;
	vid.fullbright = 256 - LittleLong (*((int *)vid.colormap + 2048));
	vid.maxwarpwidth = WARP_WIDTH;
	vid.maxwarpheight = WARP_HEIGHT;

	if (COM_CheckParm ("-scale2"))
		vid_scale = 2;
	else
		vid_scale = 1;
		
    [Application new];

	VID_Restart (vid_display, vid_scale);
}


/*
================
VID_Shutdown
================
*/
void VID_Shutdown (void)
{
#ifdef INTERCEPTOR
	if (vid_dbitmap_i)
	{
		[vid_dbitmap_i free];
		vid_dbitmap_i = 0;
	}
	if (vid_framebuffer_i)
	{
		[vid_framebuffer_i free];
		vid_framebuffer_i = 0;
	}
#endif
	[vid_window_i close];
	[vid_window_i free];
}


/*
================
VID_Update
================
*/
void	VID_Update (vrect_t *rects)
{
	if (drawdirect)
		return;

	while (rects)
	{
		UpdateFramebuffer (rects);
		rects = rects->pnext;
	}

	if (vid_display == disp_bitmap)
		UpdateBitmap ();
}


/*
================
VID_SetPalette
================
*/
void	VID_SetPalette (unsigned char *palette)
{
	Q_memcpy (vid_palette, palette, sizeof(vid_palette));
	vid_type->colormap ();
}


/*
================
VID_ShiftPalette
================
*/
void    VID_ShiftPalette (unsigned char *palette)
{

	VID_SetPalette (palette);
}


/*
==========================================================================

						NS STUFF

==========================================================================
*/


/*
=================
SetVideoEncoding
=================
*/
void SetVideoEncoding (char *encoding)
{
	vidtype_t			*type;

	Sys_Printf ("SetVideoEncoding: %s\n",encoding);
	vid_encodingstring = encoding;
	
	for (type = vid_encodingtable ; type->string ; type++)
	{
		if (strcmp(type->string, encoding) == 0)
		{
			pixbytesnative = type->pixelbytes;
			vid_encoding = type->name;
			type->colormap ();
			vid_type = type;
			return;
		}
	}
	
	Sys_Error ("Unsupported video encoding: %s\n",encoding);
}

/*
=================
AllocBuffers
=================
*/
void AllocBuffers (qboolean withnative)
{
	int		surfcachesize;
	void	*surfcache;
	int		pixels;
	int		pixbytes;
	int		vid_buffersize;

	if (vid_buffer)
	{
		D_FlushCaches ();
		Hunk_FreeToHighMark (vid_high_hunk_mark);
		vid_high_hunk_mark = 0;
		vid_buffer = NULL;
	}

	pixels = vid.width * vid.height;

	pixbytes = 1 +sizeof (*d_pzbuffer);
	if (withnative)
		pixbytes += pixbytesnative;
		
	surfcachesize = D_SurfaceCacheForRes (vid.width, vid.height);
	vid_buffersize = pixels * pixbytes + surfcachesize;

	vid_high_hunk_mark = Hunk_HighMark ();
	vid_buffer = Hunk_HighAllocName (vid_buffersize, "video");
	if (!vid_buffer)
		Sys_Error ("Couldn't alloc video buffers");

	vid.buffer = vid_buffer;

	d_pzbuffer = (unsigned short *)((byte *)vid_buffer + pixels);
	surfcache = (byte *)d_pzbuffer + pixels * sizeof (*d_pzbuffer);
	if (withnative)
		buffernative = (byte *)surfcache + surfcachesize;

	D_InitCaches (surfcache, surfcachesize);
}

/*
=================
SetupFramebuffer
=================
*/
void SetupFramebuffer (void)
{
#ifdef INTERCEPTOR
    int			windowNum;
	NXRect		cont;
	NXScreen	const *screens;
	int			screencount;

//
// get the screen list
//
	[NXApp getScreens:&screens count:&screencount];

//
// create vid_framebuffer_i
//
    vid_framebuffer_i = [[NXFramebuffer alloc]
		   initFromScreen:screens[vid_screen].screenNumber andMapIfPossible:YES];
    [vid_framebuffer_i screenBounds:&screenBounds];

	SetVideoEncoding ([vid_framebuffer_i pixelEncoding]);

	buffernative = [vid_framebuffer_i data];
	rowbytesnative = [vid_framebuffer_i bytesPerRow];

//
// create window
//
	if (vid_fullscreen)
	{
		vid.height = screenBounds.size.height / vid_scale;
		vid.width = screenBounds.size.width / vid_scale;
		cont.origin.x = 0;
		cont.origin.y = 0;
		cont.size.width = screenBounds.size.width;
		cont.size.height = screenBounds.size.height;
	}
	else
	{
		buffernative = (unsigned char *)buffernative + 8 * rowbytesnative +
				8 * pixbytesnative;
		vid.width = BASEWIDTH;
		vid.height = BASEHEIGHT;
		cont.origin.x = 8;
		cont.origin.y = screenBounds.size.height - (vid.height*vid_scale) - 8;
		cont.size.width = vid.width * vid_scale;
		cont.size.height = vid.height * vid_scale;
	}

    vid_window_i = [[FrameWindow alloc]
		 initContent:		&cont
		 style:				NX_PLAINSTYLE
		 backing:			NX_NONRETAINED
		 buttonMask:		0
		 defer:				NO
		 screen:			screens+vid_screen];
    windowNum = [vid_window_i windowNum];
    PSsetwindowlevel(40, windowNum);
    PSsetautofill(YES, windowNum);
    PSgsave();
    PSwindowdeviceround(windowNum);
    PSsetgray(NX_BLACK);
    PSsetexposurecolor();
    PSgrestore();

//
// create view
//
	vid_view_i = [[QuakeView alloc] initFrame: &screenBounds];
	[[vid_window_i setContentView: vid_view_i] free];
	[vid_window_i makeFirstResponder: vid_view_i];
	[vid_window_i setDelegate: vid_view_i];	
	[vid_window_i display];
	[vid_window_i makeKeyAndOrderFront: nil];
	NXPing ();

	AllocBuffers (false);	// no native buffer

	if (drawdirect)
	{	// the direct drawing mode to NeXT colorspace
		vid.buffer = buffernative;
		vid.rowbytes = rowbytesnative;
	}
	else
		vid.rowbytes = vid.width;

	vid.conbuffer = vid.buffer;
	vid.conrowbytes = vid.rowbytes;
	vid.conwidth = vid.width;
	vid.conheight = vid.height;
#endif
}

/*
=================
SetupBitmap
=================
*/
void SetupBitmap (void)
{
	int		depth;
	NXRect	content;

//
// open a window
//
	NXSetRect (&content, 8,136, vid.width*vid_scale, vid.height*vid_scale);
	vid_window_i = [[Window alloc]
			initContent:	&content
			style:			NX_RESIZEBARSTYLE
			backing:		NX_RETAINED
			buttonMask:		0
			defer:			NO
		];
	[vid_window_i display];
	[vid_window_i makeKeyAndOrderFront: nil];

	NXPing ();

	content.origin.x = content.origin.y = 0;
	vid_view_i = [[QuakeView alloc] initFrame: &content];
	[[vid_window_i setContentView: vid_view_i] free];
	[vid_window_i makeFirstResponder: vid_view_i];
	[vid_window_i setDelegate: vid_view_i];

	[vid_window_i addToEventMask: NX_FLAGSCHANGEDMASK];

//
// find video info
//
    depth = [Window defaultDepthLimit];
    switch (depth) {
	case NX_EightBitGrayDepth:
		SetVideoEncoding ("WWWWWWWW");
	    break;
	case NX_TwelveBitRGBDepth:
		SetVideoEncoding ("RRRRGGGGBBBBAAAA");
	    break;
	default:
	case NX_TwentyFourBitRGBDepth:
		SetVideoEncoding ("RRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA");
	    break;
//	default:	// 8 bit color shows up as an unknown...
		Sys_Error ("Unsupported window depth");
    }

	[vid_window_i setTitle: "Bitmap Quake Console"];

//
// allocate memory for the back and translation buffers
//
	vid.rowbytes = vid.width;
	rowbytesnative = vid.width * pixbytesnative;
	
	AllocBuffers (true);
	
	vid.conbuffer = vid.buffer;
	vid.conrowbytes = vid.rowbytes;
	vid.conwidth = vid.width;
	vid.conheight = vid.height;
}


/*
=================
UpdateFramebuffer
=================
*/
void UpdateFramebuffer (vrect_t *vrect)
{
	byte		*psourcebase;
	byte		*pdestbase;
	int			scale;
	
	psourcebase = vid.buffer + vrect->x + vrect->y * vid.rowbytes;

	if (vid_display == disp_bitmap)
		scale = 1;		// let NS do the scaling
	else
		scale = vid_scale;
		
	pdestbase = buffernative + scale *
			(vrect->x * pixbytesnative + vrect->y * rowbytesnative);

//
// translate from ideal to native (except 8 bpp direct) and copy to screen
//

	if (pixbytesnative == 1)
		Update8_1 (psourcebase, pdestbase, vrect->width, vrect->height,
				rowbytesnative);
	else if (pixbytesnative == 2)
		Update16_1 (psourcebase, (unsigned short *)pdestbase, vrect->width, vrect->height,
				rowbytesnative);
	else
		Update32_1 (psourcebase, (unsigned *)pdestbase, vrect->width, vrect->height,
				rowbytesnative);
}


/*
=================
UpdateBitmap
=================
*/
void UpdateBitmap (void)
{
	unsigned char	*planes[5];
	NXRect			bounds;
	int				bpp, spp, bps, bpr, colorspace;

//
// flush the screen with an image call
// 
	if (pixbytesnative == 1)
	{
		bps = 8;
		spp = 1;
		bpp = 8;
		bpr = vid.width;
		colorspace = NX_OneIsWhiteColorSpace;
		planes[0] = vid.buffer;
	}
	else if (pixbytesnative == 2)
	{
		bps = 4;
		spp = 3;
		bpp = 16;
		bpr = vid.width * 2;
		colorspace = NX_RGBColorSpace;
		planes[0] = buffernative;
	}
	else
	{
		bps = 8;
		spp = 3;
		bpp = 32;
		bpr = vid.width * 4;
		colorspace = NX_RGBColorSpace;
		planes[0] = buffernative;
	}

	[vid_view_i getBounds: &bounds];
	[vid_view_i lockFocus];

	NXDrawBitmap(
		&bounds,  
		vid.width, 
		vid.height,
		bps,
		spp,
		bpp,
		bpr,
		NO,
		NO,
		colorspace,
		planes
	);
	
	[vid_view_i unlockFocus];
    NXPing ();	
}



/*
==========================================================================

					TRANSLATION TABLE BUILDING

==========================================================================
*/

int	redramp[] = {0, 19, 59, 113, 178, 255, 300};
int greenramp[] = {0, 11, 34,  66, 104, 149, 199, 255, 300};
int blueramp[] = {0, 28, 84, 161, 255, 300};
int greyramp[] = { 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204,
				   221, 238, 255, 300};

byte	greytable[256];
byte	redtable[256];
byte	greentable[256];
byte	bluetable[256];

void FillTable (byte *table, int *ramp, int base)
{
	int		i, j, o;
	
	o = 0;
	for (i=0 ; i<16 && o < 256; i++)
	{
		j = ramp[i];
		for ( ; o<=j ; o++)
			table[o] = base + i;
	}
}

void	InitNS8Bit (void)
{
	FillTable (greytable, greyramp, 240);
	FillTable (redtable, redramp, 0);
	FillTable (greentable, greenramp, 0);
	FillTable (bluetable, blueramp, 0);
}


byte ns8trans[256] =	// FIXME: dynamically calc this so palettes work
{
0,241,242,243,244,244,245,246,247,248,249,250,251,252,253,254,
45,241,241,242,91,91,91,96,96,136,136,136,141,141,141,141,
241,46,242,243,243,97,97,97,245,246,143,143,143,143,148,148,
0,5,45,45,50,50,90,90,95,95,95,95,95,140,140,141,
0,40,40,40,40,80,80,80,80,80,120,120,120,120,120,120,
45,50,50,90,90,95,95,135,135,135,136,141,141,181,181,181,
45,90,91,91,131,131,136,136,136,176,181,181,186,226,231,236,
45,45,91,91,96,96,136,136,137,142,182,182,187,188,188,233,
188,249,248,247,246,137,137,137,244,243,243,91,242,241,241,45,
183,183,183,247,137,137,137,137,137,244,91,91,91,241,241,45,
252,251,188,188,248,248,142,142,142,244,244,243,91,242,241,45,
247,247,246,246,245,245,244,244,243,243,242,242,51,241,241,5,
236,231,231,191,186,185,185,140,140,135,135,95,90,90,45,45,
4,49,49,53,53,93,93,93,93,92,92,92,243,242,46,241,
239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,
239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,182
};

/*
===================
Table8
===================
*/
void	Table8 (void)
{
	byte	*pal;
	int		r,g,b,v;
	int		i;
	byte	*table;
	
	pal = vid_palette;
	table = (byte *)pcolormap[0];
	
	for (i=0 ; i<256 ; i++)
	{
		r = pal[0];
		g = pal[1];
		b = pal[2];
		pal += 3;
		
// use the grey ramp if all indexes are close

		if (r-g < 16 && r-g > -16 && r-b < 16 && r-b > -16)
		{
			v = (r+g+b)/3;
			*table++ = greytable[v];
			continue;
		}
		
		r = redtable[r];
		g = greentable[g];
		b = bluetable[b];
		
// otherwise use the color cube
		*table++ = r*(8*5) + g*5 + b;
	}
}

/*
===================
Table24
===================
*/
void	Table24 (void)
{
	byte	*pal;
	int		r,g,b,v;
	int		i;
	unsigned	*table;
	
	
//
// 8 8 8 encoding
//
	pal = vid_palette;
	table = (unsigned *)pcolormap[0];
	
	for (i=0 ; i<256 ; i++)
	{
		r = pal[0];
		g = pal[1];
		b = pal[2];
		pal += 3;
		
		v = (r<<16) + (g<<8) + b;
		*table++ = v;
	}
}

/*
===================
Table24Swap
===================
*/
void	Table24Swap (void)
{
	byte	*pal;
	int		r,g,b,v;
	int		i;
	unsigned	*table;

//
// 8 8 8 encoding
//
	pal = vid_palette;
	table = (unsigned *)pcolormap[0];
	
	for (i=0 ; i<256 ; i++)
	{
		r = pal[0];
		g = pal[1];
		b = pal[2];
		pal += 3;
		
		v = (r<<24) + (g<<16) + (b<<8) /*+ 255*/;
		v = NXSwapBigLongToHost (v);
		*table++ = v;
	}
}


/*
===================
Table15
===================
*/
void	Table15 (void)
{
	byte			*pal;
	int				r,g,b,v;
	int				i, k;
	unsigned char	*palette;
	unsigned short	*table;
	int				dadj;
	int		ditheradjust[4] = {(1 << 9) * 3 / 8,
									(1 << 9) * 5 / 8,
									(1 << 9) * 7 / 8,
									(1 << 9) * 1 / 8};
	
	palette = vid_palette;
	table = (unsigned short *)pcolormap;
	
//
// 5 5 5 encoding
//
	for (k=0 ; k<4 ; k++)
	{
		dadj = ditheradjust[k];

		pal = vid_palette;

		for (i=0 ; i<256 ; i++)
		{
		// shift 6 bits to get back to 0-255, & 3 more for 5 bit color
		// FIXME: scale intensity levels properly
			r = (pal[0] + dadj) >> 3;
			g = (pal[1] + dadj) >> 3;
			b = (pal[2] + dadj) >> 3;
			pal += 3;

			v = (r<<10) + (g<<5) + b;

			*table++ = v;
		}
	}
}

/*
===================
Table12
===================
*/
void	Table12 (void)
{
	byte			*pal;
	int				r,g,b,v;
	int				i, k;
	unsigned short	*table;
	int				dadj;
	static int		ditheradjust[4] = {(1 << 9) * 3 / 8,
									   (1 << 9) * 5 / 8,
									   (1 << 9) * 7 / 8,
									   (1 << 9) * 1 / 8};

	table = (unsigned short *)pcolormap;
		
//
// 4 4 4 encoding
//
	for (k=0 ; k<4 ; k++)
	{
		dadj = ditheradjust[k];

		pal = vid_palette;

		for (i=0 ; i<256 ; i++)
		{
		// shift 5 bits to get back to 0-255, & 4 more for 4 bit color
		// FIXME: scale intensity levels properly
			r = (pal[0] + dadj) >> 4;
			g = (pal[1] + dadj) >> 4;
			b = (pal[2] + dadj) >> 4;
			pal += 3;

			v = ((r<<12) + (g<<8) + (b<<4) /*+ 15*/);

			*table++ = v;
		}
	}
}

/*
===================
Table12Swap
===================
*/
void	Table12Swap (void)
{
	byte			*pal;
	int				r,g,b,v;
	int				i, k;
	unsigned short	*table;
	int				dadj;
	static int		ditheradjust[4] = {(1 << 9) * 3 / 8,
									   (1 << 9) * 5 / 8,
									   (1 << 9) * 7 / 8,
									   (1 << 9) * 1 / 8};

	table = (unsigned short *)pcolormap;
		
//
// 4 4 4 encoding
//
	for (k=0 ; k<4 ; k++)
	{
		dadj = ditheradjust[k];

		pal = vid_palette;

		for (i=0 ; i<256 ; i++)
		{
		// shift 5 bits to get back to 0-255, & 4 more for 4 bit color
		// FIXME: scale intensity levels properly
			r = (pal[0] + dadj) >> 4;
			g = (pal[1] + dadj) >> 4;
			b = (pal[2] + dadj) >> 4;
			pal += 3;

			v = ((r<<12) + (g<<8) + (b<<4) /*+ 15*/);
			v = NXSwapBigShortToHost (v);

			*table++ = v;
		}
	}
}


/*
==========================================================================

					GENERIC IMAGING FUNCTIONS

==========================================================================
*/

/*
===================
Update8_1
===================
*/
void Update8_1 (pixel_t *src, byte *dest, int width, int height,
		int destrowbytes)
{
	int				x,y;
	unsigned		rowdelta, srcdelta;
	unsigned		xcount;
	byte			*pdest;
	int				xwidth;

	pdest = dest;
	
	xcount = width >> 3;
	srcdelta = vid.width - width;

	xwidth = width - (xcount << 3);
	if (xwidth)
		Sys_Error ("Width not multiple of 8");

	if ((vid_display == disp_framebuffer) && (vid_scale == 2))
	{
		int		nextrow = destrowbytes;

	    rowdelta = destrowbytes - (width << 1)  + destrowbytes;

		if (dither)
		{
			unsigned short	*psrc;

			psrc = (unsigned short *)src;

			for (y = height ; y ; y--)
			{
		    	for (x = xcount ; x ;x--)
			    {
					unsigned	temp;

					temp = psrc[0];
					pdest[0] = ((byte *)pcolormap[0])[temp];
					pdest[1] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 1] = ((byte *)pcolormap[3])[temp];
					temp = psrc[1];
					pdest[2] = ((byte *)pcolormap[0])[temp];
					pdest[3] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow + 2] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 3] = ((byte *)pcolormap[3])[temp];
					temp = psrc[2];
					pdest[4] = ((byte *)pcolormap[0])[temp];
					pdest[5] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow + 4] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 5] = ((byte *)pcolormap[3])[temp];
					temp = psrc[3];
					pdest[6] = ((byte *)pcolormap[0])[temp];
					pdest[7] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow + 6] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 7] = ((byte *)pcolormap[3])[temp];
					temp = psrc[4];
					pdest[8] = ((byte *)pcolormap[0])[temp];
					pdest[9] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow + 8] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 9] = ((byte *)pcolormap[3])[temp];
					temp = psrc[5];
					pdest[10] = ((byte *)pcolormap[0])[temp];
					pdest[11] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow + 10] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 11] = ((byte *)pcolormap[3])[temp];
					temp = psrc[6];
					pdest[12] = ((byte *)pcolormap[0])[temp];
					pdest[13] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow + 12] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 13] = ((byte *)pcolormap[3])[temp];
					temp = psrc[7];
					pdest[14] = ((byte *)pcolormap[0])[temp];
					pdest[15] = ((byte *)pcolormap[1])[temp];
					pdest[nextrow + 14] = ((byte *)pcolormap[2])[temp];
					pdest[nextrow + 15] = ((byte *)pcolormap[3])[temp];
					pdest += 16; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;
			}
		}
		else
		{
			byte	*psrc;

			psrc = (byte *)src;

			for (y = height ; y ; y--)
			{
				for (x = xcount ; x ;x--)
		    	{
					pdest[0] = pdest[1] = pdest[nextrow] =
						pdest[nextrow + 1] = ((byte *)pcolormap[0])[psrc[0]];
					pdest[2] = pdest[3] = pdest[nextrow + 2] =
						pdest[nextrow + 3] = ((byte *)pcolormap[0])[psrc[1]];
					pdest[4] = pdest[5] = pdest[nextrow + 4] =
						pdest[nextrow + 5] = ((byte *)pcolormap[0])[psrc[2]];
					pdest[6] = pdest[7] = pdest[nextrow + 6] =
						pdest[nextrow + 7] = ((byte *)pcolormap[0])[psrc[3]];
					pdest[8] = pdest[9] = pdest[nextrow + 8] =
						pdest[nextrow + 9] = ((byte *)pcolormap[0])[psrc[4]];
					pdest[10] = pdest[11] = pdest[nextrow + 10] =
						pdest[nextrow + 11] = ((byte *)pcolormap[0])[psrc[5]];
					pdest[12] = pdest[13] = pdest[nextrow + 12] =
						pdest[nextrow + 13] = ((byte *)pcolormap[0])[psrc[6]];
					pdest[14] = pdest[15] = pdest[nextrow + 14] =
						pdest[nextrow + 15] = ((byte *)pcolormap[0])[psrc[7]];
					pdest += 16; psrc += 8;
		    	}

				psrc += srcdelta;
			    pdest += rowdelta;
			}
		}
    }
	else
	{
	    rowdelta = destrowbytes - width;

		if (dither)
		{
			unsigned short	*psrc;

			psrc = (unsigned short *)src;

			for (y = height ; y>0 ; y -= 2)
			{
		    	for (x = xcount ; x ;x--)
			    {
					pdest[0] = ((byte *)pcolormap[0])[psrc[0]];
					pdest[1] = ((byte *)pcolormap[1])[psrc[1]];
					pdest[2] = ((byte *)pcolormap[0])[psrc[2]];
					pdest[3] = ((byte *)pcolormap[1])[psrc[3]];
					pdest[4] = ((byte *)pcolormap[0])[psrc[4]];
					pdest[5] = ((byte *)pcolormap[1])[psrc[5]];
					pdest[6] = ((byte *)pcolormap[0])[psrc[6]];
					pdest[7] = ((byte *)pcolormap[1])[psrc[7]];
					pdest += 8; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;

		    	for (x = xcount ; x ;x--)
			    {
					pdest[0] = ((byte *)pcolormap[2])[psrc[0]];
					pdest[1] = ((byte *)pcolormap[3])[psrc[1]];
					pdest[2] = ((byte *)pcolormap[2])[psrc[2]];
					pdest[3] = ((byte *)pcolormap[3])[psrc[3]];
					pdest[4] = ((byte *)pcolormap[2])[psrc[4]];
					pdest[5] = ((byte *)pcolormap[3])[psrc[5]];
					pdest[6] = ((byte *)pcolormap[2])[psrc[6]];
					pdest[7] = ((byte *)pcolormap[3])[psrc[7]];
					pdest += 8; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;
			}
		}
		else
		{
			byte	*psrc;

			psrc = (byte *)src;
//			srcdelta += width;
//			rowdelta += width;

			for (y = height ; y ; y--)
			{
		    	for (x = xcount ; x ;x--)
			    {
					pdest[0] = ((byte *)pcolormap[0])[psrc[0]];
					pdest[1] = ((byte *)pcolormap[0])[psrc[1]];
					pdest[2] = ((byte *)pcolormap[0])[psrc[2]];
					pdest[3] = ((byte *)pcolormap[0])[psrc[3]];
					pdest[4] = ((byte *)pcolormap[0])[psrc[4]];
					pdest[5] = ((byte *)pcolormap[0])[psrc[5]];
					pdest[6] = ((byte *)pcolormap[0])[psrc[6]];
					pdest[7] = ((byte *)pcolormap[0])[psrc[7]];
					pdest += 8; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;
		    }
		}
    }
}


/*
===================
Update16_1
===================
*/
void Update16_1 (pixel_t *src, unsigned short *dest, int width,
		int height, int destrowbytes)
{
	int				x,y;
	unsigned		rowdelta, srcdelta;
	unsigned		xcount;
	pixel_t			*psrc;
	unsigned short	*pdest;
	int				xwidth;


	psrc = src;
	pdest = dest;
	
	xcount = width >> 3;
	srcdelta = vid.width - width;

	xwidth = width - (xcount << 3);
	if (xwidth)
		Sys_Error ("Width not multiple of 8");

	if ((vid_display == disp_framebuffer) && (vid_scale == 2))
	{
		int		nextrow = destrowbytes >> 1;

	    rowdelta = (destrowbytes - ((width << 1) << 1) + destrowbytes) >> 1;

		if (dither)
		{
			for (y = height ; y ; y--)
			{
		    	for (x = xcount ; x ;x--)
			    {
					unsigned	temp;

					temp = psrc[0];
					pdest[0] = ((unsigned short *)pcolormap[0])[temp];
					pdest[1] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 1] = ((unsigned short *)pcolormap[3])[temp];
					temp = psrc[1];
					pdest[2] = ((unsigned short *)pcolormap[0])[temp];
					pdest[3] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow + 2] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 3] = ((unsigned short *)pcolormap[3])[temp];
					temp = psrc[2];
					pdest[4] = ((unsigned short *)pcolormap[0])[temp];
					pdest[5] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow + 4] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 5] = ((unsigned short *)pcolormap[3])[temp];
					temp = psrc[3];
					pdest[6] = ((unsigned short *)pcolormap[0])[temp];
					pdest[7] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow + 6] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 7] = ((unsigned short *)pcolormap[3])[temp];
					temp = psrc[4];
					pdest[8] = ((unsigned short *)pcolormap[0])[temp];
					pdest[9] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow + 8] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 9] = ((unsigned short *)pcolormap[3])[temp];
					temp = psrc[5];
					pdest[10] = ((unsigned short *)pcolormap[0])[temp];
					pdest[11] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow + 10] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 11] = ((unsigned short *)pcolormap[3])[temp];
					temp = psrc[6];
					pdest[12] = ((unsigned short *)pcolormap[0])[temp];
					pdest[13] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow + 12] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 13] = ((unsigned short *)pcolormap[3])[temp];
					temp = psrc[7];
					pdest[14] = ((unsigned short *)pcolormap[0])[temp];
					pdest[15] = ((unsigned short *)pcolormap[1])[temp];
					pdest[nextrow + 14] = ((unsigned short *)pcolormap[2])[temp];
					pdest[nextrow + 15] = ((unsigned short *)pcolormap[3])[temp];
					pdest += 16; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;
			}
		}
		else
		{
			for (y = height ; y ; y--)
			{
			for (x = xcount ; x ;x--)
			    {
					pdest[0] = pdest[1] = pdest[nextrow] =
							pdest[nextrow + 1] = pcolormap[0][psrc[0]];
					pdest[2] = pdest[3] = pdest[nextrow + 2] =
							pdest[nextrow + 3] = pcolormap[0][psrc[1]];
					pdest[4] = pdest[5] = pdest[nextrow + 4] =
							pdest[nextrow + 5] = pcolormap[0][psrc[2]];
					pdest[6] = pdest[7] = pdest[nextrow + 6] =
							pdest[nextrow + 7] = pcolormap[0][psrc[3]];
					pdest[8] = pdest[9] = pdest[nextrow + 8] =
							pdest[nextrow + 9] = pcolormap[0][psrc[4]];
					pdest[10] = pdest[11] = pdest[nextrow + 10] =
							pdest[nextrow + 11] = pcolormap[0][psrc[5]];
					pdest[12] = pdest[13] = pdest[nextrow + 12] =
							pdest[nextrow + 13] = pcolormap[0][psrc[6]];
					pdest[14] = pdest[15] = pdest[nextrow + 14] =
							pdest[nextrow + 15] = pcolormap[0][psrc[7]];
					pdest += 16; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;
			}
    	}
	}
	else
	{
	    rowdelta = (destrowbytes - (width<<1))>>1;

		if (dither)
		{
			for (y = height ; y>0 ; y -= 2)
			{
		    	for (x = xcount ; x ;x--)
			    {
					pdest[0] = ((unsigned short *)pcolormap[0])[psrc[0]];
					pdest[1] = ((unsigned short *)pcolormap[1])[psrc[1]];
					pdest[2] = ((unsigned short *)pcolormap[0])[psrc[2]];
					pdest[3] = ((unsigned short *)pcolormap[1])[psrc[3]];
					pdest[4] = ((unsigned short *)pcolormap[0])[psrc[4]];
					pdest[5] = ((unsigned short *)pcolormap[1])[psrc[5]];
					pdest[6] = ((unsigned short *)pcolormap[0])[psrc[6]];
					pdest[7] = ((unsigned short *)pcolormap[1])[psrc[7]];
					pdest += 8; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;

		    	for (x = xcount ; x ;x--)
			    {
					pdest[0] = ((unsigned short *)pcolormap[2])[psrc[0]];
					pdest[1] = ((unsigned short *)pcolormap[3])[psrc[1]];
					pdest[2] = ((unsigned short *)pcolormap[2])[psrc[2]];
					pdest[3] = ((unsigned short *)pcolormap[3])[psrc[3]];
					pdest[4] = ((unsigned short *)pcolormap[2])[psrc[4]];
					pdest[5] = ((unsigned short *)pcolormap[3])[psrc[5]];
					pdest[6] = ((unsigned short *)pcolormap[2])[psrc[6]];
					pdest[7] = ((unsigned short *)pcolormap[3])[psrc[7]];
					pdest += 8; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;
			}
		}
		else
		{
			for (y = height ; y ; y--)
			{
			for (x = xcount ; x ;x--)
			    {
					pdest[0] = pcolormap[0][psrc[0]];
					pdest[1] = pcolormap[0][psrc[1]];
					pdest[2] = pcolormap[0][psrc[2]];
					pdest[3] = pcolormap[0][psrc[3]];
					pdest[4] = pcolormap[0][psrc[4]];
					pdest[5] = pcolormap[0][psrc[5]];
					pdest[6] = pcolormap[0][psrc[6]];
					pdest[7] = pcolormap[0][psrc[7]];
					pdest += 8; psrc += 8;
			    }

				psrc += srcdelta;
			    pdest += rowdelta;
			}
    	}
	}
}


/*
===================
Update32_1
===================
*/
void Update32_1 (pixel_t *src, unsigned *dest, int width, int height,
		int destrowbytes)
{
	int				x,y;
	unsigned		rowdelta, srcdelta;
	unsigned		xcount;
	pixel_t			*psrc;
	unsigned		*pdest;
	int				xwidth;

	psrc = src;
	pdest = dest;

	xcount = width >> 3;
	srcdelta = vid.width - width;

	xwidth = width - (xcount << 3);
	if (xwidth)
		Sys_Error ("Width not multiple of 8");

	if ((vid_display == disp_framebuffer) && (vid_scale == 2))
	{
		int		nextrow = destrowbytes >> 2;

	    rowdelta = ((destrowbytes - ((width << 1) << 2)) >> 2)  +
				(destrowbytes >> 2);

		for (y = height ; y ; y--)
		{
			for (x = xcount ; x ;x--)
		    {
				pdest[0] = pdest[1] = pdest[nextrow] =
						pdest[nextrow + 1] = pcolormap[0][psrc[0]];
				pdest[2] = pdest[3] = pdest[nextrow + 2] =
						pdest[nextrow + 3] = pcolormap[0][psrc[1]];
				pdest[4] = pdest[5] = pdest[nextrow + 4] =
						pdest[nextrow + 5] = pcolormap[0][psrc[2]];
				pdest[6] = pdest[7] = pdest[nextrow + 6] =
						pdest[nextrow + 7] = pcolormap[0][psrc[3]];
				pdest[8] = pdest[9] = pdest[nextrow + 8] =
						pdest[nextrow + 9] = pcolormap[0][psrc[4]];
				pdest[10] = pdest[11] = pdest[nextrow + 10] =
						pdest[nextrow + 11] = pcolormap[0][psrc[5]];
				pdest[12] = pdest[13] = pdest[nextrow + 12] =
						pdest[nextrow + 13] = pcolormap[0][psrc[6]];
				pdest[14] = pdest[15] = pdest[nextrow + 14] =
						pdest[nextrow + 15] = pcolormap[0][psrc[7]];
				pdest += 16; psrc += 8;
		    }

			psrc += srcdelta;
		    pdest += rowdelta;
		}
    }
	else
	{
	    rowdelta = (destrowbytes - (width<<2))>>2;

		for (y = height ; y ; y--)
		{
			for (x = xcount ; x ;x--)
		    {
				pdest[0] = pcolormap[0][psrc[0]];
				pdest[1] = pcolormap[0][psrc[1]];
				pdest[2] = pcolormap[0][psrc[2]];
				pdest[3] = pcolormap[0][psrc[3]];
				pdest[4] = pcolormap[0][psrc[4]];
				pdest[5] = pcolormap[0][psrc[5]];
				pdest[6] = pcolormap[0][psrc[6]];
				pdest[7] = pcolormap[0][psrc[7]];
				pdest += 8; psrc += 8;
		    }

			psrc += srcdelta;
		    pdest += rowdelta;
		}
	}
}


/*
==========================================================================

						NEXTSTEP VIEW CLASS

==========================================================================
*/


@implementation QuakeView

/*
=================
windowDidMove

=================
*/
- windowDidMove:sender
{
    NXPoint	aPoint;
    NXRect	winframe;

    aPoint.x = aPoint.y = 0;
    [self convertPoint:&aPoint toView:nil];
    [window convertBaseToScreen: &aPoint];
    [window getFrame: &winframe];

    if ((int)aPoint.x & 7)
    {
	[window moveTo:winframe.origin.x - ((int)aPoint.x&7) 
			:winframe.origin.y];
	[window getFrame: &winframe];
    }
    return self;
}

- windowWillResize:sender toSize:(NXSize *)frameSize
{
	NXRect		fr, cont;
	
	fr.origin.x = fr.origin.y = 0;
	fr.size = *frameSize;
	
	[Window getContentRect:&cont forFrameRect: &fr style:[window style]];

	cont.size.width = (int)cont.size.width & ~15;
	if (cont.size.width < 128)
		cont.size.width = 128;
	cont.size.height = (int)cont.size.height & ~3;
	if (cont.size.height < 32)
		cont.size.height = 32;

	[Window getFrameRect:&fr forContentRect: &cont style:[window style]];

	*frameSize = fr.size;
	
	return self;
}

- windowDidResize:sender
{
	if (vid_display == disp_framebuffer)
		Sys_Error ("How the heck are you resizing a framebuffer window?!?");

	vid.width = bounds.size.width/vid_scale;
	vid.height = bounds.size.height/vid_scale;

//
// allocate memory for the back and translation buffers
//
	vid.rowbytes = vid.width;
	rowbytesnative = vid.width * pixbytesnative;
	
	AllocBuffers (true);

	vid.conbuffer = vid.buffer;
	vid.conrowbytes = vid.rowbytes;
	vid.conwidth = vid.width;
	vid.conheight = vid.height;

	vid.recalc_refdef = 1;

	return self;
}

-(BOOL) acceptsFirstResponder
{
    return YES;
}


typedef struct
{
	int		source, dest;
} keymap_t;

keymap_t keymaps[] =
{
	{103, K_RIGHTARROW},
	{102, K_LEFTARROW},
	{100, K_UPARROW},
	{101, K_DOWNARROW},
	{111, K_PAUSE},

	{59, K_F1},
	{60, K_F2},
	{61, K_F3},
	{62, K_F4},
	{63, K_F5},
	{64, K_F6},
	{65, K_F7},
	{66, K_F8},
	{67, K_F9},
	{68, K_F10},
	{87, K_F11},
	{88, K_F12},
	
	{-1,-1}
};

keymap_t flagmaps[] =
{
	{NX_SHIFTMASK, K_SHIFT},
	{NX_CONTROLMASK, K_CTRL},
	{NX_ALTERNATEMASK, K_ALT},
	{NX_COMMANDMASK, K_ALT},
	
	{-1,-1}
};

/*
===================
keyboard methods
===================
*/
- keyDown:(NXEvent *)theEvent
{
    int	ch;
	keymap_t	*km;
	
	PSobscurecursor ();

// check for non-ascii first
	ch = theEvent->data.key.keyCode;
	for (km=keymaps;km->source!=-1;km++)
		if (ch == km->source)
		{
			Key_Event (km->dest, true);
			return self;
		}

    ch = theEvent->data.key.charCode;
	if (ch >= 'A' && ch <= 'Z')
		ch += 'a' - 'A';
    if (ch>=256)
		return self;
		
	Key_Event (ch, true);
    return self;
}

- flagsChanged:(NXEvent *)theEvent
{
	static int	oldflags;
    int		newflags;
	int		delta;
	keymap_t	*km;
	int		i;
	
	PSobscurecursor ();
    newflags = theEvent->flags;
	delta = newflags ^ oldflags;
	for (i=0 ; i<32 ; i++)
	{
		if ( !(delta & (1<<i)))
			continue;
	// changed
		for (km=flagmaps;km->source!=-1;km++)
			if ( (1<<i) == km->source)
			{
				if (newflags & (1<<i))
					Key_Event (km->dest, true);
				else
					Key_Event (km->dest, false);
			}

	}
	
	oldflags = newflags;
		
    return self;
}


- keyUp:(NXEvent *)theEvent
{
    int	ch;
 	keymap_t	*km;
  
 // check for non-ascii first
	ch = theEvent->data.key.keyCode;
	for (km=keymaps;km->source!=-1;km++)
		if (ch == km->source)
		{
			Key_Event (km->dest, false);
			return self;
		}

   ch = theEvent->data.key.charCode;
	if (ch >= 'A' && ch <= 'Z')
		ch += 'a' - 'A';
    if (ch>=256)
		return self;
	Key_Event (ch, false);
    return self;
}


- tiffShot
{
	id			imagerep, image;
	NXRect		r;
	NXStream	*stream;
	int			fd;
	int    		i; 
	char		tiffname[80]; 
	
	[vid_window_i getFrame: &r];
	r.origin.x = r.origin.y = 0;
	image = [[NXImage alloc] initSize: &r.size];
	imagerep = [[NXCachedImageRep alloc] initFromWindow:vid_window_i rect:&r];
	
	[image lockFocus];
	[imagerep draw];
	[image unlockFocus];
	
// 
// find a file name to save it to 
// 
	strcpy(tiffname,"quake00.tif");
		
	for (i=0 ; i<=99 ; i++) 
	{ 
		tiffname[5] = i/10 + '0'; 
		tiffname[6] = i%10 + '0'; 
		if (Sys_FileTime(tiffname) == -1)
			break;	// file doesn't exist
	} 
	if (i==100) 
		Sys_Error ("SCR_ScreenShot_f: Couldn't create a tiff"); 

	fd = open (tiffname, O_RDWR|O_CREAT|O_TRUNC, 0666);
	stream = NXOpenFile (fd, NX_READWRITE);
	[image writeTIFF: stream];
	NXClose (stream);
	close (fd);
	printf ("wrote %s\n", tiffname);

	[image free];
	[imagerep free];
	return self;
	
}

- screenShot: sender
{
	return [self tiffShot];
}

- setScaleFullScreen: sender
{
	VID_Shutdown ();
	if (vid_fullscreen)
	{
		vid_fullscreen = 0;
		VID_Restart (vid_display, vid_scale);
	}
	else
	{
		vid_fullscreen = 1;
		VID_Restart (vid_display, vid_scale);
	}
	return self;
}

@end

//============================================================================

@implementation FrameWindow

- windowExposed:(NXEvent *)theEvent
{
	return self;
}

@end


