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

#include <string.h>

#include <Foundation/NSObject.h>
#include <Foundation/NSString.h>
#include <Foundation/NSNotification.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSException.h>

#include <Foundation/NSBundle.h>

#include "MsgDB.h"

#include "NNTPSource.h"
#include "GUISource.h"
#include "NNTPSourceGUI.h"

#include "NNTPServer.h"


typedef struct nntpsource_group_s group_t;


@implementation NNTPSource (private)

-(void) recreateNNTPServer
{
	const char *host,*sport;
	host=[mdb msg_getMetaHeader: "Host" : mmid];
	sport=[mdb msg_getMetaHeader: "Port" : mmid];

	if (cur_host && !strcmp(host,cur_host) && (sport==cur_sport || !strcmp(sport,cur_sport)))
		return;

	if (cur_host)
		free(cur_host);
	if (host)
	{
		cur_host=strdup(host);
		if (!cur_host) abort(); /* TODO */
	}
	else
		cur_host=NULL;

	if (cur_sport)
		free(cur_sport);
	if (sport)
	{
		cur_sport=strdup(sport);
		if (!cur_sport) abort(); /* TODO */
	}
	else
		cur_sport=NULL;

	if (server)
	{
		[server enableConnect: 0];
		[server closeAllConnections];
		DESTROY(server);
	}
	if (host)
	{
		if (sport && atoi(sport)>0 && atoi(sport)<65536)
			server=[[NNTPServer alloc] initWithHost: host port: atoi(sport)];
		else
			server=[[NNTPServer alloc] initWithHost: host];
		[server setReceiver: self];
		[server enableConnect: 1];
	}
}


-(void) syncGroupHeaders: (int)gnum
{
	char buf1[32],buf2[32];
	snprintf(buf1,sizeof(buf1),"G%i-num",gnum);
	snprintf(buf2,sizeof(buf2),"%i",groups[gnum].last_num);
	[mdb msg_setMetaHeader: buf1 value: buf2 : mmid];
}


-(void) syncAllGroupHeaders
{
	int i;
	char buf1[32],buf2[32];

	for (i=0;i<num_groups;i++)
	{
		snprintf(buf1,sizeof(buf1),"G%i-name",i);
		[mdb msg_setMetaHeader: buf1 value: [NNTPServer getGroupName: groups[i].idx] : mmid];
		snprintf(buf1,sizeof(buf1),"G%i-num",i);
		snprintf(buf2,sizeof(buf2),"%i",groups[i].last_num);
		[mdb msg_setMetaHeader: buf1 value: buf2 : mmid];
	}
	snprintf(buf1,sizeof(buf1),"%i",num_groups);
	[mdb msg_setMetaHeader: "Groups" value: buf1 : mmid];
}


-(void) nntp_serverDate: (const char *)date qid: (unsigned int)qid { }
-(void) nntp_headerId: (const char *)mid  data: (const unsigned char *)data : (int)length  qid: (unsigned int)qid { }
-(void) nntp_articleRange: (int)group : (int)num  data: (const unsigned char *)data : (int)length  qid: (unsigned int)qid { }
-(void) nntp_articleId: (const char *)mid  data: (const unsigned char *)data : (int)length  qid: (unsigned int)qid
{
	unsigned char *d;
	msg_id_t mdb_id;
	mdb_id=[mdb midForId: mid];
	if (!mdb_id)
		mdb_id=[mdb createMessageWithId: mid  source: self];

	if (length==-1)
	{
		[mdb setMessageData: NULL length: -1 : mdb_id];
		return;
	}
	d=malloc(length);
	if (!d) abort();
	memcpy(d,data,length);
	[mdb setMessageData: d length: length : mdb_id];
}
-(void) nntp_groupList: (const unsigned char *)data : (int)length  qid: (unsigned int)qid { }


-(void) nntp_fail: (NSString *)reason qid: (unsigned int)qid
{
	[[mdb notificationCenter]
		postNotificationName: MsgDB_LogMessageNotification
		object: mdb
		userInfo:
			[NSDictionary
				dictionaryWithObject: [NSString stringWithFormat: @"'%@' failed: %@",[server qidDescription: qid],reason]
				forKey: @"Message"]];
}
-(void) nntp_fail_connect: (NSString *)reason
{
	[[mdb notificationCenter]
		postNotificationName: MsgDB_LogMessageNotification
		object: mdb
		userInfo:
			[NSDictionary
				dictionaryWithObject: [NSString stringWithFormat: _(@"Failed to connect: %@"),reason]
				forKey: @"Message"]];
}
-(void) nntp_message: (NSString *)msg
{
	[[mdb notificationCenter]
		postNotificationName: MsgDB_LogMessageNotification
		object: mdb
		userInfo:
			[NSDictionary
				dictionaryWithObject: msg
				forKey: @"Message"]];
}
-(void) nntp_progress: (int)bytes : (unsigned int)qid
{
	[[mdb notificationCenter]
		postNotificationName: MsgDB_LogMessageNotification
		object: mdb
		userInfo:
			[NSDictionary
				dictionaryWithObject:
					[NSString stringWithFormat: _(@"Downloaded %ikb for '%@'"),
						bytes/1024,[server qidDescription: qid]]
				forKey: @"Message"]];
}

-(void) nntp_groupInfo: (int)group : (int)num : (int)first : (int)last qid: (unsigned int)qid
{
	int i;
	for (i=0;i<num_groups;i++)
		if (group==groups[i].idx)
			break;
	if (i==num_groups)
	{
		if (first!=-1 || last!=-1)
			[[mdb notificationCenter]
				postNotificationName: MsgDB_LogMessageNotification
				object: mdb
				userInfo:
					[NSDictionary
						dictionaryWithObject: [NSString stringWithFormat: _(@"Group '%s' contains message %i - %i."),
							[NNTPServer getGroupName: group],
							first,last]
						forKey: @"Message"]];
		else
			[[mdb notificationCenter]
				postNotificationName: MsgDB_LogMessageNotification
				object: mdb
				userInfo:
					[NSDictionary
						dictionaryWithObject: [NSString stringWithFormat: _(@"Group '%s' doesn't exist."),
							[NNTPServer getGroupName: group],
							first,last]
						forKey: @"Message"]];
		return;
	}
	if (num==-1) return;

	if (last<=groups[i].last_num)
	{
		[[mdb notificationCenter]
			postNotificationName: MsgDB_LogMessageNotification
			object: mdb
			userInfo:
				[NSDictionary
					dictionaryWithObject: [NSString stringWithFormat: _(@"No new messages in '%s'."),
						[NNTPServer getGroupName: groups[i].idx]]
					forKey: @"Message"]];
		return;
	}
	if (groups[i].last_num>=first) first=groups[i].last_num+1;

	[[mdb notificationCenter]
		postNotificationName: MsgDB_LogMessageNotification
		object: mdb
		userInfo:
			[NSDictionary
				dictionaryWithObject: [NSString stringWithFormat: _(@"%i new messages in '%s', retrieving headers..."),
					last-first+1,[NNTPServer getGroupName: groups[i].idx]]
				forKey: @"Message"]];

//	printf("requesting headers for %i-%i=%i\n",first,last,last-first+1);
	[server getHeaderRange: first : last group: group priority: 0];
}

-(void) nntp_headerRange: (int)group : (int)first : (int)last : (int)num
	: (char **)list partial: (BOOL)partial qid: (unsigned int)qid
{
static const char *fields[8]=
{0,"Real-Subject","Real-From","Real-Date","Real-Message-ID",
"Real-References","Real-Bytes","Real-Lines"};

/* TODO: can't be static since different NNTPSource:s might have different
mdb:s. should probably keep in an instance variable. */
	int field_nums[8];

	int i;
	int gnum;

	if (!num) return;

//	printf("got %i headers in\n",num);
	for (gnum=0;gnum<num_groups;gnum++)
		if (group==groups[gnum].idx)
			break;
	if (gnum==num_groups) abort();
	groups[gnum].last_num=last;
	[self syncGroupHeaders: gnum];

	for (i=0;i<8;i++)
		if (fields[i])
			field_nums[i]=[mdb getMetaHeaderNum: fields[i]];

	for (i=0;i<num;i++)
	{ /* TODO: optimize? */
		int j;
		msg_id_t mid=0;
		char *c,*b;

		/* find the Message-ID */
		for (j=4,c=list[i];*c && j;c++) if (*c=='\t') j--;
		if (!*c) continue;
		for (b=c;*b && *b!='\t';b++) ;
		if (!*b) continue;
		*b=0;

		mid=[mdb createMessageWithId: c  source: self];
		if (!mid) /* the message already exists */
		{
			mid=[mdb midForId: c];
			[mdb msg_setSource: self : mid];
		}
		*b='\t';

		j=0;
		for (c=list[i];*c;j++)
		{
			b=c;
			for (;*c && *c!='\t';c++) ;
			if (*c=='\t')
				*c++=0;
			if (*b)
			{
				if (j<8)
				{
					const char *f;
					f=fields[j];
					if (f)
						[mdb msg_setMetaHeaderWithNumber: field_nums[j] value: b : mid];
				}
				else
				{
#if 0 /* ignore, we probably aren't interested in it anyway */
				/* Since there have been 8 fields before this one, there must
				  have been at least 8 tabs. Thus, we can safely copy Real- right
				  before the extra header. */
					char *f;
					b-=5;
					memcpy(b,"Real-",5);
					f=strchr(b,':');
					if (!f) continue;
					*f=0;
					f+=2;
					[mdb msg_setMetaHeader: b value: f : mid];
#endif
				}
			}
		}
		[mdb msg_addToFolder: [NNTPServer getGroupName: group] : mid];
	}
//	printf("done adding\n");

	if (partial)
	{
		int last_msg_num=strtol(list[num-1],NULL,10);
		[[mdb notificationCenter]
			postNotificationName: MsgDB_LogMessageNotification
			object: mdb
			userInfo:
				[NSDictionary
					dictionaryWithObject: [NSString stringWithFormat:
						_(@"Got headers in '%s': %5i/%5i (%5i) ..."),
						[NNTPServer getGroupName: groups[gnum].idx],
						last_msg_num-first+1,last-first+1,num]
					forKey: @"Message"]];
	}
	else
	{ /* TODO: try to keep track of actual number of messages? */
		[[mdb notificationCenter]
			postNotificationName: MsgDB_LogMessageNotification
			object: mdb
			userInfo:
				[NSDictionary
					dictionaryWithObject: [NSString stringWithFormat:
						_(@"Got headers in '%s': %5i/%5i (%5i) Done."),
						[NNTPServer getGroupName: groups[gnum].idx],
						last-first+1,last-first+1,num]
					forKey: @"Message"]];
	}
}


-(void) nntp_postArticle: (BOOL)success  qid: (unsigned int)qid
{
	NSNumber *n=[[NSNumber alloc] initWithInt: qid];
	NSObject *w;

	w=[request_map objectForKey: n];
	if (!w)
		[NSException raise: @"NNTPSource internal error" format: @"!w in nntp_postArticle:qid:"];
	RETAIN(w);
	[request_map removeObjectForKey: n];
	DESTROY(n);
	[w postedArticle: success];
	DESTROY(w);
}

@end


@implementation NNTPSource

-(msg_id_t) mid
{
	return mmid;
}

-initWithMsg: (msg_id_t)mid db: (MsgDB *)amdb
{
	self=[super init];
	if (!self) return nil;
	mmid=mid;
	mdb=amdb;
	{
		int i;
		const char *c;
		char buf[32];

		c=[mdb msg_getMetaHeader: "Groups" : mmid];
		if (c)
			num_groups=atoi(c);
		else
			num_groups=0;

		groups=malloc(sizeof(group_t)*num_groups);
		if (!groups) return nil;

		for (i=0;i<num_groups;i++)
		{
			snprintf(buf,sizeof(buf),"G%i-name",i);
			c=[mdb msg_getMetaHeader: buf : mmid];
			if (!c) return nil;
			groups[i].idx=[NNTPServer getGroupNum: c];

			snprintf(buf,sizeof(buf),"G%i-num",i);
			c=[mdb msg_getMetaHeader: buf : mmid];
			if (!c)
				groups[i].last_num=0;
			else
				groups[i].last_num=atoi(c);
		}
	}
	[self recreateNNTPServer];
	request_map=[[NSMutableDictionary alloc] init];
	return self;
}

-(void) disconnect
{
	if (server)
	{
		[server enableConnect: 0];
		[server closeAllConnections];
	}
}

-(void) dealloc
{
//	printf("dealloc source %p\n",self);
	if (server)
	{
		[server enableConnect: 0];
		[server closeAllConnections];
		[server setReceiver: nil];
		[server killAllConnections];
		[server release];
	}

	if (cur_host)
		free(cur_host);
	if (cur_sport)
		free(cur_sport);
	cur_host=cur_sport=NULL;

	DESTROY(request_map);

	[super dealloc];
}

-(void) update
{
	int i;
	if (!server) return;
	for (i=0;i<num_groups;i++)
		[server getGroupInfo: groups[i].idx];
}

/* TODO: gcc issue: error message for this has messed up type (unsigned) */
-(unsigned int) getMessage: (msg_id_t)mid  priority: (int)pri
{
	const char *c,*d;
	int size;

	c=[mdb msg_getMessageID: mid];
	if (!server || !c)
	{
		[mdb setMessageData: NULL length: -1 : mid];
		return 0;
	}

	d=[mdb msg_getHeader: "Bytes" : mid];
	if (d)
		size=atoi(d);
	else
		size=3000;
	return [server getArticleById: c  size: size  priority: pri];
}

-(BOOL) cancelGetMessage: (msg_id_t)mid  id: (unsigned int)d
{
	if ([server cancelQid: d  kill: NO])
		return YES;
	return NO;
}


-(void) postArticle: (unsigned char *)data  length: (int)length
	sender: (NSObject *)sender
{
	unsigned int qid;
	NSNumber *n;
	qid=[server postArticle: data  length: length  priority: 2];
	n=[[NSNumber alloc] initWithInt: qid];
	[request_map setObject: sender forKey: n];
	DESTROY(n);
}


@end

