//
//  SUUpdateAlert.m
//  Sparkle
//
//  Created by Andy Matuschak on 3/12/06.
//  Copyright 2006 Andy Matuschak. All rights reserved.
//

// -----------------------------------------------------------------------------
//	Headers:
// -----------------------------------------------------------------------------

#import "SUUpdateAlert.h"

#import "SUHost.h"
#import <WebKit/WebKit.h>

#import "SUConstants.h"


@interface WebView (SUTenFiveProperty)

-(void)	setDrawsBackground: (BOOL)state;

@end


@implementation SUUpdateAlert

- (id)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost
{
	self = [super initWithHost:host windowNibName:@"SUUpdateAlert"];
	if (self)
	{
		host = [aHost retain];
		updateItem = [item retain];
		[self setShouldCascadeWindows:NO];
		
		// Alex: This dummy line makes sure that the binary is linked against WebKit.
		// The SUUpdateAlert.xib file contains a WebView and if we don't link against WebKit,
		// we will get a runtime crash when decoding the NIB. It is better to get a link error.
		[WebView MIMETypesShownAsHTML];
	}
	return self;
}

- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [host bundlePath]]; }

- (void)dealloc
{
	[updateItem release];
	[host release];
	[super dealloc];
}

- (void)setVersionDisplayer: (id<SUVersionDisplay>)disp
{
	versionDisplayer = disp;
}

- (void)endWithSelection:(SUUpdateAlertChoice)choice
{
	[releaseNotesView stopLoading:self];
	[releaseNotesView setFrameLoadDelegate:nil];
	[releaseNotesView setPolicyDelegate:nil];
	[releaseNotesView removeFromSuperview]; // Otherwise it gets sent Esc presses (why?!) and gets very confused.
	[self close];
	if ([delegate respondsToSelector:@selector(updateAlert:finishedWithChoice:)])
		[delegate updateAlert:self finishedWithChoice:choice];
}

- (IBAction)installUpdate: (id)sender
{
	[self endWithSelection:SUInstallUpdateChoice];
}

- (IBAction)openInfoURL: (id)sender
{
	[self endWithSelection:SUOpenInfoURLChoice];
}

- (IBAction)skipThisVersion: (id)sender
{
	[self endWithSelection:SUSkipThisVersionChoice];
}

- (IBAction)remindMeLater: (id)sender
{
	[self endWithSelection:SURemindMeLaterChoice];
}

- (void)displayReleaseNotes
{
	// Set the default font	
	[releaseNotesView setPreferencesIdentifier:[SPARKLE_BUNDLE bundleIdentifier]];
	[[releaseNotesView preferences] setStandardFontFamily:[[NSFont systemFontOfSize:8] familyName]];
	[[releaseNotesView preferences] setDefaultFontSize:(int)[NSFont systemFontSizeForControlSize:NSSmallControlSize]];
	[releaseNotesView setFrameLoadDelegate:self];
	[releaseNotesView setPolicyDelegate:self];
	
	// Stick a nice big spinner in the middle of the web view until the page is loaded.
	NSRect frame = [[releaseNotesView superview] frame];
	releaseNotesSpinner = [[[NSProgressIndicator alloc] initWithFrame:NSMakeRect(NSMidX(frame)-16, NSMidY(frame)-16, 32, 32)] autorelease];
	[releaseNotesSpinner setStyle:NSProgressIndicatorSpinningStyle];
	[releaseNotesSpinner startAnimation:self];
	webViewFinishedLoading = NO;
	[[releaseNotesView superview] addSubview:releaseNotesSpinner];
	
	// If there's a release notes URL, load it; otherwise, just stick the contents of the description into the web view.
	if ([updateItem releaseNotesURL])
	{
		if ([[updateItem releaseNotesURL] isFileURL])
		{
			[[releaseNotesView mainFrame] loadHTMLString:@"Release notes with file:// URLs are not supported for security reasons&mdash;Javascript would be able to read files on your file system." baseURL:nil];
		}
		else
		{
			[[releaseNotesView mainFrame] loadRequest:[NSURLRequest requestWithURL:[updateItem releaseNotesURL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30]];
		}
	}
	else
	{
		[[releaseNotesView mainFrame] loadHTMLString:[updateItem itemDescription] baseURL:nil];
	}	
}

- (BOOL)showsReleaseNotes
{
	NSNumber *shouldShowReleaseNotes = [host objectForInfoDictionaryKey:SUShowReleaseNotesKey];
	if (shouldShowReleaseNotes == nil)
	{
		// UK 2007-09-18: Don't show release notes if RSS item contains no description and no release notes URL:
		return( ([updateItem itemDescription] != nil
			&& [[[updateItem itemDescription] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0)
			|| [updateItem releaseNotesURL] != nil );
	}
	else
		return [shouldShowReleaseNotes boolValue];
}

- (BOOL)allowsAutomaticUpdates
{
	BOOL		allowAutoUpdates = YES;	// Defaults to YES.
	if( [host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey] )
		allowAutoUpdates = [host boolForInfoDictionaryKey: SUAllowsAutomaticUpdatesKey];
	
	// UK 2007-08-31: Give delegate a chance to modify this choice:
	if( delegate && [delegate respondsToSelector: @selector(updateAlert:shouldAllowAutoUpdate:)] )
		[delegate updateAlert: self shouldAllowAutoUpdate: &allowAutoUpdates];
	
	return allowAutoUpdates;
}

- (void)awakeFromNib
{	
	NSString*	sizeStr = [host objectForInfoDictionaryKey:SUFixedHTMLDisplaySizeKey];

	if( [host isBackgroundApplication] )
		[[self window] setLevel:NSFloatingWindowLevel];	// This means the window will float over all other apps, if our app is switched out ?! UK 2007-09-04
	[[self window] setFrameAutosaveName: sizeStr ? @"" : @"SUUpdateAlertFrame"];
		
	// We're gonna do some frame magic to match the window's size to the description field and the presence of the release notes view.
	NSRect	frame = [[self window] frame];
	BOOL	showReleaseNotes = [self showsReleaseNotes];	// UK 2007-09-18
	if (!showReleaseNotes)	// UK 2007-09-18
	{
		// Resize the window to be appropriate for not having a huge release notes view.
		frame.size.height -= [releaseNotesView frame].size.height + 40; // Extra 40 is for the release notes label and margin.
        
        if ([self allowsAutomaticUpdates])
            frame.size.height += 10; // Make room for the check box.
		
		// Hiding the resize handles is not enough on 10.5, you can still click
		//	where they would be, so we set the min/max sizes to be equal to
		//	inhibit resizing completely:
		[[self window] setShowsResizeIndicator: NO];
		[[self window] setMinSize: frame.size];
		[[self window] setMaxSize: frame.size];
	}
    else if (![self allowsAutomaticUpdates])
	{
		NSRect boxFrame = [[[releaseNotesView superview] superview] frame];
		boxFrame.origin.y -= 20;
		boxFrame.size.height += 20;
		[[[releaseNotesView superview] superview] setFrame:boxFrame];
	}
		
	if( [updateItem fileURL] == nil )	// UK 2007-08-31 (whole if clause)
	{
		[installButton setTitle: SULocalizedString( @"Learn More...", @"Alternate title for 'Install Update' button when there's no download in RSS feed." )];
		[installButton setAction: @selector(openInfoURL:)];
	}
	
	// Make sure button widths are OK:
	#define DISTANCE_BETWEEN_BUTTONS		3
	#define DISTANCE_BETWEEN_BUTTON_GROUPS	12
	
	CGFloat				minimumWindowWidth = [[self window] frame].size.width -NSMaxX([installButton frame]) +NSMinX([skipButton frame]);	// Distance between contents and left/right edge.
	NSDictionary*		attrs = [NSDictionary dictionaryWithObjectsAndKeys: [installButton font], NSFontAttributeName, nil];
	NSSize				titleSize = [[installButton title] sizeWithAttributes: attrs];
	titleSize.width += (16 + 8) * 2;	// 16 px for the end caps plus 8 px padding at each end or it'll look as ugly as calling -sizeToFit.
	NSRect				installBtnBox = [installButton frame];
	installBtnBox.origin.x += installBtnBox.size.width -titleSize.width;
	installBtnBox.size.width = titleSize.width;
	[installButton setFrame: installBtnBox];
	minimumWindowWidth += titleSize.width;
	
	titleSize = [[laterButton title] sizeWithAttributes: attrs];
	titleSize.width += (16 + 8) * 2;	// 16 px for the end caps plus 8 px padding at each end or it'll look as ugly as calling -sizeToFit.
	NSRect				laterBtnBox = [installButton frame];
	laterBtnBox.origin.x = installBtnBox.origin.x -DISTANCE_BETWEEN_BUTTONS -titleSize.width;
	laterBtnBox.size.width = titleSize.width;
	[laterButton setFrame: laterBtnBox];
	minimumWindowWidth += DISTANCE_BETWEEN_BUTTONS +titleSize.width;
	
	titleSize = [[skipButton title] sizeWithAttributes: attrs];
	titleSize.width += (16 + 8) * 2;	// 16 px for the end caps plus 8 px padding at each end or it'll look as ugly as calling -sizeToFit.
	NSRect				skipBtnBox = [skipButton frame];
	skipBtnBox.size.width = titleSize.width;
	[skipButton setFrame: skipBtnBox];
	minimumWindowWidth += DISTANCE_BETWEEN_BUTTON_GROUPS +titleSize.width;
	
	if( showReleaseNotes )	// UK 2007-09-18 (whole block)
	{
		if( sizeStr )
		{
			NSSize		desiredSize = NSSizeFromString( sizeStr );
			NSSize		sizeDiff = NSZeroSize;
			// NSBox*		boxView = (NSBox*)[[releaseNotesView superview] superview];
			
			//[boxView setBorderType: NSNoBorder];
			[releaseNotesView setDrawsBackground: NO];
			
			sizeDiff.width = desiredSize.width -[releaseNotesView frame].size.width;
			sizeDiff.height = desiredSize.height -[releaseNotesView frame].size.height;
			frame.size.width += sizeDiff.width;
			frame.size.height += sizeDiff.height;
			
			// No resizing:
			[[self window] setShowsResizeIndicator:NO];
			[[self window] setMinSize:frame.size];
			[[self window] setMaxSize:frame.size];
		}
	}
	
	if( frame.size.width < minimumWindowWidth )
		frame.size.width = minimumWindowWidth;

	[[self window] setFrame: frame display: NO];
	[[self window] center];
	
	if (showReleaseNotes)	// UK 2007-09-18
	{
		[self displayReleaseNotes];
	}
	
	[[[releaseNotesView superview] superview] setHidden: !showReleaseNotes];	// UK 2007-09-18

}

-(BOOL)showsReleaseNotesText
{
	return( [host objectForInfoDictionaryKey:SUFixedHTMLDisplaySizeKey] == nil );
}


- (BOOL)windowShouldClose:note
{
	[self endWithSelection:SURemindMeLaterChoice];
	return YES;
}

- (NSImage *)applicationIcon
{
	return [host icon];
}

- (NSString *)titleText
{
	return [NSString stringWithFormat:SULocalizedString(@"A new version of %@ is available!", nil), [host name]];
}

- (NSString *)descriptionText
{
	NSString *updateItemVersion = [updateItem displayVersionString];
    NSString *hostVersion = [host displayVersion];
	// Display more info if the version strings are the same; useful for betas.
    if( !versionDisplayer && [updateItemVersion isEqualToString:hostVersion] )
	{
        updateItemVersion = [updateItemVersion stringByAppendingFormat:@" (%@)", [updateItem versionString]];
        hostVersion = [hostVersion stringByAppendingFormat:@" (%@)", [host version]];
    }
	else
		[versionDisplayer formatVersion: &updateItemVersion andVersion: &hostVersion];
    return [NSString stringWithFormat:SULocalizedString(@"%@ %@ is now available--you have %@. Would you like to download it now?", nil), [host name], updateItemVersion, hostVersion];
}

- (void)webView:(WebView *)sender didFinishLoadForFrame:frame
{
    if ([frame parentFrame] == nil) {
        webViewFinishedLoading = YES;
		[releaseNotesSpinner setHidden:YES];
		[sender display]; // necessary to prevent weird scroll bar artifacting
    }
}

- (void)webView:sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:frame decisionListener:listener
{
    if (webViewFinishedLoading) {
        [[NSWorkspace sharedWorkspace] openURL:[request URL]];
		
        [listener ignore];
    }    
    else {
        [listener use];
    }
}

// Clean up the contextual menu.
- (NSArray *)webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element defaultMenuItems:(NSArray *)defaultMenuItems
{
	NSMutableArray *webViewMenuItems = [[defaultMenuItems mutableCopy] autorelease];
	
	if (webViewMenuItems)
	{
		NSEnumerator *itemEnumerator = [defaultMenuItems objectEnumerator];
		NSMenuItem *menuItem = nil;
		while ((menuItem = [itemEnumerator nextObject]))
		{
			NSInteger tag = [menuItem tag];
			
			switch (tag)
			{
				case WebMenuItemTagOpenLinkInNewWindow:
				case WebMenuItemTagDownloadLinkToDisk:
				case WebMenuItemTagOpenImageInNewWindow:
				case WebMenuItemTagDownloadImageToDisk:
				case WebMenuItemTagOpenFrameInNewWindow:
				case WebMenuItemTagGoBack:
				case WebMenuItemTagGoForward:
				case WebMenuItemTagStop:
				case WebMenuItemTagReload:		
					[webViewMenuItems removeObjectIdenticalTo: menuItem];
			}
		}
	}
	
	return webViewMenuItems;
}

- (void)setDelegate:del
{
	delegate = del;
}

@end
