/*
 * SlideShowView - version 1.0
 *
 * brighton@phuket.nbn.com, 13 Nov 91
 */

#import <appkit/NXImage.h>        // NXImage
#import <appkit/Panel.h>          // NXRunAlertPanel
#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 "SlideShowViewPart.h"
#import "PrefController.h"
#import "Thinker.h"

static const char *module = "SlideShow";
static int needUpdate = 0;

int
defaultIntValue(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
}

void
pRect(rect)
NXRect *rect;
{
    printf("[O %.0f,%.0f],[S %.0f,%.0f]\n",
        rect->origin.x,
        rect->origin.y,
        rect->size.width,
        rect->size.height);
}

@implementation SlideShowView

- imageSet
{
    image = [imageList objectAt:currentFrame];
    [image getSize:&imageRect.size];
    [self setImageConstraints]; // sets maxCoord

    return self;
}

- initFrame:(NXRect *)frameRect
{
    broken = FALSE;
    randomize = FALSE;
    fancy = FALSE;
    slide = FALSE;
    debug = 0;

    [super initFrame:frameRect];

    [self resetState];
    [self loadTiffFiles];

    currentFrame = 0;
    [self imageSet];

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

    if (debug) {
        printf("initFrame: %d, imageList 0x%x, image 0x%x, imageRect ",
            currentFrame, imageList, image);
        pRect(&imageRect);
    }

    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);

    if (debug) {
        int i;
        printf("drawSelf: ");
        pRect(&imageRect);
        for (i = 0; i < rectCount; i++) {
            printf("\trects[%d] ", i);
            pRect(&rects[i]);
        }
    }

    if (broken) return self;

    if (fancy) [image2 dissolve:(1.0-fadeDelta) toPoint:&altRect.origin];

    [image dissolve:fadeDelta toPoint:&p];

    return self;
}

- oneStep
{
    if (![self timePassed: steptime]) return self;
    if (broken) return self;

    switch (state) {
        case FV_Move:
        case FV_FancyMove:
	    if (needUpdate) {
		needUpdate = 0;
                [self resetState];
                [self resetFiles];
	    }
            break;
        default:
            break;
    }

    switch (state) {
        case FV_FadeIn:    [self fadeIn];    break;
        case FV_FadeOut:   [self fadeOut];   break;
        case FV_Move:      [self fadeMove];  break;
        case FV_FancyMove: [self fancyMove]; break;
        case FV_FancyFade: [self fancyFade]; break;
    }

    return self;
}


- fadeIn
{
    fadeDelta += defFadeDelta;
    if (fadeDelta >= 1.0) {
        fadeDelta = 1.0;
        state = FV_FadeOut;
        steptime = defWaitTime;
    }

    if (debug > 1) {
        printf("in 0x%x %.2f: ", image, fadeDelta);
        pRect(&imageRect);
    }

    PSsetgray(0.0);
    NXRectFill(&imageRect);
    [image dissolve:fadeDelta toPoint:&imageRect.origin];

    return self;
}

- fadeOut
{
    fadeDelta -= defFadeDelta;
    if (fadeDelta <= 0.0) {
        fadeDelta = 0.0;
        state = FV_Move;
    }

    if (debug > 1) {
        printf("out 0x%x %.2f: ", image, fadeDelta);
        pRect(&imageRect);
    }

    PSsetgray(0.0);
    NXRectFill(&imageRect);
    [image dissolve:fadeDelta toPoint:&imageRect.origin];

    steptime = defStepTime;

    return self;
}

- fadeMove
{
    if (randomize)
        currentFrame = (int)floor(randBetween(0, numberOfFrames));
    else
        if (++currentFrame >= numberOfFrames)
            currentFrame = 0;

    image = [imageList objectAt:currentFrame];
    [image getSize:&imageRect.size];

    [self setImageConstraints];

    if (slide) {
        [self slideImageRectOrigin];
    } else {
        imageRect.origin.x = floor(randBetween(0, maxCoord.x));
        imageRect.origin.y = floor(randBetween(0, maxCoord.y));
    }

    if (debug) {
        printf("\nfadeMove: frame %d, imageList 0x%x, image 0x%x\n",
            currentFrame, fadeDelta, imageList, image);
        printf("\timageRect "); pRect(&imageRect);
        printf("\tmaxCoord [%.0f,%.0f]\n\n", maxCoord.x, maxCoord.y);
    }

    state = FV_FadeIn;
    steptime = defStepTime;

    return self;
}

- fancyMove
{
    if (randomize)
        currentFrame = (int)floor(randBetween(0, numberOfFrames));
    else
        if (++currentFrame >= numberOfFrames)
            currentFrame = 0;

    // set fadeout image to previously faded-in image
    // set fadein image to new image

    image2 = image;
    altRect = imageRect;

    image = [imageList objectAt:currentFrame];
    [image getSize:&imageRect.size];

    [self setImageConstraints];

    if (slide) {
        [self slideImageRectOrigin];
    } else {
        imageRect.origin.x = floor(randBetween(0, maxCoord.x));
        imageRect.origin.y = floor(randBetween(0, maxCoord.y));
    }

    if (debug) {
        printf("fancyMove: frame %d, imageList 0x%x, image 0x%x\n",
            currentFrame, imageList, image);
        printf("\t  altRect "); pRect(&altRect);
        printf("\timageRect "); pRect(&imageRect);
    }

    fadeDelta = 0.0;
    state = FV_FancyFade;
    steptime = defStepTime;

    return self;
}

- fancyFade
{
    float delta_out;

    // fade out one image while fading in another

    fadeDelta += defFadeDelta;
    if (fadeDelta >= 1.0)
    {
        fadeDelta = 1.0;
        state = FV_FancyMove;
        steptime = defWaitTime;
    }

    delta_out = 1.0 - fadeDelta;

    if (debug > 1)
        printf("ffade: %.2f(%.2f)\n", fadeDelta, delta_out);

    PSsetgray(0.0);
    NXRectFill(&altRect);

    [image2 dissolve:delta_out toPoint:&altRect.origin];
    [image dissolve:fadeDelta toPoint:&imageRect.origin];

    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;
}

- resetState
{
    oldfilelist = filelist;
    [self getDefaults];

    steptime = defStepTime;

    if (fancy)
        state = FV_FancyMove;
    else
        state = FV_Move;

    return self;
}

#ifdef ENABLE_RELOAD

// This code almost works,  but if the filelist has changed,  
// we get a SIGBUS when the window changes.

- resetFiles
{
    if (oldfilelist && strcmp(oldfilelist, filelist) != 0) {

        if (debug) printf("resetFiles: new filelist: %s\n", filelist);

        oldfilelist = filelist;

        [self freeTiffFiles];
        [self loadTiffFiles];

        currentFrame = 0;
        [self imageSet];
    }

    return self;
}

- freeTiffFiles
{
    const char *method = "freeTiffFiles";

    if (debug)
        printf("%s: imageList 0x%x, count %d",
            method, imageList, [imageList count]);

    [imageList freeObjects];
    [imageList free];

    return self;
}

#else

- resetFiles
{
    if (oldfilelist && strcmp(oldfilelist, filelist) != 0) {

        if (debug) printf("resetFiles: new filelist: %s\n", filelist);

        oldfilelist = filelist;

        NXRunAlertPanel(module,
            "You must restart BackSpace to enable new Filelist",
            NULL, NULL, NULL);
    }

    return self;
}

#endif

- loadTiffFiles
{
    char buf[MAXPATHLEN], *b, *p;
    int linenumber, fd;
    id local_image;
    NXSize s;
    FILE *fp;

    const char *method = "loadTiffFiles";
    const char *alertmsg = 
    "Could not open %s\n\nUse \"dwrite %s Filelist filename\"";

    static BOOL firstload = TRUE;

    if ((fp = fopen(filelist, "r")) == (FILE *)NULL) {
        if (firstload) {
            NXRunAlertPanel(module, alertmsg,
                NULL, NULL, NULL, filelist, module);
            firstload = FALSE;
        }
        broken = TRUE;
        return self;
    }

    imageList = [[List alloc] init];
    if (debug) printf("%s: imageList 0x%x\n", method, imageList);

    numberOfFrames = 0;
    linenumber = 0;

    while ((b = fgets(buf, MAXPATHLEN-1, fp)) != (char *)NULL) {

        linenumber++;

        // strip newline
        if ((p = index(buf, '\n')) != (char *)NULL) *p = '\0';

        // ignore blank lines and comments
        if (strlen(buf) == 0) continue;
        if (buf[0] == '#') continue;

        // sanity
        if (buf[0] != '/') {
            fprintf(stderr, "%s: %s: line %d: must start with '/'\n",
                module, filelist, linenumber);
            continue;
        }

        // check access
        if ((fd = open(buf, O_RDONLY)) < 0) {
            fprintf(stderr, "%s: ", module);
            perror(buf);
            continue;
        }
        close(fd);

        if (local_image = [NXImage findImageNamed:buf]) {

            if (debug) printf("%s: findImageNamed: %s\n", method, buf);

        } else {

            if (debug) printf("%s: initFromFile: %s\n", method, buf);

            if (!(local_image = [[NXImage alloc] initFromFile:buf])) {
                fprintf(stderr, "%s: initFromFile failed: %s\n",
                    module, buf);
                continue;
            }

            // make sure it's really an image
            [local_image getSize:&s];
            if (s.width == 0 || s.height == 0) {
                fprintf(stderr, "%s: not an image: %s\n", module, buf);
                continue;
            }

            [local_image setName:buf];
        }

        [imageList addObject:local_image];
        numberOfFrames++;
    }

    if (numberOfFrames == 0) {
        if (!broken) {
            NXRunAlertPanel(module,
                "Could not open any frames listed in \"%s\"",
                NULL, NULL, NULL, filelist);
            broken = TRUE;
        }
        return self;
    }

    return self;
}


- getDefaults
{
    const char *app = module;                   // "SlideShow"
    const char *method = "getDefaults";

    NXUpdateDefaults();

    filelist = NXGetDefaultValue(app, "Filelist");

    defStepTime = atoi(NXGetDefaultValue(app, "Steptime"));
    defWaitTime = atoi(NXGetDefaultValue(app, "Waittime"));

    defFadeDelta = atof(NXGetDefaultValue(app, "FadeDelta"));

    if (defaultIntValue(NXGetDefaultValue(app, "Randomize")))
        randomize = TRUE;
    else
        randomize = FALSE;

    if (defaultIntValue(NXGetDefaultValue(app, "Fancy")))
        fancy = TRUE;
    else
        fancy = FALSE;

    if (defaultIntValue(NXGetDefaultValue(app, "Slide")))
        slide = TRUE;
    else
        slide = FALSE;

    if (debug = defaultIntValue(NXGetDefaultValue(app, "Debug")))
        printf("%s: %s: debug %d\n", module, method, debug);

    return self;
}

+ updateSettings
{
    needUpdate++;
    NXUpdateDefaults();
    return self;
}

+ initialize
{
    static NXDefaultsVector SlideShowDefaults = {
        { "Filelist", "/LocalLibrary/Images/SlideShow.list" },
        { "Steptime", "50" },
        { "Waittime", "2000" },
        { "FadeDelta", "0.05" },
        { "Randomize", "NO" },
        { "Fancy", "NO" },
        { "Slide", "NO" },
        { "Debug", "0" },
        { NULL }
    };

    NXRegisterDefaults(module, SlideShowDefaults);

    return self;
}

- doViewPref:sender
{
    const char *nibfile = "SlideShowPref.nib";

    if (!prefPanel)
        prefPanel = [NXApp loadNibSection:nibfile owner:self];

    if (!prefPanel) {
        NXRunAlertPanel(module, "Cannot open %s", NULL, NULL, NULL, nibfile);
        return self;
    }

    [prefPanel makeKeyAndOrderFront:sender];
    [PrefController updatePanel];

    return self;
}

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

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

@end
