/*
copyright 2002 Alexander Malmberg <alexander@malmberg.org>
          2012 Riccardo Mottola <rm@gnu.org>
*/

/*
TODO:

cleanups, better organization
*/

#include <string.h>
#include <stdio.h>

#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSNotification.h>
#include <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>

#include "MsgDB.h"


#define HASH_SIZE (1031*5)



NSString
	*MsgDB_MsgDStatusNotification=@"MsgDB_MsgDStatusNotification",
	*MsgDB_MsgMetaChangeNotification=@"MsgDB_MsgMetaChangeNotification",

	*MsgDB_LogMessageNotification=@"MsgDB_LogMessageNotification",

	*MsgDB_FolderAddMsgNotification=@"MsgDB_FolderAddMsgNotification",
	*MsgDB_FolderAddNotification=@"MsgDB_FolderAddNotification",

	*MsgDB_SourceAddNotification=@"MsgDB_SourceAddNotification",
	*MsgDB_SourceRemoveNotification=@"MsgDB_SourceRemoveNotification",
	*MsgDB_SourceChangeNotification=@"MsgDB_SourceChangeNotification";


@implementation MidNotification
+ notificationWithName: (NSString *)n  object: (id)o mid: (msg_id_t)m  folder: (NSObject<Msg_Folder> *)f  header: (int)h
{
	MidNotification *mn;
	mn=[[self alloc] initWithName: n object: o mid: m  folder: f  header: h];
	return [mn autorelease];
}

- initWithName: (NSString *)n  object: (id)o  mid: (msg_id_t)m  folder: (NSObject<Msg_Folder> *)f  header: (int)h
{
	if (!(self=[super init])) return nil;
	name=[n copy];
	object=[o retain];
	mid=m;
	folder=f;
	header=h;
	return self;
}

-(void) dealloc
{
	DESTROY(name);
	DESTROY(object);
	[super dealloc];
}

-(NSString *) name
{
	return name;
}

-(id) object
{
	return object;
}

-(NSDictionary *) userInfo
{
	return nil;
}

-(msg_id_t) mid
{
	return mid;
}

-(NSObject<Msg_Folder> *) folder
{
	return folder;
}

-(const char *) headerName
{
	return [[self object] getMetaHeaderName: header];
}
@end


@interface NSDictionary (MsgDB_extensions)
-(id) keyForObject: (id)o;
@end

@implementation NSDictionary (MsgDB_extensions)
-(id) keyForObject: (id)o
{
	NSEnumerator *e;
	id k;
	e=[self keyEnumerator];
	for (;(k=[e nextObject]);)
		if (o==[self objectForKey: k])
			return k;
	return nil;
}
@end


typedef struct msgdb_hash_s
{
	int size;
	msg_id_t *h;
} hash_t;


typedef struct
{
	int mhid;
	char *value;
} meta_header_t;

typedef struct message_s
{
	char *message_id;
	meta_header_t *mhs;
	int num_mhs;
	msg_id_t hash_next;

	int dstatus;
	char *data;
	int data_length;

	unsigned int src_get_id;
} message_t;


@interface MsgDB_Folder : NSObject <Msg_Folder>
{
	msg_id_t *msg;
	int num_msg;
}

-(BOOL) containsMessage: (msg_id_t)mid;
-(void) addMessage: (msg_id_t)mid;
-(void) removeMessage: (msg_id_t)mid;
@end

@implementation MsgDB_Folder
- init
{
	if (!(self=[super init])) return nil;
	return self;
}

-(void) dealloc
{
	if (msg)
	{
		free(msg);
	}
	msg=NULL;
	num_msg=0;
	[super dealloc];
}

-(BOOL) containsMessage: (msg_id_t)mid
{ /* should do binary search here */
	int l,h,m;

	l=0;
	h=num_msg-1;
	while (l<=h)
	{
		m=(l+h)/2;
		if (msg[m]==mid) return YES;
		if (mid<msg[m])
			h=m-1;
		else
			l=m+1;
	}
	return NO;
}

-(void) addMessage: (msg_id_t)mid
{ /* could do binary search, but this will probably be faster in practice */
	int i,j;
	for (i=num_msg-1;i>=0;i--)
	{
		if (msg[i]==mid)
			return;
		if (msg[i]<mid)
			break;
	}
	i++;
	msg=realloc(msg,sizeof(msg_id_t)*(num_msg+1));
	for (j=num_msg;j>i;j--)
		msg[j]=msg[j-1];
	msg[i]=mid;
	num_msg++;

/*	int i;
	for (i=0;i<num_msg;i++)
		if (msg[i]==mid)
			return;
	msg=realloc(msg,sizeof(msg_id_t)*(num_msg+1));
	msg[num_msg++]=mid;*/
}

-(void) removeMessage: (msg_id_t)mid
{
	int i;
	for (i=0;i<num_msg;i++)
		if (msg[i]==mid)
			break;
	for (i++;i<num_msg;i++)
		msg[i-1]=msg[i];
	num_msg--;
}


-(int) numMessages
{
	return num_msg;
}
-(msg_id_t *) getMessages
{
	return msg;
}
@end


@interface MsgDB (private)
-(int) loadFromDisk;
-(void) createFromScratch;
-(MsgDB_Folder *) createFolderWithName: (NSString *)name;
@end


static inline unsigned int hash_id(const char *mid)
{
	const char *c;
	unsigned int v=257;
	for (c=mid;*c;c++)
		v=(v<<5)+v+*c;
	return v;
}

@interface MsgDB (private_hash)
-(void) addToHash: (msg_id_t)mid;
-(msg_id_t) findHash: (const char *)message_id;
@end

@implementation MsgDB (private_hash)
-(void) addToHash: (msg_id_t)mid
{
	message_t *m=messages+mid;
	unsigned int hv=hash_id(m->message_id);
	hv%=hash->size;
	m->hash_next=hash->h[hv];
	hash->h[hv]=mid;
}

-(msg_id_t) findHash: (const char *)message_id
{
	unsigned int hv=hash_id(message_id);
	msg_id_t mid;
	hv%=hash->size;
	for (mid=hash->h[hv];mid;mid=messages[mid].hash_next)
		if (!strcmp(message_id,messages[mid].message_id))
			return mid;
	return 0;
}
@end


#include <sys/stat.h>

@implementation MsgDB

- initWithDirectory: (NSString *)adir
{
	self=[super init];
	if (!self) return nil;

	dir=[adir copy];
//	fprintf(stderr,"init %p in %@\n",self,dir);

	mkdir([dir cString],0777); /* TODO */

	num_messages=1;

	hash=malloc(sizeof(hash_t));
	if (!hash) return nil;
	hash->size=HASH_SIZE;
	hash->h=malloc(sizeof(msg_id_t)*hash->size);
	memset(hash->h,0,sizeof(msg_id_t)*hash->size);

	ncenter=[NSNotificationCenter defaultCenter];

	sources=[[NSMutableDictionary alloc] init];
	folders=[[NSMutableDictionary alloc] init];

	{
		int i;
		char buf[128];
		Class c;
		const char *next;
		msg_id_t mid;
		NSNumber *n;
		NSObject<Msg_Source> *src;

		if (![self loadFromDisk])
			[self createFromScratch];

		mid=main_id;
		while (1)
		{
			next=[self msg_getMetaHeader: "Next-source" : mid];
			if (!next) break;

			i=atoi(next);
			if (i==-1) break;
			snprintf(buf,sizeof(buf),"source-%i",i);
			mid=[self midForId: buf];
			if (!mid)
			{
				fprintf(stderr,"Warning: Broken source chain!\n");
				break;
			}

//			fprintf(stderr,"got '%s' type '%s'\n",buf,[self msg_getMetaHeader: "Type" : mid]);

			c=objc_lookUpClass([self msg_getMetaHeader: "Type" : mid]);
			n=[[NSNumber alloc] initWithInt: i];
			src=[[c alloc] initWithMsg: mid db: self];
			[sources setObject: src forKey: n];
			[src release];
			[n release];
		}
		last_source_id=mid;
	}
	return self;
}

-(void) dealloc
{ /* TODO: fix all the leaks */
//	fprintf(stderr,"MsgDB: deallocing %p\n",self);

	{
		CREATE_AUTORELEASE_POOL(arp);
		NSEnumerator *e;
		NSObject<Msg_Source> *src;
		for (e=[sources objectEnumerator];(src=[e nextObject]);)
			[src disconnect];
#ifdef DEBUG_AUTORELEASE_COUNT
		fprintf(stderr,"dealloc arp contains %i objects\n",[arp autoreleaseCount]);
#endif
		DESTROY(arp);
	}
	DESTROY(sources);
	DESTROY(dir);
	DESTROY(folders);
	{
		int i,j;
		message_t *m;

		for (i=0;i<num_meta_headers;i++)
			free(meta_headers[i]);
		free(meta_headers);

		for (i=1,m=messages+1;i<num_messages;i++,m++)
		{
			free(m->message_id);
			for (j=0;j<m->num_mhs;j++)
				free(m->mhs[j].value);
			if (m->data)
				free(m->data);
		}
	}
	free(messages);
	[super dealloc];
}


-(NSNotificationCenter *)notificationCenter
{
	return ncenter;
}

-(void)setNotificationCenter: (NSNotificationCenter *)nc
{
	ncenter=nc;
}


-(msg_id_t) midForId: (const char *)msgid
{
	int i;
	message_t *m;
	if (hash)
		return [self findHash: msgid];
	for (i=1,m=messages+1;i<num_messages;i++,m++)
	{
		if (!strcmp(msgid,m->message_id))
			return i;
	}
	return 0;
}

-(msg_id_t) createMessageWithId: (const char *)msgid  source: (NSObject<Msg_Source> *)src
{
	message_t *m;
	if ([self midForId: msgid]) return 0;
	m=realloc(messages,sizeof(message_t)*(num_messages+1));
	if (!m) abort();
	messages=m;
	m=messages+num_messages;
	memset(m,0,sizeof(message_t));
	m->message_id=strdup(msgid);
	if (!m->message_id) abort();
	num_messages++;
	if (src)
	{
		int num=[[sources keyForObject: src] intValue];
		char buf[16];
		snprintf(buf,sizeof(buf),"%i",num);
		[self msg_setMetaHeader: "Source" value: buf : num_messages-1];
	}
	[self addToHash: num_messages-1];
	return num_messages-1;
}


-(void) msg_setSource: (NSObject<Msg_Source> *)src : (msg_id_t) mid
{
	if (src)
	{
		int num=[[sources keyForObject: src] intValue];
		char buf[16];
		snprintf(buf,sizeof(buf),"%i",num);
		[self msg_setMetaHeader: "Source" value: buf : mid];
	}
}


-(int) getMetaHeaderNum: (const char *)hname
{
	int i;
	char **c;
	for (i=0;i<num_meta_headers;i++)
		if (!strcmp(hname,meta_headers[i]))
			return i;
	c=realloc(meta_headers,sizeof(char *)*(num_meta_headers+1));
	if (!c) abort();
	meta_headers=c;
	c+=num_meta_headers;
	(*c)=strdup(hname);
	if (!(*c)) abort();
	num_meta_headers++;
	return i;
}

-(const char *) getMetaHeaderName: (int)num
{
	if (num<0 || num>=num_meta_headers) return NULL;
	return meta_headers[num];
}


-(void) updateAll
{
	CREATE_AUTORELEASE_POOL(arp);
	NSEnumerator *e;
	NSObject<Msg_Source> *src;
	for (e=[sources objectEnumerator];(src=[e nextObject]);)
		[src update];
#ifdef DEBUG_AUTORELEASE_COUNT
	fprintf(stderr,"updateAll arp contains %i objects\n",[arp autoreleaseCount]);
#endif
	DESTROY(arp);
}


-(const char *)msg_getHeader: (const char *)hname : (msg_id_t)mid
{
	char *buf;
	const char *c;
	buf=malloc(strlen(hname)+6);
	if (!buf) abort();
	strcpy(buf,"Real-");
	strcat(buf,hname);
	c=[self msg_getMetaHeader: buf : mid];
	free(buf);
	if (c) return c;
	return NULL; /* TODO */
}


-(const char *) msg_getMessageID: (msg_id_t)mid
{
	message_t *m=messages+mid;
	return m->message_id;
}


-(const char *)msg_getMetaHeaderWithNumber: (int)hn : (msg_id_t)mid
{
	int i;
	message_t *m=messages+mid;
	for (i=0;i<m->num_mhs;i++)
		if (m->mhs[i].mhid==hn)
			return m->mhs[i].value;
	return NULL;
}

-(void)msg_setMetaHeaderWithNumber: (int) hn  value: (const char *)value : (msg_id_t)mid
{
	int i;
	char *c;
	message_t *m=messages+mid;
	meta_header_t *mh;

	if (!value) abort();
	c=strdup(value);
	if (!c) abort();
	for (i=0;i<m->num_mhs;i++)
		if (m->mhs[i].mhid==hn)
		{
			free(m->mhs[i].value);
			m->mhs[i].value=c;

			[ncenter postNotification:
				[MidNotification
					notificationWithName: MsgDB_MsgMetaChangeNotification
					object: self
					mid: mid
					folder: nil
					header: hn]];
			return;
		}
	mh=realloc(m->mhs,sizeof(meta_header_t)*(m->num_mhs+1));
	if (!mh) abort();
	m->mhs=mh;
	mh+=m->num_mhs++;
	mh->mhid=hn;
	mh->value=c;

	[ncenter postNotification:
		[MidNotification
			notificationWithName: MsgDB_MsgMetaChangeNotification
			object: self
			mid: mid
			folder: nil
			header: hn]];
}


-(const char *)msg_getMetaHeader: (const char *)hname : (msg_id_t)mid
{
	return [self msg_getMetaHeaderWithNumber: [self getMetaHeaderNum: hname] : mid];
}

-(void)msg_setMetaHeader: (const char *)hname  value: (const char *)value : (msg_id_t)mid
{
	[self msg_setMetaHeaderWithNumber: [self getMetaHeaderNum: hname]  value: value : mid];
}


-(void)msg_addToFolder: (const char *)foldername : (msg_id_t)mid
{
	const char *c;
	char *buf;
	MsgDB_Folder *f=[self createFolderWithName: [NSString stringWithCString: foldername]];

	if ([f containsMessage: mid]) return;

	c=[self msg_getMetaHeader: "Folder" : mid];
	if (c)
	{
		buf=malloc(strlen(c)+1+strlen(foldername)+1);
		if (!buf) abort();
		sprintf(buf,"%s %s",c,foldername);
		[self msg_setMetaHeader: "Folder" value: buf : mid];
		free(buf);
	}
	else
		[self msg_setMetaHeader: "Folder" value: foldername : mid];

	[f addMessage: mid];
	[ncenter postNotification:
		[MidNotification
			notificationWithName: MsgDB_FolderAddMsgNotification
			object: self
			mid: mid
			folder: f
			header: -1]];
}


-(void)dumpMessages
{
	int i/*,n*/;
/*	msg_id_t mid;*/
	for (i=1;i<num_messages;i++)
		[self dumpMessage: i];
#if 0
	fprintf(stderr,"hash size %i, num_msg=%i\n",hash->size,num_messages);
	for (i=0;i<hash->size;i++)
	{
		fprintf(stderr,"%03x:",i);
		n=0;
		for (mid=hash->h[i];mid;mid=messages[mid].hash_next)
//			fprintf(stderr," %i",mid);
			n++;
		fprintf(stderr," %i\n",n);
	}
#endif
	fprintf(stderr,"..\n");
}

-(void)dumpMessage: (msg_id_t)mid
{
	message_t *m;
	int i;
	printf("-- message %i\n",mid);
	if (mid<0 || mid>=num_messages)
	{
		printf("no such message\n");
		return;
	}
	m=messages+mid;
	printf("Message-Id: %s\n",m->message_id);
	for (i=0;i<m->num_mhs;i++)
		printf("%2i : %3i '%s'  '%s'\n",i,m->mhs[i].mhid,[self getMetaHeaderName: m->mhs[i].mhid],m->mhs[i].value);
}


-(void) syncToDisk
{
	CREATE_AUTORELEASE_POOL(arp);
	const char *fname=
		[[[dir stringByAppendingPathComponent: @"metahead.txt"]
			stringByStandardizingPath]
			fileSystemRepresentation];
	FILE *f;
	int i,j;
	message_t *m;

//	fprintf(stderr,"saving to '%s'\n",fname);
	f=fopen(fname,"wt");
	if (!f)
	{
		fprintf(stderr,"couldn't save to '%s': %m\n",fname);
		return;
	}

	fprintf(f,"%i\n",num_meta_headers);
	for (i=0;i<num_meta_headers;i++)
	{
          fprintf(f,"%i ",(int)strlen(meta_headers[i]));
		fwrite(meta_headers[i],1,strlen(meta_headers[i]),f);
		fputc('\n',f);
	}
	fprintf(f,"%i\n",num_messages);
	for (i=1,m=messages+1;i<num_messages;i++,m++)
	{
          fprintf(f,"%i %i ",m->num_mhs,(int)strlen(m->message_id));
		fwrite(m->message_id,1,strlen(m->message_id),f);
		fputc('\n',f);
		for (j=0;j<m->num_mhs;j++)
		{
                  fprintf(f,"%i %i ",m->mhs[j].mhid,(int)strlen(m->mhs[j].value));
                  fwrite(m->mhs[j].value,1,strlen(m->mhs[j].value),f);
			fputc('\n',f);
		}
	}

	fclose(f);

#ifdef DEBUG_AUTORELEASE_COUNT
	fprintf(stderr,"syncToDisk arp contains %i objects\n",[arp autoreleaseCount]);
#endif
	DESTROY(arp);
}

-(int) loadFromDisk
{
	CREATE_AUTORELEASE_POOL(arp);
	const char *fname=
		[[[dir stringByAppendingPathComponent: @"metahead.txt"]
			stringByStandardizingPath]
			fileSystemRepresentation];
	FILE *f;
	int i,j,k,l;
	message_t *m;

	int *mhidx;
	char **mhname;
	int nmh;

	f=fopen(fname,"rt");
	if (!f)
		return 0;

	if (fscanf(f,"%i\n",&nmh)!=1)
	{
		fprintf(stderr,"parse error\n");
		return 0;
	}
	mhidx=malloc(nmh*sizeof(int));
	mhname=malloc(nmh*sizeof(char *));
	if (!mhidx || !mhname) abort();
	for (i=0;i<nmh;i++)
		mhidx[i]=-1;
	for (i=0;i<nmh;i++)
	{
		fscanf(f,"%i ",&j);
		mhname[i]=malloc(j+1);
		fread(mhname[i],1,j,f);
		mhname[i][j]=0;
		fgetc(f);
	}
	fscanf(f,"%i\n",&num_messages);
	messages=malloc(sizeof(message_t)*num_messages);
	memset(messages,0,sizeof(message_t)*num_messages);
	if (!messages) abort();
	for (i=1,m=messages+1;i<num_messages;i++,m++)
	{
		fscanf(f,"%i %i ",&m->num_mhs,&l);
		m->message_id=malloc(l+1);
		if (!m->message_id) abort();
		fread(m->message_id,1,l,f);
		m->message_id[l]=0;
		fgetc(f);
		m->mhs=malloc(sizeof(meta_header_t)*m->num_mhs);
		if (!m->mhs) abort();
		for (j=0;j<m->num_mhs;j++)
		{
			fscanf(f,"%i %i",&k,&l);
			fgetc(f);
			if (mhidx[k]==-1)
				mhidx[k]=[self getMetaHeaderNum: mhname[k]];
			m->mhs[j].mhid=mhidx[k];
			m->mhs[j].value=malloc(l+1);
			if (!m->mhs[j].value) abort();
			fread(m->mhs[j].value,1,l,f);
			m->mhs[j].value[l]=0;
			fgetc(f);
		}
	}

	{
		char **folder_names=NULL;
		MsgDB_Folder **f=NULL;
		int nfn=0;
		char *c,*d;
		int j;
		int idx=[self getMetaHeaderNum: "Folder"];
		for (i=1,m=messages+1;i<num_messages;i++,m++)
		{
			[self addToHash: i];

			for (j=0;j<m->num_mhs;j++)
				if (m->mhs[j].mhid==idx)
					break;
			if (j==m->num_mhs) continue;
			for (c=m->mhs[j].value;*c;)
			{
				for (d=c;*d && *d!=' ';d++) ;
				for (j=0;j<nfn;j++)
					if (!strncmp(folder_names[j],c,d-c) && strlen(folder_names[j])==d-c)
						break;
				if (j==nfn)
				{
					folder_names=realloc(folder_names,sizeof(char *)*(nfn+1));
					f=realloc(f,sizeof(MsgDB_Folder *)*(nfn+1));
					if (!f || !folder_names) abort();
					folder_names[nfn]=malloc(d-c+1);
					if (!folder_names[nfn]) abort();
					strncpy(folder_names[nfn],c,d-c);
					folder_names[nfn][d-c]=0;
					f[nfn]=[self createFolderWithName: [NSString stringWithCString: folder_names[nfn]]];
					nfn++;
				}
				[f[j] addMessage: i];
				c=d;
				if (*c==' ') c++;
			}
		}
		free(f);
		free(folder_names);
	}

	free(mhidx);
	for (i=0;i<nmh;i++)
		free(mhname[i]);
	free(mhname);

	fclose(f);

	main_id=[self midForId: "main"];
	if (!main_id)
	{
		fprintf(stderr,"No main message in database. Database really broken!");
		abort();
	}

#ifdef DEBUG_AUTORELEASE_COUNT
	fprintf(stderr,"loadFromDisk arp contains %i objects\n",[arp autoreleaseCount]);
#endif
	DESTROY(arp);

	return 1;
}


-(MsgDB_Folder *) createFolderWithName: (NSString *)name
{
	MsgDB_Folder *f;
	if ([folders objectForKey: name])
		return [folders objectForKey: name];
	f=[[MsgDB_Folder alloc] init];
	[folders setObject: f forKey: name];
	[f release];

	[ncenter postNotification:
		[MidNotification
			notificationWithName: MsgDB_FolderAddNotification
			object: self
			mid: 0
			folder: f
			header: -1]];

	return f;
}


-(NSDictionary *) folders
{
	return folders;
}


-(void)msgp_setDStatus: (int)dstatus : (msg_id_t)mid
{
	message_t *m=&messages[mid];
	if (m->dstatus==dstatus) return;
	m->dstatus=dstatus;
	[ncenter postNotification:
		[MidNotification
			notificationWithName: MsgDB_MsgDStatusNotification
			object: self
			mid: mid
			folder: nil
			header: -1]];
}


-(id<Msg_Source>) msg_source: (msg_id_t)mid
{
	NSNumber *n;
	id<Msg_Source> src;
	const char *c;

	c=[self msg_getMetaHeader: "Source" : mid];
	if (!c)
		return nil;
	n=[[NSNumber alloc] initWithInt: atoi(c)];
	src=[sources objectForKey: n];
	[n release];
	if (!src)
		return nil;
	return src;
}


-(void)msg_wantData: (msg_id_t)mid  priority: (int)pri
{
	id<Msg_Source> src;
	message_t *m=&messages[mid];

	if (m->dstatus==DSTATUS_DATA) return;
	if (m->dstatus==DSTATUS_PENDING) return;

	src=[self msg_source: mid];
	if (!src)
	{
		[self msgp_setDStatus: DSTATUS_NOSOURCE : mid];
		return;
	}
	[self msgp_setDStatus: DSTATUS_PENDING : mid];
	m->src_get_id=[src getMessage: mid  priority: pri];
}

-(void) msg_cancelWantData: (msg_id_t)mid
{
	id<Msg_Source> src;
	message_t *m=&messages[mid];

	if (!m->src_get_id)
		return;
	src=[self msg_source: mid];
	if ([src cancelGetMessage: mid id: m->src_get_id])
	{
		m->src_get_id=0;
		m->dstatus=DSTATUS_NODATA;
	}
}


-(void)msg_needData: (msg_id_t)mid
{
	[self msg_wantData: mid  priority: 10];
}


-(int)msg_getData: (const unsigned char **)data length: (int *)length : (msg_id_t)mid
{
	message_t *m=&messages[mid];
	if (m->dstatus==DSTATUS_DATA)
	{
		*data=m->data;
		*length=m->data_length;
		return 1;
	}
	if (m->dstatus==DSTATUS_NODATA)
	{
//		[self msg_needData: mid]; /* TODO? */
		*data=NULL;
		*length=0;
	}
	return m->dstatus;
}

-(int)msg_dstatus: (msg_id_t)mid
{
	return messages[mid].dstatus;
}

-(void) setMessageData: (unsigned char *)d length: (int)l : (msg_id_t) mid
{
	message_t *m=&messages[mid];

	m->src_get_id=0;

	if (l==-1)
	{
		/* if we already have data we keep it and ignore the error */
		if (m->dstatus==DSTATUS_DATA) return;
		[self msgp_setDStatus: DSTATUS_ERROR : mid];
		return;
	}
	if (m->dstatus==DSTATUS_DATA)
	{
		free(m->data);
		m->data=NULL;
		m->data_length=0;
		m->dstatus=DSTATUS_NODATA;
	}
	m->data=d;
	m->data_length=l;
	[self msgp_setDStatus: DSTATUS_DATA : mid];

	/* If we don't already have headers for this message we parse them from
	the data. We're only interested in some headers, though, and we want the
	raw header data, so we parse manually. It won't be less accurate than
	xover headers. */
	/* TODO: re-parse anyway? */
//	if (![self msg_getMetaHeader: "Real-Message-ID" : mid])
	{
		unsigned char *b,*end;
		const char *c;
		NSMutableData *header=[[NSMutableData alloc] initWithCapacity: 100];
		NSMutableData *value=[[NSMutableData alloc] initWithCapacity: 100];

		b=m->data;
		end=b+m->data_length;
		while (b<end)
		{
			if (*b=='\n') break;
			[header setLength: 0];
			[header appendBytes: "Real-" length: 5];
			[value setLength: 0];
			while (*b!=':' && b<end)
			{
				[header appendBytes: b length: 1];
				b++;
			}
			b+=2;
			if (b>=end) break;

			while (1)
			{
				if (*b=='\n' && b+1<end && (b[1]>32 || b[1]=='\n')) break;
				if (*b=='\n' || *b=='\r')
				{
					b++;
					continue;
				}
				[value appendBytes: b length: 1];
				b++;
			}
			b++;
			[header appendBytes: "\x0" length: 1];
			[value appendBytes: "\x0" length: 1];

			c=[header bytes];
			if (!strcmp(c,"Real-Subject") ||
			    !strcmp(c,"Real-Message-ID") ||
			    !strcmp(c,"Real-From") ||
			    !strcmp(c,"Real-Date") ||
			    !strcmp(c,"Real-References") ||
			    !strcmp(c,"Real-Bytes") ||
			    !strcmp(c,"Real-Lines") )
			{
				[self msg_setMetaHeader: c value: [value bytes] : mid];
			}
		}

		DESTROY(header);
		DESTROY(value);
	}
}


-(void) createFromScratch
{
	main_id=[self createMessageWithId: "main"  source: nil];
}


-(NSArray *) sources
{
	return [sources allValues];
}

-(NSObject<Msg_Source> *) addSourceOfType: (Class)c
{
	msg_id_t mid;
	int idx;
	const char *b;
	char buf[64];
	NSObject<Msg_Source> *src;
	NSNumber *n;

	b=[self msg_getMetaHeader: "Max-source" : main_id];
	if (!b)
		idx=1;
	else
		idx=atoi(b)+1;

	sprintf(buf,"%i",idx);
	[self msg_setMetaHeader: "Max-source" value: buf : main_id];

	[self msg_setMetaHeader: "Next-source" value: buf : last_source_id];

	sprintf(buf,"source-%i",idx);
	last_source_id=mid=[self createMessageWithId: buf  source: nil];
	if (!mid)
	{ /* TODO */
		abort();
	}
	[self msg_setMetaHeader: "Type"  value: [NSStringFromClass(c) cString] : mid];

	n=[[NSNumber alloc] initWithInt: idx];
	src=[[c alloc] initWithMsg: mid db: self];
	[sources setObject: src forKey: n];
	[src autorelease];
	[n release];

	[ncenter postNotificationName: MsgDB_SourceAddNotification
		object: self];

	return src;
}

-(void) removeSource: (NSObject<Msg_Source> *)src
{
	msg_id_t mid,next_mid,src_mid;
	const char *next;
	int i;
	char buf[64];

	[src retain];
	[sources removeObjectForKey: [sources keyForObject: src]];

	src_mid=[src mid];
	mid=main_id;
	while (1)
	{
		next=[self msg_getMetaHeader: "Next-source" : mid];
		if (!next) break;

		i=atoi(next);
		if (i==-1) break;
		snprintf(buf,sizeof(buf),"source-%i",i);
		next_mid=[self midForId: buf];
		if (!next_mid)
		{
			fprintf(stderr,"Warning: Broken source chain!\n");
			break;
		}
		if (next_mid==src_mid)
		{
			if ([self msg_getMetaHeader: "Next-source" : src_mid])
				[self msg_setMetaHeader: "Next-source" value: [self msg_getMetaHeader: "Next-source" : src_mid] : mid];
			else
				[self msg_setMetaHeader: "Next-source" value: "-1" : mid];
			if (last_source_id==src_mid)
				last_source_id=mid;
			break;
		}
		mid=next_mid;
	}

	/* TODO: delete actual message */

	[src release];

	[ncenter postNotificationName: MsgDB_SourceRemoveNotification
		object: self];
}

@end

