// The source for the loginwindow screensaver is due to Eric P. Scott.
// A very small number of modifications were made by myself to add
// blacking behavior and also the TimeToMove and TimeToBlack defaults.
// Works like a charm under NS 3.1, but since it uses an unreleased API,
// there is no guarantee of anything in future versions.  - David Bau

#import <libc.h>
#import <appkit/appkit.h>

@interface ImageView:View
{
    id image;
    id hostname;
    NXRect textFrame;
    NXRect imageFrame;
    List *screenWindows;
    double timeToMove;
    double timeToBlack;
    long startTime;
    int blackened;
}
+ initialize;
- drawSelf:(const NXRect *)rects:(int)rectCount;
- moveImage;
- moveText;
- oneStep;
- initFrame:(const NXRect *)frameRect;
- init;
- stopScreenSaver;
- startScreenSaver;
- (double)interval;

@end

@interface ScreenSaver:Object
{
    id view;
    id saver;
    ImageView *imageSaver;
    id selector;
    float timeout;
    id pie;
    id timeout_field;
    DPSTimedEntry te;
}

+ alloc;
- startScreenSaver;
- stopScreenSaver;
- createScreenSaver;
- step;

@end

float frandom(void)
{
    return (random()&0x7fffffff)/(float)2147483648.0;
}

float randBetween(float low, float high)
{
    if (low>high) {
	register float temp;

    	temp=low;
	low=high;
	high=temp;
    }
    high-=low;
    high*=frandom();
    return low+high;
}

@implementation ImageView

+ initialize
{
    static NXDefaultsVector ScreenSaverDefaults = {
        {"TimeToMove", "120"},
        {"TimeToBlack", "1800"},
        {NULL}
    };
    NXRegisterDefaults("loginwindow",ScreenSaverDefaults);
    return self;
}

- drawSelf:(const NXRect *)rects:(int)rectCount
{
    PSsetgray(NX_BLACK);
    NXRectFill(rects);
    if (!blackened) {
        [image composite:NX_SOVER toPoint:&imageFrame.origin];
        [hostname drawInside:&textFrame inView:self];
    }
    return self;
}

- moveImage
{
    imageFrame.origin.x=(float)floor((double)randBetween(0.0,
	bounds.size.width-imageFrame.size.width));
    imageFrame.origin.y=(float)floor((double)randBetween(0.0,
	bounds.size.height-imageFrame.size.height));
    return self;
}

- moveText
{
    register int i, j, k, l;

    i=0; do {
	textFrame.origin.x=floor((double)randBetween(0.0,
	    bounds.size.width-textFrame.size.width));
	textFrame.origin.y=floor((double)randBetween(0.0,
	    bounds.size.height-textFrame.size.height));
	if (!NXIntersectsRect(&textFrame, &imageFrame)) break;
    } while (++i<50);
    if (i>49) {
	register double w;

	if (imageFrame.size.width/2+imageFrame.origin.x<
	    bounds.size.width/2) {
	    k=imageFrame.origin.x+imageFrame.size.width;
	    w=bounds.size.width;
	}
	else {
	    k=0;
	    w=imageFrame.origin.x;
	}
	l=w;
	if (imageFrame.size.height/2+imageFrame.origin.y<
	    bounds.size.height/2) {
	    i=imageFrame.origin.y+imageFrame.size.height;
	    w=bounds.size.height;
	}
	else {
	    i=0;
	    w=imageFrame.origin.y;
	}
	j=w;
	if (textFrame.size.width<l-k&&textFrame.size.height<j-i) {
	    textFrame.origin.x=k;
	    textFrame.origin.y=i;
	}
    }
    return self;
}

- oneStep
{
    struct timeval tv;
    struct timezone tz;

    if (blackened) return self;

    /* check if TimeToBlack has elapsed */
    if (timeToBlack>0.0) {
        gettimeofday(&tv,&tz);
        if (tv.tv_sec-startTime>=timeToBlack) blackened=YES;
    }

    [self moveImage];
    [self moveText];
    [self display];
    return self;
}

- initFrame:(const NXRect *)frameRect
{
    extern char *lwHostName(char *);
    register char *h;
    id b;
    char hn[256];
    char hn2[512];
    char path[1026];

    [super initFrame:frameRect];

    /* pick up image */
    (void)sprintf(path, "%s/%s",
	[[NXBundle mainBundle] directory], "ScreenSaver.bundle");
    b=[[NXBundle alloc] initForDirectory:path];
    [b getPath:path forResource:"image.tiff" ofType:(const char *)NULL];
    image=[[NXImage alloc] initFromFile:path];
    [image getSize:&imageFrame.size];

    /* pick up host name */
    if (h=lwHostName(hn)) {
	register int i;

	for (i=0;i<strlen(h);i++) {
	    hn2[i*2]=h[i];
	    hn2[i*2+1]=' ';
	}
	hn2[strlen(h)*2]='\0';
	hostname=[[TextFieldCell alloc] initTextCell:hn2];
	[hostname setEditable:NO];
	[hostname setTextGray:0.333];
	[hostname setBackgroundGray:NX_BLACK];
	[hostname setFont:[Font newFont:"Times-Italic" size:36.0]];
	[hostname calcCellSize:&textFrame.size];
    }
    [self oneStep];
    return self;
}

- init
{
    extern void makeWindowBlack(int);
    register id w;
    NXRect fr;
    const NXScreen *scrns;
    int nscrns;

    if (!screenWindows) {
	register int i;

	[NXApp getScreens:&scrns count:&nscrns];
	screenWindows=[[List alloc] initCount:nscrns];
	for (i=0;i<nscrns;i++) {
	    w=[[Window alloc]
		initContent:&scrns[i].screenBounds
		style:NX_PLAINSTYLE
		backing:NX_NONRETAINED
		buttonMask:0
		defer:NO];
	    [screenWindows addObject:w];
	    [w setBackgroundGray:NX_BLACK];
	    [w addToEventMask:NX_MOUSEMOVEDMASK];
	    PSsetwindowlevel(50, [w windowNum]);
	    makeWindowBlack([w windowNum]);
	}
	w=[screenWindows objectAt:0];
	[w getFrame:&fr];
	w=[w contentView];
	[w getBounds:&fr];
	[self initFrame:&fr];
	srandom(time((long *)NULL));
	[w addSubview:self]; 
    }
    return self;
}

- stopScreenSaver
{
    [screenWindows makeObjectsPerform:@selector(orderOut:) with:self];
    return self;
}

- startScreenSaver
{
    struct timeval tv;
    struct timezone tz;

    /* pick up starting time */
    gettimeofday(&tv,&tz);
    startTime=tv.tv_sec;

    /* don't blacken at the start */
    blackened=NO;

    /* pick up defaults */
    sscanf(NXReadDefault("loginwindow","TimeToMove"),"%lf",&timeToMove);
    if (timeToMove>1800.0) timeToMove=1800.0;
    if (timeToMove<1.0) timeToMove=1.0;
    sscanf(NXReadDefault("loginwindow","TimeToBlack"),"%lf",&timeToBlack);
    if (timeToBlack<0.0) timeToBlack= -1.0;

    /* make the screensaver visible */
    [screenWindows makeObjectsPerform:@selector(orderFront:) with:self];
    [screenWindows makeObjectsPerform:@selector(display)];
    return self;
}

- (double)interval
{
    return timeToMove;
}


@end

void step(DPSTimedEntry te, double now, void *userData)
{
    [(ScreenSaver *)userData step];
}

@implementation ScreenSaver

+ alloc
{
    static ScreenSaver *inst;
    
    if (inst) return inst;
    self=[super alloc];
    inst=self;
    imageSaver=[[ImageView alloc] init];
    return self;
}

- startScreenSaver
{
    PShidecursor();
    [self createScreenSaver];
    if ([saver respondsTo:@selector(startScreenSaver)])
	[saver startScreenSaver];
    te=DPSAddTimedEntry([saver interval], step, (void *)self,
	NX_MODALRESPTHRESHOLD);
    return self;
}

- stopScreenSaver
{
    PSshowcursor();
    DPSRemoveTimedEntry(te);
    if ([saver respondsTo:@selector(stopScreenSaver)]) [saver stopScreenSaver];
    return self;
}

- createScreenSaver
{
    saver=(id)imageSaver;
    return self;
}

- step
{
    [saver lockFocus];
    [saver oneStep];
    [saver unlockFocus];
    return self;
}


@end
