//
//  DockCam.m
//  DockCam
//
//  Created by Nicholas Riley on Wed Jun 26 2002.
//  Copyright (c) 2002 Nicholas Riley. All rights reserved.
//

/*
 Done in 1.0b1:
    Add refresh command
    Add pause command
    Add configuration interface
    Add show/hide status window
    Add UI element validation: cancel/load
    Dock menu
    Add drag&drop / copy-paste
    Fix window resizing to do it from the top left
    Fix combo box items not saving
    Fix status palette not saving position
 Done in 1.0b2:
    Fix missing image problem with autosave (Fix/report bug with NaN)
    Fix window resizing to happen only when underlying image changes size
    Hand cursor for dragging (modulo Cocoa bug)
    Fix every other time you open Preferences, the focus changes
 Done in 1.0b3:
    Switch to CURLHandle
 Done in 1.0b4:
    Lazily create (NS|C)URLHandle
    Fix return in combo box not triggering default button
    Misc. bug fixes
 To do:
    Fix memory leak in libcurl/CURLHandle (emailed Dan Wood 2002.06.30)
    Report bug with aspect ratio being useless
    Report bug (and post on weblog) with dragging images: if it's the same image as in an image view, then it draws at the top left of the screen briefly

*/

// debugging: change 1 to 0 to disable features
#define DOCKCAM_DRAW_IMAGE 1
#define DOCKCAM_CREATE_IMAGE 1
#define DOCKCAM_DISPLAY_STATUS 1
#define DOCKCAM_ACCEPT_DATA 1
#define DOCKCAM_LOAD_DATA 1

#import <CURLHandle/CURLHandle.h>
#import <CURLHandle/CURLHandle+extras.h>

#import "DockCam.h"
#import "DockCamStatus.h"
#import "DockCamPrefs.h"
#import "NJRVendingImageView.h"
#import "NJRWindowMagnetism.h"

BOOL successfulLoad;

@implementation DockCam

#pragma mark interface validation

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem;
{
    SEL action = [anItem action];
    BOOL loadInProgress = ([imageHandle status] == NSURLHandleLoadInProgress);
    if (action == @selector(reloadImage:)) return !loadInProgress;
    else if (action == @selector(cancelLoading:)) return loadInProgress;
    else if (action == @selector(pauseTimer:) || action == @selector(resumeTimer:)) return delayTimer != nil;
    return YES;
}

#pragma mark timers

- (void)loadImage:(NSTimer *)timer;
{
    static NSAutoreleasePool *pool = nil;

    NSLog(@"loading...");
    delayTimer = nil;
#if DOCKCAM_ACCEPT_DATA
    [data release]; data = nil;
#endif
    pool = [[NSAutoreleasePool alloc] init];
#if DOCKCAM_ACCEPT_DATA
    data = [[NSMutableData alloc] init];
#endif
    [status setStatus: @"Retrieving imageÉ"];
#if DOCKCAM_LOAD_DATA
    [imageHandle loadInBackground]; // retains imageHandle until load is complete
#else
    // fake completion of loading
    [self URLHandleResourceDidFinishLoading: imageHandle];
#endif
    [pool release]; pool = nil;
}

- (void)scheduleLoadingWithInterval:(NSTimeInterval)interval;
{
    delayTimer = [NSTimer scheduledTimerWithTimeInterval: interval
                                                  target: self selector: @selector(loadImage:) userInfo: nil
                                                 repeats: NO];
}

- (void)scheduleLoading;
{
    [delayTimer invalidate]; delayTimer = nil;
    if ([DockCamPrefs boolForPref: DCImageRefreshEnabled]) {
        if ([imageHandle status] == NSURLHandleLoadInProgress) return;
        [self scheduleLoadingWithInterval: [DockCamPrefs intForPref: DCImageRefreshInterval]];
    }
}

#pragma mark actions

- (IBAction)reloadImage:(id)sender;
{
    if ([imageHandle status] == NSURLHandleLoadInProgress) return;
    [delayTimer invalidate]; delayTimer = nil;
    [self loadImage: nil];
}

- (IBAction)cancelLoading:(id)sender;
{
    if ([imageHandle status] != NSURLHandleLoadInProgress) return;
    [imageHandle cancelLoadInBackground];
}

- (IBAction)pauseTimer:(NSMenuItem *)sender;
{
    timeLeft = [[delayTimer fireDate] timeIntervalSinceDate: [NSDate date]];
    [delayTimer invalidate];
    [status setStatus: [NSString stringWithFormat: @"Timer paused (%.0f %@)", timeLeft,
          round(timeLeft) == 1 ? @"second remains" : @"seconds remain"]];
    [imageWindow setTitle: @"DockCam (paused)"];
    // XXX pauseResumeItem would be 'sender' if dock menu senders weren't all NSApp
    [pauseResumeItem setAction: @selector(resumeTimer:)];
    [pauseResumeItem setTitle: @"Resume Timer"];
}

- (IBAction)resumeTimer:(NSMenuItem *)sender;
{
    // XXX pauseResumeItem would be 'sender' if dock menu senders weren't all NSApp
    [self scheduleLoadingWithInterval: timeLeft];
    timeLeft = 0;
    [status setStatus: @"Timer resumed"];
    [imageWindow setTitle: @"DockCam"];
    [pauseResumeItem setAction: @selector(pauseTimer:)];
    [pauseResumeItem setTitle: @"Pause Timer"];
}

#pragma mark preference changes

- (void)locationChanged:(NSNotification *)notification;
{
    NSString *locationString = [DockCamPrefs stringForPref: DCImageLocation];
    [self cancelLoading: nil];
    [location release]; location = nil;
    if (locationString == nil || [locationString isEqualToString: @""]) {
        [status setStatus: @"No image location specified in Preferences"];
        return;
    }
    locationString = (NSString *)CFURLCreateStringByAddingPercentEscapes(
        NULL, (CFStringRef) locationString, NULL, NULL,
        CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
    location = [[NSURL URLWithString: locationString] retain];
    [locationString release];
    [status setLocation: location];
    [imageHandle release]; imageHandle = nil;
    imageHandle = (CURLHandle *)[location URLHandleUsingCache: NO];
    if (imageHandle != nil) {
        [imageHandle retain];
        [imageHandle addClient: self];
        NSLog(@"handle is %@", imageHandle);
        [self loadImage: nil];
    } else {
        // permanent failure, explicitly do NOT restart timer here
        [status setFailedWithReason: @"Image location (URL) not found, check Preferences"];
    }
}

- (void)intervalChanged:(NSNotification *)notification;
{
    [self scheduleLoading];
}

- (void)showInBackgroundChanged:(NSNotification *)notification;
{
    [imageWindow setHidesOnDeactivate: ![DockCamPrefs boolForPref: DCShowImageInBackground]];
}

#pragma mark startup

- (void)startUp;
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [status setStatus: @"Starting upÉ"];
    previousImageSize = [DockCamPrefs sizeForPref: DCImageSize];
    [notificationCenter addObserver: self selector: @selector(locationChanged:)
                               name: DCPrefChangedNotification
                             object: DCImageLocation];
    [notificationCenter addObserver: self selector: @selector(intervalChanged:)
                               name: DCPrefChangedNotification
                             object: DCImageRefreshEnabled];
    [notificationCenter addObserver: self selector: @selector(intervalChanged:)
                               name: DCPrefChangedNotification
                             object: DCImageRefreshInterval];
    [notificationCenter addObserver: self selector: @selector(showInBackgroundChanged:)
                               name: DCPrefChangedNotification
                             object: DCShowImageInBackground];
    [self locationChanged: nil];
    if (location == nil) [self orderFrontPreferencesPanel: self];
    [self showInBackgroundChanged: nil];
}

- (void)awakeFromNib;
{
    NSArray *autosaveFrame = [[[NSUserDefaults standardUserDefaults] stringForKey: @"NSWindow Frame DockCam"] componentsSeparatedByString: @" "];
    NSRect frame;
#if !DOCKCAM_DISPLAY_STATUS
    status = nil;
#endif
    // XXX workaround for bug in 10.1.5 (and earlier?): loading from autosave corrupts frame
    if (autosaveFrame != nil && [autosaveFrame count] == 9) {
        frame.origin.x = [[autosaveFrame objectAtIndex: 0] floatValue];
        frame.origin.y = [[autosaveFrame objectAtIndex: 1] floatValue];
        frame.size.width = [[autosaveFrame objectAtIndex: 2] floatValue];
        frame.size.height = [[autosaveFrame objectAtIndex: 3] floatValue];
        [imageWindow setFrame: frame display: NO];
    }
    [[NJRWindowMagnetism alloc] initForWindow: imageWindow];
    [self performSelector: @selector(startUp) withObject: nil afterDelay: 0];
}

// XXX workaround for bug in 10.1.5 (and earlier?): loading from autosave corrupts frame
- (void)windowDidMove:(NSNotification *)aNotification
{
    [imageWindow saveFrameUsingName: @"DockCam"];
}

- (void)windowDidResize:(NSNotification *)notification;
{
    [self windowDidMove: notification];
}

#pragma mark preferences

- (IBAction)orderFrontPreferencesPanel:(id)sender;
{
    if (!preferencesController)  {
        preferencesController = [[DockCamPrefs alloc] init];
    }
    [preferencesController showWindow:self];
}

#pragma mark URL loading

- (void)URLHandle:(NSURLHandle *)sender resourceDataDidBecomeAvailable:(NSData *)newBytes;
{
#if DOCKCAM_ACCEPT_DATA
    [data appendData: newBytes];
#endif
}

- (void)URLHandleResourceDidBeginLoading:(NSURLHandle *)sender; {}

- (void)URLHandleResourceDidFinishLoading:(NSURLHandle *)sender;
{
#if DOCKCAM_CREATE_IMAGE
    NSImage *scaledImage = nil;
    NSSize scaledSize;
    unsigned int styleMask = [imageWindow styleMask];
    NSRect contentRect = [NSWindow contentRectForFrameRect: [imageWindow frame] styleMask: styleMask];
    NSRect frame;
    image = [[NSImage alloc] initWithData: data];
#endif
    [data release]; data = nil;
#if DOCKCAM_CREATE_IMAGE
    if (image == nil) {
        [status setFailedWithReason: @"Image format unrecognized"];
        [self scheduleLoading];
        return;
    }
    scaledSize = imageSize = [image size];
#if DOCKCAM_DRAW_IMAGE
    if (abs(imageSize.height - imageSize.width) < 1) scaledImage = [image retain];
    else {
        if (imageSize.height > imageSize.width) scaledSize.width = imageSize.height;
        else scaledSize.height = imageSize.width;
        scaledImage = [[NSImage alloc] initWithSize: scaledSize];
        [scaledImage lockFocus];
        [image compositeToPoint: NSMakePoint((scaledSize.width - imageSize.width) / 2.,
                                             (scaledSize.height - imageSize.height) / 2.)
                      operation: NSCompositeCopy];
        [scaledImage unlockFocus];
    }
    [imageView setImage: nil]; // autoreleases image
    contentRect.origin.y += contentRect.size.height - imageSize.height;
    contentRect.size = imageSize;
    frame = [NSWindow frameRectForContentRect: contentRect styleMask: styleMask];
    if (!NSEqualSizes(imageSize, previousImageSize)) {
        [imageWindow setFrame: frame display: NO];
        previousImageSize = imageSize;
        [DockCamPrefs setSize: imageSize forPref: DCImageSize];
    }
    [imageWindow setMaxSize: frame.size];
    [imageView setImage: image];
    [imageView setToolTip: [location description]];
    [NSApp setApplicationIconImage: scaledImage];
#endif /* DOCKCAM_DRAW_IMAGE */
    [image release];
    [scaledImage release];
    [status setRetrievedWithSize: imageSize];
#endif /* DOCKCAM_CREATE_IMAGE */
    if (!successfulLoad || ![imageWindow isVisible]) {
        [imageWindow makeKeyAndOrderFront: self];
        successfulLoad = YES;
    }
    [self scheduleLoading];
}

- (void)URLHandleResourceDidCancelLoading:(NSURLHandle *)sender;
{
    [status setStatus: @"Image retrieval cancelled"];
    [self scheduleLoading];
}

- (void)URLHandle:(NSURLHandle *)sender resourceDidFailLoadingWithReason:(NSString *)reason;
{
    [status setFailedWithReason: reason];
    [self scheduleLoading];
}

@end

@implementation DockCam (NSWindowDelegate)

- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)proposedFrameSize;
{
    NSRect rect = {NSZeroPoint, proposedFrameSize};
    NSSize contentSize = [NSWindow contentRectForFrameRect: rect styleMask: [sender styleMask]].size;
    contentSize.height = contentSize.width * (imageSize.height / imageSize.width);
    rect.size = contentSize;
    return [NSWindow frameRectForContentRect: rect styleMask: [sender styleMask]].size;
}

- (IBAction)copy:(id)sender;
{
    [imageView putImageOnPasteboard: [NSPasteboard generalPasteboard]];
}

@end

@implementation DockCam (NSApplicationNotifications)

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
{
    [CURLHandle curlHelloSignature:'XxXx' acceptAll:YES];
}

- (void)applicationWillTerminate:(NSNotification *)aNotification;
{
    [CURLHandle curlGoodbye];
}

@end

@implementation DockCam (NSApplicationDelegate)

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender;
{
    return successfulLoad; // window hidden until first succesful load
}

@end