/*
 *
 * Copyright 2002 Alexander Malmberg <alexander@malmberg.org>
 *           2012 The Free Software Foundation
 *
 * Authors: Alexander Malmberg <alexander@malmberg.org>
 *          Riccardo Mottola <rm@gnu.org>
 *
 */

#import <Foundation/NSObject.h>
#import <Foundation/NSBundle.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSString.h>
#import <AppKit/NSAttributedString.h>
#import <AppKit/NSGraphics.h>
#import <AppKit/NSGraphicsContext.h>
#import <AppKit/NSFileWrapper.h>
#import <AppKit/NSTextView.h>
#import <AppKit/NSScrollView.h>
#import <AppKit/NSClipView.h>
#import <AppKit/NSTextStorage.h>
#import <AppKit/NSFont.h>
#import <AppKit/NSLayoutManager.h>
#import <AppKit/NSSavePanel.h>

#import "MessageViewController.h"

#import "Pref_MessageViewing.h"
#import "main.h"

#import "GUISource.h"

#import <Pantomime/CWMessage.h>
#import <Pantomime/CWMIMEMultipart.h>
#import <Pantomime/CWMIMEUtility.h>
#import <Pantomime/CWInternetAddress.h>
#import <Pantomime/CWUUFile.h>

//#define USE_MIME_STUFF

#ifdef USE_MIME_STUFF
#import <Mime/Mime.h>

static MimeManager *mime_manager;
#endif


static int get_depth(NSString *t,int start,int end)
{
	int i;
	int nd;
	unichar uc;

	for (i=start,nd=0;i<end;i++)
	{
		uc=[t characterAtIndex: i];
		if (uc=='>') nd++;
		else if (uc>32)
			break;
	}
	return nd;
}


/* we intentionally do _not_ retain object */
@interface MessageViewController_Link : NSObject
{
#define LINK_SOURCE  1
#define LINK_MULTIPART_ALTERNATIVE 2
#define LINK_DATA_SHOW 3
#define LINK_DATA_SHOW_TEXT 4
#define LINK_DATA_SAVE 5
#define LINK_DOWNLOAD 6
@public
	int type;
	id object;
	int value;
}
+ linkWithType: (int)t  object: (id)o  value: (int)v;
- initWithType: (int)t  object: (id)o  value: (int)v;
@end

@implementation MessageViewController_Link
+ linkWithType: (int)t  object: (id)o  value: (int)v
{
	return [[[self alloc] initWithType: t object: o value: v] autorelease];
}

- initWithType: (int)t  object: (id)o  value: (int)v
{
	self=[super init];
	type=t;
	object=o;
	value=v;
	return self;
}
@end


@interface MessageViewController (private)
-(void) _renderContent:(id)p  parent:(CWPart *)parent  to:(NSMutableAttributedString *)str;
-(void) _renderPart:(id)p to:(NSMutableAttributedString *)str;

-(void) displayMessage;

-(void) getData;
@end

@implementation MessageViewController (private)


static NSDictionary *colorForDepth(int d)
{
#define MAX_DEPTH 6
static NSDictionary *dicts[MAX_DEPTH];

	if (d)
	{
		d=((d-1)%(MAX_DEPTH-1))+1;
	}
	if (!dicts[d])
	{
		NSColor *c;
		switch (d)
		{
		default:
		case 0: c=[NSColor blackColor];  break;
		case 1: c=[NSColor blueColor];   break;
		case 2: c=[NSColor brownColor];  break;
		case 3: c=[NSColor purpleColor]; break;
		case 4: c=[NSColor orangeColor]; break;
		case 5: c=[NSColor redColor];    break;
		}
		dicts[d]=[[NSDictionary alloc] initWithObjectsAndKeys:
			c,NSForegroundColorAttributeName,nil];
	}
	return dicts[d];
}



static NSDictionary *header_bold_dict,*header_dict,*header_link_dict;


/* helpers for _render* */
static void append(NSMutableAttributedString *str,NSDictionary *attrs,
	NSString *format,...)
{
	va_list args;
	NSAttributedString *as;
	NSString *s;
	va_start(args,format);
	s=[[NSString alloc] initWithFormat:format arguments:args];
	va_end(args);
	as=[[NSAttributedString alloc] initWithString:s attributes:attrs];
	[str appendAttributedString: as];
	DESTROY(as);
	DESTROY(s);
}

static void appendLink(NSMutableAttributedString *str,int type,id object,
	int value,NSString *format, ...)
{
	va_list args;
	NSMutableAttributedString *as;
	NSString *s;

	va_start(args,format);
	s=[[NSString alloc] initWithFormat: format  arguments: args];
	va_end(args);

	as=[[NSMutableAttributedString alloc] initWithString:s attributes:header_link_dict];
	[as addAttribute: NSLinkAttributeName
		value: [MessageViewController_Link linkWithType: type  object: object
			value: value]
		range: NSMakeRange(0,[as length])];

	[str appendAttributedString: as];

	DESTROY(as);
	DESTROY(s);
}


static void appendPlainText(NSMutableAttributedString *str,
	NSDictionary *fd,NSString *text)
{
	int len;

	len=[str length];

	[str replaceCharactersInRange: NSMakeRange(len,0) withString: text];
	[str setAttributes: fd range: NSMakeRange(len,[text length])];

	if ([Pref_MessageViewing colorMessages])
	{
		int i,j,d;
		for (i=j=0;i<[text length];i++)
		{
			if ([text characterAtIndex: i]=='\n')
			{
				if (i>j)
				{
					d=get_depth(text,j,i);
					/*if (d)*/
						[str addAttributes: colorForDepth(d)
							range: NSMakeRange(len+j,i-j)];
				}
				j=i+1;
			}
		}
		if (i>j)
		{
			d=get_depth(text,j,i);
			/*if (d)*/
				[str addAttributes: colorForDepth(d)
					range: NSMakeRange(len+j,i-j)];
		}
	}
	append(str,nil,@"\n");
}


-(void) _render_multipart: (CWMIMEMultipart *)mp  type: (NSString *)ct
	to: (NSMutableAttributedString *)str
{
	CWPart *bp;
	int i;

	if ([ct isEqual: @"multipart/alternative"])
	{
		int disp;

		if (cur_options && (disp=(int)NSMapGet(cur_options,mp)))
			disp--;
		else
			disp=[mp count]-1;

		bp=[mp partAtIndex: disp];
		append(str,header_bold_dict,_(@"Currently shown:"));
		append(str,header_dict,@" %@\n",[bp contentType]);
		append(str,header_bold_dict,_(@"Alternatives:"));
		for (i=0;i<[mp count];i++)
		{
			appendLink(str,LINK_MULTIPART_ALTERNATIVE,mp,i+1,@" %@",
				[[mp partAtIndex: i] contentType]);
		}
		append(str,nil,@"\n");

		[self _renderPart: bp to:str];
	}
	else
	{ /* handle multipart/mixed and everything we don't understand */
		int i;
		if (![ct isEqual: @"multipart/mixed"])
			append(str,header_bold_dict,_(@"Displaying multipart with %i parts: %@\n"),
				[mp count],ct);

		for (i=0;i<[mp count];i++)
		{
			bp=[mp partAtIndex: i];
			append(str,header_bold_dict,_(@"\nPart:"));
			append(str,header_dict,@" %i %@\n",i,[bp contentType]);
			[self _renderPart:bp to:str];
		}
	}
}


-(void) _render_article_text: (NSString *)text
	to: (NSMutableAttributedString *)str
{
	NSDictionary *fd;
	NSFont *f;

	//	NSRange r;

	if (cur_font)
		f=[Pref_MessageViewing font2];
	else
		f=[Pref_MessageViewing font1];
	
	fd=[[NSDictionary alloc]
		initWithObjectsAndKeys: f,NSFontAttributeName,nil];

#if 0 /* TODO: need to update with new Pantomime interface */
	/* TODO: this should probably be optional */
	r=[CWMIMEUtility rangeOfUUEncodedStringFromString: text
		range: NSMakeRange(0,[text length])];

	if (r.location==NSNotFound)
#endif
		appendPlainText(str,fd,text);
#if 0
	else
	{
		NSFileWrapper *fw;

		if (r.location>0)
			appendPlainText(str,fd,[text substringWithRange: NSMakeRange(0,r.location)]);

		/* TODO: this is inefficient */
		fw=[CWMIMEUtility fileWrapperFromUUEncodedString: [text substringWithRange: r]];

		append(str,header_bold_dict,_(@"UUEncoded file:"));
		append(str,header_dict,@" %@    ",[fw preferredFilename]);
		appendLink(str,LINK_DATA_SAVE,text,0,_(@"Save..."));
		append(str,header_dict,@"\n");

		append(str,header_bold_dict,_(@"Size:"));
		append(str,header_dict,@" %i\n",[[fw regularFileContents] length]);

		DESTROY(fw);

		if (NSMaxRange(r)<[text length])
			appendPlainText(str,fd,[text substringWithRange: NSMakeRange(NSMaxRange(r),[text length]-NSMaxRange(r))]);
	}
#endif

	DESTROY(fd);
}


-(void) _renderContent:(id)p  parent:(CWPart *)parent
	to:(NSMutableAttributedString *)str
{
	NSString *ct=[parent contentType];

#ifdef USE_MIME_STUFF
	NSObject<MimeHandler> *mh;
#endif
	NSData *d;
	NSString *s;

	int dbytes;


	/* Always display these if they are available. */
	if ([parent contentDescription])
	{
		append(str,header_bold_dict,_(@"Description:"));
		append(str,header_dict,@" %@\n",[parent contentDescription]);
	}
	if ([parent filename])
	{
		append(str,header_bold_dict,_(@"Filename:"));
		append(str,header_dict,@" %@\n",[parent filename]);
	}


	/* Some content classes/types we handle internally. */
	if ([p isKindOfClass: [CWMIMEMultipart class]])
	{
		[self _render_multipart: (CWMIMEMultipart *)p  type: ct  to: str];
		return;
	}

	if ([p isKindOfClass: [NSString class]] && [ct isEqual: @"text/plain"])
	{
		[self _render_article_text: (NSString *)p  to: str];
		return;
	}

	if ([p isKindOfClass: [CWPart class]])
	{
		[self _renderPart: p to: str];
		return;
	}


	/* We don't know how to handle it, let all the mime stuff try. */

	if ([p isKindOfClass: [NSString class]])
	{
		s=(NSString *)p;
		d=nil;
#ifdef USE_MIME_STUFF
		mh=[mime_manager mimeHandlerWithString: s  type: ct];
#endif
	}
	else if ([p isKindOfClass: [NSData class]])
	{
		s=nil;
		d=(NSData *)p;
#ifdef USE_MIME_STUFF
		mh=[mime_manager mimeHandlerWithData: d  type: ct];
#endif
	}
	else
	{
		append(str,header_bold_dict,
			_(@"Unable to render content of class '%@' (type '%@'). Please report this as a bug.\n"),
			NSStringFromClass([p class]),ct);
		return;
	}

#ifdef USE_MIME_STUFF
	AUTORELEASE(mh);
#endif

	/* Common header and options for all types. */
	append(str,header_bold_dict,_(@"Type:"));
	append(str,header_dict,@" %@\n",ct);

	if (d)
	{
		append(str,header_bold_dict,_(@"Size:"));
		append(str,header_dict,@" %i\n ",[d length]);
	}

	appendLink(str,LINK_DATA_SAVE,parent,0,_(@"Save..."));

	dbytes=0;
	if (d)
	{
		append(str,nil,@"   ");
		if (cur_options && ((int)NSMapGet(cur_options,p))==1)
		{
			appendLink(str,LINK_DATA_SHOW,p,0,_(@"Hide raw data"));
			dbytes=[d length];
			if (dbytes>1024)
				dbytes=1024;
		}
		else
		{
			appendLink(str,LINK_DATA_SHOW,p,0,_(@"Show raw data"));
		}
	}

	append(str,nil,@"   ");

	if (cur_options && ((int)NSMapGet(cur_options,p))==2)
	{
		appendLink(str,LINK_DATA_SHOW_TEXT,p,0,_(@"Hide text"));
	}
	else
	{
		appendLink(str,LINK_DATA_SHOW_TEXT,p,0,_(@"Try to show as text"));
	}

	append(str,nil,@"\n");

	if (cur_options)
	{
		if (dbytes && d)
		{
			int i;
			NSData *d=(NSData *)p;
			const unsigned char *b;

			NSDictionary *fd;
			fd=[NSDictionary dictionaryWithObjectsAndKeys:
				[NSFont userFixedPitchFontOfSize: 0],NSFontAttributeName,nil];

			b=[d bytes];
			for (i=0;i<dbytes;)
			{
				if (!(i&15)) append(str,fd,@"\n");
				if (i+4<=dbytes)
				{
					append(str,fd,@" %02x %02x %02x %02x ",b[0],b[1],b[2],b[3]);
					b+=4;
					i+=4;
					continue;
				}
				append(str,fd,@" %02x",b[0]);
				b++;
				i++;
			}
			append(str,fd,@"\n");
			if (i<[d length])
				append(str,fd,@"...\n");
			else
				append(str,fd,@".\n");
			return;
		}
		else if (((int)NSMapGet(cur_options,p))==2)
		{ /* TODO: do this cleaner? automatically? */
			if (d)
			{ /* TODO? if it's data, pretend it's ASCII and try to handle again? */
			/* TODO: recursive parsing is broken */
				NSString *s=[[NSString alloc] initWithData: d encoding: NSASCIIStringEncoding];
				append(str,nil,@"%@\n",s);
//				[self _renderContent: s  parent: parent  to: str];
				DESTROY(s);
			}
			else
			{
				append(str,nil,@"%@\n",s);
			}
			return;
		}
	}


#ifdef USE_MIME_STUFF
	/* Now try to get a representation we can handle. */

	{
		NSAttributedString *as=[mh representationUsingClass: [NSAttributedString class]];
		if (as)
		{
			[str appendAttributedString: as];
			append(str,nil,@"\n");
			DESTROY(as);
			return;
		}
	}

	{
		NSString *s=[mh representationUsingClass: [NSString class]];
		if (s)
		{
			append(str,nil,@"%@",s);
			append(str,nil,@"\n");
			DESTROY(s);
			return;
		}
	}

	{
		NSImage *i=[mh representationUsingClass: [NSImage class]];
		if (i)
		{
			NSFileWrapper *fw=[[NSFileWrapper alloc] initRegularFileWithContents: nil];

			[fw setIcon: i];
			DESTROY(i);

			[str appendAttributedString:
				[NSAttributedString attributedStringWithAttachment:
					AUTORELEASE([[NSTextAttachment alloc] initWithFileWrapper: fw])]];

			DESTROY(fw);
			return;
		}
	}

	/* There's no representation we can handle; use the icon instead (if
	available). */
	{
		NSImage *i=[mh icon];
		if (i)
		{
			NSFileWrapper *fw=[[NSFileWrapper alloc] initRegularFileWithContents: nil];

			[fw setIcon: i];

			[str appendAttributedString:
				[NSAttributedString attributedStringWithAttachment:
					AUTORELEASE([[NSTextAttachment alloc] initWithFileWrapper: fw])]];

			DESTROY(fw);
			return;
		}
	}
#endif

	/* No useful representation, and no icon. */
	append(str,header_dict,_(@"No icon for part.\n"));
}

-(void) _renderPart:(id)p to:(NSMutableAttributedString *)str
{
	if ([p isKindOfClass: [CWMessage class]])
	{
		CWMessage *m=(CWMessage *)p;
		append(str,header_bold_dict,_(@"Subject:"));
		append(str,header_dict,@" %@\n",[m subject]);
		append(str,header_bold_dict,_(@"From:"));
		append(str,header_dict,@" %@\n",[[m from] stringValue]);
		append(str,header_bold_dict,_(@"Newsgroups:"));
		append(str,header_dict,@" %@\n",[m headerValueForName: @"Newsgroups"]);
		append(str,header_bold_dict,_(@"Date:"));
		append(str,header_dict,@" %@\n",[m receivedDate]);
		append(str,header_bold_dict,_(@"Content-type:"));
		append(str,header_dict,@" %@\n",[m contentType]);
		append(str,nil,@"\n");
		[self _renderContent:[m content] parent: m to:str];
	}
	else if ([p isKindOfClass: [CWPart class]])
	{ /* handles MimeBodyPart and any extensions */
		[self _renderContent:[(CWPart *)p content] parent: (CWPart *)p to:str];
	}
	else
	{
		append(str,header_bold_dict,
			_(@"Unable to render part of class '%@'. Please report this as a bug.\n"),
			NSStringFromClass([p class]));
	}
}


-(void) displayMessage
{
	NSTextStorage *ts;

	if (!cur_message)
		return;

	ts=[tv textStorage];
	[ts beginEditing];

	[ts deleteCharactersInRange: NSMakeRange(0,[ts length])];

	if (show_source)
	{
		NSString *s;
		append(ts,header_bold_dict,_(@"Raw source:\n\n"));
		s=[[NSString alloc] initWithData: [cur_message rawSource] encoding: NSASCIIStringEncoding];
		append(ts,nil,@"%@\n",s);
		DESTROY(s);
	}
	else
	{
		[self _renderPart: cur_message  to: ts];
	}

	[ts endEditing];
}


/* TODO: sometimes the scrollbar isn't updated when the message
changes */
-(void) getData
{
	int length;
	const unsigned char *data;
	int status;

	if (cur_options)
	{
		NSFreeMapTable(cur_options);
		cur_options=NULL;
	}
	DESTROY(cur_message);

	if (!mid)
	{
		[tv setString: @""];
		return;
	}

	status=[mdb msg_getData: &data length: &length : mid];
	if (status==DSTATUS_NODATA)
	{
		const char *c=[mdb msg_getHeader: "Bytes" : mid];
		int limit;

		limit=[Pref_MessageViewing autoDownloadSizeLimit];

		if (c && atoi(c) && atoi(c)<=limit)
		{
			[mdb msg_needData: mid];
			return;
		}
	}

	[tv scrollPoint: NSMakePoint(0,0)];
	[tv setSelectedRange: NSMakeRange(0,0)];

	if (status==DSTATUS_DATA)
	{
		NSData *d;

		d=[[NSData alloc] initWithBytes: data length: length];
		if([mdb msg_getHeader: "ContentType" : mid])
		  {
		    cur_message=[(CWMessage *)[CWMessage alloc] initWithData: d];
		  }
		else
		  {
		    cur_message=[(CWMessage *)[CWMessage alloc] initWithData: d
					      charset:[Pref_MessageViewing defaultEncoding]];
		  }
		if(![cur_message contentType])
		  {
		    [cur_message setContentType:@"text/plain"];
		  }
		else
		  {
		    ASSIGN(
			   cur_message,
			   [(CWMessage *)[CWMessage alloc] initWithData: d
					 charset:[cur_message charset]]
			   );
		  }
		[d release];

		{
			const char *c;
			c=[mdb msg_getMetaHeader: "RStatus" : mid];
			if (!c || strcmp(c,"1"))
				[mdb msg_setMetaHeader: "RStatus" value: "1" : mid];
		}

		/* reset all options */
		cur_font=0;
		show_source=0;
		cur_options=NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks,
			NSIntMapValueCallBacks,1);

		[self displayMessage];
		return;
	} /* DSTATUS_DATA */

	{
		NSTextStorage *ms;
		int list=0;
		ms=[tv textStorage];

		[ms beginEditing];
		[ms deleteCharactersInRange: NSMakeRange(0,[ms length])];

		switch (status)
		{
		default:
			append(ms,header_dict,_(@"Unknown status %i for %s."),
				status,[mdb msg_getMessageID: mid]);
			break;
	
		case DSTATUS_NODATA:
			append(ms,header_dict,_(@"No data has been downloaded for %s."),
				[mdb msg_getMessageID: mid]);

			append(ms,header_dict,@"\n\n");

			append(ms,header_bold_dict,_(@"Size:"));
			if ([mdb msg_getHeader: "Bytes" : mid])
				append(ms,header_dict,@" %s",[mdb msg_getHeader: "Bytes" : mid]);
			else
				append(ms,header_dict,_(@" unknown"));

			append(ms,header_dict,@"\n\n");

			appendLink(ms,LINK_DOWNLOAD,nil,0,_(@"Download"));
			break;

		case DSTATUS_ERROR:
			append(ms,header_dict,_(@"Data for %s is unavailable due to an error."),
				[mdb msg_getMessageID: mid]);
			break;

		case DSTATUS_PENDING:
			append(ms,header_dict,_(@"Data for %s is currently being downloaded."),
				[mdb msg_getMessageID: mid]);
			break;

		case DSTATUS_NOSOURCE:
			append(ms,header_dict,_(@"Data cannot be downloaded for %s since it has no source."),
				[mdb msg_getMessageID: mid]);
			list=1;
			break;
		}

		if (list)
		{
			append(ms,header_dict,_(@"\n\nYou can set a source for this message by clicking on one of the sources:\n\n"));
			{
				NSArray *s=[mdb sources];
				int i,c=[s count];
				for (i=0;i<c;i++)
				{
					appendLink(ms,LINK_SOURCE,[s objectAtIndex: i],0,
						@"%@\t%@\n",
							[(id<GUISource>)[s objectAtIndex: i] sourceName],
							[(id<GUISource>)[s objectAtIndex: i] sourceType]);
				}
			}
		}

		[ms endEditing];
	}
}

@end


@implementation MessageViewController

-(BOOL) textView: (NSTextView *)t
	clickedOnLink: (MessageViewController_Link *)l atIndex: (unsigned int)idx
{
	if (![l isKindOfClass: [MessageViewController_Link class]])
		return NO;

	if (l->type==LINK_SOURCE)
	{
		int i,c;
		NSArray *s;

		/* make sure the source still exists */
		s=[mdb sources];
		c=[s count];
		for (i=0;i<c;i++)
		{
			if ([s objectAtIndex: i]==l->object)
			{
				[mdb msg_setSource: l->object  : mid];
				[mdb msg_needData: mid];
				return YES;
			}
		}
		NSBeep();
		return YES;
	}

	if (l->type==LINK_MULTIPART_ALTERNATIVE)
	{
		if (!cur_options)
			return YES; /* shouldn't happen */
		NSMapInsert(cur_options,l->object,(void *)l->value);
		[self displayMessage];
		return YES;
	}

	if (l->type==LINK_DATA_SHOW)
	{
		if (!cur_options)
			return YES;
		if (((int)NSMapGet(cur_options,l->object))==1)
			NSMapRemove(cur_options,l->object);
		else
			NSMapInsert(cur_options,l->object,(void *)1);
		[self displayMessage];
		return YES;
	}
	if (l->type==LINK_DATA_SHOW_TEXT)
	{
		if (!cur_options)
			return YES;
		if (((int)NSMapGet(cur_options,l->object))==2)
			NSMapRemove(cur_options,l->object);
		else
			NSMapInsert(cur_options,l->object,(void *)2);
		[self displayMessage];
		return YES;
	}

	if (l->type==LINK_DATA_SAVE)
	{
		NSSavePanel *sp;
		int result;
		NSData *d;
		NSString *filename;

		if ([l->object isKindOfClass: [NSString class]])
		{
			NSString *s=(NSString *)l->object;
			NSRange r;
			CWUUFile *fw;
			r=[CWUUFile rangeOfUUEncodedStringFromString: s
				range: NSMakeRange(0,[s length])];

			fw=[CWUUFile fileFromUUEncodedString: [s substringWithRange: r]];
			filename=AUTORELEASE(RETAIN([fw name]));
			d=AUTORELEASE(RETAIN([fw data]));
		}
		else
		{
			CWPart *p=(CWPart *)l->object;
			d=(NSData *)[p content];
			filename=[p filename];
		}

		sp=[NSSavePanel savePanel];
		[sp setTitle: _(@"Save data")];
		
		if (filename)
		{
			result=[sp runModalForDirectory: [[NSFileManager defaultManager]
				currentDirectoryPath] file: filename];
		}
		else
			result=[sp runModal];
		if (result!=NSOKButton) return YES;

		{
			FILE *f=fopen([[[sp filename] stringByStandardizingPath]
				fileSystemRepresentation],"wb");
			if (!f)
			{
				NSBeep(); /* TODO */
				return YES;
			}
			if (fwrite([d bytes],[d length],1,f)!=1)
				NSBeep();
			fclose(f);
		}
		return YES;
	}

	if (l->type==LINK_DOWNLOAD)
	{
		[mdb msg_needData: mid];
		return YES;
	}

	NSLog(@"unknown link type %i\n",l->type);
	return YES;
}


-(void) messageUpdate: (MidNotification *)n
{
	if ([n mid]!=mid) return;
	[self getData];
}


- initWithMsgDB: (MsgDB *)m  textView: (NSTextView *)textview
	scrollView: (NSScrollView *)scrollview;
{
	if (!(self=[super init])) return nil;

	if (!header_bold_dict)
	{ /* TODO */
		NSColor *fore_color=[NSColor colorWithCalibratedRed:0.1 green:0.1 blue:0.6 alpha: 1.0];
//		NSColor *back_color=[NSColor colorWithCalibratedRed:0.7 green:0.7 blue:0.7 alpha: 1.0];
		header_bold_dict=[[NSDictionary dictionaryWithObjectsAndKeys:
//			back_color,NSBackgroundColorAttributeName,
			fore_color,NSForegroundColorAttributeName,
			[NSFont boldSystemFontOfSize: 0],NSFontAttributeName,
			nil] retain];
		header_dict=[[NSDictionary dictionaryWithObjectsAndKeys:
//			back_color,NSBackgroundColorAttributeName,
			fore_color,NSForegroundColorAttributeName,
			[NSFont userFontOfSize: 0],NSFontAttributeName,
			nil] retain];
		header_link_dict=[[NSDictionary dictionaryWithObjectsAndKeys:
//			back_color,NSBackgroundColorAttributeName,
			[NSColor redColor],NSForegroundColorAttributeName,
			[NSFont userFontOfSize: 0],NSFontAttributeName,
			nil] retain];
	}

#ifdef USE_MIME_STUFF
	if (!mime_manager)
	{
		mime_manager=[[MimeManager alloc] init];
	}
#endif

	ASSIGN(mdb,m);
	ASSIGN(tv,textview);
	ASSIGN(sv,scrollview);

	[tv setDelegate: self];

	[[NSNotificationCenter defaultCenter]
		addObserver: self
		selector: @selector(messageUpdate:)
		name: MsgDB_MsgDStatusNotification
		object: mdb];

	mid=0;

	[[NSNotificationCenter defaultCenter]
	  addObserver: self
	  selector: @selector(encodingChanged:)
	  name: DefaultEncodingChangedNotification
	  object: nil];

	return self;
}

-(void) dealloc
{
	if (cur_options)
	{
		NSFreeMapTable(cur_options);
		cur_options=NULL;
	}

	DESTROY(cur_message);
	[[NSNotificationCenter defaultCenter]
		removeObserver: self];
	[tv setDelegate: nil];
	DESTROY(tv);
	DESTROY(sv);
	DESTROY(mdb);
	[super dealloc];
}


-(void) setMid: (msg_id_t)m
{
	if (mid==m) return;
	if (mid)
		[mdb msg_cancelWantData: mid];
	mid=m;
	[self getData];
}


-(BOOL) atEnd
{
	NSRect r1=[[sv contentView] documentRect];
	NSRect r2=[[sv contentView] documentVisibleRect];
	NSRect r3=[[sv contentView] frame];
	if (r2.origin.y+r3.size.height<r1.size.height)
		return NO;
	else
		return YES;
}


/* Attempt to scroll down in an intelligent way */
-(BOOL) scrollDown
{
	NSRect r2=[[sv contentView] documentVisibleRect];
	double y;

	if ([self atEnd]) return YES;

	if ([Pref_MessageViewing intelligentScroll])
	{ /* TODO: this might need tweaking */
		int idx,i2,sig_index;
		NSString *s=[[tv textStorage] string];
		unichar ch;

		sig_index=[[[tv textStorage] string] rangeOfString: @"\n-- \n"].location;

		y=r2.origin.y+r2.size.height;
		if (y>16) y-=16;
		idx=[[tv layoutManager] glyphIndexForPoint: NSMakePoint(0,y)
			inTextContainer: [tv textContainer]];
		i2=idx;

		/* First skip backwards and check if the current line is quoted */
		for (;idx>=0;idx--)
			if ([s characterAtIndex: idx]=='\n')
				break;
		idx++;

		/* Find the first unquoted line */
		for (;idx<[s length];)
		{
			for (;idx<[s length];idx++)
			{
				ch=[s characterAtIndex: idx];
				if (ch=='>' || ch>32)
					break;
			}
			if (idx>=[s length]) break;
			if ([s characterAtIndex: idx]=='>')
			{
				for (idx++;idx<[s length];idx++)
					if ([s characterAtIndex: idx]=='\n')
						break;
				idx++;
			}
			else
				break;
		}

		/* nothing more interesting in this message? */
		if (idx>=[s length])
			return YES;

		/* if we're already past the signature, return YES */
		if (sig_index!=NSNotFound && idx>sig_index)
			return YES;

		/* if we're on a really long, unquoted line we want to continue
		from the point in the line we were at before */
		if (idx<i2)
			idx=i2;

		/* Aim a bit above the target line so we include some context */
		y=[[tv layoutManager] boundingRectForGlyphRange: NSMakeRange(idx,1)
			inTextContainer: [tv textContainer]].origin.y;
		y=y-r2.size.height*1/3;
		[tv scrollPoint: NSMakePoint(0,y)];
	}
	else
	{
		y=r2.origin.y+r2.size.height*2/3;
		[tv scrollPoint: NSMakePoint(0,y)];
	}
	return NO;
}


/* TODO: real lines? better approximation? */
-(void) lineUp
{
	NSRect r2=[[sv contentView] documentVisibleRect];
	double y;

	y=r2.origin.y-16;

	[tv scrollPoint: NSMakePoint(0,y)];
}

-(void) lineDown
{
	NSRect r2=[[sv contentView] documentVisibleRect];
	double y;

	y=r2.origin.y+16;

	[tv scrollPoint: NSMakePoint(0,y)];
}


-(void) switchFont
{
	cur_font=!cur_font;
	[self displayMessage];
}

-(void) showSource
{
	show_source=!show_source;
	[self displayMessage];
}


-(void) messageDownload
{
	int status;
	if (!mid)
	{
		NSBeep();
		return;
	}
	status=[mdb msg_dstatus: mid];
	if (status==DSTATUS_DATA ||
	    status==DSTATUS_NOSOURCE ||
	    status==DSTATUS_PENDING /* TODO? */)
		return;

	[mdb msg_needData: mid];
}


-(void) messageSave
{
	const unsigned char *data;
	int length;
	NSSavePanel *sp;
	int result,status;

	if (!mid) return;

	status=[mdb msg_getData: &data length: &length : mid];
	if (status!=DSTATUS_DATA)
	{
		NSBeep();
		return;
	}

	sp=[NSSavePanel savePanel];
	[sp setTitle: _(@"Save message")];
	result=[sp runModal];
	if (result!=NSOKButton) return;

	/* just for safety for when I'll be freeing data (TODO?) */
	status=[mdb msg_getData: &data length: &length : mid];
	if (status!=DSTATUS_DATA)
	{
		NSBeep();
		return;
	}

	{
		FILE *f=fopen([[[sp filename] stringByStandardizingPath]
			fileSystemRepresentation],"wb");
		if (!f)
		{
			NSBeep(); /* TODO */
			return;
		}
		if (fwrite(data,length,1,f)!=1)
			NSBeep();
		fclose(f);
	}
}


-(void) composeFollowup: (id)sender
{
	if (!cur_message)
	{
		NSBeep();
		return;
	}
	[app_delegate composeFollowupToMessage: cur_message];
}

-(void) encodingChanged: (NSNotification *)n
{
  [self getData];
  [self displayMessage];
}

@end

