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

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include <tosser/FidoNet.h>

#define BREAK  (SetSignal(0L,0L)  & SIGBREAKF_CTRL_C)

jbNewList(struct jbList *list);
jbAddNode(struct jbList *list, struct jbNode *node);
jbFreeList(struct jbList *list,struct jbNode *firstnode,ULONG sizenode);

ULONG jbcpos;

jbstrcpy(UBYTE *dest,UBYTE *src,ULONG maxlen)
{
   ULONG d=0;
   UBYTE stopchar1,stopchar2;

   while(src[jbcpos]==32 || src[jbcpos]==9) jbcpos++;

   if(src[jbcpos]=='"')
   {
      jbcpos++;
      stopchar1='"';
      stopchar2=0;
   }
   else
   {
   	stopchar1=' ';
   	stopchar2=9;
   }

   while(src[jbcpos]!=stopchar1 && src[jbcpos]!=stopchar2 && src[jbcpos]!=10 && src[jbcpos]!=0 && d<maxlen-1)
   {
      if(src[jbcpos]=='\\' && src[jbcpos+1]!=0 && src[jbcpos+1]!=10)
      {
         jbcpos++;
         dest[d++]=src[jbcpos++];
      }

      else
         dest[d++]=src[jbcpos++];
   }
   dest[d]=0;
   if(src[jbcpos]==9 || src[jbcpos]==' ' || src[jbcpos]=='"') jbcpos++;

   if(d!=0 || stopchar1=='"') return(TRUE);

   return(FALSE);
}

struct jbList
{
   struct jbNode *First;
   struct jbNode *Last;
};

struct jbNode
{
   struct jbNode *Next;
};

#define VERSION "1.2"

UBYTE *ver="$VER: CrashMaint "VERSION" ("__COMMODORE_DATE__")";

struct Area
{
   struct Area *Next;
   UBYTE Tagname[80];
   UBYTE Path[80];
   ULONG KeepNum,KeepDays;
};

struct jbList AreaList;

struct Msg
{
   struct Msg *Next;
   ULONG Num;
   ULONG NewNum;
   ULONG Day;
};

struct jbList MsgList;

#define MAINT     0
#define RENUMBER  1
#define VERBOSE   2
#define SETTINGS  3
#define PATTERN   4

ULONG argarray[]={NULL,NULL,NULL,NULL,NULL};
UBYTE argstr[]="MAINT/S,RENUMBER/S,VERBOSE/S,SETTINGS/K,PATTERN=PAT/K";

struct RDArgs *rdargs;

ULONG today;

CleanUp(int err)
{
   if(rdargs) FreeArgs(rdargs);
   jbFreeList(&AreaList,AreaList.First,sizeof(struct Area));
   _exit(err);
}

UBYTE *MonthNames[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

MakeFidoDate(UBYTE *dest)
{
   struct DateTime dt;
   UBYTE date[40],time[40];

   DateStamp(&dt.dat_Stamp);
   dt.dat_Format=FORMAT_CDN;
   dt.dat_Flags=0;
   dt.dat_StrDay=NULL;
   dt.dat_StrDate=date;
   dt.dat_StrTime=time;
   DateToStr(&dt);

   date[2]=0;
   date[5]=0;

   sprintf(dest,"%s %s %s  %s",date,MonthNames[atoi(&date[3])-1],&date[6],time);
}

int Compare(struct Msg **m1,struct Msg **m2)
{
   if((*m1)->Num > (*m2)->Num) return(1);
   if((*m1)->Num < (*m2)->Num) return(-1);
   return(0);
}

BOOL Sort(struct jbList *list)
{
   struct Msg *msg,**buf,**work;
   ULONG alloc,nodecount = 0;

   for(msg=list->First;msg;msg=msg->Next)
      nodecount++;

   if(!nodecount) return;

   if(!(buf=(struct FileToProcess **)AllocMem(nodecount * 4,MEMF_ANY)))
      return(FALSE);

   alloc=nodecount*4;

   work=buf;

   for(msg=list->First;msg;msg=msg->Next)
      *work++=msg;

   qsort(buf,nodecount,4,Compare);

   jbNewList(list);

   for(work=buf;nodecount--;)
      jbAddNode(list,(struct jbNode *)*work++);

   FreeMem(buf,alloc);
   return(TRUE);
}

struct StoredMsg StoredMsg;

ProcessArea(struct Area *area)
{
   BPTR l;
   struct FileInfoBlock *fib;
   struct Msg *msg;
   ULONG del,num;
   UBYTE buf[100],newbuf[100],buf2[20];
   ULONG highwater,oldhighwater;
   BPTR fh;

   highwater=0;
   oldhighwater=0;

   jbNewList(&MsgList);

   Printf("Processing %s...\n",area->Tagname);

   if(l=Lock(area->Path,SHARED_LOCK))
   {
      if(!(fib=(struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock),MEMF_CLEAR)))
      {
         Printf("Out of memory\n");
         UnLock(l);
         CleanUp(10);
      }

      Examine(l,fib);

      while(ExNext(l,fib) && !BREAK)
         if(strlen(fib->fib_FileName)>=5)
            if(stricmp(&fib->fib_FileName[strlen(fib->fib_FileName)-4],".msg")==0)
            {
               if(atol(fib->fib_FileName) > 1)
               {
                  if(!(msg=(struct Msg *)AllocMem(sizeof(struct Msg),MEMF_CLEAR)))
                  {
                     Printf("Out of memory\n");
                     jbFreeList(&MsgList,MsgList.First,sizeof(struct Msg));
                     UnLock(l);
                     FreeMem(fib,sizeof(struct FileInfoBlock));
                     CleanUp(10);
                  }

                  jbAddNode(&MsgList,(struct jbNode *)msg);

                  msg->Num=atol(fib->fib_FileName);
                  msg->Day=fib->fib_Date.ds_Days;
               }
            }

      UnLock(l);
      FreeMem(fib,sizeof(struct FileInfoBlock));
   }
   else
   {
      Printf(" Error: Couldn't scan directory %s\n",area->Path);
      return;
   }

   if(MsgList.First && !BREAK)
   {
      strcpy(buf,area->Path);
      AddPart(buf,"1.msg",100);

      if(fh=Open(buf,MODE_OLDFILE))
      {
         if(Read(fh,&StoredMsg,sizeof(struct StoredMsg))==sizeof(struct StoredMsg))
         {
            highwater=StoredMsg.ReplyTo;
            oldhighwater=StoredMsg.ReplyTo;
         }
         Close(fh);
      }

      if(!Sort(&MsgList))
      {
         jbFreeList(&MsgList,MsgList.First,sizeof(struct Msg));
         Printf("Out of memory\n");
         CleanUp(10);
      }

      if(argarray[MAINT] && area->KeepNum!=0)
      {
         num=0;

         for(msg=MsgList.First;msg;msg=msg->Next)
            num++;

         msg=MsgList.First;
         del=0;

         while(num>area->KeepNum && !BREAK)
         {
            while(msg->Num==0)
               msg=msg->Next;

            strcpy(buf,area->Path);
            sprintf(buf2,"%lu.msg",msg->Num);
            AddPart(buf,buf2,100);

            if(msg->Num == highwater)
               highwater=0;

            if(argarray[VERBOSE])
               Printf(" Deleting message #%lu\n",msg->Num);

            DeleteFile(buf);
            msg->Num=0;
            num--;
            del++;
         }

         Printf(" %lu messages deleted, %lu messages left\n",del,num);
      }

      if(argarray[MAINT] && area->KeepDays!=0)
      {
         num=0;
         del=0;

         for(msg=MsgList.First;msg && !BREAK;msg=msg->Next)
         {
            if(today - msg->Day > area->KeepDays && msg->Num!=0)
            {
               strcpy(buf,area->Path);
               sprintf(buf2,"%lu.msg",msg->Num);
               AddPart(buf,buf2,100);

               if(msg->Num == highwater)
                  highwater=0;

               if(argarray[VERBOSE])
                  Printf(" Deleting message #%lu\n",msg->Num);

               DeleteFile(buf);
               msg->Num=0;
               del++;
            }
            else
            {
               num++;
            }
         }

         Printf(" %lu messages deleted, %lu messages left\n",del,num);
      }

      if(argarray[RENUMBER])
      {
         num=2;
         msg=MsgList.First;

         while(msg && !BREAK)
         {
            while(msg && msg->Num==0)
               msg=msg->Next;

            if(msg)
            {
               msg->NewNum=num;
               num++;
               msg=msg->Next;
            }
         }

         for(msg=MsgList.First;msg;msg=msg->Next)
            if(msg->Num!=0 && msg->Num!=msg->NewNum)
            {
               strcpy(buf,area->Path);
               sprintf(buf2,"%lu.msg",msg->Num);
               AddPart(buf,buf2,100);

               strcpy(newbuf,area->Path);
               sprintf(buf2,"%lu.msg",msg->NewNum);
               AddPart(newbuf,buf2,100);

               if(highwater == msg->Num)
                  highwater=msg->NewNum;

               if(argarray[VERBOSE])
                  Printf(" Renaming message %lu to %lu\n",msg->Num,msg->NewNum);

               Rename(buf,newbuf);
            }
         Printf(" Area renumbered\n");
      }
   }
   else if(!BREAK)
   {
      Printf(" Area is empty\n");
   }

   jbFreeList(&MsgList,MsgList.First,sizeof(struct Msg));

   if(highwater!=oldhighwater)
   {
      strcpy(StoredMsg.From,"CrashMail");
      strcpy(StoredMsg.To,"All");
      strcpy(StoredMsg.Subject,"HighWater mark");
      MakeFidoDate(StoredMsg.DateTime);

      StoredMsg.TimesRead=0;
      StoredMsg.DestNode=0;
      StoredMsg.OrigNode=0;
      StoredMsg.Cost=0;
      StoredMsg.OrigNet=0;
      StoredMsg.DestNet=0;
      StoredMsg.DestZone=0;
      StoredMsg.OrigZone=0;
      StoredMsg.OrigPoint=0;
      StoredMsg.DestPoint=0;
      StoredMsg.ReplyTo=highwater;
      StoredMsg.Attr=SENT | PVT;
      StoredMsg.NextReply=0;

      strcpy(buf,area->Path);
      AddPart(buf,"1.msg",100);

      if(fh=Open(buf,MODE_NEWFILE))
      {
         Write(fh,&StoredMsg,sizeof(struct StoredMsg));
         Write(fh,"",1);
         Close(fh);
      }
   }
}

UBYTE cfgbuf[4000];

BOOL ReadConfig(UBYTE *file)
{
   BPTR fh;
   UBYTE cfgword[20];
   UBYTE tag[80];
   UBYTE path[80];
   struct Area *tmparea,*LastArea;

   if(!(fh=Open(file,MODE_OLDFILE)))
      return(FALSE);

   while(FGets(fh,cfgbuf,4000))
   {
      jbcpos=0;
      jbstrcpy(cfgword,cfgbuf,20);

      if(stricmp(cfgword,"KEEPDAYS")==0 && LastArea)
      {
         if(jbstrcpy(tag,cfgbuf,80))
            LastArea->KeepDays=atol(tag);
      }

      if(stricmp(cfgword,"KEEPNUM")==0 && LastArea)
      {
         if(jbstrcpy(tag,cfgbuf,80))
            LastArea->KeepNum=atol(tag);
      }

      if(stricmp(cfgword,"AREA")==0 || stricmp(cfgword,"NETMAIL")==0)
      {
         jbstrcpy(tag,cfgbuf,80);

         jbstrcpy(path,cfgbuf,80);
         jbstrcpy(path,cfgbuf,80);

         if(stricmp(path,"MSG")==0 && stricmp(tag,"DEFAULT")!=0)
         {
            if(jbstrcpy(path,cfgbuf,80))
            {
               if(!(tmparea=(struct Area *)AllocMem(sizeof(struct Area),MEMF_CLEAR)))
               {
                  Printf("Out of memory\n");
                  Close(fh);
                  CleanUp(10);
               }

               jbAddNode(&AreaList,(struct jbNode *)tmparea);
               LastArea=tmparea;

               strcpy(tmparea->Tagname,tag);
               strcpy(tmparea->Path,path);
            }
         }
      }
   }
   Close(fh);
   return(TRUE);
}

_main()
{
   struct Area *area;
   struct DateStamp ds;
   BOOL config;
   UBYTE parsedpattern[400];
   
   DateStamp(&ds);
   today=ds.ds_Days;

   jbNewList(&AreaList);

   if(!(rdargs=(struct RDArgs *)ReadArgs(argstr,argarray,NULL)))
   {
      PrintFault(IoErr(),NULL);
      CleanUp(10);
   }

   if(argarray[MAINT]==0 && argarray[RENUMBER]==0)
   {
      PutStr("Nothing to do.\n");
      CleanUp(0);
   }

   if(argarray[PATTERN])
   {
      if(ParsePatternNoCase((UBYTE *)argarray[PATTERN],parsedpattern,399)==-1)
      {
         PrintFault(IoErr(),(UBYTE *)argarray[PATTERN]);
         CleanUp(0);
      }
   }

   config=FALSE;

   if(argarray[SETTINGS]==NULL)
   {
      if(ReadConfig("PROGDIR:CrashMail.prefs"))
         config=TRUE;

      else if(ReadConfig("MAIL:CrashMail.prefs"))
         config=TRUE;

      if(!config)
      {
         Printf("Couldn't read config file PROGDIR:CrashMail.prefs or MAIL:CrashMail.prefs\n");
         CleanUp(10);
      }
   }
   else
   {
      if(!ReadConfig((UBYTE *)argarray[SETTINGS]))
      {
         Printf("Couldn't read config file %s\n",(long)argarray[SETTINGS]);
         CleanUp(10);
      }
   }

   for(area=AreaList.First;area && !BREAK;area=area->Next)
   {
      BOOL match;

      match=FALSE;

      if(!argarray[PATTERN])
         match=TRUE;

      else if(MatchPatternNoCase(parsedpattern,area->Tagname))
         match=TRUE;

      if(match)
         ProcessArea(area);
   }

   if(BREAK)
      Printf("*** Break\n");

   CleanUp(0);
}


/*
   Break
   Config
   Days
*/