/*
 * This is part of an example of a client application for EasyBTX.
 *
 * Written by: Max Boehm, 01/29/95
 *
 * You may freely copy, distribute and reuse the code in this example.  
 * Don't even talk to me about warranties.
 */


#import "Controller.h"
#import "MyText.h"


@implementation Controller


const char *appDefaultOwner;


+ initialize
{
    static NXDefaultsVector myDefaults = {
	{"rulefile",""},
	{"inputfile",""},
	{"outputfile",""},
        {NULL}};
    appDefaultOwner=[NXApp appName];
    NXRegisterDefaults(appDefaultOwner, myDefaults);
    return self;
}


/************************ Application Delegate **********************/


- appDidInit:sender
{
    [stateWindow setFrameUsingName:"state"];
    [stateWindow setFrameAutosaveName:"state"];
    [asciiWindow setFrameUsingName:"ascii"];
    [asciiWindow setFrameAutosaveName:"ascii"];
    
    strcpy(ruleFile,NXGetDefaultValue(appDefaultOwner,"rulefile"));
    strcpy(inputFile,NXGetDefaultValue(appDefaultOwner,"inputfile"));
    strcpy(outputFile,NXGetDefaultValue(appDefaultOwner,"outputfile"));
    
    return self;
}


- appWillTerminate:sender
{
    NXWriteDefault(appDefaultOwner,"rulefile",ruleFile);
    NXWriteDefault(appDefaultOwner,"inputfile",inputFile);
    NXWriteDefault(appDefaultOwner,"outputfile",outputFile);
    
    return self;
}


/***************************** Controller ***************************/


- setRules:(const char*)path
/* open rule file */
{
    if (access(path,F_OK)<0 || [stateMachine openRules:path]==nil)
	fprintf(stderr,"can't read rule file %s.\n", path);
    else
	fprintf(stderr,"rule file   = %s\n", path);
    return self;
}
    

- setInput:(const char*)path
/* open input file and set inputStream */
{
    NXStream *in;
    
    if (access(path,F_OK)<0)		/* if file does not exist... */
	creat(path,0600);		/* create it */
    in=NXMapFile(path,NX_READONLY);
    if (in==NULL)
	fprintf(stderr,"can't map input file %s.\n", path);
    else {
	[stateMachine setInputStream:in];
	fprintf(stderr,"input file  = %s\n", path);
    }
    return self;
}

    
- setOutput:(const char*)path
/* open output file and set outputStream */
{
    NXStream *out;

    if (access(path,F_OK)<0)		/* if file does not exist... */
	creat(path,0600);		/* create it */
    out=NXMapFile(path,NX_WRITEONLY);
    if (out==NULL)
	fprintf(stderr,"can't map output file %s.\n", path);
    else {
	NXSeek(out,0,NX_FROMEND);
	[stateMachine setOutputStream:out];
	fprintf(stderr,"output file = %s\n", path);
    }
    return self;
}


- closeStreams
{
    NXStream *in=[stateMachine inputStream];
    NXStream *out=[stateMachine outputStream];

    if (in!=NULL)
	NXCloseMemory(in,NX_FREEBUFFER);

    if (out!=NULL) {
	NXSaveToFile(out,outputFile);	    /* write memory stream to file */
	NXCloseMemory(out,NX_FREEBUFFER);
    }
    
    [stateMachine setInputStream:NULL];
    [stateMachine setOutputStream:NULL];
    
    return self;
}


- reset:sender
/* reset rule file, input stream, output stream and state. */
{
    [self closeStreams];
    
    [self setRules:ruleFile];

    [self setInput:inputFile];
    [self setOutput:outputFile];
    
    [stateMachine setStateString:"start"];	/* set initial state */
    [stateField setStringValue:"start"];

    return self;
}


- selectRules:sender
/* select a rule file */
{
    id openPanel=[OpenPanel new];
    const char *types[] = { "rules", NULL };
    const char *file=strrchr(ruleFile,'/');
    
    if (file) file++;
    if ([openPanel runModalForDirectory:ruleFile file:file types:types]
    ==NX_OKTAG) {
	strcpy(ruleFile,[openPanel filename]);
	[self setRules:ruleFile];
    }
    
    return self;
}


- selectInput:sender
/* select an input file */
{
    id openPanel=[OpenPanel new];
    const char *file=strrchr(inputFile,'/');
    
    if (file) file++;
    if ([openPanel runModalForDirectory:inputFile file:file]==NX_OKTAG) {
	strcpy(inputFile,[openPanel filename]);
	[self setInput:inputFile];
    }

    return self;
}


- selectOutput:sender
/* select an output file */
{
    id openPanel=[OpenPanel new];
    const char *file=strrchr(outputFile,'/');
    
    if (file) file++;
    if ([openPanel runModalForDirectory:outputFile file:file]==NX_OKTAG) {
	strcpy(outputFile,[openPanel filename]);
	[self setOutput:outputFile];
    }
    
    return self;
}


- debugSession:sender
/* connect to EasyBTX, but do not dial out to videotex system.
 * Register self for the receiver of stopInState: and userInputAtRow:col:
 */
{
    id server;
    
    [asciiWindow orderFront:self];
    [stateWindow makeKeyAndOrderFront:self];
    
    server=[stateMachine connect];	/* connect to EasyBTX server */

    if (server) {
	[server setNotify:self];	/* receiver of userInputAtRow:col: */
	[stateMachine setStopReceiver:self];	/* receiver of stopInState: */
	[self reset:self];
	fprintf(stderr,"connected to server.\n");
    }
    else
	fprintf(stderr,"can't connect to server.\n");

    return self;
}


- startSession:sender
/* connect to EasyBTX and dial out */
{
    const char *msg[8] = { "ok", "IO-error", "no/wrong modem echo",
    "unexpected modem answer", "timeout", "device in use", "can't open device",
    "connection already established" };
    int ret;
    
    [asciiWindow orderOut:self];
    [stateWindow orderOut:self];
    
    [self reset:self];

    ret=[stateMachine openDialog:self];		/* dial out */
    
    if (ret!=0)
	fprintf(stderr,"can't connect to videotex system (%s).\n",msg[-ret]);

    return self;
}


- stopSession:sender
/* hang up */
{
    [stateMachine closeDialog];		/* hang up */

    [self closeStreams];		/* flush output stream to file */

    /* the previous dialog may be saved in a file */
    /* [[stateMachine server] saveCEPT:"client_debug.btx"]; */

    return self;
}


- openBTXWindow:sender
{
    id server=[stateMachine connect];
    [server openWindow];
    return self;
}


- closeBTXWindow:sender
{
    id server=[stateMachine connect];
    [server closeWindow];
    return self;
}


- executeMatchingRule:sender
/* executes a matching rule by generating a userInputAtRow:col: message */
{
    int row, col;
    id server;

    server=[stateMachine server];
    if (server) {
	[server getCursorRow:&row col:&col];
	[self userInputAtRow:row col:col];
    }
    return self;
}


- stateChanged:sender
{
    [stateMachine setStateString:[stateField stringValue]];
    return self;
}


/************** method called by stateMachine (stopProtocol) ************/


- stopInState:(const char*)state
/* This methos is called, when a stop command of a rule is executed. state
 * is the <next_state> field of this rule.
 * It will be called with state="timeout", when the userInputAtRow:col:
 * message is not received within a timeout of TIMEOUT seconds.
 * it will be called with state="invalid", when the connection to the EasyBTX
 * server is broken.
 * In most cases this method simply closes the videotex dialog by sending the
 * closeDialog message.
 */
{
    fprintf(stderr,"controller received stopInState:%s\n",state);

    /* [stateMachine closeDialog]; */

    /* Normally the closeDialog: method should be called here.
     * We still want to receive the userInputAtRow:col: and stopInState:
     * messages for debugging of rule files. Therfore we finish the session
     * by calling the closeBTX: method instead of the closeDialog: method.
     */
    [[stateMachine server] closeBTX];		/* hang up */

    [self closeStreams];			/* flush output stream */

    return self;
}


/************ method called by EasyBTXServer (btxClientMethods) **********/


- (oneway void) userInputAtRow:(in int)row col:(in int)col
/* This method is called, when a user input at row, col is expected.
 * The method simply displays the ASCII data of the current page in
 * asciiWindow and calls the userInputAtRow:col: method of stateMachine.
 */
{
    char *data, str[80];

    sprintf(str,"userInputAtRow:%d col:%d\n",row,col);
    [asciiWindow setTitle:str];
    
    /* read 24 rows and 40 columns ASCII data from videotex screen */
    [[stateMachine server] readString:&data row:1 col:1 length:24*40];
    
    [asciiText setASCII:data];

    free(data);		/* free memory allocated by Distributed Objects */
    
    /* execute matching rule */
    [stateMachine userInputAtRow:row col:col];
    
    /* cancel registration of timeout: message when debugging */
    [stateMachine perform:@selector(timeout:) with:nil afterDelay:-1
	cancelPrevious:YES];
    
    /* display <next_state> */
    [stateField setStringValue:[stateMachine stateString]];
}


@end
