/*
 *  YapOutput.m
 *  Author: Ali Ozer
 *  Created: Mar 6, 1989
 *  Modified: Jun & Jul 1989 for 1.0. Added NX_HANDLER for error detection.
 *  Modified: Aug 90 for 2.0. Added use of second context for robustness.
 *  Modified: Jan 92 for BBFig by Izumi Ohzawa (izumi@pinoko.berkeley.edu)
 *
 *  This class is a subclass of view that manages the output in Yap. It
 *  provides a method (executeCodeFrom:) to execute PS from a text object.
 *  The PostScript output will be displayed in the view. The output is
 *  also cached in a bitmap for fast redraw response.
 *
 *  You may freely copy, distribute and reuse the code in this example.
 *  NeXT disclaims any warranty of any kind, expressed or implied,
 *  as to its fitness for any particular use.
 */

/*
 #	A portion of code in this file has been taken from NX_HitDetect example
 #	as obtained from the ps-file-server@adobe.com.
 #	The code has been heavily modified and merged with the Yap code as above.
 #	The following sections (a, d, e, and f) are included to keep Adobe happy.
 #	[Izumi Ohzawa]  
 #
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

#import <appkit/appkit.h>

#import <objc/error.h>
#import <libc.h>
#import <string.h>
#import <sys/file.h>
#import <stdio.h>

#import "YapOutput.h"
#import "YapDocument.h"
#import "YapApp.h"
#import "YapWrap.h"	// Generated from YapWrap.psw
#import "BBox.h"	// Generated from BBox.psw

/* This section of code for specifying BBox my dragging a box by mouse.
 * Stolen from Draw.app's GraphicView.m
 *
 */
/* Code-cleaning macros */

#define stopTimer(timer) if (timer) { \
    NXEndTimer(timer); \
    timer = NULL; \
}

#define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);

/*
 * Returns the rectangle which has p1 and p2 as its corners.
 */
static void getRegion(NXRect *region, const NXPoint *p1, const NXPoint *p2)
{
    region->size.width = p1->x - p2->x;
    region->size.height = p1->y - p2->y;
    if (region->size.width < 0.0) {
	region->origin.x = p2->x + region->size.width;
	region->size.width = ABS(region->size.width);
    } else {
	region->origin.x = p2->x;
    }
    if (region->size.height < 0.0) {
	region->origin.y = p2->y + region->size.height;
	region->size.height = ABS(region->size.height);
    } else {
	region->origin.y = p2->y;
    }
}



@implementation YapOutput

/*
 * initFrame: initializes the instance and creates a cache.
 */
- initFrame:(const NXRect *)viewFrame
{
    [super initFrame:viewFrame];
    [self setCacheCleared:YES];
    [self setCacheShown:NO];
    [self setMeshON:NO];
    [self setBlueON:NO];
    [self setFigureBB:NO];
    blackBG = YES;
    currentDocument = nil;
    rubberBandColor = NX_COLORRED;
    cache = [[Window allocFromZone:[self zone]]
		initContent:&bounds 
		style:NX_PLAINSTYLE
		backing:NX_RETAINED
		buttonMask:0
		defer:NO];

    return self;
}

- setRubberBandColor:(NXColor)color
{
    rubberBandColor = color;
    return self;
}

- (BOOL)acceptsFirstResponder { return YES; }

/*  This method handles a mouse down.  */
- mouseDown:(NXEvent *)event
{
NXPoint	  p;
int oldMask;

//    [window makeFirstResponder:self];		/* give me the first responder status */
    p = event->location;
    [self convertPoint:&p fromView:nil];	/* to this view's coordinate from window's */
    oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];


    [self dragSelect:event];

//    [window flushWindow];
    [window setEventMask:oldMask];

    return self;
}



#define DRAG_MASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK)

/*
 * Allows the user the drag out a box to select all objects either
 * intersecting the box, or fully contained within the box (depending
 * on the state of the ALTERNATE key).  After the selection is made,
 * the slist is updated.
 */
- dragSelect:(NXEvent *)event
{
NXPoint p, last, start;
NXTrackingTimer *timer = NULL;
NXRect visibleRect, oldRegion;
BOOL  canScroll, oldRegionSet = NO;
BOOL didDrag = NO;

    p = start = event->location;
    [self convertPoint:&start fromView:nil];
    last = start;

    [self lockFocus];

    [self getVisibleRect:&visibleRect];
    NXSetRect(&region, 0.0, 0.0, 0.0, 0.0);	// drag happend, reset current rubberband region
    [self drawSelf:&visibleRect :1];		// clear old rubber band rectangle
    canScroll = !NXEqualRect(&visibleRect, &bounds);
    if (canScroll) startTimer(timer);

    PSsetlinewidth(0.0);

    event = [NXApp getNextEvent:DRAG_MASK];
    while (event->type != NX_MOUSEUP) {
	if (event->type == NX_TIMER) event->location = p;
	p = event->location;
	[self convertPoint:&p fromView:nil];
	if(!didDrag && event->type == NX_MOUSEDRAGGED)
            didDrag = YES;

	if(p.x < 0.0)		// bounds.origin.x ?
	    p.x = 0.0;
	else if(p.x > (bounds.origin.x+bounds.size.width))
	    p.x = bounds.origin.x+bounds.size.width;
	if(p.y < 0.0)
	    p.y = 0.0;
	else if(p.y > (bounds.origin.y+bounds.size.height))
	    p.y = bounds.origin.y+bounds.size.height;

	if (p.x != last.x || p.y != last.y) {
	    getRegion(&region, &p, &start);
	    [window disableFlushWindow];
	    if (oldRegionSet) {
		NXInsetRect(&oldRegion, -1.0, -1.0);
		[self drawSelf:&oldRegion :1];
	    }
	    if (canScroll) {
		[self scrollRectToVisible:&region];
		[self scrollPointToVisible:&p];
	    }
	    NXSetColor(rubberBandColor);
	    PSrectstroke(region.origin.x, region.origin.y, region.size.width, region.size.height);
	    [[window reenableFlushWindow] flushWindow];
	    oldRegion = region; oldRegionSet = YES;
	    last = p;
	    NXPing();
	}
	p = event->location;
	event = [NXApp getNextEvent:DRAG_MASK];
    }

    if (canScroll) stopTimer(timer);


    [self drawSelf:&visibleRect :1];


    [self unlockFocus];

    // Report the user-specified BBox to the document's BBox field
    if(currentDocument && didDrag)
        [currentDocument setBBoxField: region.origin.x :region.origin.y
		:region.origin.x+region.size.width  :region.origin.y+region.size.height];
    return self;
}


- scrollPointToVisible:(const NXPoint *)point
{
    NXRect r;

    r.origin.x = point->x - 5.0;
    r.origin.y = point->y - 5.0;
    r.size.width = r.size.height = 10.0;

    return [self scrollRectToVisible:&r];
}



/*
 * Set/get parameters that determine behaviour.
 */
- (BOOL)isCacheCleared
{
    return clearCache;
}

- (BOOL)isCacheShown
{
    return showCache;
}

- (BOOL)isMeshON
{
    return meshON;
}

- (BOOL)isBlueON
{
    return blueON;
}

- (BOOL)isFigureBB
{
    return figureBB;
}

- setCacheCleared:(BOOL)flag
{
    clearCache = flag;
    return self;
}

- setCacheShown:(BOOL)flag
{
    showCache = flag;
    return self;
}

- setMeshON:(BOOL)flag
{
    meshON = flag;
    return self;
}

- setBlueON:(BOOL)flag
{
    blueON = flag;
    return self;
}

- setFigureBB:(BOOL)flag
{
    figureBB = flag;
    return self;
}


/*
 * sizeTo:: is called whenever the view is resized. It resizes the bitmap cache
 * along with the view. It doesn't do anything if the new size is equal to the
 * old one.
 */
- sizeTo:(NXCoord)width :(NXCoord)height
{
    if (width == frame.size.width && height == frame.size.height) return self;

    [super sizeTo:width :height];
    [cache sizeWindow:width :height];
    
    return self;
}

/*
 * drawSelf: simply shows the cache.
 */
- drawSelf:(NXRect *)rects :(int)rectCount
{
    /* if rectCount == 3, Scrolling diagonally; use last two rectangles */
/*
    if (rectCount == 3) {
	rects++;
	PScomposite (NX_X(rects), NX_Y(rects), NX_WIDTH(rects), NX_HEIGHT(rects), 
			[cache gState], NX_X(rects), NX_Y(rects), NX_COPY);
	rects++;
	PScomposite (NX_X(rects), NX_Y(rects), NX_WIDTH(rects), NX_HEIGHT(rects), 
			[cache gState], NX_X(rects), NX_Y(rects), NX_COPY);
    } else {
*/
	PScomposite (NX_X(rects), NX_Y(rects), NX_WIDTH(rects), NX_HEIGHT(rects), 
			[cache gState], NX_X(rects), NX_Y(rects), NX_COPY);
/*
    }
*/

    NXSetColor(rubberBandColor);
    PSsetlinewidth(0.0);
    PSrectstroke(region.origin.x, region.origin.y, region.size.width, region.size.height);
    return self;
}

- free
{
    [cache free];
    return [super free];
}

/*
 * Ugly function to write PostScript error.
 */
void WritePostScriptError (char *errStr, int errStrLength, NXHandler *errorState)
{
    char *streamAddr;
    int streamLength, maxLength;
    NXStream *errorStream;

    if ((errorState->code == dps_err_ps) && 
	(errorStream = NXOpenMemory (NULL, 0, NX_WRITEONLY))) {
        DPSPrintErrorToStream (errorStream, (DPSBinObjSeq)(errorState->data2));
	NXFlush (errorStream);
	NXGetMemoryBuffer (errorStream, &streamAddr, &streamLength, &maxLength);
	if (streamLength > errStrLength-1) streamLength = errStrLength-1;
	strncpy (errStr, streamAddr, streamLength);
	errStr[streamLength] = 0;
	NXCloseMemory (errorStream, NX_FREEBUFFER);
    } else {
	sprintf (errStr, "A non-PostScript error while running program.");
    }
}

/*
 * SwitchContextsWithFocus() will make the specified context the current
 * context and make it focus on the same area the old context was 
 * focused on.
 */
static void SwitchContextsWithFocus (DPSContext newContext)
{
    float c1x, c1y, c2x, c2y;
    float winCTM[6];
    int realWinNum;
    
    GetFocus (&c1x, &c1y, &c2x, &c2y, winCTM, &realWinNum);
    DPSSetContext (newContext);
    ReFocus (realWinNum, winCTM, c1x, c1y, c2x, c2y);
}


- setCurrentDocument:anDoc
{
    currentDocument = anDoc;
    return self;
}


#define STATUSLENGTH 200	// Some large number for error string length

/*
 * executeCodeFrom: treats the contents of the specified text object as
 * a PostScript program and executes it. The code is copied from the
 * text object into a memory stream and then sent to the server using
 * DPSWriteData(). 
 *
 * For protection against errors, the PostScript code is interpreted in a
 * context separate from the Application's own context (which is created
 * in the +new method of Application).  We first focus on the cache,
 * note the various parameters (global window number, the transformation
 * matrix, and the clip path), and then switch to the alternate context and
 * reapply the parameters to establish a focus on the same area.
 *
 * Protection against PostScript errors is provided through the use of
 * NX_DURING/NX_HANDLER.   If an error occurs, we immediately blast the
 * second context and report the first error encountered.  If no errors
 * occur during the execution, then we hang on to the context as it can
 * be reused.
 *
 * Note that the NXEPSImageRep class provides a similar (but more powerful)
 * sort of functionality for EPS files.  Use that class rather than the code
 * here if you wish to make use of EPS files in your application.  This code
 * here is meant for unstructured, short pieces of PostScript code, 
 * eactly the kind that Yap encounters...
 */
- executeCodeFrom:docObject andReturnBB: (float *)llx : (float *)lly
				     : (float *)urx : (float *)ury
				     usertime: (int *) utime
{
//    int utime;		/* Time taken to execute the code */
    char status[STATUSLENGTH];	/* Array for error messages */
    NXStream *psStream;		/* Memory stream for the PostScript */
    char *psBuffer;		/* The buffer used by the stream */
    int psLen;			/* And the length of this buffer */
    static DPSContext yapContext = NULL;	/* The second context */
    DPSContext curContext = DPSGetCurrentContext();
    NXHandler exception;

    currentDocument = docObject;		// save which window's PS code is rendered.
    /* Order front output window */
    [[self window] orderFront:self];

    /* Open a memory stream and write the text into it... */ 

    if (psStream = NXOpenMemory (NULL, 0, NX_WRITEONLY)) {
	int dummy;
        NXPrintf (psStream, "/yaptime usertime def "
		      	    "/-showpage {} def\n");
        [[[docObject document] docView] writeText:psStream];
        NXPrintf (psStream, "\n/yaptime usertime yaptime sub def\n");
        NXFlush (psStream);
	NXGetMemoryBuffer (psStream, &psBuffer, &psLen, &dummy);
    } else {
	[[self window] setTitle:"Can't open memory stream!"];
	return self;
    }

    [[self window] setTitle:"BUSY"];

    /* Lock focus on the cache. If user wishes to see the cache, bring it up. */

    [[cache contentView] lockFocus];
    if (clearCache) NXEraseRect (&bounds);
    if (showCache) [[cache center] orderFront:self];  

    /* Create the second context if it needs to be created. */

    if (yapContext == NULL) {
	const char *app = [NXApp appName];
	yapContext = DPSCreateContext(NXGetDefaultValue(app, "NXHost"),
				      NXGetDefaultValue(app, "NXPSName"),
				      NULL, NULL);
	DPSSetContext (curContext);
    }

    /* Focus the second context to whatever the first context is focused on. */

    SwitchContextsWithFocus (yapContext);

    /* This will let us know if there were any errors. */

    exception.code = 0;

    NX_DURING {
	DPSPrintf(yapContext, "/yapwidth %f def /yapheight %f def\n",
                            bounds.size.width, bounds.size.height);
//	if(blackBG)
//	if(meshON)		// temporarily taken over
//	   BlackBG();

	if(blueON) {
		DPSPrintf(yapContext, "0 0 0.5 setrgbcolor clippath fill\n");
	}

	if(meshON)
	   ShowMesh();		/* draws point grid */
	if(figureBB)
	   SendBBfig();	/* pswrap func containing bbfig */
	DPSWriteData(yapContext, psBuffer, psLen);
	NXPing();  /* This does not return until the execution is done. */
		    /* If there were any errors, we jump to the handler. */
	GetUserTime (utime);		/* utime already pointer */
	sprintf (status, "BBFig Postscript Output (Execution Time %d ms)", *utime);

	if(figureBB)
	   GetBBox(llx, lly, urx, ury);	/* get BoundingBox; args are pointers */
	else
	{
	   *llx = *lly = 0.0;
	   *urx = *ury = 999.0;
	   *utime = -1;
	}

/*
	sprintf (status, "%%%%BoundingBox: %5.0f %5.0f %5.0f %5.0f",
			*llx, *lly, *urx, *ury);
*/

    } NX_HANDLER {
	exception = NXLocalHandler;	/* Make note of the error... */
    } NX_ENDHANDLER

    /* Switch back to the app context. */

    DPSSetContext(curContext);

    /* In case of error, report it and blow the second context away. */
    /* -> Destroy the second context anyway */
    DPSDestroyContext(yapContext);
    yapContext = NULL;

    if (exception.code != 0) {
//	DPSDestroyContext(yapContext);
//	yapContext = NULL;
	WritePostScriptError (status, STATUSLENGTH, &exception);
	*llx = *lly = 0.0;
	*urx = *ury = 999.0;
	*utime = -1;	/* error indication */
    }

    if (showCache) [cache orderOut:self];
    [[cache contentView] unlockFocus];
    [self display];
    [[self window] setTitle:status];

    NXCloseMemory (psStream, NX_FREEBUFFER);
    
    return self;
}

@end


