/*
copyright 2002 Alexander Malmberg <alexander@malmberg.org>
*/

#include <math.h>

#include <Foundation/NSObject.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSUserDefaults.h>
#include <Foundation/NSInvocation.h>
#include <AppKit/NSWindow.h>
#include <AppKit/NSScrollView.h>
#include <AppKit/NSTableView.h>
#include <AppKit/NSTableColumn.h>
#include <AppKit/NSSplitView.h>
#include <AppKit/NSTextView.h>
#include <AppKit/NSTextContainer.h>
#include <AppKit/NSClipView.h>
#include <AppKit/NSCell.h>
#include <AppKit/NSFont.h>

#include <Pantomime/MimeUtility.h>

#include "MsgDB.h"

#include "FolderWindowController.h"

#include "FolderThreader.h"

#include "KeyWindow.h"
#include "MessageViewController.h"
#include "main.h"

#include "Pref_ReadAhead.h"


#define SORT_THREAD          0
#define SORT_R_THREAD        1
#define SORT_SUBJECT         2
#define SORT_FROM            3
#define SORT_DATE            4
#define SORT_ORDER           5
#define SORT_R_SUBJECT       6
#define SORT_R_FROM          7
#define SORT_R_DATE          8
#define SORT_R_ORDER         9
#define SORT_MAX             9

static int sort_subject(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	NSString *s1,*s2;
	s1=[ft subjectForMessage: [n1 unsignedIntValue]];
	s2=[ft subjectForMessage: [n2 unsignedIntValue]];
	return [s1 localizedCaseInsensitiveCompare: s2];
}

static int sort_from(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	NSString *s1,*s2;
	s1=[ft fromForMessage: [n1 unsignedIntValue]];
	s2=[ft fromForMessage: [n2 unsignedIntValue]];
	return [s1 localizedCaseInsensitiveCompare: s2];
}

static int sort_date(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	NSDate *d1,*d2;
	d1=[ft dateForMessage: [n1 unsignedIntValue]];
	d2=[ft dateForMessage: [n2 unsignedIntValue]];

	return [d1 compare: d2];
}


static int sort_r_subject(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	return -sort_subject(n1,n2,ft);
}

static int sort_r_from(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	return -sort_from(n1,n2,ft);
}

static int sort_r_date(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	return -sort_date(n1,n2,ft);
}


static int sort_order(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	if (ft->msgs[[n1 unsignedIntValue]].mid<ft->msgs[[n2 unsignedIntValue]].mid)
		return NSOrderedAscending;
	else
		return NSOrderedDescending;
}

static int sort_r_order(NSNumber *n1,NSNumber *n2,FolderThreader *ft)
{
	if (ft->msgs[[n1 unsignedIntValue]].mid>ft->msgs[[n2 unsignedIntValue]].mid)
		return NSOrderedAscending;
	else
		return NSOrderedDescending;
}


static int (*(sort_funcs[SORT_MAX+1]))(NSNumber *,NSNumber *,FolderThreader *ft)=
{NULL,NULL,
sort_subject,sort_from,sort_date,sort_order,
sort_r_subject,sort_r_from,sort_r_date,sort_r_order};


@interface IndentCell : NSCell
{
	int level;
}
-(void) setLevel: (int)l;
@end

@implementation IndentCell
-(void) setLevel: (int)l
{
	level=l;
}

-(void) drawWithFrame: (NSRect)f  inView: (NSView *)v
{
	if (level>8)
	{
		NSString *s=[[NSString alloc] initWithFormat: @"%i",level];
		[s drawInRect: f  withAttributes: nil];
		DESTROY(s);
	}
	f.origin.x+=level*16;
	f.size.width-=level*16;
	[super drawWithFrame: f  inView: v];
}
@end


#define RA_NEXT_UNREAD 0
#define RA_NEXT        1
#define RA_PREV        2
#define RA_NEXT_THREAD 3
#define RA_MAX         4
#define RA_NONE        4

/* TODO: read-ahead to a greater depth?
(ie. use the 'weight'; get the two next unread, then three, etc.
*/

@interface FolderWindowController (readahead)
-(void) readAheadForMessage: (int)num;
-(void) cancelCurrentReadAhead;

-(void) readAheadWeight: (int)which;
@end

@implementation FolderWindowController (readahead)

static float ra_priority[RA_MAX];

-(void) _readAheadMid: (msg_id_t)mid : (int)limit : (int)pri;
{
	const char *c;
	int size;
	int status;
	c=[mdb msg_getHeader: "Bytes" : mid];
	if (!c) return;
	size=atoi(c);
	if (!size || size>limit)
	{
		return;
	}

	status=[mdb msg_dstatus: mid];
	if (status==DSTATUS_ERROR) return;

	[mdb msg_wantData: mid  priority: pri];
}

/* TODO: more options */
-(void) readAheadForMessage: (int)num
{
	int limit;
	int i;
	Class p=[Pref_ReadAhead class];

	if (![Pref_ReadAhead readAhead])
		return;

	limit=[Pref_ReadAhead readAheadSizeLimit];

	for (i=0;i<RA_MAX;i++)
		cur_read_ahead[i]=0;

	if (sort_mode==SORT_THREAD)
	{
		int s;

		if ([p readAheadNextUnread])
		{
			/* next unread */
			s=[ft findNextUnreadFrom: num+1];
			if (s!=-1)
				cur_read_ahead[RA_NEXT_UNREAD]=ft->msgs[s].mid;
		}

		if ([p readAheadNextThread])
		{
			/* next thread */
			s=[ft findNextMax: 0 from: num+1];
			if (s!=ft->num_msgs)
			{
				s=[ft findNextUnreadFrom: s];
				if (s!=-1)
					cur_read_ahead[RA_NEXT_THREAD]=ft->msgs[s].mid;
			}
		}

		s=num;
		if ([p readAheadPrevious])
		{
			/* previous message */
			if (s>0)
				cur_read_ahead[RA_PREV]=ft->msgs[s-1].mid;
		}

		if ([p readAheadNext])
		{
			/* next message */
			if (s<ft->num_msgs-1)
				cur_read_ahead[RA_NEXT]=ft->msgs[s+1].mid;
		}
	}
	else
	{
		if ([p readAheadPrevious] && num>0)
		{
			cur_read_ahead[RA_PREV]=
				ft->msgs[[[sorted_msgs objectAtIndex: num-1] unsignedIntValue]].mid;
		}
		if ([p readAheadNext] && num<[sorted_msgs count]-1)
		{
			cur_read_ahead[RA_NEXT]=
				ft->msgs[[[sorted_msgs objectAtIndex: num+1] unsignedIntValue]].mid;
		}
	}

	/* TODO: if eg. next unread and next are the same, the max of their
	priority should be used */
	for (i=0;i<RA_MAX;i++)
	{
		if (!cur_read_ahead[i]) continue;
		[self _readAheadMid: cur_read_ahead[i] : limit : ra_priority[i]-2];
	}
}

-(void) cancelCurrentReadAhead
{
	int i;
	for (i=0;i<RA_MAX;i++)
	{
		if (cur_read_ahead[i])
		{
			[mdb msg_cancelWantData: cur_read_ahead[i]];
			cur_read_ahead[i]=0;
		}
	}
}

-(void) readAheadWeight: (int)which
{ /* TODO: tweak? */
	int i;

	for (i=0;i<RA_MAX;i++)
	{
		if (i==which)
		{
			ra_priority[i]+=0.8;
			if (ra_priority[i]>3)
				ra_priority[i]=3;
		}
		else
		{
			ra_priority[i]-=1.0;
			if (ra_priority[i]<-5)
				ra_priority[i]=-5;
		}
	}
}

@end


@interface FolderWindowController (private) <FolderThreaderTarget>

-(int) indexOf: (msg_id_t) mid;
-(void) selectMid: (msg_id_t) mid;
-(msg_id_t) selectedMid;

-(void) setSortMode: (int)ns;
-(void) fixSort;

@end

@implementation FolderWindowController (private)

-(void) selectMessage: (int)num
{
	if (num<0 || num>=ft->num_msgs)
		return;

	[list selectRow: num byExtendingSelection: NO];
	if (num<5)
		[list scrollRowToVisible: 0];
	else
		[list scrollRowToVisible: num-5];
	if (num+5<ft->num_msgs)
		[list scrollRowToVisible: num+5];
	else
		[list scrollRowToVisible: ft->num_msgs-1];
	[list scrollRowToVisible: num];
}


-(msg_id_t) selectedMid
{
	int s=[list selectedRow];
	if (s==-1)
		return 0;

	if (sort_mode!=SORT_THREAD)
	{
		[self fixSort];
		s=[[sorted_msgs objectAtIndex: s] unsignedIntValue];
	}

	return ft->msgs[s].mid;
}

-(int) indexOf: (msg_id_t)mid
{
	int r;
	int i,c;

	r=[ft indexOf: mid];
	if (r==-1) return -1;
	if (sort_mode==SORT_THREAD) return r;

	[self fixSort];
	c=[sorted_msgs count];
	for (i=0;i<c;i++)
		if ([[sorted_msgs objectAtIndex: i] unsignedIntValue]==r)
			return i;
	return -1;
}

-(void) selectMid: (msg_id_t)mid
{
	[self selectMessage: [self indexOf: mid]];
}


-(void) sort
{
	if (sort_mode==SORT_THREAD)
	{
	}
	else if (sort_mode==SORT_R_THREAD)
	{
		int i,n,p,np;
		NSNumber *idx;

		DESTROY(sorted_msgs);

		n=ft->num_msgs;
		sorted_msgs=[[NSMutableArray alloc] initWithCapacity: n];
		p=n;
		while (p>0)
		{
			np=p-1;
			for (p=np;p>=0;p--)
				if (!ft->msgs[p].level)
					break;
			if (p<0) p=0;

			for (i=p;i<=np;i++)
			{
				idx=[[NSNumber alloc] initWithUnsignedInt: i];
				[sorted_msgs addObject: idx];
				[idx release];
			}
		}
	}
	else
	{
		if (!sorted_msgs)
		{
			int i,n;
			msg_id_t *msgs;
			NSNumber *idx;

			n=[folder numMessages];
			msgs=[folder getMessages];
			sorted_msgs=[[NSMutableArray alloc] initWithCapacity: n];
			for (i=0;i<n;i++)
			{
				/* This will creats _tons_ of NSNumber objects, but that's what
				NSMutableArray wants, so so be it. */
				idx=[[NSNumber alloc] initWithUnsignedInt: [ft indexOf: msgs[i]]];
				[sorted_msgs addObject: idx];
				[idx release];
			}
		}
		[sorted_msgs sortUsingFunction: (int(*)(id,id,void *))sort_funcs[sort_mode] context: ft];
	}

	[list reloadData];
}

-(void) setSortMode: (int)ns
{
	int old_selection;
	if (sort_mode==ns) return;

	old_selection=[self selectedMid];
	sort_mode=ns;

	[self sort];
	
	if (old_selection)
		[self selectMid: old_selection];
}


-(void) fixSort
{
	if (sorted_msgs) return;
	[self sort];
	if (cur_selection)
		[self selectMid: cur_selection];
	cur_selection=0;
}

@end


@implementation FolderWindowController

-(void) updateFolder: (MidNotification *)n
{
	int idx;
	if ([[n name] isEqual: MsgDB_FolderAddMsgNotification] && [n folder]==folder)
	{
		msg_id_t old_selection;

		old_selection=[self selectedMid];

		if (sorted_msgs) /* TODO: doing it incrementally would be better */
		{
			if (sort_mode!=SORT_THREAD)
				cur_selection=old_selection;
			DESTROY(sorted_msgs);
		}

		idx=[ft addMsg: [n mid]];

		[list reloadData];
		if (sort_mode==SORT_THREAD)
		{
			if (old_selection)
				[list selectRow: [ft indexOf: old_selection] byExtendingSelection: NO];
		}
		return;
	}

	if ([[n name] isEqual: MsgDB_MsgMetaChangeNotification] &&
	    !strcmp([n headerName],"RStatus") && (idx=[ft indexOf: [n mid]])!=-1)
	{
		const char *c;
		int nrstatus;
		c=[mdb msg_getMetaHeader: "RStatus" : [n mid]];
		nrstatus=atoi(c);
		if (nrstatus!=ft->msgs[idx].rstatus)
		{
			ft->msgs[idx].rstatus=nrstatus;
			[list reloadData];
		}
		return;
	}

	if ([[n name] isEqual: MsgDB_MsgMetaChangeNotification] &&
	    (idx=[ft indexOf: [n mid]])!=-1)
	{
		[list reloadData];
		/* TODO: only need reload if the message is visible */
		return;
	}
}


-(int) numberOfRowsInTableView: (NSTableView *)tv
{
	if (sort_mode==SORT_THREAD)
		return ft->num_msgs;
	else
	{
		if (sorted_msgs)
			return [sorted_msgs count];

		if (sort_mode==SORT_R_THREAD)
			return ft->num_msgs;
		else
			return [folder numMessages];
	}
}

-(id) tableView: (NSTableView *)tv  objectValueForTableColumn: (NSTableColumn *)tc  row: (int)row
{
	if (sort_mode!=SORT_THREAD)
	{
		if (!sorted_msgs)
			[self fixSort];
		row=[[sorted_msgs objectAtIndex: row] unsignedIntValue];
	}

	if (tc==c_subject)
		return [ft subjectForMessage: row];
	else if (tc==c_from)
		return [ft fromForMessage: row];
	else if (tc==c_date)
	{
		NSDate *d=[ft dateForMessage: row];
		NSTimeInterval ti=[d timeIntervalSinceNow];
		NSString *frmt;
		if (ti>0) /* TODO: make this customizable? */
			return d;
		else if (ti>-18*6*60)
			frmt=@"%H:%M:%S";
		else if (ti>-(6*24+18)*60*60)
			frmt=@"%a %H:%M:%S";
		else
			return d;

		return [d descriptionWithCalendarFormat: frmt  timeZone: nil locale: nil];
	}
	else
		return @"Unknown column";
}

-(void) tableView: (NSTableView *)tv willDisplayCell: (NSCell *)c forTableColumn: (NSTableColumn *)tc row: (int)row
{
	if (sort_mode!=SORT_THREAD)
	{
		if (!sorted_msgs)
			[self fixSort];
		row=[[sorted_msgs objectAtIndex: row] unsignedIntValue];
	}

	if (ft->msgs[row].rstatus)
		[c setFont: [NSFont systemFontOfSize: 0]];
	else
		[c setFont: [NSFont boldSystemFontOfSize: 0]];

	if (tc==c_subject)
	{
		if (sort_mode==SORT_THREAD || sort_mode==SORT_R_THREAD)
			[(IndentCell *)c setLevel: ft->msgs[row].level];
		else
			[(IndentCell *)c setLevel: 0];
	}
}


-(void) folderSortThread
{
	[self setSortMode: SORT_THREAD];
}

-(void) folderSortReverseThread
{
	[self setSortMode: SORT_R_THREAD];
}

-(void) folderSortSubject
{
	[self setSortMode: SORT_SUBJECT];
}

-(void) folderSortReverseSubject
{
	[self setSortMode: SORT_R_SUBJECT];
}

-(void) folderSortFrom
{
	[self setSortMode: SORT_FROM];
}

-(void) folderSortReverseFrom
{
	[self setSortMode: SORT_R_FROM];
}

-(void) folderSortDate
{
	[self setSortMode: SORT_DATE];
}

-(void) folderSortReverseDate
{
	[self setSortMode: SORT_R_DATE];
}

-(void) folderSortOrder
{
	[self setSortMode: SORT_ORDER];
}

-(void) folderSortReverseOrder
{
	[self setSortMode: SORT_R_ORDER];
}


- initWithMsgDB: (MsgDB *)m  folder: (NSString *)fname
{
	NSWindow *win;

	win=[[KeyWindow alloc] initWithContentRect: NSMakeRect(100,100,550,500)
		styleMask: NSClosableWindowMask|NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask
		backing: NSBackingStoreRetained
		defer: YES];
	if (!(self=[super initWithWindow: win])) return nil;

	folder=[[m folders] objectForKey: fname];
	if (!folder)
	{
		RELEASE(self);
		return nil;
	}
	[folder retain];

	ASSIGN(mdb,m);
	folder_name=[fname copy];

	ft=[[FolderThreader alloc] initWithMsgDB: mdb  target: self];

	{
		NSSplitView *spv;
		spv=[[NSSplitView alloc] initWithFrame: NSMakeRect(0,0,550,500)];
		[spv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];

		{
			[win setTitle: folder_name];

			c_subject=[[NSTableColumn alloc] initWithIdentifier: @"Subject"];
			[[c_subject headerCell] setStringValue: _(@"Subject")];
			[c_subject setEditable: NO];
			[c_subject setResizable: YES];
			[c_subject setWidth: 300];

			{
				IndentCell *c=[[IndentCell alloc] init];
				[c_subject setDataCell: c];
				[c release];
			}

			c_from=[[NSTableColumn alloc] initWithIdentifier: @"From"];
			[[c_from headerCell] setStringValue: _(@"From")];
			[c_from setEditable: NO];
			[c_from setResizable: YES];
			[c_from setWidth: 100];

			c_date=[[NSTableColumn alloc] initWithIdentifier: @"Date"];
			[[c_date headerCell] setStringValue: _(@"Date")];
			[c_date setEditable: NO];
			[c_date setResizable: YES];
			[c_date setWidth: 100];

			listsv=[[NSScrollView alloc] initWithFrame: NSMakeRect(0,0,250,250)]; /* TODO */
			[listsv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];
			[listsv setAutoresizesSubviews: YES];
			[listsv setHasVerticalScroller: YES];
			[listsv setHasHorizontalScroller: YES];
			[listsv setBorderType: NSBezelBorder];

			list=[[NSTableView alloc] initWithFrame: [[listsv contentView] frame]];
			[list setAllowsColumnReordering: YES];
			[list setAllowsColumnResizing: YES];
			[list setAllowsMultipleSelection: NO];
			[list setAllowsColumnSelection: NO];
			[list addTableColumn: c_subject];
			[list addTableColumn: c_from];
			[list addTableColumn: c_date];
			[list setDataSource: self];
			[list setDelegate: self];
			[list setDoubleAction: @selector(openMessage:)];

			[list setAutosaveName: [NSString stringWithFormat: @"Folder_%@",fname]];
			[list setAutosaveTableColumns: YES];

			[listsv setDocumentView: list];
			[spv addSubview: listsv];
		}

		{ /* TODO: fix properly */
			NSScrollView *sv;
			NSTextView *tv;

			sv=[[NSScrollView alloc] initWithFrame: NSMakeRect(0,0,250,250)];
			[sv setHasVerticalScroller: YES];
			[sv setHasHorizontalScroller: YES];
			[sv setBorderType: NSBezelBorder];

			tv=[[NSTextView alloc] initWithFrame: [[sv contentView] frame]];
			[tv setHorizontallyResizable: NO];
			[tv setVerticallyResizable: YES];
			[tv setEditable: NO];
			[[tv textContainer] setWidthTracksTextView: YES];
			[[tv textContainer] setHeightTracksTextView: NO];
			[[tv textContainer] setContainerSize: NSMakeSize(1e6,1e6)];
			[tv setAutoresizingMask: NSViewWidthSizable|NSViewHeightSizable];

			[sv setDocumentView: tv];

			[spv addSubview: sv];

			mv=[[MessageViewController alloc] initWithMsgDB: mdb  textView: tv scrollView: sv];

			[sv release];
			[tv release];
		}
		[win setContentView: spv];
		[spv release];

		{
			NSString *frame_name=[NSString stringWithFormat: @"Folder_%@",fname];
			[win setFrameUsingName: frame_name];
			[win setFrameAutosaveName: frame_name];
		}
	}

	[win setDelegate: self];
	[win release];

	[[NSNotificationCenter defaultCenter]
		addObserver: self
		selector: @selector(updateFolder:)
		name: MsgDB_FolderAddMsgNotification
		object: mdb];

	[[NSNotificationCenter defaultCenter]
		addObserver: self
		selector: @selector(updateFolder:)
		name: MsgDB_MsgMetaChangeNotification
		object: mdb];


	{
		int n,i;
		msg_id_t *m;
		n=[folder numMessages];
		m=[folder getMessages];
		for (i=0;i<n;i++)
			[ft addMsg: m[i]];
		[list reloadData];
		sort_mode=SORT_THREAD;
	}
	{
		int ns;
		ns=[[NSUserDefaults standardUserDefaults]
			integerForKey: [NSString stringWithFormat: @"FolderSortMode_%@",folder_name]];
		if (ns<0 || ns>SORT_MAX) ns=SORT_THREAD;
		[self setSortMode: ns];
	}

	return self;
}

-(void) dealloc
{
	if (folder_name)
	{
		[[NSUserDefaults standardUserDefaults]
			setInteger: sort_mode  forKey: [NSString stringWithFormat: @"FolderSortMode_%@",folder_name]];
	}

	[[NSNotificationCenter defaultCenter] removeObserver: self];

	DESTROY(listsv);
	DESTROY(list);
	DESTROY(c_subject);
	DESTROY(c_date);
	DESTROY(c_from);

	DESTROY(mv);

	DESTROY(ft);

	DESTROY(sorted_msgs);

	DESTROY(folder);
	DESTROY(folder_name);
	DESTROY(mdb);

	[super dealloc];
}


-(void) tableViewSelectionDidChange: (NSNotification *)n
{
	[self cancelCurrentReadAhead];
	[mv setMid: [self selectedMid]];
	[self readAheadForMessage: [self indexOf: [self selectedMid]]]; /* TODO */
}


-(void) openMessage: (id)sender
{
	if ([self selectedMid])
		[app_delegate openMessageWindow: [self selectedMid]];
}


-(void) messageToggleRead: (id)sender
{
	int selected=[list selectedRow];
	if (selected==-1) return;

	if (sort_mode!=SORT_THREAD)
	{
		[self fixSort];
		selected=[[sorted_msgs objectAtIndex: selected] unsignedIntValue];
	}

	if (ft->msgs[selected].rstatus==1)
		[mdb msg_setMetaHeader: "RStatus" value: "0" : ft->msgs[selected].mid];
	else
		[mdb msg_setMetaHeader: "RStatus" value: "1" : ft->msgs[selected].mid];
}

-(void) moveToNextUnread: (id)sender
{
	int s=[list selectedRow];
	[self readAheadWeight: RA_NEXT_UNREAD];
	if (sort_mode==SORT_THREAD)
		[ft selectFirstUnreadFrom: s+1]; /* this is valid even if s==-1 */
	else
	{
		int i,c;
		[self fixSort];
		c=[sorted_msgs count];
		for (i=s+1;i<c;i++)
			if (!ft->msgs[ [[sorted_msgs objectAtIndex: i] unsignedIntValue] ].rstatus)
			{
				[self selectMessage: i];
				break;
			}
	}
}

-(void) scrollNextUnread: (id)sender
{
	if ([mv scrollDown])
		[self moveToNextUnread: nil];
}


-(void) skipBranch: (id)sender
{
	if (sort_mode==SORT_THREAD)
	{
		int s=[list selectedRow];
		if (s==-1) return;
		s=[ft findNextMax: ft->msgs[s].level  from: s+1];
		[ft selectFirstUnreadFrom: s];
	}
}

-(void) skipBranchMark: (id)sender
{
	if (sort_mode==SORT_THREAD)
	{
		int s=[list selectedRow],n;
		if (s==-1) return;
		n=[ft findNextMax: ft->msgs[s].level  from: s+1];
		[ft markAsRead: s:n-1];
		[ft selectFirstUnreadFrom: n];
	}
}

-(void) skipThread: (id)sender
{
	[self readAheadWeight: RA_NEXT_THREAD];
	if (sort_mode==SORT_THREAD)
	{
		int s=[list selectedRow];
		if (s==-1) return;
		s=[ft findNextMax: 0  from: s+1];
		[ft selectFirstUnreadFrom: s];
	}
}

-(void) skipThreadMark: (id)sender
{
	[self readAheadWeight: RA_NEXT_THREAD];
	if (sort_mode==SORT_THREAD)
	{
		int s=[list selectedRow],n,p;
		if (s==-1) return;
		n=[ft findNextMax: 0  from: s+1];
		p=[ft findPrevMax: 0  from: s];
		[ft markAsRead: p:n-1];
		[ft selectFirstUnreadFrom: n];
	}
}

-(void) moveToParent: (id)sender
{
	if (sort_mode==SORT_THREAD)
	{
		int s=[list selectedRow];
		if (s==-1) return;
		if (!ft->msgs[s].level) return;
		[self selectMessage: [ft findPrevMax: ft->msgs[s].level-1  from: s-1]];
	}
}


-(void) markAll: (id)sender
{
	int i;
	for (i=0;i<ft->num_msgs;i++)
	{
		if (ft->msgs[i].rstatus)
			continue;
		[mdb msg_setMetaHeader: "RStatus" value: "1" : ft->msgs[i].mid];
	}
}


-(int) keyDown: (NSEvent *)ke  inWindow: (NSWindow *)w
{
	NSString *str=[ke charactersIgnoringModifiers];
	unichar c;

	if ([str length]!=1) return 0;
	c=[str characterAtIndex: 0];

	if (!([ke modifierFlags]&~NSShiftKeyMask) || [ke modifierFlags]==NSFunctionKeyMask)
	{
		int s=[list selectedRow];

		switch (c)
		{
		case NSUpArrowFunctionKey:
		case 'p':
			if (s>0)
			{
				[self readAheadWeight: RA_PREV];
				[self selectMessage: s-1];
			}
			return 1;

		case NSDownArrowFunctionKey:
			if (s<ft->num_msgs-1)
			{
				[self readAheadWeight: RA_NEXT];
				[self selectMessage: s+1];
			}
			return 1;

		case NSHomeFunctionKey:
			if (ft->num_msgs)
				[self selectMessage: 0];
			return 1;

		case NSEndFunctionKey:
			if (ft->num_msgs)
				[self selectMessage: ft->num_msgs-1];
			return 1;

		case NSPageDownFunctionKey:
			if (s==-1)
				[self selectMessage: 0];
			else
			{
				s=s+floor(([[listsv contentView] frame].size.height/[list rowHeight]));
				s--;
				if (s>=ft->num_msgs)
					s=ft->num_msgs-1;
				/* might happen if the window is really small (less than one
				row visible */
				if (s<0) s=0;
				[self selectMessage: s];
			}
			return 1;

		case NSPageUpFunctionKey:
			if (s==-1)
				[self selectMessage: 0];
			else
			{
				s=s-floor(([[listsv contentView] frame].size.height/[list rowHeight]));
				s++;
				if (s<0)
					s=0;
				if (s>=ft->num_msgs)
					s=ft->num_msgs-1;
				[self selectMessage: s];
			}
			return 1;

		case 's':
			[mv lineUp];
			return 1;

		case 'x':
			[mv lineDown];
			return 1;

		case ' ':
			[self scrollNextUnread: nil];
			return 1;

		case 'n':
			[self moveToNextUnread: nil];
			return 1;

		case 'b':
			if (sort_mode!=SORT_THREAD) return 0;
			[self skipBranch: nil];
			return 1;

		case 't':
			if (sort_mode!=SORT_THREAD) return 0;
			[self skipThread: nil];
			return 1;

		case 'B':
			if (sort_mode!=SORT_THREAD) return 0;
			[self skipBranchMark: nil];
			return 1;

		case 'T':
			if (sort_mode!=SORT_THREAD) return 0;
			[self skipThreadMark: nil];
			return 1;

		case 'P':
			if (sort_mode!=SORT_THREAD) return 0;
			[self moveToParent: nil];
			return 1;

		case 'm':
			[self messageToggleRead: nil];
			return 1;

		case 'A':
			[self markAll: nil];
			return 1;
		}
	}
	return 0;
}


-(void) composeNewArticle: (id)sender
{
	[app_delegate composeArticle: [NSDictionary dictionaryWithObject: folder_name forKey: @"Newsgroups"]];
}


/* Same as in PreferencesWindowController. Let the MessageViewController
act as a delegate for messages that it's interested in. */
-(BOOL) respondsToSelector: (SEL)s
{
	if ([super respondsToSelector: s])
		return YES;

	return [mv respondsToSelector: s];
}

-(void) forwardInvocation: (NSInvocation *)i
{
	if ([mv respondsToSelector: [i selector]])
	{
		[i invokeWithTarget: mv];
		return;
	}
	[super forwardInvocation: i];
}

-(NSMethodSignature *) methodSignatureForSelector: (SEL)sel
{
	NSMethodSignature *ms;

	ms=[super methodSignatureForSelector: sel];
	if (ms)
		return ms;

	ms=[mv methodSignatureForSelector: sel];
	return ms;
}

@end

