/*
 * MovieShowView - version 1.1
 *
 * hacked from: "SlideShowView" by brighton@phuket.nbn.com, 13 Nov 91
 * hacked by: Paul Burchard <burchard@math.utah.edu>, 2 Jun 92
 * thanks to sam streeper for corrections!
 */

#import <appkit/NXImage.h>        // NXImage
#import <appkit/Panel.h>          // NXRunAlertPanel()
#import <appkit/Text.h>           // NXOrderStrings()
#import <dpsclient/wraps.h>       // PSsetgray()
#import <defaults.h>              // NXDefaults stuff
#import <libc.h>                  // lots of sys stuff
#import <math.h>                  // floor()
#import <string.h>                // strcmp(), index()
#import <c.h>                     // TRUE, FALSE, etc
#import <sys/dir.h>               // opendir(), etc

#import "MovieShowViewPart.h"
#import "Thinker.h"


// States.
#define msh_DARK 0
#define msh_BEGIN 1
#define msh_FRAME 2
#define msh_END 3


static const char *module = "MovieShow";


static int stringToBool(const char *answer)
{
    if (strcasecmp(answer, "yes") == 0) return 1;
    if (strcasecmp(answer, "on") == 0) return 1;

    return atoi(answer); // returns zero if no digits encountered
}


@implementation MovieShowView

+ initialize
{
    static NXDefaultsVector MovieShowDefaults = {
        { "Movie", "~/Library/Images/Weather-920515.anim" },
        { "FrameTime", "5" },
	{ "BeginPause", "12" },
	{ "EndPause", "30" },
	{ "DarkTime", "0" },
	{ "Jump", "YES" },
        { "SlideFrames", "NO" },
        { "SlidePauses", "NO" },
	{ "TimeUnit", "25" },
        { NULL }
    };

    NXRegisterDefaults(module, MovieShowDefaults);

    return self;
}

- getDefaults
{
    const char *app = module;


    // Read from defaults database.
    NXUpdateDefaults();

    theMovie = NXGetDefaultValue(app, "Movie");

    timeunit = (unsigned)atoi(NXGetDefaultValue(app, "TimeUnit"));
    frametime = (unsigned)atoi(NXGetDefaultValue(app, "FrameTime"));
    beginpause = (unsigned)atoi(NXGetDefaultValue(app, "BeginPause"));
    endpause = (unsigned)atoi(NXGetDefaultValue(app, "EndPause"));
    darktime = (unsigned)atoi(NXGetDefaultValue(app, "DarkTime"));

    if(stringToBool(NXGetDefaultValue(app, "SlideFrames"))) slideframes = YES;
    else slideframes = NO;
    if(stringToBool(NXGetDefaultValue(app, "SlidePauses")))
    	slidebegin = slideend = YES;
    else slidebegin = slideend = NO;
    if(stringToBool(NXGetDefaultValue(app, "Jump"))) jump = YES;
    else jump = NO;
    
    
    // Clean up.
    if(timeunit == 0) timeunit = 1;
    if(frametime == 0) frametime = 1;
    if(beginpause == 0) slidebegin = NO;
    if(endpause == 0) slideend = NO;
    
    return self;
}

- initFrame:(NXRect *)frameRect
{
    broken = NO;
    running = NO;

    [super initFrame:frameRect];
    [self getDefaults];

    [self loadMovie];
    if(!broken)
    {
	image = [imageList objectAt:0];
	[image getSize:&imageRect.size];
	[self setImageConstraints];
    }
    
    state = msh_DARK;
    currentFrame = 0;
    wait = 1;
    countdown = 1;

    slideDelta.x = randBetween(0.5, 4.0);
    slideDelta.y = randBetween(0.5, 4.0);

    return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    NXPoint p;

    if (!rects || !rectCount) return self;

    [super drawSelf:rects :rectCount];

    p.x = floor(imageRect.origin.x);
    p.y = floor(imageRect.origin.y);
    
    // Draw current image.
    if(broken) return self;
    if(!running) [self cacheMovie];
    [image composite:NX_SOVER toPoint:&p];

    return self;
}

- oneStep
{
    int prevFrame;
    
    // Wait for designated time period.
    if(![self timePassed:(wait*timeunit)]) return self;
    if(!running) [self cacheMovie];
    if(broken) return self;
    
    // Erase previous image.
    prevFrame = currentFrame;
    PSsetgray(0.0);
    NXRectFill(&imageRect);

    // State machine.
    if(--countdown == 0) 
    {
        // Determine new state and movie frame.
        switch(state)
	{
	case msh_DARK:
	    if(beginpause != 0) { state = msh_BEGIN; currentFrame = 0; }
	    else { state = msh_FRAME; currentFrame = 0; }
	    break;
	case msh_BEGIN:
	    state = msh_FRAME;
	    currentFrame = 0;
	    break;
	case msh_FRAME:
	    if(currentFrame < (numberOfFrames-1))
		{ state = msh_FRAME; currentFrame++; }
	    else if(endpause != 0)
		{ state = msh_END; currentFrame = (numberOfFrames-1); }
	    else if(darktime != 0) { state = msh_DARK; currentFrame = 0; }
	    else if(beginpause != 0) { state = msh_BEGIN;  currentFrame = 0; }
	    else { state = msh_FRAME; currentFrame = 0; }
	    break;
	case msh_END:
	    if(darktime != 0) { state = msh_DARK; currentFrame = 0; }
	    else if(beginpause != 0) { state = msh_BEGIN; currentFrame = 0; }
	    else { state = msh_FRAME; currentFrame = 0; }
	    break;
	default:
	    state = msh_DARK; currentFrame = 0;
	    break;
	}

	// Set waiting period and countdown repeater for new state.
	switch(state)
	{
	case msh_DARK:
	    wait = darktime;
	    countdown = 1;
	    break;
	case msh_BEGIN:
	    wait = (slidebegin ? 1 : beginpause);
	    countdown = (slidebegin ? beginpause : 1);
	    break;
	case msh_FRAME:
	    wait = (slideframes ? 1 : frametime);
	    countdown = (slideframes ? frametime : 1);
	    break;
	case msh_END:
	    wait = (slideend ? 1 : endpause);
	    countdown = (slideend ? endpause : 1);
	    break;
	default:
	    wait = 1;
	    countdown = 1;
	    break;
	}
    }

    // Perform graphics action.
    image = [imageList objectAt:currentFrame];
    [image getSize:&imageRect.size];
    [self setImageConstraints];
    if(jump && currentFrame<prevFrame)
    {
	imageRect.origin.x = floor(randBetween(0, maxCoord.x));
	imageRect.origin.y = floor(randBetween(0, maxCoord.y));
    }
    switch(state)
    {
    case msh_BEGIN:
    	if(slidebegin) [self slideImageRectOrigin];
	[image composite:NX_SOVER toPoint:&imageRect.origin];
	break;
    case msh_FRAME:
    	if(slideframes) [self slideImageRectOrigin];
	[image composite:NX_SOVER toPoint:&imageRect.origin];
	break;
    case msh_END:
    	if(slideend) [self slideImageRectOrigin];
	[image composite:NX_SOVER toPoint:&imageRect.origin];
	break;
    }
    return self;
}

- slideImageRectOrigin
{
    float rand;
    NXPoint p;

    p.x = imageRect.origin.x + slideDelta.x;
    p.y = imageRect.origin.y + slideDelta.y;

    rand = randBetween(0.5, 4.0);

    if (p.x < 0) {
        p.x = 0;
        slideDelta.x = rand;
    } else
    if (p.x > maxCoord.x) {
        p.x = maxCoord.x;
        slideDelta.x = -rand;
    }

    rand = randBetween(0.5, 4.0);

    if (p.y < 0) {
        p.y = 0;
        slideDelta.y = rand;
    } else
    if (p.y > maxCoord.y) {
        p.y = maxCoord.y;
        slideDelta.y = -rand;
    }

    imageRect.origin.x = p.x;
    imageRect.origin.y = p.y;

    return self;
}

- loadMovie
{
    char filename[MAXPATHLEN], animname[MAXPATHLEN], animframe[MAXPATHLEN];
    int i, len, fd;
    id local_image;


    // Reasonable directory name?
    if(broken) return self;
    if(!theMovie || !(theMovie[0]=='/' || theMovie[0]=='~'))
    {
	NXRunAlertPanel(module, "Please use \"dwrite %s Movie mymovie.anim\" to choose a movie, then restart the screensaver.",
	    NULL, NULL, NULL, theMovie, module);
        broken = TRUE;
        return self;
    }

    // Clean up directory name, replacing initial '~' with HOME.
    if(theMovie[0] == '~')
    {
	strcpy(filename, NXHomeDirectory());
	if(theMovie[1] != '/') strcat(filename, "/");
	strcat(filename, theMovie+1);	
    }
    else strcpy(filename, theMovie);
    len = strlen(filename);
    if(len>1 && filename[len-1]=='/') filename[--len] = 0;
    
    // Loop through TIFF files in directory, inserting numerically in list.
    imageList = [[List alloc] init];
    strcpy(animname, rindex(filename,'/')+1);
    *rindex(animname,'.') = 0;
    for (i=0; ; i++)
    {
	sprintf(animframe, "%s/%s.%d.tiff", filename, animname, i+1);
	if (!(local_image = [NXImage findImageNamed:animframe]))
	{
	    if ((fd=open(animframe, O_RDONLY)) < 0) break;
	    close(fd);

	    local_image = [[NXImage alloc] initFromFile:animframe];
	    if (local_image == NULL) break;	// never null, even if no file
	    [local_image setName:animframe];
	}

	[imageList addObject:local_image];
    }
    currentFrame = 0;

    // Check if empty movie.
    if((numberOfFrames = [imageList count]) <= 0)
    {
        NXRunAlertPanel(module, "Could not open any frames in movie folder \"%s\"\n\nPlease use \"dwrite %s Movie mymovie.anim\" to choose another movie, then restart the screensaver.",
	    NULL, NULL, NULL, filename, module);
        broken = TRUE;
        return self;
    }

    return self;
}

- cacheMovie
{
    id this_image;
    int i;

    // Force lazy images to load now so that first movie run is correct speed.
    if(running) return self;
    running = YES;

    numberOfFrames = [imageList count];
    for(i=0; i<numberOfFrames; i++)
    {
    	this_image = [imageList objectAt:i];
	if([this_image lockFocus]) [this_image unlockFocus];
	else fprintf(stderr, "%s: bad image: %s\n",
	    module, [this_image name]);
    }
    return self;
}

// override this method so that Thinker can't set the default image.
- setImage:newImage
{
    return self;
}

// override
- (BOOL) useBufferedWindow
{
    return YES;
}

@end
