/*
 *
 *  AM --- AmigaMail
 *  (C) 1991, 1992 by Christian Riede
 *
 *  AM is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY.  No author or distributor accepts responsibility to anyone
 *  for the consequences of using it or for whether it serves any
 *  particular purpose or works at all, unless he says so in writing.
 *  Refer to the GNU General Public License, Version 1, for full details.
 *  
 *  Everyone is granted permission to copy, modify and redistribute AM,
 *  but only under the conditions described in the GNU General Public
 *  License, Version 1.  A copy of this license is supposed to have been 
 *  given to you along with AM so you can know your rights and responsi-
 *  bilities.  It should be in a file named COPYING.  Among other things,
 *  the copyright notice and this notice must be preserved on all copies.
 *
 *  
 *
 */

#include "am.h"
#include "server.h"

UBYTE *vers = (UBYTE *)("\0$VER: contentsserver:"AMIGAMAIL_VERSION);

struct MsgPort *ServerPort;

struct List Mailboxes;

char Command[80];
char InputBuffer[MAIL_FIELDLENGTH];
char seqname[160];
char filename[160];

int main(int argc, char *argv[])
{
	int run;
	struct AMMessage *Msg;
	struct Mail *Mail,*NewMail;
	struct NotifyPort *NewPort;
	ULONG sequential;
	FILE *seq;
	BPTR l;
	struct Mailbox *Mailbox;

	if (FindPort(SERVERNAME))
	{
		puts("Server already running!");
		exit(0);
	}

	ReadConfig(0);

	/* create NIL: filehandles for System() */
	if (!(AMSystemTags[0].ti_Data = (ULONG)
		Open((UBYTE *)"NIL:",MODE_OLDFILE)))
		exit(23);
	if (!(AMSystemTags[1].ti_Data = (ULONG)
		Open((UBYTE *)"NIL:",MODE_NEWFILE)))
	{
		Close((BPTR)AMSystemTags[0].ti_Data);
		exit(24);
	}

	if (!(ServerPort = CreatePort(SERVERNAME,0)))
	{
		Close((BPTR)AMSystemTags[0].ti_Data);
		Close((BPTR)AMSystemTags[1].ti_Data);
		exit(21);
	}

#if DEBUG
	kprintf("ServerPort = 0x%lx\n",(ULONG)ServerPort);
#endif

	/* initialize the Mailbox List */
	Mailboxes.lh_Head = (struct Node *)&(Mailboxes.lh_Tail);
	Mailboxes.lh_TailPred = (struct Node *)&(Mailboxes.lh_Head);
	Mailboxes.lh_Tail = 0;
	Mailboxes.lh_Type = AMNT_NOTIFY_PORT_LIST;

#if DEBUG
	kputs("Server running!\n");
#endif

	run = TRUE;
	while (run)
	{
		WaitPort(ServerPort);

		while (Msg = (struct AMMessage *)GetMsg(ServerPort))
		{
			switch(Msg->Action)
			{
				case ACT_DIE:
#if DEBUG
					kputs("contentsserver: ACTION_DIE\n");
#endif
					/* die only if no mailboxes are loaded */
					Msg->rc = IsListEmpty(&Mailboxes);
					run = !Msg->rc;
					break;

				case ACT_LOCKCONTENTS: /* Parms: Msg->Username, Msg->Folder */
#if DEBUG
					kputs("contentsserver: ACT_LOCKCONTENTS\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							(Mailbox->mb_LockCount)++;
							Msg->rc = TRUE;
						}

					if (!Msg->rc)
					{
#if DEBUG
						kputs("reading contents\n");
#endif
						if (Mailbox = (struct Mailbox *) 
							AllocMem(sizeof(struct Mailbox),MEMF_CLEAR))
						{
							strcpy(Mailbox->mb_Username,Msg->Username);
							strcpy(Mailbox->mb_Folder,Msg->Folder);

							(Mailbox->mb_LockCount)++;

							Mailbox->mb_Mailbox.lh_Head = (struct Node *)
								&(Mailbox->mb_Mailbox.lh_Tail);
							Mailbox->mb_Mailbox.lh_TailPred = (struct Node *)
								&(Mailbox->mb_Mailbox.lh_Head);
							Mailbox->mb_Mailbox.lh_Tail = 0;
							Mailbox->mb_Mailbox.lh_Type = AMNT_MAILBOX;

							Mailbox->mb_NotifyPortList.lh_Head = (struct Node *)
								&(Mailbox->mb_NotifyPortList.lh_Tail);
							Mailbox->mb_NotifyPortList.lh_TailPred = (struct Node *)
								&(Mailbox->mb_NotifyPortList.lh_Head);
							Mailbox->mb_NotifyPortList.lh_Tail = 0;
							Mailbox->mb_NotifyPortList.lh_Type = AMNT_NOTIFY_PORT_LIST;

							if (ReadContents(Msg->Folder,
								&(Mailbox->mb_Mailbox),
								&(Mailbox->mb_Strings)))
							{
								AddHead(&Mailboxes,(struct Node *)Mailbox);
								Msg->rc = TRUE;
							}
							else
							{
								sprintf(Command,"Contentsfile of user %s corrupt!",Msg->Username);
								writelog(Command);
							}
						}
					}
					break;
				
				case ACT_UNLOCKCONTENTS: /* Parms: Msg->Username, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_UNLOCKCONTENTS\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
#if 0 /* this causes enforcer hits for some reason ... :-( */
							/* reply immediately */
							Msg->rc = TRUE;
							ReplyMsg((struct Message *)Msg);
							Msg = 0;
#endif
							(Mailbox->mb_LockCount)--;

							if (!Mailbox->mb_LockCount)
							{
								if (Mailbox->mb_Changes)
								{
#if DEBUG
									kputs("Changes were made, saving...\n");
#endif
									/* Garbage collection in Headerstrings */
									GarbageCollection(&Mailbox->mb_Strings,
										&Mailbox->mb_Mailbox);
									/* write */
									WriteContents(Mailbox->mb_Folder,
										&Mailbox->mb_Mailbox,
										&Mailbox->mb_Strings);
								}
								else
								{
#if DEBUG
									kputs("No changes were made, cleanup...\n");
#endif
									FreeMailbox(&Mailbox->mb_Mailbox);
									FreeStrings(&Mailbox->mb_Strings);
								}
								Remove(&(Mailbox->mb_Node));
								FreeMem((APTR)Mailbox,sizeof(struct Mailbox));
							}

						}
					break;


				case ACT_GETCONTENTS: /* Parms: Msg->Username, Msg->Mailbox, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_GETCONTENTS\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							Msg->rc = TRUE;

							/* delete all entries in client's list */
							while (Mail = (struct Mail *)RemHead(Msg->Mailbox))
								FreeMail(Mail);

							/* give him the new list */
							for (Mail = (struct Mail *)Mailbox->mb_Mailbox.lh_Head;
								Mail->m_Node.ln_Succ;
								Mail = (struct Mail *)Mail->m_Node.ln_Succ)
							{
								/* allocate new struct */
								if (!(NewMail = AllocMem(sizeof(struct Mail),MEMF_CLEAR)))
									break; /* !!!! Client might not get the complete list */
	
								/* copy from our list */
								*NewMail = *Mail;
	
								/* add to the client's list */
								AddHead(Msg->Mailbox,(struct Node *)NewMail);
							}
						}
					break;

				case ACT_MARK_READ: /* Parms: Msg->Username, Msg->Number, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_MARK_READ\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
							kprintf("Number: %ld\n",Msg->Number);
#endif
							if (Mail = GetNum(&Mailbox->mb_Mailbox,Msg->Number))
							{
#if DEBUG
								kputs("Found item\n");
#endif
								if (!(Mail->read & MAIL_READ))
									(Mailbox->mb_Changes)++;
								Mail->read |= MAIL_READ;
	
								/* notify clients */
								Notify(&(Mailbox->mb_NotifyPortList),
									NOTIFY_ITEM_CHANGED,
									Msg->Number,0);

								Msg->rc = TRUE;
							}
						}
					break;

				case ACT_MARK_UNREAD: /* Parms: Msg->Username, Msg->Number, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_MARK_UNREAD\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							if (Mail = GetNum(&Mailbox->mb_Mailbox,Msg->Number))
							{
								if (Mail->read & MAIL_READ)
									(Mailbox->mb_Changes)++;
								Mail->read &= ~MAIL_READ;
	
								/* notify clients */
								Notify(&(Mailbox->mb_NotifyPortList),
									NOTIFY_ITEM_CHANGED,
									Msg->Number,0);

								Msg->rc = TRUE;
							}
						}
					break;

				case ACT_MARK_OLD: /* Parms: Msg->Username, Msg->Number, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_MARK_OLD\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							if (Mail = GetNum(&Mailbox->mb_Mailbox,Msg->Number))
							{
								if (Mail->read & MAIL_NEW)
									(Mailbox->mb_Changes)++;
								Mail->read &= ~MAIL_NEW;
	
								/* notify clients */
								Notify(&(Mailbox->mb_NotifyPortList),
									NOTIFY_ITEM_CHANGED,
									Msg->Number,0);

								Msg->rc = TRUE;
							}
						}
					break;

				case ACT_ADD_ITEM: /* Parms: Msg->Username, Msg->Number, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_ADD_ITEM\n");
#endif
					Msg->NewMail = 0;
					Msg->rc=FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							if (Mail = AddMail(Mailbox,Msg->Number))
							{
								/* return a copy of the new item */
								NewMail = AllocMem(sizeof(struct Mail),MEMF_CLEAR);
								if (NewMail)
									*NewMail = *Mail;

								Msg->NewMail = NewMail;
								Msg->rc = TRUE;
	
								/* give a copy of the new item to all who requested */
								Notify(&Mailbox->mb_NotifyPortList,
									NOTIFY_NEWITEM,
									Msg->Number,NewMail);

								(Mailbox->mb_Changes)++;
							}
						}
					break;
				
				case ACT_DELETE_ITEM: /* Parms: Msg->Username, Msg->Number, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_DELETE_ITEM\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							if (Mail = GetNum(&Mailbox->mb_Mailbox,Msg->Number))
							{
								Remove(&(Mail->m_Node));
								Notify(&Mailbox->mb_NotifyPortList,
									NOTIFY_ITEM_DELETED,
									Msg->Number,0);
								FreeMail(Mail);
								(Mailbox->mb_Changes)++;
								Msg->rc = TRUE;
							}
						}
					break;

				case ACT_NEW_SEQ: /* Parms: Msg->Username, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_NEQ_SEQ\n");
#endif
					Msg->rc = 0; /* 0 indicates failure */

					/* get seq */
					sprintf(seqname,"%s/.seq",Msg->Folder);
					if (seq = fopen(seqname,"r"))
					{
						fscanf(seq,"%ld",&sequential);
						fclose(seq);
					}
					else
						sequential = 1;


					sprintf(filename,"%s/%ld",Msg->Folder,sequential);
					while (l = Lock((UBYTE *)filename,ACCESS_READ)) /* file exists ? */
					{
						UnLock(l);
						sprintf(filename,"%s/%d",Msg->Folder,++sequential);
					}

					/* increment sequential */
					if (seq = fopen(seqname,"w"))
					{
						fprintf(seq,"%d",sequential+1);
						fclose(seq);
					}

					Msg->Number = sequential;
					Msg->rc = TRUE;

					break;

				case ACT_NEW_MSGID: /* Parms: Msg->Username */
#if DEBUG
					kputs("contentsserver: ACT_NEW_MSGID\n");
#endif
					GetNewMsgId(Msg->MsgId);
					break;

				case ACT_NOTIFY_ON: /* Parms: Msg->Username, Msg->NotifyPort, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_NOTIFY_ON\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							if (NewPort = AllocMem(sizeof(struct NotifyPort),MEMF_CLEAR))
							{
								NewPort->np_Node.ln_Type = AMNT_NOTIFY_PORT_LIST;
								NewPort->np_Port = Msg->NotifyPort;
								AddHead(&(Mailbox->mb_NotifyPortList),&(NewPort->np_Node));
								Msg->rc = TRUE;
							}
						}
					break;
				
				case ACT_NOTIFY_OFF: /* Parms: Msg->Username, Msg->NotifyPort, Msg->Folder  */
#if DEBUG
					kputs("contentsserver: ACT_NOTIFY_OFF\n");
#endif
					Msg->rc = FALSE;
					for (Mailbox = (struct Mailbox *) Mailboxes.lh_Head;
						Mailbox->mb_Node.ln_Succ;
						Mailbox = (struct Mailbox *) Mailbox->mb_Node.ln_Succ)
						if ((!strcmp(Mailbox->mb_Username,Msg->Username)) &&
							(!strcmp(Mailbox->mb_Folder,Msg->Folder)))
						{
#if DEBUG
							kputs("Found user/folder\n");
#endif
							for (NewPort = (struct NotifyPort *)Mailbox->mb_NotifyPortList.lh_Head;
								NewPort->np_Node.ln_Succ;
								NewPort = (struct NotifyPort *)NewPort->np_Node.ln_Succ)
								if (NewPort->np_Port == Msg->NotifyPort)
								{
									Remove(&(NewPort->np_Node));
									FreeMem(NewPort,sizeof(struct NotifyPort));
									Msg->rc = TRUE;
									break;
								}
						}
					break;

				case ACT_VERSION:
#if DEBUG
					kputs("contentsserver: ACT_VERSION\n");
#endif
					Msg->MsgId = AMIGAMAIL_VERSION;
					break;

				default:
#if DEBUG
					printf("unknown action: %ld",Msg->Action);
#endif
					break;
			}
			if (Msg)  /* Msg set to 0 if already replied */
				ReplyMsg((struct Message *)Msg);
		}
	}

	Close((BPTR)AMSystemTags[0].ti_Data);
	Close((BPTR)AMSystemTags[1].ti_Data);
	DeletePort(ServerPort);

	exit(0);
}
