/* Copyright 1992 Derek Beatty */
/* Note: this requires a version of Subprocess with the zombie fix! */

/* See the file Theory for the algorithm. */
 
#import "loTeX.h"
#import <appkit/appkit.h>
#import "Subprocess.h"
#import "Controller.h"
#import "XTAction.h"
#import "XTScroller.h"
#import "XText.h"

@implementation loTeX

char appDirectory[1024];

char *tempPat= "loTeXNNNNNN";

/* if you change these, fix default_defaults below also */
NXDefaultsVector loTeXDefaults = {
	{"tex", "latex"},
	{"busyTimer", "1."},
	{"sleepTimer", "3."},
	{"killTimer", "30."},
	{"KeyBase", ""},	// for XText
	{"KeyBindings", ""},	// for XText 
	{"preamble", "\\documentstyle[fullpage]{article}\\nonstopmode\n\n\\begin{document}\n"},
	{"postamble", "\\end{document}\n"},	
	{0, 0}
};

/* ---------------------------------------------------------------------- */
/* User really screwed up. */
- default_defaults:sender
{
    /* if you change these, fix loTeXDefaults above also */
    [tex_command setStringValue:"latex"];
    [idle_tf setStringValue:"1."];
    [sleep_tf setStringValue:"3."];
    [kill_tf setStringValue:"30."];
    [preamble setStringValue:
    "\\documentstyle[fullpage]{article}\\nonstopmode\n\n\\begin{document}\n"];
    [postamble setStringValue:"\\end{document}\n"];	
    return self;
}

/* ---------------------------------------------------------------------- */
/* User screwed up. */
- revert_defaults:sender
{
    [tex_command setStringValue:(char *)NXGetDefaultValue("loTeX", "tex")];
    [idle_tf setStringValue:(char *)NXGetDefaultValue("loTeX", "busyTimer")];
    [idle_slider setDoubleValue:[idle_tf doubleValue]];
    [sleep_tf setStringValue:(char *)NXGetDefaultValue("loTeX","sleepTimer")];
    [sleep_slider setDoubleValue:[sleep_tf doubleValue]];
    [kill_tf setStringValue:(char *)NXGetDefaultValue("loTeX", "killTimer")];
    [kill_slider setDoubleValue:[kill_tf doubleValue]];
    [preamble setStringValue:(char *)NXGetDefaultValue("loTeX", "preamble")];
    [postamble setStringValue:(char *)NXGetDefaultValue("loTeX", "postamble")];	
    return self;
}

/* ---------------------------------------------------------------------- */
/* Dispatch an alarm to the object. */
void timedHandler(DPSTimedEntry teNumber, double now, char *userData)
{
    id self= (id) userData;
    [self timedHandler:teNumber]; // do work in method to access instance vars
}


/* ---------------------------------------------------------------------- */
/* Set up defaults, symbol palatte, temp file name, delegate, TeXview */
- appDidInit:sender
{	
    char *suffix;
    port_t tv;

    editor_empty= YES;
    NXRegisterDefaults("loTeX", loTeXDefaults);
    [self revert_defaults:self];
    [symbol_controller setup];
    strcpy(appDirectory, NXArgv[0]);
    if (suffix= rindex(appDirectory, '/')) *suffix= '\0';
    if (appDirectory[0]) chdir(appDirectory);
    getwd(appDirectory);
    chdir("/tmp/");
    tmpFileName= (char *)malloc(strlen(tempPat)+1);
    strcpy(tmpFileName, tempPat);
    tmpFileName= NXGetTempFilename(tmpFileName, 5); // not really right
    [[editor_sv docView] setDelegate:self]; // tell me when text changes
    // set up Emacs key bindings from XText
    [[editor_sv docView] 
     setInitialAction:[[[XTDispatchAction alloc] 
                        initBase:NXGetDefaultValue("loTeX", "KeyBase")
			estream:nil]
		       addBindings:NXGetDefaultValue("loTeX", "KeyBindings")
		       estream:nil]];
    // put up a reasonable font
    [[editor_sv docView] setFont:[Font newFont:"Ohlfs" size:11.0]];
    // show the window  
    [text_window makeKeyAndOrderFront:self];
    tv= NXPortFromName("TeXview", NULL);	// make sure TeXview is alive
    if (!tv) {
	NXRunAlertPanel("loTeX", "can't find TeXview", 0, 0, 0);
	return self;
    }
    return self;
}

/* ---------------------------------------------------------------------- */
/* Clean up defaults and other garbage */
- (BOOL) appWillTerminate:sender
{
    char *trash;
    
    if (editor_empty) {
	NXWriteDefault("loTeX", "tex", [tex_command stringValue]);
	NXWriteDefault("loTeX", "busyTimer", [idle_tf stringValue]);
	NXWriteDefault("loTeX", "sleepTimer", [sleep_tf stringValue]);
	NXWriteDefault("loTeX", "killTimer", [kill_tf stringValue]);
	NXWriteDefault("loTeX", "preamble", [preamble stringValue]);
	NXWriteDefault("loTeX", "postamble", [postamble stringValue]);
	
	trash= malloc(strlen(tmpFileName)+10);
	sprintf(trash,"%s.aux", tmpFileName); unlink(trash);
	sprintf(trash,"%s.log", tmpFileName); unlink(trash);
	sprintf(trash,"%s.toc", tmpFileName); unlink(trash);
	sprintf(trash,"%s.blg", tmpFileName); unlink(trash);
	sprintf(trash,"%s.bbl", tmpFileName); unlink(trash);
	sprintf(trash,"%s.lof", tmpFileName); unlink(trash);
	sprintf(trash,"%s.idx", tmpFileName); unlink(trash);
	sprintf(trash,"%s.glo", tmpFileName); unlink(trash);
	sprintf(trash,"%s.ind", tmpFileName); unlink(trash);
	sprintf(trash,"%s.ilg", tmpFileName); unlink(trash);
	sprintf(trash,"%s.ent", tmpFileName); unlink(trash);
	sprintf(trash,"%s.tex", tmpFileName); unlink(trash);
	sprintf(trash,"%s.dvi", tmpFileName); unlink(trash);
	free(trash);
	return YES;
    } else {
	NXRunAlertPanel("loTeX", "Cannot Quit with text in the window.",
			0, 0, 0);
	return NO;
    }
}

/* ---------------------------------------------------------------------- */
/* Hope springs eternal. */
- show_help:sender
{
    int ok;
    char *filename;
    id speak;
    port_t edit_p;
    
    filename= (char*) malloc(strlen(appDirectory) + 12);
    sprintf(filename, "%s/Help.rtfd", appDirectory);
    speak= [NXApp appSpeaker];
    edit_p= NXPortFromName("Edit", 0);
    [speak setSendPort:edit_p];
    [speak openFile:filename ok:&ok];
    if (!ok) {
	NXRunAlertPanel("loTeX", "Error providing Help", 0, 0, 0);
    }
    free(filename);
    return self;
}


/* ---------------------------------------------------------------------- */
/* User typed something.  Pay attention. */
- textDidGetKeys: (id) sender isEmpty:(BOOL) empty
{
    editor_empty= empty;
    if (!empty) changed= 2;
    else changed= 0;
    if (running || sleeping) return self;
    else {
    	if (busy) {
	    DPSRemoveTimedEntry(busy); busy= 0;
	}
	busy= DPSAddTimedEntry([idle_tf doubleValue],
		(DPSFDProc)timedHandler, 
		(void*)self, 
		(int) NX_BASETHRESHOLD);
	if (!busy) perror("bad assumption");
	return self;
    }
}

/* ---------------------------------------------------------------------- */
/* Handler an alarm. */
- (void) timedHandler:(DPSTimedEntry)teNumber
{
    if (teNumber == busy) {
    	DPSRemoveTimedEntry(busy); busy= 0;
	if (!sleeping && !running && changed) [self start_tex:self];
    } else if (teNumber == sleeping) {
    	DPSRemoveTimedEntry(sleeping); sleeping= 0;
	if (changed) [self start_tex:self];
    } else if (teNumber == running) {
    	// the following will cause us to be called as delegate, so we
	// defer cleanup to there
    	[tex_sp terminate:self];
    } 
    /* else arrived here due to a race condition */
}

/* ---------------------------------------------------------------------- */
/* Typeset the user's... prose, I suppose.  */
- start_tex:(id) sender
{
    NXStream *tex_stream;
    char *texname;
    char *tex_cmd_buf;
    const char *tex_command_string= [tex_command stringValue];

    if (!changed) {
    	perror("running TeX without reason");
    } else if (running) {
    	perror("tried to start TeX while it was running");
    } else {
	changed--;
	tex_stream= NXOpenMemory(NULL, 0, NX_WRITEONLY);
	NXPrintf(tex_stream, "%s", [preamble stringValue]);
	[[editor_sv docView] writeText:tex_stream];
	NXPrintf(tex_stream, "%s", [postamble stringValue]);
	texname= (char *)malloc(strlen(tmpFileName) + 7);
	sprintf(texname,"%s.tex", tmpFileName);
	NXSaveToFile(tex_stream, texname);
	free(texname);
	NXCloseMemory(tex_stream, NX_FREEBUFFER);
	
	outputBuffer= NULL;
	if (!tex_sp) tex_sp= [Subprocess new];
	tex_cmd_buf= (char *)malloc(strlen(tex_command_string)
					+  strlen(tmpFileName) + 13);
	// run tex.  
	sprintf(tex_cmd_buf, "%s -v %s.tex", 
	    tex_command_string, tmpFileName);
	// run TeX.  (More reliable with pty support or without?)
	[tex_sp init:tex_cmd_buf 
		    withDelegate:self andPtySupport:NO andStdErr:YES];
	[tex_sp send:"r"];		// seems to be needed...
	running= DPSAddTimedEntry([kill_tf doubleValue],
	    (DPSFDProc)timedHandler, 
	    (void*)self, 
	    (int)NX_BASETHRESHOLD);
	if (!running) perror("bad assumption");
	free(tex_cmd_buf); 
    }
    return self;
}

/* ---------------------------------------------------------------------- */
/*  Buffer the output from the process. */
-subprocessOutput:(char *)buffer
{
    int buflen;
    
    // if TeX stops at the "? " prompt tell it to continue
    if (buffer) buflen= strlen(buffer);
    if (buffer && buflen > 3 && !strcmp(buffer+buflen-3,"\n? ")) {
	[tex_sp send:"r"];
    }
    
    if (outputBuffer) {
	outputBuffer=
	   realloc(outputBuffer,strlen(outputBuffer)+strlen(buffer)+2);
    } else {
	outputBuffer= malloc(strlen(buffer)+1);
	outputBuffer[0]= '\0';
    }
    strcpy(outputBuffer+strlen(outputBuffer), buffer);
    // comment out the next line for more speed
    [tex_messages setStringValue:outputBuffer];
    return self;
}

/* ---------------------------------------------------------------------- */
/* End of tex subprocess: stuff buffered output in Messages panel. */
- subprocessDone
{
    if (running) {
    	DPSRemoveTimedEntry(running); running= 0;
    }
    if (outputBuffer) {
        [tex_messages setStringValue:outputBuffer];
	free(outputBuffer);
	outputBuffer= NULL;
    } else {
	[tex_messages setStringValue:""];
    }
    /* If I send TeXview a remote openFile:ok: message here, TeXview
     * will become the key window.  I don't want that.
     */
    
    // sleep before running tex again
    if (changed) {
        if (sleeping) {
	    perror("already sleeping when TeX finished");
	} else {
	    sleeping= DPSAddTimedEntry([sleep_tf doubleValue],
		(DPSFDProc)timedHandler, 
		(void*)self, 
		(int)NX_BASETHRESHOLD);
	    if (!sleeping) perror("bad assumption");
	}
    }  
    return self;
}

@end
