#include "QF/sys.h"

#include "ZView.h"
#include "ZScrollView.h"
#include "QuakeEd.h"
#include "Map.h"
#include "XYView.h"
#include "CameraView.h"

id  zview_i;

id  zscrollview_i, zscalemenu_i, zscalebutton_i;

float   zplane;
float   zplanedir;

extern NSBezierPath  *path;

@implementation ZView
/*
==================
initWithFrame:
==================
*/
- (id) initWithFrame: (NSRect)frameRect
{
	NSPoint         pt;
	NSBezierPath    *path;

	font = [[NSFont systemFontOfSize: 10] retain];
	path = checker = [NSBezierPath new];
	[path setLineWidth: 0.3];
	[path moveToPoint: NSMakePoint (-16, -16)];
	[path relativeLineToPoint: NSMakePoint (32, 32)];
	[path moveToPoint: NSMakePoint (-16, 16)];
	[path relativeLineToPoint: NSMakePoint (32, -32)];

	origin[0] = 0.333;
	origin[1] = 0.333;

	[super initWithFrame: frameRect];
	[self allocateGState];
	[self clearBounds];

	zview_i = self;
	scale = 1;

	// initialize the pop up menus
	zscalebutton_i = [[NSPopUpButton alloc] init];
	[zscalebutton_i setTarget: self];
	[zscalebutton_i setAction: @selector (scaleMenuTarget:)];

	[zscalebutton_i addItemWithTitle: @"12.5%"];
	[zscalebutton_i addItemWithTitle: @"25%"];
	[zscalebutton_i addItemWithTitle: @"50%"];
	[zscalebutton_i addItemWithTitle: @"75%"];
	[zscalebutton_i addItemWithTitle: @"100%"];
	[zscalebutton_i addItemWithTitle: @"200%"];
	[zscalebutton_i addItemWithTitle: @"300%"];
	[zscalebutton_i selectItemAtIndex: 4];

	// initialize the scroll view
	zscrollview_i = [[ZScrollView alloc]
	                   initWithFrame: frameRect
	                         button1: zscalebutton_i];
	[zscrollview_i setAutoresizingMask: NSViewWidthSizable |
	 NSViewHeightSizable];

	[zscrollview_i setDocumentView: self];

	[_super_view setBoundsOrigin: NSMakePoint (0, 0)];

	minheight = 0;
	maxheight = 64;

	pt.x = -[_super_view bounds].size.width / 2;
	pt.y = -128;

	[self newRealBounds];

	[self setOrigin: pt scale: 1];

	return zscrollview_i;
}

- (BOOL) isOpaque
{
	return YES;
}

- (id) setXYOrigin: (NSPoint *)pt
{
	origin[0] = pt->x + 0.333;
	origin[1] = pt->y + 0.333;
	return self;
}

- (float) currentScale
{
	return scale;
}

/*
===================
setOrigin:scale:
===================
*/
- (id) setOrigin: (NSPoint)pt scale: (float)sc
{
	NSRect  sframe;
	NSRect  bounds;
	NSRect  size;

	// calculate the area visible in the cliprect
	scale = sc;

	bounds = [_super_view bounds];
	bounds.origin = pt;
	bounds.size.width /= 1;
	bounds.size.height /= scale;

	size = NSMakeRect (0, oldminheight, 1, oldmaxheight - oldminheight);
	// union with the realbounds
	bounds = NSUnionRect (size, bounds);
	// redisplay everything
	bounds.origin = NSMakePoint (-bounds.size.width / 2, bounds.origin.y);
	sframe = bounds;
	sframe.origin.x *= 1;
	sframe.origin.y *= scale;
	sframe.size.width *= 1;
	sframe.size.height *= scale;

	// size this view
	[quakeed_i disableFlushWindow];
	[self setFrame: sframe bounds: bounds scale: NSMakeSize (1, scale)];

	// scroll and scale the clip view
	pt.x *= 1;
	pt.y *= scale;
	[_super_view setBoundsOrigin: pt];

	[quakeed_i enableFlushWindow];
	[zscrollview_i display];

	return self;
}

/*
====================
scaleMenuTarget:

Called when the scaler popup on the window is used
====================
*/
- (id) scaleMenuTarget: sender
{
	NSRect      rect;
	NSPoint     mid, org, orig;
	float       nscale;

	nscale = [[sender titleOfSelectedItem] floatValue] / 100;

	if (nscale == scale)
		return NULL;
	// keep the center of the view constant
	rect = [_super_view bounds];
	mid.x = rect.size.width / 2;
	mid.y = rect.size.height / 2;

	org.x = (rect.origin.x + mid.x) / 1;
	org.y = (rect.origin.y + mid.y) / scale;

	orig.x = org.x - mid.x / 1;
	orig.y = org.y - mid.y / nscale;

	[self setOrigin: orig scale: nscale];

	return self;
}

- (id) clearBounds
{
	topbound = 999999;
	bottombound = -999999;

	return self;
}

- (id) getBounds: (float *)top: (float *)bottom
{
	*top = topbound;
	*bottom = bottombound;
	return self;
}

/*
==================
addToHeightRange:
==================
*/
- (id) addToHeightRange: (float)height
{
	if (height < minheight)
		minheight = height;
	if (height > maxheight)
		maxheight = height;
	return self;
}

/*
==================
newSuperBounds

When _super_view is resized
==================
*/
- (id) newSuperBounds
{
	oldminheight++;
	[self newRealBounds];

	return self;
}

/*
===================
newRealBounds

Should only change the scroll bars, not cause any redraws.
If realbounds has shrunk, nothing will change.
===================
*/
- (id) newRealBounds
{
	NSRect  bounds;
	NSRect  sframe;
	NSRect  size;

	NSClipView  *cv = (NSClipView *) _super_view;

	if (minheight == oldminheight && maxheight == oldmaxheight)
		return self;
	oldminheight = minheight;
	oldmaxheight = maxheight;

	minheight -= 16;
	maxheight += 16;

	// calculate the area visible in the cliprect
	bounds = [cv bounds];
	bounds.size.width /= 1;
	bounds.size.height /= scale;

	size = NSMakeRect (0, minheight, 1, maxheight - minheight);

	bounds = NSUnionRect (size, bounds);

	sframe = bounds;
	sframe.size.width *= 1;
	sframe.size.height *= scale;

	// size this view
	[quakeed_i disableFlushWindow];
	[self setFrame: sframe bounds: bounds scale: NSMakeSize (1, scale)];

	[[_super_view superview] reflectScrolledClipView: cv];

	[quakeed_i enableFlushWindow];
	[[zscrollview_i verticalScroller] display];

	return self;
}

/*
============
drawGrid

Draws tile markings every 64 units, and grid markings at the grid scale if
the grid lines are >= 4 pixels apart

Rect is in global world (unscaled) coordinates
============
*/

- (id) drawGrid: (NSRect)rect
{
	int     y, stopy;
	float   top, bottom;
	int     left, right;
	int     gridsize;
	BOOL    showcoords;
	NSMutableDictionary  *attribs = [NSMutableDictionary dictionary];

	showcoords = [quakeed_i showCoordinates];

	gridsize = [xyview_i gridsize];

	left = _bounds.origin.x;
	right = 24;
	bottom = rect.origin.y - 1;
	top = rect.origin.y + rect.size.height + 2;

	// grid

	// can't just divide by grid size because of negetive coordinate
	// truncating direction
	if (gridsize >= 4 / scale) {
		y = floor (bottom / gridsize);
		stopy = floor (top / gridsize);

		y *= gridsize;
		stopy *= gridsize;
		if (y < bottom)
			y += gridsize;
		[path removeAllPoints];

		for ( ; y <= stopy; y += gridsize) {
			if (y & 31) {
				[path moveToPoint: NSMakePoint (left, y)];
				[path lineToPoint: NSMakePoint (right, y)];
			}
		}

//		endUserPath (upath, dps_ustroke);
		[[NSColor colorWithCalibratedRed: 0.8
		                           green: 0.8
		                            blue: 1.0
		                           alpha: 1.0] set];    // thin grid color
		[path stroke];
	}

	// half tiles
	y = floor (bottom / 32);
	stopy = floor (top / 32);

	if (!(((int) y + 4096) & 1))
		y++;
	y *= 32;
	stopy *= 32;
	if (stopy >= top)
		stopy -= 32;
	[path removeAllPoints];

	for ( ; y <= stopy; y += 64) {
		[path moveToPoint: NSMakePoint (left, y)];
		[path lineToPoint: NSMakePoint (right, y)];
	}

	// endUserPath (upath, dps_ustroke);
	[[NSColor colorWithCalibratedWhite: 12.0 / 16.0 alpha: 1.0]
	 set];
	[path stroke];

	// tiles
	y = floor (bottom / 64);
	stopy = floor (top / 64);

	y *= 64;
	stopy *= 64;
	if (y < bottom)
		y += 64;
	if (stopy >= top)
		stopy -= 64;
	[path removeAllPoints];

	[[NSColor colorWithCalibratedWhite: 0.0 / 16.0 alpha: 1.0] set]; // for text
	[font set];

	for ( ; y <= stopy; y += 64) {
		if (showcoords) {
			NSString  *s = [NSString stringWithFormat: @"%i", y];
			[s drawAtPoint: NSMakePoint (left, y) withAttributes: attribs];
		}
		[path moveToPoint: NSMakePoint (left + 24, y)];
		[path lineToPoint: NSMakePoint (right, y)];
	}

	// divider
	[path moveToPoint: NSMakePoint (0, _bounds.origin.y)];
	[path lineToPoint: NSMakePoint (0, _bounds.origin.y + _bounds.size.height)];

//	endUserPath (upath, dps_ustroke);
	[[NSColor colorWithCalibratedWhite: 10.0 / 16.0 alpha: 1.0] set];
	[path stroke];

	// origin
	[[NSColor colorWithCalibratedWhite: 4.0 / 16.0 alpha: 1.0] set];
	[path removeAllPoints];
	[path setLineWidth: 5];
	[path moveToPoint: NSMakePoint (right, 0)];
	[path lineToPoint: NSMakePoint (left, 0)];
	[path stroke];
	[path setLineWidth: 1];

	return self;
}

- (id) drawZplane
{
	[[NSColor colorWithCalibratedRed: 0.2 green: 0.2 blue: 0.0 alpha: 1.0] set];
	[path  appendBezierPathWithArcWithCenter: NSMakePoint (0, zplane)
	                                  radius: 4
	                              startAngle: 0
	                                endAngle: 180];
	[path fill];
	return self;
}

/*
===============================================================================
drawSelf
===============================================================================
*/

- (void) drawRect: (NSRect)rect
{
	// NSRect      visRect;
// Sys_Printf("ZView:drawRect\n");
	minheight = 999999;
	maxheight = -999999;

	// allways draw the entire bar
	// visRect =[self visibleRect];
	rect = [self visibleRect];

	[quakeed_i zNoRestore: rect];

	// erase window
	NSEraseRect (rect);

	// draw grid
	[self drawGrid: rect];

	// draw zplane
	[self drawZplane]; //FIXME zplane doesn't do anything yet

	// draw all entities
	[map_i makeUnselectedPerform: @selector (ZDrawSelf)];

	// possibly resize the view
	[self newRealBounds];
}

/*
==============
XYDrawSelf
==============
*/
- (void) XYDrawSelf
{
	NSBezierPath        *path;
	NSAffineTransform   *trans;

	[[NSColor colorWithCalibratedRed: 0 green: 0.5 blue: 1.0 alpha: 1.0] set];

	trans = [NSAffineTransform transform];
	[trans translateXBy: origin[0] yBy: origin[1]];

	path = [checker copy];
	[path transformUsingAffineTransform: trans];
	[path stroke];
	[path release];
}

/*
==============
getPoint: (NSPoint *)pt
==============
*/
- (id) getPoint: (NSPoint *)pt
{
	pt->x = origin[0] + 0.333;      // offset a bit to avoid edge cases
	pt->y = origin[1] + 0.333;
	return self;
}

- (id) setPoint: (NSPoint *)pt
{
	origin[0] = pt->x;
	origin[1] = pt->y;
	return self;
}

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

MOUSE CLICKING

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

/*
================
dragLoop:
================
*/
static NSPoint  oldreletive;

- (id) dragFrom: (NSEvent *)startevent
   useGrid: (BOOL)ug
   callback: (void (*)(float dy))callback
{
	NSEvent     *event;
	NSPoint     startpt, newpt;
	NSPoint     reletive, delta;
	int         gridsize;

	gridsize = [xyview_i gridsize];

	startpt = [startevent locationInWindow];
	startpt = [self convertPoint: startpt fromView: NULL];

	oldreletive.x = oldreletive.y = 0;
	reletive.x = 0;

	while (1) {
		unsigned  eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask
		                      | NSRightMouseUpMask | NSRightMouseDraggedMask
		                      | NSApplicationDefinedMask;
		event = [NSApp nextEventMatchingMask: eventMask untilDate: [NSDate
		                                                            distantFuture]
		                              inMode: NSEventTrackingRunLoopMode dequeue:
		         YES];

		if ([event type] == NSLeftMouseUp || [event type] == NSRightMouseUp)
			break;
		newpt = [event locationInWindow];
		newpt = [self convertPoint: newpt fromView: NULL];

		reletive.y = newpt.y - startpt.y;

		if (ug)                     // we want truncate-towards-0 behavior here
			reletive.y = gridsize * (int) (reletive.y / gridsize);

		if (reletive.y == oldreletive.y)
			continue;
		delta.y = reletive.y - oldreletive.y;
		oldreletive = reletive;
		callback (delta.y);
	}

	return self;
}

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

void
ZDragCallback (float dy)
{
	sb_translate[0] = 0;
	sb_translate[1] = 0;
	sb_translate[2] = dy;

	[map_i makeSelectedPerform: @selector (translate)];

	[quakeed_i redrawInstance];
}

- (id) selectionDragFrom: (NSEvent *)theEvent
{
	Sys_Printf ("dragging selection\n");
	[self dragFrom: theEvent useGrid: YES callback: ZDragCallback];
	[quakeed_i updateCamera];
	return self;
}

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

void
ZScrollCallback (float dy)
{
	NSRect      basebounds;
	NSPoint     neworg;
	float       scale;

	basebounds = [[zview_i superview] bounds];
	basebounds = [zview_i convertRect: basebounds fromView: [zview_i
	                                                         superview]];

	neworg.y = basebounds.origin.y - dy;

	scale = [zview_i currentScale];

	oldreletive.y -= dy;
	[zview_i setOrigin: neworg scale: scale];
}

- (id) scrollDragFrom: (NSEvent *)theEvent
{
	Sys_Printf ("scrolling view\n");
	[self dragFrom: theEvent useGrid: YES callback: ZScrollCallback];
	return self;
}

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

void
ZControlCallback (float dy)
{
	int  i;

	for (i = 0; i < numcontrolpoints; i++)
		controlpoints[i][2] += dy;

	[[map_i selectedBrush] calcWindings];
	[quakeed_i redrawInstance];
}

- (BOOL) planeDragFrom: (NSEvent *)theEvent
{
	NSPoint     pt;
	vec3_t      dragpoint;

	if ([map_i numSelected] != 1)
		return NO;
	pt = [theEvent locationInWindow];
	pt = [self convertPoint: pt fromView: NULL];

	dragpoint[0] = origin[0];
	dragpoint[1] = origin[1];
	dragpoint[2] = pt.y;

	[[map_i selectedBrush] getZdragface: dragpoint];
	if (!numcontrolpoints)
		return NO;
	Sys_Printf ("dragging brush plane\n");

	pt = [theEvent locationInWindow];
	pt = [self convertPoint: pt fromView: NULL];

	[self dragFrom: theEvent useGrid: YES callback: ZControlCallback];

	[[map_i selectedBrush] removeIfInvalid];

	[quakeed_i updateCamera];
	return YES;
}

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

/*
===================
mouseDown
===================
*/
- (void) mouseDown: (NSEvent *)theEvent
{
	NSPoint     pt;
	int         flags;
	vec3_t      p1;

	pt = [theEvent locationInWindow];
	pt = [self convertPoint: pt fromView: NULL];

	p1[0] = origin[0];
	p1[1] = origin[1];
	p1[2] = pt.y;

	flags =
	    [theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask |
	                                NSAlternateKeyMask | NSCommandKeyMask);

	// shift click to select / deselect a brush from the world
	if (flags == NSShiftKeyMask) {
		[map_i selectRay: p1: p1: NO];
		return;
	}

	// alt click = set entire brush texture
	if (flags == NSAlternateKeyMask) {
		[map_i setTextureRay: p1: p1: YES];
		return;
	}

	// control click = position view
	if (flags == NSControlKeyMask) {
		[cameraview_i setZOrigin: pt.y];
		[quakeed_i updateAll];
		[cameraview_i ZmouseDown: &pt flags: [theEvent modifierFlags]];
		return;
	}

	// bare click to drag icons or new brush drag
	if (flags == 0) {
		// check eye
		if ([cameraview_i ZmouseDown: &pt flags: [theEvent modifierFlags]])
			return;
		if ([map_i numSelected]) {
			if (pt.x > 0) {
				if ([self planeDragFrom: theEvent])
					return;
			}
			[self selectionDragFrom: theEvent];
			return;
		}
	}

	Sys_Printf ("bad flags for click %x %g %g\n", flags, pt.x, pt.y);
	NopSound ();
	return;
}

/*
===================
rightMouseDown
===================
*/
- (void) rightMouseDown: (NSEvent *)theEvent
{
	NSPoint     pt;
	int         flags;

	pt = [theEvent locationInWindow];
	pt = [self convertPoint: pt fromView: NULL];

	flags =
	    [theEvent modifierFlags] & (NSShiftKeyMask | NSControlKeyMask |
	                                NSAlternateKeyMask | NSCommandKeyMask);

	// click = scroll view
	if (flags == 0)
		[self scrollDragFrom: theEvent];
	Sys_Printf ("bad flags for click\n");
	NopSound ();
}

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

                        XY mouse view methods

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

/*
================
modalMoveLoop
================
*/
- (id) modalMoveLoop: (NSPoint *)basept
   : (vec3_t)movemod
   : converter
{
	vec3_t      originbase;
	NSEvent     *event;
	NSPoint     newpt;
	vec3_t      delta;

	int  i;

	VectorCopy (origin, originbase);

	// modal event loop using instance drawing
	goto drawentry;

	while ([event type] != NSLeftMouseUp) {
		// calculate new point
		newpt = [event locationInWindow];
		newpt = [converter convertPoint: newpt fromView: NULL];

		delta[0] = newpt.x - basept->x;
		delta[1] = newpt.y - basept->y;
		delta[2] = delta[1];        // height change

		for (i = 0; i < 3; i++)
			origin[i] = originbase[i] + movemod[i] * delta[i];

	drawentry:
		// instance draw new frame
		[quakeed_i newinstance];
		[self display];

		unsigned  eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;

		event = [NSApp nextEventMatchingMask: eventMask untilDate: [NSDate
		                                                            distantFuture]
		                              inMode: NSEventTrackingRunLoopMode dequeue:
		         YES];
	}

	// draw the brush back into the window buffer
//	[xyview_i display];

	return self;
}

/*
===============
XYmouseDown
===============
*/
- (BOOL) XYmouseDown: (NSPoint *)pt
{
	vec3_t  movemod;

	if (fabs (pt->x - origin[0]) > 16 || fabs (pt->y - origin[1]) > 16)
		return NO;
	movemod[0] = 1;
	movemod[1] = 1;
	movemod[2] = 0;

	[self modalMoveLoop: pt: movemod: xyview_i];

	return YES;
}

@end
