//
//  MyApplication.m
//
//  Created by Dr. H. Nikolaus Schaller on Sun Aug 17 2003.
//  Copyright (c) 2003 __MyCompanyName__. All rights reserved.
//

#import "MyApplication.h"
#import "MyDocument.h"

// this is used to track subresources
// we carry an array of these objects to show the Activity window

@interface SubResource : NSObject
{
	WebDataSource *datasrc;		// this is always the main resource!
	NSURLRequest *req;
	NSURLResponse *resp;
	NSError *error;
	long long totalBytesExpected;
	unsigned bytesReceived;
	BOOL done;
}

- (id) initWithDataSource:(WebDataSource *) src andRequest:(NSURLRequest *) r;
- (void) didFailLoadingWithError:(NSError *) error;
- (void) didFinishLoadingFromDataSource;
- (void) didReceiveContentLength:(unsigned) len;
- (void) didReceiveResponse:(NSURLResponse *) resp;
- (NSString *) objectValueForKey:(NSString *) ident;
- (BOOL) isForWebFrame:(WebFrame *) frame;

@end

@implementation SubResource

- (id) initWithDataSource:(WebDataSource *) src andRequest:(NSURLRequest *) r;
{
	if((self=[super init]))
		{
		datasrc=[src retain];
		req=[r retain];
		totalBytesExpected=-1;
		}
	return self;
}

- (void) dealloc;
{
	[resp release];
	[req release];
	[datasrc release];
	[error release];
	[super dealloc];
}

- (NSString *) description;
{
	NSURL *url=[resp URL];
	if(!url)
		url=[req URL];
	return [NSString stringWithFormat:@"%u of %d for %@%@", bytesReceived, (int) totalBytesExpected, url, done?@" done":@""];
}

- (void) didFailLoadingWithError:(NSError *) e;
{
	error=[e retain];
}

- (void) didFinishLoadingFromDataSource;
{
	done=YES;
}

- (void) didReceiveContentLength:(unsigned) len;
{
	bytesReceived += len;
}

- (void) didReceiveResponse:(NSURLResponse *) r;
{
	resp=[r retain];
	totalBytesExpected=[r expectedContentLength];
}

- (NSString *) bytes:(unsigned) val;
{
	if(val < 100)
		return [NSString stringWithFormat:@"%u Byte", val];
	if(val < 100000)
		return [NSString stringWithFormat:@"%.1f kB", val/1024.0];
	return [NSString stringWithFormat:@"%.1f MB", val/(1024.0*1024.0)];
}

- (NSString *) objectValueForKey:(NSString *) ident;
{
#if 0
	NSLog(@"key %@ of %@", ident, self);
#endif
	if([ident isEqualToString:@"address"])
		{
		if(resp)
			return [[resp URL] absoluteString];
		return [[req URL] absoluteString];
		}
	else
		{ // status
		if(error)
			return [error localizedDescription];
		if(![datasrc response])
			return @"no response";
		if(done)
			return [NSString stringWithFormat:@"%@", [self bytes:bytesReceived]];
		if(totalBytesExpected >= 0)
			return [NSString stringWithFormat:@"%@ of %@", [self bytes:bytesReceived], [self bytes:(unsigned) totalBytesExpected]];
		return [NSString stringWithFormat:@"%@ bytes of ?", [self bytes:bytesReceived]];	// still unknown
		}
}

- (BOOL) isForWebFrame:(WebFrame *) frame;
{
	return [datasrc webFrame] == frame;
}

@end

@implementation MyApplication

- (NSString *) plistPath:(NSString *) database	// History.plist, Bookmarks.plist, Downloads.plist
{
	return [[[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:[[NSProcessInfo processInfo] processName]] stringByAppendingPathComponent:database];
}

- (BOOL) applicationShouldOpenUntitledFile:(NSApplication *) sender;
{
	return YES;
}

- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *) app;
{
	return NO;
}

- (void) applicationDidFinishLaunching:(NSNotification *) n
{
#if 1
	NSLog(@"AppController applicationDidFinishLaunching");
#endif
	[[activity window] makeKeyAndOrderFront:nil];
}

- (void) updateHistoryMenu;
{
	NSEnumerator *e=[[[WebHistory optionalSharedHistory] orderedLastVisitedDays] objectEnumerator];
	NSMenu *menu=[separatorAfterHistory menu];
	int end=[menu indexOfItem:separatorAfterHistory];
	int idx=[menu indexOfItem:separatorBeforeHistory]+1;
	NSCalendarDate *day;
	while((day=[e nextObject]))
		{
		NSEnumerator *f=[[[WebHistory optionalSharedHistory] orderedItemsLastVisitedOnDay:day] objectEnumerator];
		WebHistoryItem *item;
		while((item=[f nextObject]))
			{
			NSMenuItem *m;
			NSString *title=[item alternateTitle];
			if(!title)
				title=[item title];	// no alternate title
			if(!title)
				title=[item URLString];	// no title
			if(idx < end)
				{ // replace existing entries
				m=[menu itemAtIndex:idx++];
				[m setTitle:title];
				[m setAction:@selector(loadPageFromHistoryItem:)];
				}
			else
				{ // add a new one
				m=[menu insertItemWithTitle:title action:@selector(loadPageFromHistoryItem:) keyEquivalent:@"" atIndex:idx];
				end=++idx;	// target=nil, i.e. first responder
				}
			[m setRepresentedObject:item];
			}
		// may/should handle group-by-day by creating submenus
		}
	while(idx < end)
		[menu removeItemAtIndex:idx], end--;	// if new list is shorter
}

- (void) saveHistory;
{
	NSError *error;
	[[NSFileManager defaultManager] createDirectoryAtPath:[[self plistPath:@"History.plist"] stringByDeletingLastPathComponent] attributes:nil];	// create directory (ignore errors)
	if(![[WebHistory optionalSharedHistory] saveToURL:[NSURL fileURLWithPath:[self plistPath:@"History.plist"]] error:&error])
		;	// silently ignore errors
}

- (void) updateBookmarksMenuItem:(NSMenuItem *) item forBookmark:(NSDictionary *) bm;
{
	if([bm objectForKey:@"Children"])
			{ // has children
				// make item have a sumbenu
				// populate submenu
			}
}

- (void) updateBookmarksMenu
{
//	NSMenu *menu=[separatorAfterBookmarks menu];
	// go through all root element children
	// start behind separator
	[self updateBookmarksMenuItem:nil forBookmark:bookmarks];
	// make list longer/shorter
}

- (void) saveBookmarks;
{
	[self updateBookmarksMenu];
	[bookmarks writeToFile:[self plistPath:@"Bookmarks.plist"] atomically:YES];
	[bookmarksTable reloadData];
}

- (void) awakeFromNib
{
	NSError *error;
	WebHistory *myHistory = [[[WebHistory alloc] init] autorelease];
	NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
#if 1
	NSLog(@"AppController awakeFromNib");
	//	NSLog(@"%@", [[NSDocumentController sharedDocumentController] defaultType]);
	NSLog(@"AppController bookmarks %@", [self plistPath:@"Bookmarks.plist"]);
	NSLog(@"AppController history %@", [self plistPath:@"History.plist"]);
#endif
	[[WebPreferences standardPreferences] setPrivateBrowsingEnabled:NO];	// disable whatever it was set to on relaunch...
	[WebHistory setOptionalSharedHistory:myHistory];
	if(![myHistory loadFromURL:[NSURL fileURLWithPath:[self plistPath:@"History.plist"]] error:&error])
		; // silently ignore errors
	[self updateHistoryMenu];
	[nc addObserver:self selector:@selector(historyDidRemoveAllItems:)
						 name:WebHistoryAllItemsRemovedNotification object:myHistory];
	[nc addObserver:self selector:@selector(historyDidAddItems:)
						 name:WebHistoryItemsAddedNotification object:myHistory];
	[nc addObserver:self selector:@selector(historyDidRemoveItems:)
						 name:WebHistoryItemsRemovedNotification object:myHistory];
	bookmarks=[[NSDictionary alloc] initWithContentsOfFile:[self plistPath:@"Bookmarks.plist"]];
	[bookmarksTable reloadData];
}

/*
	dealloc
		[activities release];
*/

- (void) historyDidRemoveAllItems:(NSNotification *) n
{
	[self updateHistoryMenu];
	[self saveHistory];
}

- (void) historyDidAddItems:(NSNotification *) n
{
	[self updateHistoryMenu];
	[self saveHistory];
}

- (void) historyDidRemoveItems:(NSNotification *) n
{
	[self updateHistoryMenu];
	[self saveHistory];
}

- (IBAction) loadPageFromHistoryItem:(id) historyItem
{ // no active document!
	id myDocument = [[NSDocumentController sharedDocumentController] openUntitledDocumentOfType:@"" display:YES];
	[myDocument loadPageFromHistoryItem:historyItem];
}

- (IBAction) clearHistory:(id) sender
{
    [[WebHistory optionalSharedHistory] removeAllItems];	// will post a notification
}

- (void) updateActivity
{
#if 1
	NSLog(@"activities=%@", activities);
#endif
	[activity reloadData];
//	[[currentURL controlView] setNeedsDisplay:YES];	// update to show progress
}

// use these to track resource loading!

- (id) webView:(WebView *) sender identifierForInitialRequest:(NSURLRequest *) request fromDataSource:(WebDataSource *) src;
{
	SubResource *s=[[[SubResource alloc] initWithDataSource:src andRequest:request] autorelease];
#if 1
	NSLog(@"%@ identifierForInitialRequest: %@", src, request);
#endif
	if(!activities)
			activities=[[NSMutableArray arrayWithCapacity:10] retain];
	[activities addObject:s];
	[self updateActivity];
	return s;	// new activity tracker
}

// update Load... (%u of %u objects)

- (void) webView:(WebView *) sender resource:(id) ident didFailLoadingWithError:(NSError *) error fromDataSource:(WebDataSource *) src;
{
	[(SubResource *) ident didFailLoadingWithError:error];
#if 1
	NSLog(@"%@ subres %@ didFailLoadingWithError: %@", src, ident, error);
#endif
	[self updateActivity];
}

- (void) webView:(WebView *) sender resource:(id) ident didFinishLoadingFromDataSource:(WebDataSource *) src;
{
	[(SubResource *) ident didFinishLoadingFromDataSource];
#if 1
	NSLog(@"%@ subres %@ didFinishLoadingFromDataSource", src, ident);
#endif
	[self updateActivity];
}

- (void) webView:(WebView *) sender resource:(id) ident didReceiveContentLength:(unsigned) len fromDataSource:(WebDataSource *) src;
{
	[(SubResource *) ident didReceiveContentLength:len];
#if 1
	NSLog(@"%@ subres %@ didReceiveContentLength: %u", src, ident, len);
#endif
	[self updateActivity];
}

- (void) webView:(WebView *) sender resource:(id) ident didReceiveResponse:(NSURLResponse *) resp fromDataSource:(WebDataSource *) src;
{
	[(SubResource *) ident didReceiveResponse:resp];
#if 1
	NSLog(@"%@ subres %@ didReceiveResponse: %@", src, ident, resp);
#endif
	[self updateActivity];
}

// forwarded from MyDocument:

- (void) removeSubresourcesForFrame:(WebFrame *) frame
{
	int i=[activities count];
	while(i-- > 0)
			{ // delete all subresources that refer to the same frame
				SubResource *r=[activities objectAtIndex:i];
				if([r isForWebFrame:frame])
					[activities removeObjectAtIndex:i];
			}
	[self updateActivity];
}

// Activity window

// FIXME - handle close of document windows - the outlineview needs to reload data!!!
//
// FIXME: we should use a completely different approach!
// first level are all WebViews (Documents)
// next level(s) are all WebFrames
// wnd there we list the webFrame provisionalSource or dataSource
// and all its subresources (whether loading or not loading)
// i.e. we simply query the WebKit model

- (id) outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
#if 0
	NSLog(@"child:%u %@", index, item);
#endif
	if(outlineView == activity)
			{
				if(item == nil)
						{
							return [[[[[NSDocumentController sharedDocumentController] documents] objectAtIndex:index] webView] mainFrame];
						}
				if([item isKindOfClass:[WebDataSource class]])
						{
							return [[(WebDataSource *) item subresources] objectAtIndex:index];
						}
				if([item isKindOfClass:[WebFrame class]])
						{
							unsigned children=[[(WebFrame *) item childFrames] count];
							if(index < children)
								return [[(WebFrame *) item childFrames] objectAtIndex:index];
							return [activities objectAtIndex:index-children]; // get i-th data source
						}
			}
	if(outlineView == bookmarksTable)
			{
				if(item == nil)
					item=bookmarks;	// root object
				return [[item objectForKey:@"Children"] objectAtIndex:index];
			}
	return nil;
}

- (BOOL) outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
		return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
}

- (int) outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
	if(outlineView == activity)
			{
				if(item == nil)
						{
							return [[[NSDocumentController sharedDocumentController] documents] count];
						}
				if([item isKindOfClass:[WebDataSource class]])
						{
							return [[(WebDataSource *) item subresources] count];
						}
				if([item isKindOfClass:[WebFrame class]])
						{
							return [[(WebFrame *) item childFrames] count] +									// subframes
							[activities count];	// and our subresources
						}
			}
	if(outlineView == bookmarksTable)
			{
				if(item == nil)
					item=bookmarks;	// root object
				return [[item objectForKey:@"Children"] count];
			}
	return 0;
}

- (id) outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
	NSString *ident=[tableColumn identifier];
#if 0
	NSLog(@"display %@", item);
#endif
	if(outlineView == activity)
			{
				if([item isKindOfClass:[WebDataSource class]])
						{
							WebDataSource *src=(WebDataSource *) item;
							if([ident isEqualToString:@"address"])
									{
										if([src response])
											return [[[src response] URL] absoluteString];
										return [[[src request] URL] absoluteString];
									}
							else
									{ // status
										// error status???
										return [NSString stringWithFormat:@"%@", [[src data] length]];
									}
						}
				if([item isKindOfClass:[WebFrame class]])
						{
							WebDataSource *src=[(WebFrame *) item dataSource];
							if([ident isEqualToString:@"address"])
									{
										NSString *title=[src pageTitle];
										if(!title)
											title=[[[src response] URL] absoluteString];
										if(!title)
											title=[[[src initialRequest] URL] absoluteString];
										if(!title)
											title=@"unknown";
										return title;
									}
							else
									{
										return [NSString stringWithFormat:@"%u Objects", 1+[[src subresources] count]];
									}
						}
				if([item isKindOfClass:[SubResource class]])
					return [(SubResource *) item objectValueForKey:ident];
				return NSStringFromClass([item class]);
			}
	if(outlineView == bookmarksTable)
			{
				if(!ident) ident=@"";
				if([[item objectForKey:@"WebBookmarkType"] isEqualToString:@"WebBookmarkTypeLeaf"])
					return [[item objectForKey:@"URIDictionary"] objectForKey:ident];	// @"" or @"title"
				if([[item objectForKey:@"WebBookmarkType"] isEqualToString:@"WebBookmarkTypeList"])
						{
							if([ident isEqualToString:@"title"])
								return [item objectForKey:@"Title"];
							return @"";
						}
			}
	return @"?";
}

- (void) outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
	NSString *ident=[tableColumn identifier];
	if(outlineView == bookmarksTable)
			{
				if(!ident) ident=@"";
				if([[item objectForKey:@"WebBookmarkType"] isEqualToString:@"WebBookmarkTypeLeaf"])
						{
							[[item objectForKey:@"URIDictionary"] setObject:object forKey:ident];	// @"" or @"title"
							[self saveBookmarks];
						}
				else if([[item objectForKey:@"WebBookmarkType"] isEqualToString:@"WebBookmarkTypeList"])
						{
							if([ident isEqualToString:@"title"])
									{
										[item setObject:object forKey:@"Title"];
										[self saveBookmarks];
									}
						}
			}
}

// double click -> go to link
// single click -> select
// 2 single clicks -> start editing

- (IBAction) singleClick:(id) sender;
{
#if 1
	NSLog(@"single clicked item=%@", [sender itemAtRow:[sender clickedRow]]);
#endif
	if([sender clickedRow] < 0)
		return;	// outside
	if(sender == bookmarksTable)
			{ // click on item
				if([sender clickedRow] == [sender selectedRow])
						{ // was already selected: start editing
							[sender editColumn:[sender clickedColumn] row:[sender clickedRow] withEvent:nil select:NO];
						}
			}
}

- (IBAction) doubleClick:(id) sender;
{
	NSString *url=[[[sender itemAtRow:[sender clickedRow]] objectForKey:@"URIDictionary"] objectForKey:@""];
#if 1
	NSLog(@"double clicked item=%@", [sender itemAtRow:[sender clickedRow]]);
#endif
	if([sender clickedRow] < 0)
		return;	// outside
// get clicked item
// if leaf - extract url
// and open
}

- (IBAction) showBookmarks:(id) sender;
{
	[bookmarksPanel orderFront:sender]; 
}

// addBookmarkToRecord

- (void) addBookmarkChild:(NSMutableDictionary *) child toRecord:(NSMutableDictionary *) list;
{ // add a node
	NSMutableArray *children=[list objectForKey:@"Children"];
	if(!children)
			{
				[list setObject:children=[NSMutableArray arrayWithCapacity:5] forKey:@"Children"];
				[list setObject:@"WebBookmarkTypeList" forKey:@"WebBookmarkType"];
			}
	[children addObject:child];
}

- (void) addList:(NSString *) title toRecord:(NSMutableDictionary *) list;
{ // add a title node
	NSMutableDictionary *record=[NSMutableDictionary dictionaryWithObjectsAndKeys:
															 title, @"Title",
															 @"WebBookmarkTypeList", @"WebBookmarkType",
															 [[NSProcessInfo processInfo] globallyUniqueString], @"WebBookmarkUUID",
															 nil];
	[self addBookmarkChild:record toRecord:list];
}

- (void) addBookmark:(NSString *) title forURL:(NSString *) str toRecord:(NSMutableDictionary *) list;
{ // add a leaf node
	NSMutableDictionary *record=[NSMutableDictionary dictionaryWithObjectsAndKeys:
															 [NSMutableDictionary dictionaryWithObjectsAndKeys:title, @"title", str, @"", nil], @"URIDictionary",
															 str, @"URLString",
															 @"WebBookmarkTypeLeaf", @"WebBookmarkType",
															 [[NSProcessInfo processInfo] globallyUniqueString], @"WebBookmarkUUID",
															 nil];
	[self addBookmarkChild:record toRecord:list];
}

- (void) addBookmark:(NSString *) title forURL:(NSString *) str;
{
	title=[title stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
	if([title length] == 0)
		return;	// no title
	if(!bookmarks)
			{ // create root object
				bookmarks=[[NSMutableDictionary alloc] initWithCapacity:10];
				[bookmarks setObject:[NSNumber numberWithInt:1] forKey:@"WebBookmarkFileVersion"];
				[bookmarks setObject:@"Root" forKey:@"WebBookmarkUUID"];
				[bookmarks setObject:@"" forKey:@"Title"];
			}
	[self addBookmark:title forURL:str toRecord:bookmarks];
	[bookmarksPanel orderFront:nil];
	[self saveBookmarks];
}

// could we implement this through KVB?

- (IBAction) showPreferences:(id) sender;
{
	WebPreferences *pref=[WebPreferences standardPreferences];	// all windows share this
	NSString *home=[[NSUserDefaults standardUserDefaults] objectForKey:@"HomePage"];
#if 0
	NSLog(@"prefs = %@: %@", [pref identifier], pref);
	pref=[[WebPreferences alloc] initWithIdentifier:@"test"];
	[pref setAutosaves:YES];
	[pref setJavaScriptEnabled:YES];	// should autosave
	NSLog(@"prefs = %@: %@", [pref identifier], pref);
	NSLog(@"prefs fantasyFontFamily: %@", [pref fantasyFontFamily]);
	pref=[WebPreferences standardPreferences]
#endif
	if(!home) home=@"about:blank";
	[homePref setStringValue:home];
	[loadImagesPref setState:[pref loadsImagesAutomatically]?NSOnState:NSOffState];
	[enableJavaScriptPref setState:[pref isJavaScriptEnabled]?NSOnState:NSOffState];
	[popupBlockerPref setState:![pref javaScriptCanOpenWindowsAutomatically]?NSOnState:NSOffState];
	[privateBrowsingPref setState:[pref privateBrowsingEnabled]?NSOnState:NSOffState];
	[enableCSSPref setState:[pref authorAndUserStylesEnabled]?NSOnState:NSOffState];
	[prefsWindow makeKeyAndOrderFront:sender];
}

// FIXME: this is not called for FormCells if the window is closed!

- (IBAction) prefChanged:(id) sender;
{
	WebPreferences *pref=[WebPreferences standardPreferences];	// all windows share this
	if(sender == [homePref controlView])
			{ // text field
				[[NSUserDefaults standardUserDefaults] setObject:[sender stringValue] forKey:@"HomePage"];
			}
	// this is a little inefficient since it writes unchanged values - we should connect the outlets of the individual checkboxes and change only single attributes
	else if(sender == [loadImagesPref controlView])
			{ // checkbox
				[pref setLoadsImagesAutomatically:[loadImagesPref state] == NSOnState];
				[pref setJavaScriptEnabled:[enableJavaScriptPref state] == NSOnState];
				[pref setJavaScriptCanOpenWindowsAutomatically:[popupBlockerPref state] != NSOnState];
				[pref setPrivateBrowsingEnabled:[privateBrowsingPref state] == NSOnState];
				[pref setAuthorAndUserStylesEnabled:[enableCSSPref state] == NSOnState];
			}
}

- (IBAction) openLocation:(id) sender;
{
	// allow to type an URL and open a new document with this URL
}

@end
