/*
 *  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 3.0. Localized.
 *
 *  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.
 */

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

#import "YapOutput.h"
#import "YapApp.h"
#import "YapWrap.h"

#define BUSY_STRING NXLocalString ("BUSY", NULL, "String shown when PostScript is being executed")
#define EXECUTION_COMPLETE_STRING NXLocalString("Yap Postscript Output (Execution Time %d ms)", NULL, "Shown when PostScript has executed successfully")
#define NONPOSTSCRIPT_ERROR_STRING NXLocalString("A non-PostScript error !0qe running program.", NULL, "Shown if a non-PostScript error occurs during execution")
#define NOCONTEXT_STRING NXLocalString("Could not connect to window server.", NULL, "Shown if a connection cannot be created with the window server")

@implementation YapOutput

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

    return self;
}

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

- (BOOL)isCacheShown
{
    return showCache;
}

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

- setCacheShown:(BOOL)flag
{
    showCache = 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 */
	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);
    }

    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))) {
        DPSPrintErrorToStr!0r(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, NONPOSTSCRIPT_ERROR_STRING);
    }
}

/*
 * 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);
}

#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:textObj
{
    int utime;			/* Time taken to execute the code */
    char status[STATUSLENG!0s	/* 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;

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

    if (psStream = NXOpenMemory (NULL, 0, NX_WRITEONLY)) {
	int dummy;
        NXPrintf (psStream, "/yaptime usertime def /yapsave save def "
			    "/yapwidth %f def /yapheight %f def "
		      	    "/showpage {} def\n",
                            bounds.size.width, bounds.size.height);
        [textObj writeText:psStream];
        NXPrintf (psStream, "\nyapsave restore "
			    "/yaptime usertime yaptime sub def\n");
        NXFlush (psStream);
	NXGetMemoryBuffer (psStream, &psBuffer, &psLen, &dummy);
    } else {
	[[self window] setTitle:NONPOSTSCRIPT_ERROR_STRING];
	return self;
    }

    [[self window] setTitle:BUSY_STRING];

    /* 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 = DPSCreateNonsecureContext(
			NXGetDefaultValue(app, "NXHost"),
			NXGetDefaultValue(app, "NXPSName"),
			NULL, NULL, 60000, [self zone]);
	DPSSetContext (curContext);
    }

    if (yapContext) {

	/* 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 {
	    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);
	    sprintf (status, EXECUTION_COMPLETE_STRING, utime);
	} 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. */
    
	if (exception.code != 0) {
	    DPSDestroyContext(yapContext);
	    yapContext = NULL;
	    WritePostScriptError (status, STATUSLENGTH, &exception);
	}

    } else {
	sprintf (status, NOCONTEXT_STRING, utime);
    }

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

    NXCloseMemory (psStream, NX_FREEBUFFER);
    
    return self;
}


@end