#include <CrashTick.h>
#include <misc.h>

#include <dos/dos.h>

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

extern APTR mempool;

uchar raidbuf[500];
extern struct Config config;

void rawSendInfo(struct jbFile *jb,struct Node4D *from4d,uchar *toname,struct ConfigNode *cnode);
void convertstar(uchar *buf,ulong len);

short rawSendList(struct jbFile *jb,uword type,struct Node4D *from4d,uchar *toname,struct ConfigNode *cnode);
void rawSendHelp(struct jbFile *jb,struct Node4D *from4d,uchar *toname,struct ConfigNode *cnode);

void rInitMessage(struct jbFile *jb,uchar *fromname,uchar *toname,struct Node4D *from4d,struct Node4D *to4d,UBYTE *subject);
void rAddLine(struct jbFile *jb,UBYTE *fmt,...);
void rCloseMessage(struct jbFile *jb);

short Raid(uchar *file)
{
   BPTR fh;
   struct jbFile *pktjb;
   uchar nodebuf[20],buf[50],pattern[105];
   uword c,d,iswild;
   short hasfrom,hasto,hassubj,stop,done,hadline;
   struct Node4D from4d,to4d;
   uchar subject[72],fromname[36],errorbuf[100];
   uchar password[50];
   struct ConfigNode *cnode;
   short sendhelp,sendquery,sendunlinked,sendlist,sendinfo;

   if(!(fh=Open(file,MODE_OLDFILE)))
   {
      LogWrite(1,RAID,getString(RAID_FILE_NOT_FOUND),file);
      return(10);
   }

   hadline=FALSE;
   
   hasfrom=FALSE;
   hasto=FALSE;
   hassubj=FALSE;

   stop=FALSE;

   while(!stop && FGets(fh,raidbuf,500))
   {
      if(raidbuf[0] && raidbuf[strlen(raidbuf)-1]=='\n')
          raidbuf[strlen(raidbuf)-1]=0;

      if(raidbuf[0]==0)
      {
         stop=TRUE;
      }
      else if(strnicmp(raidbuf,"From:",5)==0)
      {
         c=5;
         d=0;

         while(raidbuf[c]==' ')
            c++;

         while(d!=19 && raidbuf[c]!=0 && raidbuf[c]!='@' && raidbuf[c]!=' ')
            nodebuf[d++]=raidbuf[c++];

         nodebuf[d]=0;

         if(Parse4D(nodebuf,&from4d))
         {
            if(raidbuf[c]=='@')
            {
               c++;

               while(raidbuf[c]!=' ' && raidbuf[c]!=0)
                  c++;
            }

            while(raidbuf[c]==' ')
               c++;

            if(raidbuf[c]=='(')
            {
               c++;
               d=0;

               while(d!=35 && raidbuf[c]!=0 && raidbuf[c]!=')')
                  fromname[d++]=raidbuf[c++];

               fromname[d]=0;

               if(fromname[0] != 0)
                  hasfrom=TRUE;
            }
         }
      }
      else if(strnicmp(raidbuf,"To:",3)==0)
      {
         c=3;
         d=0;

         while(raidbuf[c]==' ')
            c++;

         while(d!=19 && raidbuf[c]!=0 && raidbuf[c]!='@' && raidbuf[c]!=' ')
            nodebuf[d++]=raidbuf[c++];

         nodebuf[d]=0;

         if(Parse4D(nodebuf,&to4d))
            hasto=TRUE;
      }
      else if(strnicmp(raidbuf,"Subject:",8)==0)
      {
         c=8;

         while(raidbuf[c]==' ')
            c++;

         if(raidbuf[c]!=0)
         {
            hassubj=TRUE;
            strncpy(subject,&raidbuf[c],71);
            subject[71]=0;
         }
      }
   }

   if(!hasfrom || !hasto || !hassubj)
   {
      if(!hasfrom)
         LogWrite(1,RAID,getString(RAID_PARSE_NO_FROM),file);

      if(!hasto)
         LogWrite(1,RAID,getString(RAID_PARSE_NO_TO),file);

      if(!hassubj)
         LogWrite(1,RAID,getString(RAID_PARSE_NO_SUBJECT),file);

      Close(fh);
      return(10);
   }

   LogWrite(4,RAID,getString(RAID_GOT_REQUEST_FROM),
      fromname,
      from4d.Zone,from4d.Net,from4d.Node,from4d.Point);

   if(!(pktjb=CreatePacket(&to4d,errorbuf)))
   {
      LogWrite(1,RAID,"%s",errorbuf);
      Close(fh);
      return(10);
   }

   rInitMessage(pktjb,config.cfg_Sysop,fromname,&to4d,&from4d,"Raid response");

   for(cnode=(struct ConfigNode *)config.CNodeList.First;cnode;cnode=cnode->Next)
      if(Compare4D(&cnode->Node,&from4d)==0) break;

   if(!cnode)
   {
      LogWrite(3,RAID,"%s",getString(RAID_REJECTED_UNCONFIG));
      rAddLine(pktjb,"Sorry, your request cannot be served since you are not configured here.");
      rCloseMessage(pktjb);
      ClosePacket(pktjb);
      Close(fh);
      return(0);
   }

   jbcpos=0;
   jbstrcpy(password,subject,50);

   if(stricmp(password,cnode->RaidPW)!=0)
   {
      LogWrite(3,RAID,getString(RAID_REJECTED_WRONG_PW),password);
      rAddLine(pktjb,"Sorry, wrong password \"%s\"",password);
      rCloseMessage(pktjb);
      ClosePacket(pktjb);
      Close(fh);
      return(0);
   }

   stop=FALSE;

   sendlist=FALSE;
   sendquery=FALSE;
   sendunlinked=FALSE;
   sendhelp=FALSE;
   sendinfo=FALSE;

   while(!stop && FGets(fh,raidbuf,500))
   {
      if(raidbuf[0] && raidbuf[strlen(raidbuf)-1]=='\n')
          raidbuf[strlen(raidbuf)-1]=0;

      if(raidbuf[0])
      {
         hadline=TRUE;

         if(strncmp(raidbuf,"---",3)==0)
         {
            stop=TRUE;
         }
         else if(raidbuf[0]=='%')
         {
            jbcpos=0;
            jbstrcpy(buf,raidbuf,50);

            if(stricmp(buf,"%PAUSE")==0)
            {
               if(cnode->Flags & NODE_PASSIVE)
               {
                  rAddLine(pktjb,"%-30.30s Your system is already passive",raidbuf);
               }
               else
               {
                  cnode->Flags|=NODE_PASSIVE;
                  config.configchanged=TRUE;
                  rAddLine(pktjb,"%-30.30s Your system is marked as passive.",raidbuf);
                  rAddLine(pktjb,"%-30.20s Send %%RESUME to get files again.","");
               }
            }
            else if(stricmp(buf,"%RESUME")==0)
            {
               if(cnode->Flags & NODE_PASSIVE)
               {
                  cnode->Flags&=~NODE_PASSIVE;
                  config.configchanged=TRUE;
                  rAddLine(pktjb,"%-30.30s Your system is active again.",raidbuf);
               }
               else
               {
                  rAddLine(pktjb,"%-30.30s Your system is not paused.",raidbuf);
               }
            }
            else if(stricmp(buf,"%PWD")==0)
            {
               if(jbstrcpy(buf,raidbuf,40))
               {
                  strcpy(cnode->RaidPW,buf);
                  config.configchanged=TRUE;
                  rAddLine(pktjb,"%-30.30s Raid password changed.",raidbuf);
               }
               else
               {
                  rAddLine(pktjb,"%-30.30s No new password specified.",raidbuf);
               }
            }
            else if(stricmp(buf,"%LIST")==0)
            {
               sendlist=TRUE;
               rAddLine(pktjb,"%-30.30s Sending list of all areas",raidbuf);
            }
            else if(stricmp(buf,"%QUERY")==0)
            {
               sendquery=TRUE;
               rAddLine(pktjb,"%-30.30s Sending query",raidbuf);
            }
            else if(stricmp(buf,"%UNLINKED")==0)
            {
               sendunlinked=TRUE;
               rAddLine(pktjb,"%-30.30s Sending list of unlinked areas",raidbuf);
            }
            else if(stricmp(buf,"%HELP")==0)
            {
               sendhelp=TRUE;
               rAddLine(pktjb,"%-30.30s Sending helpfile",raidbuf);
            }
            else if(stricmp(buf,"%INFO")==0)
            {
               sendinfo=TRUE;
               rAddLine(pktjb,"%-30.30s Sending configuration info",raidbuf);
            }
            else
            {
               rAddLine(pktjb,"%-30.30s Unknown command",raidbuf);
            }
         }
         else if(raidbuf[0]=='-')
         {
            struct Area *area;
            struct TickNode *tn;

            strncpy(buf,&raidbuf[1],50);
            buf[49]=0;

            convertstar(buf,50);

            iswild=ParsePatternNoCase(buf,pattern,104);

            if(iswild)
            {
               rAddLine(pktjb,"%s",raidbuf);
               rAddLine(pktjb,"");
            }
            
            done=FALSE;

            for(area=(struct Area *)config.AreaList.First;area;area=area->Next)
            {
               if(MatchPatternNoCase(pattern,area->Name))
               {
                  for(tn=(struct TickNode *)area->TickNodes.First;tn;tn=tn->Next)
                     if(tn->ConfigNode == cnode) break;

                  if(tn)
                  {
                     done=TRUE;

                     if(area->Flags & AREA_MANDATORY)
                     {
                        if(iswild)
                           rAddLine(pktjb,"   You are not allowed to detach from %s",area->Name);

                        else
                           rAddLine(pktjb,"%-30.30s You are not allowed to detach from that area",raidbuf);
                     }
                     else
                     {
                         LogWrite(4,RAID,getString(RAID_DETACHED),area->Name);
                         jbFreeNode(&area->TickNodes,(struct jbNode *)tn,sizeof(struct TickNode));
                         config.configchanged=TRUE;

                         if(iswild)
                            rAddLine(pktjb,"   Detached from %s",area->Name);

                         else
                            rAddLine(pktjb,"%-30.30s Detached",raidbuf);
                     }
                  }
               }
            }

            if(!done)
            {
               if(iswild)
                  rAddLine(pktjb,"   You were not connected to any matching areas");

               else
                  rAddLine(pktjb,"%-30.30s You are not attached to that area",raidbuf);
            }

            if(iswild)
               rAddLine(pktjb,"");
         }
         else
         {
            struct Area *area;
            struct TickNode *tn;

            if(raidbuf[0]=='+')
               strncpy(buf,&raidbuf[1],50);

            else
               strncpy(buf,raidbuf,50);

            buf[49]=0;

            convertstar(buf,50);

            iswild=ParsePatternNoCase(buf,pattern,104);

            if(iswild)
            {
               rAddLine(pktjb,"%s",raidbuf);
               rAddLine(pktjb,"");
            }

            done=FALSE;

            for(area=(struct Area *)config.AreaList.First;area;area=area->Next)
            {
               if(MatchPatternNoCase(pattern,area->Name))
               {
                  for(tn=(struct TickNode *)area->TickNodes.First;tn;tn=tn->Next)
                     if(tn->ConfigNode == cnode) break;

                  if(!tn)
                  {
                     if(CheckFlags(area->Group,cnode->FullAccessGroups) || CheckFlags(area->Group,cnode->ReadOnlyGroups) || CheckFlags(area->Group,cnode->WriteOnlyGroups))
                     {
                        done=TRUE;

                        if(!(tn=AllocCleared(sizeof(struct TickNode))))
                        {
                           uchar namebuf[200];
                           NameFromFH(pktjb->fh,namebuf,200);
                           jbClose(pktjb);
                           DeleteFile(namebuf);
                           Close(fh);
                           LogWrite(1,SYSTEMERR,"%s",getString(ERR_OUT_OF_MEMORY));
                           return(10);
                        }

                        tn->ConfigNode=cnode;
                        jbAddNode(&area->TickNodes,(struct jbNode *)tn);
                        config.configchanged=TRUE;

                        if(CheckFlags(area->Group,cnode->ReadOnlyGroups) || area->Flags & AREA_DEFREADONLY)
                        {
                           tn->Flags|=TICKNODE_READONLY;

                           if(iswild)
                              rAddLine(pktjb,"   Attached to %s with receive-only access",area->Name);

                           else
                              rAddLine(pktjb,"%-30.30s Attached with receive-only access",raidbuf);

                           LogWrite(4,RAID,getString(RAID_ATTACHED_READ_ONLY),area->Name);
                        }
                        else if(CheckFlags(area->Group,cnode->WriteOnlyGroups))
                        {
                           tn->Flags|=TICKNODE_WRITEONLY;

                           if(iswild)
                              rAddLine(pktjb,"   Attached to %s with send-only access",area->Name);

                           else
                              rAddLine(pktjb,"%-30.30s Attached with send-only access",raidbuf);

                           LogWrite(4,RAID,getString(RAID_ATTACHED_SEND_ONLY),area->Name);
                        }
                        else
                        {
                           if(iswild)
                              rAddLine(pktjb,"   Attached to %s",area->Name);

                           else
                              rAddLine(pktjb,"%-30.30s Attached",raidbuf);

                           LogWrite(4,RAID,getString(RAID_ATTACHED),area->Name);
                        }
                     }
                  }
                  else
                  {
                     done=TRUE;

                     if(iswild)
                        rAddLine(pktjb,"   You are already attached to %s",area->Name);

                     else
                        rAddLine(pktjb,"%-30.30s You are already attached to that area",raidbuf);
                  }
               }
            }

            if(!done)
            {
               LogWrite(3,RAID,getString(RAID_UNKNOWN_AREA_NO_ACCESS),buf);

               if(iswild)
                  rAddLine(pktjb,"   There were no matching areas to connect to");

               else
                  rAddLine(pktjb,"%-30.30s Unknown area",raidbuf);
            }

            if(iswild)
               rAddLine(pktjb,"");
         }
      }
   }

   if(!hadline)
      rAddLine(pktjb,"Nothing to do.");

   rCloseMessage(pktjb);

   if(sendhelp)
   {
      rawSendHelp(pktjb,&to4d,fromname,cnode);
   }

   if(sendinfo)
   {
      rawSendInfo(pktjb,&to4d,fromname,cnode);
   }

   if(sendlist)
   {
      rawSendList(pktjb,SENDLIST_FULL,&to4d,fromname,cnode);
   }

   if(sendquery)
   {
      rawSendList(pktjb,SENDLIST_QUERY,&to4d,fromname,cnode);
   }

   if(sendunlinked)
   {
      rawSendList(pktjb,SENDLIST_UNLINKED,&to4d,fromname,cnode);
   }

   ClosePacket(pktjb);

   Close(fh);

   if(config.configchanged)
   {
      if(!WriteConfig(&config,errorbuf))
         LogWrite(1,SYSTEMERR,errorbuf);
   }

   return(0);
}

void rawSendInfo(struct jbFile *jb,struct Node4D *from4d,uchar *toname,struct ConfigNode *cnode)
{
   uchar c;

   LogWrite(4,RAID,getString(RAID_SENDING_INFO),
      cnode->Node.Zone,
      cnode->Node.Net,
      cnode->Node.Node,
      cnode->Node.Point);

   rInitMessage(jb,config.cfg_Sysop,toname,from4d,&cnode->Node,"Raid configuration info");

   rAddLine(jb,"Configuration for %lu:%lu/%lu.%lu:",
      cnode->Node.Zone,
      cnode->Node.Net,
      cnode->Node.Node,
      cnode->Node.Point);

   rAddLine(jb,"");

   rAddLine(jb,"        Sysop: %s",cnode->SysopName);
   rAddLine(jb,"Tick password: %s",cnode->TickPW);
   rAddLine(jb,"Raid password: %s",cnode->RaidPW);

   if(cnode->Packmode == 0 || cnode->Packer == PACKER_NONE)
   {
      rAddLine(jb,"     Packmode: Don't pack");
   }
   else
   {
      uchar buf[100];

      buf[0]=0;

      if(cnode->Packmode == PACKMODE_PACKALL)
         strcpy(buf,"     Packmode: Pack files and tic");

      if(cnode->Packmode == PACKMODE_PACKTIC)
         strcpy(buf,"     Packmode: Pack tic only");

      if(cnode->Packer == PACKER_LHA)
         strcat(buf,", pack with LHA");

      if(cnode->Packer == PACKER_ZIP)
         strcat(buf,", pack with ZIP");

      rAddLine(jb,buf);
   }

   switch(cnode->Pri)
   {
      case PRI_NORMAL:
         rAddLine(jb,"     File pri: Normal");
         break;

      case PRI_CRASH:
         rAddLine(jb,"     File pri: Crash");
         break;

      case PRI_DIRECT:
         rAddLine(jb,"     File pri: Direct");
         break;

      case PRI_HOLD:
         rAddLine(jb,"     File pri: Hold");
         break;
   }

   rAddLine(jb,"");

   if(cnode->Flags & NODE_PASSIVE)
      rAddLine(jb," * You are passive and will not receive any files");

   if(cnode->Flags & NODE_NOTIFY)
      rAddLine(jb," * You will receive notifications");

   if(cnode->Flags & NODE_AUTOADD)
      rAddLine(jb," * New areas from you will be auto-added");

   if(cnode->Flags & NODE_TINYSEENBY)
      rAddLine(jb," * You will receive .tic files with tiny seen-by section");

   rAddLine(jb,"");
   rAddLine(jb,"You have full access to these groups:");
   rAddLine(jb,"");

   for(c='A';c<='Z';c++)
      if(CheckFlags(c,cnode->FullAccessGroups))
      {
         if(config.cfg_GroupNames[c-'A'][0]!=0)
            rAddLine(jb,"%lc: %s",c,config.cfg_GroupNames[c-'A']);

         else
            rAddLine(jb,"%lc",c);
      }

   rAddLine(jb,"");
   rAddLine(jb,"You have receive-only access to these groups:");
   rAddLine(jb,"");

   for(c='A';c<='Z';c++)
      if(CheckFlags(c,cnode->ReadOnlyGroups))
      {
         if(config.cfg_GroupNames[c-'A'])
            rAddLine(jb,"%lc: %s",c,config.cfg_GroupNames[c-'A']);

         else
            rAddLine(jb,"%lc",c);
      }

   rAddLine(jb,"");
   rAddLine(jb,"You have send-only access to these groups:");
   rAddLine(jb,"");

   for(c='A';c<='Z';c++)
      if(CheckFlags(c,cnode->WriteOnlyGroups))
      {
         if(config.cfg_GroupNames[c-'A'])
            rAddLine(jb,"%lc: %s",c,config.cfg_GroupNames[c-'A']);

         else
            rAddLine(jb,"%lc",c);
      }

   rCloseMessage(jb);
}

void rawSendHelp(struct jbFile *jb,struct Node4D *from4d,uchar *toname,struct ConfigNode *cnode)
{
   struct jbFile *infh;
   uchar helpbuf[100];

   LogWrite(4,RAID,getString(RAID_SENDING_HELP),
      cnode->Node.Zone,
      cnode->Node.Net,
      cnode->Node.Node,
      cnode->Node.Point);

   if(!(infh=jbOpen(config.cfg_RaidHelp,MODE_OLDFILE,5000)))
   {
      LogWrite(4,RAID,getString(RAID_NO_HELPFILE),config.cfg_RaidHelp);
      return;
   }

   rInitMessage(jb,config.cfg_Sysop,toname,from4d,&cnode->Node,"Raid help");

   while(jbFGets(infh,helpbuf,100))
   {
      if(helpbuf[0]!=0)
         helpbuf[strlen(helpbuf)-1]=0;

      rAddLine(jb,"%s",helpbuf);
   }

   jbClose(infh);

   rCloseMessage(jb);
}

struct SortStat
{
   struct SortStat *Next;
   struct Area *Area;
};

int CompareSort(struct SortStat **s1, struct SortStat **s2)
{
   if((*s1)->Area->Group < (*s2)->Area->Group) return(-1);
   if((*s1)->Area->Group > (*s2)->Area->Group) return(1);

   return(stricmp((*s1)->Area->Name,(*s2)->Area->Name));
}

void SortSortStat(struct jbList *list)
{
   ULONG nc,alloc;
   struct SortStat *ss,**buf,**work;

   nc=0;

   for(ss=(struct SortStat *)list->First;ss;ss=ss->Next)
      nc++;

   if(nc==0)
      return;

   alloc=nc*4;

   if(!(buf=(struct SortStat **)LibAllocPooled(mempool,alloc)))
      return;

   work=buf;

   for(ss=(struct SortStat *)list->First;ss;ss=ss->Next)
       *work++=ss;

   qsort(buf,nc,4,CompareSort);

   jbNewList(list);

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

   LibFreePooled(mempool,buf,alloc);
}

extern ulong DayStatsWritten;

void CalculateWeekKB(struct Area *area,unsigned char *buf)
{
   if(area->FirstTime.ds_Days == 0 || DayStatsWritten == 0)
   {
      strcpy(buf,"?");
   }
   else
   {
      int c;
      unsigned long sum,days;

      days=DayStatsWritten-area->FirstTime.ds_Days;
      if(days > 7) days=7;

      sum=0;

      for(c=1;c<days+1;c++)
         sum+=area->Last8DaysBytes[c];

      sprintf(buf,"%lu",sum/1024);
   }
}

short rawSendList(struct jbFile *jb,uword type,struct Node4D *from4d,uchar *toname,struct ConfigNode *cnode)
{
   uchar buf[50];
   struct TickNode *tn;
   struct Area *area;
   struct jbList SortList;
   struct DateStamp ds;
   struct SortStat *ss,*lastss;
   short sendlisttotal,sendlistlinked;
   uchar ast;

   switch(type)
   {
      case SENDLIST_QUERY:
         LogWrite(4,RAID,getString(RAID_SENDING_QUERY),
            cnode->Node.Zone,
            cnode->Node.Net,
            cnode->Node.Node,
            cnode->Node.Point);
         break;

      case SENDLIST_UNLINKED:
         LogWrite(4,RAID,getString(RAID_SENDING_UNLINKED),
            cnode->Node.Zone,
            cnode->Node.Net,
            cnode->Node.Node,
            cnode->Node.Point);
         break;

      case SENDLIST_FULL:
         LogWrite(4,RAID,getString(RAID_SENDING_LIST),
            cnode->Node.Zone,
            cnode->Node.Net,
            cnode->Node.Node,
            cnode->Node.Point);
         break;
   }         

   jbNewList(&SortList);

   DateStamp(&ds);

   for(area=(struct Area *)config.AreaList.First;area;area=area->Next)
   {
      short add;

      for(tn=(struct TickNode *)area->TickNodes.First;tn;tn=tn->Next)
         if(tn->ConfigNode == cnode) break;

      add=FALSE;

      switch(type)
      {
         case SENDLIST_QUERY:
            if(tn) add=TRUE;
            break;

         case SENDLIST_UNLINKED:
            if((!tn) && (CheckFlags(area->Group,cnode->FullAccessGroups) || CheckFlags(area->Group,cnode->ReadOnlyGroups) || CheckFlags(area->Group,cnode->WriteOnlyGroups))) add=TRUE;
            break;

         case SENDLIST_FULL:
            if(tn || CheckFlags(area->Group,cnode->FullAccessGroups) || CheckFlags(area->Group,cnode->ReadOnlyGroups) || CheckFlags(area->Group,cnode->WriteOnlyGroups)) add=TRUE;
            break;
      }

      if(add)
      {
         if(!(ss=(struct SortStat *)AllocCleared(sizeof(struct SortStat))))
         {
            jbFreeList(&SortList,sizeof(struct SortStat));
            LogWrite(1,SYSTEMERR,"%s",getString(ERR_OUT_OF_MEMORY));
            return(10);
         }

         jbAddNode(&SortList,(struct jbNode *)ss);
         ss->Area=area;
      }
   }

   SortSortStat(&SortList);

   rInitMessage(jb,config.cfg_Sysop,toname,from4d,&cnode->Node,"Raid list of areas");

   switch(type)
   {
      case SENDLIST_QUERY:
         rAddLine(jb,"This is a list of all connected file areas at %lu:%lu/%lu.%lu:",
            from4d->Zone,
            from4d->Net,
            from4d->Node,
            from4d->Point);
         break;

      case SENDLIST_FULL:
         rAddLine(jb,"This is a list of all available file areas at %lu:%lu/%lu.%lu:",
            from4d->Zone,
            from4d->Net,
            from4d->Node,
            from4d->Point);
         break;

      case SENDLIST_UNLINKED:
         rAddLine(jb,"This is a list of all unlinked file areas at %lu:%lu/%lu.%lu:",
            from4d->Zone,
            from4d->Net,
            from4d->Node,
            from4d->Point);
         break;
   }

   rAddLine(jb,"");

   lastss=NULL;

   sendlisttotal=0;
   sendlistlinked=0;

   for(ss=(struct SortStat *)SortList.First;ss;ss=ss->Next)
   {
      if(!lastss || lastss->Area->Group!=ss->Area->Group)
      {
         if(lastss)
            rAddLine(jb,"");

         if(ss->Area->Group) rAddLine(jb," Group: %s",config.cfg_GroupNames[ss->Area->Group-'A']);
         else                rAddLine(jb," Group: %s","<No group>");

         rAddLine(jb,"");

         rAddLine(jb," Tagname                       Description                         KB/week");
         rAddLine(jb," ----------------------------  ---------------------------------   -------");
      }

      CalculateWeekKB(ss->Area,buf);

      ast=' ';

      for(tn=(struct TickNode *)ss->Area->TickNodes.First;tn;tn=tn->Next)
         if(tn->ConfigNode == cnode) break;

      if(tn && type == SENDLIST_FULL)
      {
         ast='*';
         sendlistlinked++;
      }

      sendlisttotal++;

      rAddLine(jb,"%lc%-28.28s  %-33.33s  %8.8s",ast,ss->Area->Name,ss->Area->Description,buf);
      lastss=ss;
   }

   rAddLine(jb,"");

   switch(type)
   {
      case SENDLIST_QUERY:
         rAddLine(jb,"%lu linked areas.",sendlisttotal);
         break;

      case SENDLIST_UNLINKED:
         rAddLine(jb,"%lu unlinked areas.",sendlisttotal);
         break;

      case SENDLIST_FULL:
         rAddLine(jb,"Totally %lu areas, you are connected to %lu of them.",sendlisttotal,sendlistlinked);
         rAddLine(jb,"A '*' means that you are connected to the area.");
         break;
   }

   jbFreeList(&SortList,sizeof(struct SortStat));
   rCloseMessage(jb);
}

void convertstar(uchar *buf,ulong len)
{
   uchar tmp[100];
   ulong c,d;
   
   d=0;
   
   for(c=0;buf[c]!=0 && d!=79;c++)
   {
      if(buf[c]=='*')
      {
         tmp[d++]='#';
         if(d!=79) tmp[d++]='?';
      }
      else
      {
         tmp[d++]=buf[c];
      }
   }

   tmp[d]=0;

   mystrncpy(buf,tmp,len);
}

void DoSendAFList(short type,struct ConfigNode *cnode)
{
   struct Aka *bestaka;
   struct jbFile *jb;
   uchar errorbuf[100];

   bestaka=BestAka(&cnode->Node);

   if(!(jb=CreatePacket(&bestaka->Node,errorbuf)))
   {
      LogWrite(1,RAID,"%s",errorbuf);
      return;
   }

   switch(type)
   {
      case SENDLIST_FULL:
         rawSendList(jb,SENDLIST_FULL,&bestaka->Node,cnode->SysopName,cnode);
         break;

      case SENDLIST_QUERY:
         rawSendList(jb,SENDLIST_QUERY,&bestaka->Node,cnode->SysopName,cnode);
         break;

      case SENDLIST_UNLINKED:
         rawSendList(jb,SENDLIST_UNLINKED,&bestaka->Node,cnode->SysopName,cnode);
         break;

      case SENDLIST_INFO:
         rawSendInfo(jb,&bestaka->Node,cnode->SysopName,cnode);
         break;

      case SENDLIST_HELP:
         rawSendHelp(jb,&bestaka->Node,cnode->SysopName,cnode);
         break;
   }

   ClosePacket(jb);
}

BOOL SendAFList(short type,UBYTE *node)
{
   struct Node4D n4d;
   struct ConfigNode *cnode;
   BOOL sent;

   if(stricmp(node,"ALL")==0)
   {
      sent=FALSE;

      for(cnode=config.CNodeList.First;cnode;cnode=cnode->Next)
         if(cnode->Flags & NODE_NOTIFY)
         {
            sent=TRUE;
            DoSendAFList(type,cnode);
         }

      if(!sent)
         LogWrite(1,RAID,"%s",getString(RAID_NOBODY_TO_SEND_TO));
   }
   else
   {
      if(!Parse4D(node,&n4d))
      {
         LogWrite(1,USERERR,getString(ERR_INVALID_NODE),node);
         return(10);
      }

      for(cnode=config.CNodeList.First;cnode;cnode=cnode->Next)
         if(Compare4D(&cnode->Node,&n4d)==0) break;

      if(cnode)
      {
         DoSendAFList(type,cnode);
      }
      else
      {
         LogWrite(1,USERERR,getString(ERR_UNKNOWN_NODE),n4d.Zone,n4d.Net,n4d.Node,n4d.Point);
         return(10);
      }
   }

   return(0);
}

UBYTE rFromName[40];
UBYTE rToName[40];
UBYTE rSubject[80];
struct Node4D rTo4D;
struct Node4D rFrom4D;
ULONG rLines;
ULONG rPart;

void rInitMessage(struct jbFile *jb,uchar *fromname,uchar *toname,struct Node4D *from4d,struct Node4D *to4d,UBYTE *subject)
{
   strcpy(rFromName,fromname);
   Copy4D(&rFrom4D,from4d);
   strcpy(rToName,toname);
   Copy4D(&rTo4D,to4d);
   strcpy(rSubject,subject);

   WriteHeader(jb,fromname,toname,from4d,to4d,subject);

   rLines=0;
   rPart=1;
}

void rAddLine(struct jbFile *jb,UBYTE *fmt,...)
{
   uchar buf[100];
   va_list args;

   if(rLines >= config.cfg_RaidMaxLines-2 && config.cfg_RaidMaxLines!=0)
   {
      rLines=2;
      rPart++;

      jbFPrintf(jb,"\x0d(Continued in next message)\x0d");
      rCloseMessage(jb);
      sprintf(buf,"%s (part %ld)",rSubject,rPart);
      WriteHeader(jb,rFromName,rToName,&rFrom4D,&rTo4D,buf);
      jbFPrintf(jb,"(Continued from previous message)\x0d\x0d");
   }

   va_start(args, fmt);
   jbVFPrintf(jb,fmt,args);
   va_end(args);

   jbPutChar(jb,'\x0d');

   rLines++;
}

void rCloseMessage(struct jbFile *jb)
{
   jbPutChar(jb,0);
}


