
/*

   Mail_Scan.HBBS
   ==============

   Door for scanning new mail.


   ideas
   -----
     copy mail to another user
     forward mail to another user
     allow editing of header (from, to etc..)


   todo
   ----

   create a savemsglist() and loadmsg() list

   put routines into a shared lib.


*/

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/alib_protos.h>

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

#include <HBBS/ANSI_Codes.h>
#include <HBBS/Defines.h>
#include <HBBS/access.h>
#include <HBBS/types.h>
#include <HBBS/structures.h>
#include <HBBS/hbbscommon_protos.h>
#include <HBBS/hbbscommon_pragmas.h>
#include <HBBS/Hbbsnode_protos.h>
#include <HBBS/Hbbsnode_pragmas.h>
#include <HBBS/release.h>
char *versionstr="$VER: Mail_Scan "RELEASE_STR;

#include "/Mail_Common.h"


struct Library *HBBSCommonBase=NULL;
struct Library *HBBSNodeBase=NULL;

struct BBSGlobalData *BBSGlobal=NULL;
struct NodeData *N_ND=NULL;
int N_NodeNum=-1;
char outstr[1024]; // temp string for displaying text..

int    gargc;         // these are just copies of main()'s argc and argv..
char   **gargv;

LONG __stack=16384;

struct List *MsgList=NULL;
V_BIGNUM Msgs=0;
struct MsgNode *ListNode=NULL;

#include "/maillib.i"

#ifdef __SASC
int CXBRK(void) { return(0); }
int _CXBRK(void) { return(0); }
void chkabort(void) {}
#endif


static VOID cleanup(ULONG num)
{
  if (HBBSNodeBase)
  {
    HBBS_CleanUpDoor();
    CloseLibrary (HBBSNodeBase);
  }

  if (HBBSCommonBase)
  {
    HBBS_CleanUpCommon();
    CloseLibrary (HBBSCommonBase);
  }

  if (num) printf("Door Error = %d\n",num);

  exit(0);
}

static VOID init(char *name)
{
  if(!(HBBSCommonBase = OpenLibrary("HBBSCommon.library",0)))
  {
    cleanup(1);
  }

  if (!(HBBS_InitCommon()))
  {
    cleanup(2);
  }

  if(!(HBBSNodeBase = OpenLibrary("HBBSNode.library",0)))
  {
    cleanup(3);
  }

  if (!(HBBS_InitDoor(N_NodeNum,name)))
  {
    cleanup(4);
  }
  SetProgramName(name);
}

#define SCAN_ALL          0
#define SCAN_PUBLIC       1
#define SCAN_USER         2
#define SCAN_CURRENTCONF  3
#define SCAN_SYSOP        4
#define SCAN_ALLCONFS     5
#define SCAN_ACONF        6

struct ScanData
{
  struct Node node;
  LONG LastScanNum;
};

void UpdateScanPointers(struct List *MsgList)
{
  // ooh, no here's a complicated routine! :-)
  // basically we have to determine from a list of messages
  // which ones are the last ones of a type of message
  // and then update the last scanned pointers to match!

  struct MsgNode *ListNode=NULL;
  V_SMALLNUM LastType=MSGT_VOID;
  V_SMALLNUM LastConf=0;
  V_BIGNUM LastNum=0;

  struct List *ScanList;
  struct ScanData *ScanNode;
  LONG loop;

  LONG LastScan[MSGTYPES];

  // create a list with the same amount of nodes as conferences..

  if (ScanList=HBBS_CreateList())
  {
    for (loop=0;loop<BBSGlobal->Conferences;loop++)
    {
      if (ScanNode=(struct ScanData*)HBBS_CreateNode(NULL,sizeof(struct ScanData)))
      {
        ScanNode->LastScanNum=0;
        AddTail(ScanList,(struct Node*)ScanNode);
      }
    }

    for (loop=1;loop<MSGTYPES;loop++) LastScan[loop]=0;

    if (HBBS_NodesInList(ScanList)==BBSGlobal->Conferences)
    {
      for (ListNode=(struct MsgNode *)MsgList->lh_Head ; ListNode->node.ln_Succ ; ListNode=(struct MsgNode*)ListNode->node.ln_Succ)
      {
        if ((LastType!=ListNode->Type) || (LastType==MSGT_CONFERENCE && LastConf != ListNode->ConfNum))
        {
          switch(LastType)
          {
            case MSGT_PUBLIC:
            case MSGT_SYSOP:
            case MSGT_PRIVATE:
            case MSGT_FIDONET:
            case MSGT_INTERNET:
              if (LastNum>LastScan[LastType]) LastScan[LastType]=LastNum;
              break;

            case MSGT_CONFERENCE:
              ScanNode=(struct ScanData *)GetNode(ScanList,LastConf-1); // index start = 0 hence -1
              ScanNode->LastScanNum=LastNum;
              break;
          }
          LastType=ListNode->Type;
          LastConf=ListNode->ConfNum;
        }
        LastNum=ListNode->Number;
      }
      // do this bit again, as the last one in the list won't get caught above..
      switch(LastType)
      {
        case MSGT_PUBLIC:
        case MSGT_SYSOP:
        case MSGT_PRIVATE:
        case MSGT_FIDONET:
        case MSGT_INTERNET:
          if (LastNum>LastScan[LastType]) LastScan[LastType]=LastNum;
          break;

        case MSGT_CONFERENCE:
          ScanNode=(struct ScanData *)GetNode(ScanList,LastConf-1); // index start = 0 hence -1
          ScanNode->LastScanNum=LastNum;
          break;

      }


      for (loop=1;loop<MSGTYPES;loop++)
      {
        switch(loop)
        {
          case MSGT_PUBLIC:
          case MSGT_SYSOP:
          case MSGT_PRIVATE:
            SaveLastScanned(loop,0,LastScan[loop]);
            break;
        }
      }
      for (loop=0;loop<BBSGlobal->Conferences;loop++)
      {
        ScanNode=(struct ScanData *)GetNode(ScanList,loop); // index start = 0 hence -1
        SaveLastScanned(MSGT_CONFERENCE,loop+1,ScanNode->LastScanNum);
      }
    }
    FreeStrList(ScanList);
  }
}

void DisplayHeaders(struct List *MsgList)
{
  V_BIGNUM ListNum=0;
  V_SMALLNUM LastType=MSGT_VOID;
  V_SMALLNUM LastConf=0,loop;
//  char tmpstr[1024];
  struct MsgNode *ListNode=NULL;

  DOOR_WriteText(ANSI_RESET ANSI_CLS);

  for (ListNode=(struct MsgNode *)MsgList->lh_Head ; ListNode->node.ln_Succ ; ListNode=(struct MsgNode*)ListNode->node.ln_Succ)
  {
    if ((LastType!=ListNode->Type) || (LastType==MSGT_CONFERENCE && LastConf != ListNode->ConfNum))
    {
      LastType=ListNode->Type;
      LastConf=ListNode->ConfNum;
      DOOR_WriteText(ANSI_RESET ANSI_FG_BLUE "----"ANSI_FG_CYAN"-"ANSI_FG_WHITE"- " ANSI_FG_YELLOW);
      switch(LastType)
      {
        case MSGT_SYSOP:
          strcpy(outstr,"Sysop");
          break;
        case MSGT_CONFERENCE:
          strcpy(outstr,HBBS_ListName(BBSGlobal->ConfList,ListNode->ConfNum-1)); // index starts at 0
          break;
        case MSGT_PRIVATE:
          strcpy(outstr,"Private");
          break;
        case MSGT_PUBLIC:
          strcpy(outstr,"Public");
          break;

      }
      strcat(outstr," Mail "ANSI_FG_WHITE "-" ANSI_FG_CYAN "-" ANSI_FG_BLUE);
      for (loop=strlen(outstr);loop<87;loop++) // 87 to compensate for the ansi control sequences
      {
        outstr[loop]='-';
      }
      outstr[loop]='\0';
      strcat(outstr,"\r\n");

      DOOR_WriteText(outstr);
    }
    sprintf(outstr,ANSI_RESET ANSI_FG_WHITE "%d) " ANSI_FG_CYAN "From "ANSI_FG_BLUE ": " ANSI_FG_WHITE "%-25s " ANSI_FG_CYAN "Subject "ANSI_FG_BLUE": " ANSI_FG_WHITE "%-38s\r\n",ListNum,ListNode->From,ListNode->node.ln_Name);
    DOOR_WriteText(outstr);

    ListNum++;
  }
}

V_BIGNUM LoadUnreadList(struct List **MsgList)
{
  char tmpfilename[1024];

  sprintf(tmpfilename,"HBBS:Mail/Users/%ld/UnreadList.CFG",N_ND->User.CallData.UserID);
  return(LoadMsgList(MsgList,tmpfilename));

  // *C* URGENT, all nodes in list must be validated!!! i.e. check msg is still to you, still exists etc...
}


void SaveUnreadList(struct List *MsgList)
{
  char tmpfilename[1024];

  if (!MsgList) return;

  sprintf(tmpfilename,"HBBS:Mail/Users/%ld/UnreadList.CFG",N_ND->User.CallData.UserID);
  SaveMsgList(MsgList,tmpfilename,SML_UNREAD);
}

void PromptUser( void )
{
  char tmpfilename[1024],tmpstr[1024];
  LONG num=-1;
  BOOL Done=FALSE,First=TRUE;
  struct MsgNode *ListNode=NULL;

  DisplayHeaders(MsgList);
  do
  {
    // if it's the first time, then the default is [R]ead, otherwise it's [S]kip

//    sprintf(outstr,ANSI_RESET ANSI_FG_YELLOW"[%s"ANSI_FG_WHITE"R"ANSI_RESET ANSI_FG_YELLOW"]"ANSI_FG_WHITE"ead All, "ANSI_FG_YELLOW"[%s"ANSI_FG_WHITE"S"ANSI_RESET ANSI_FG_YELLOW"]"ANSI_FG_WHITE"kip, "ANSI_FG_YELLOW"["ANSI_FG_WHITE"L"ANSI_FG_YELLOW"]"ANSI_FG_WHITE"ist again, "ANSI_FG_YELLOW"["ANSI_FG_WHITE"M"ANSI_FG_YELLOW"]"ANSI_FG_WHITE"ark as read, or # of message >",First ? ANSI_BOLD : "",!First ? ANSI_BOLD : "");
//    DOOR_WriteText(outstr);

    strcpy(N_ND->CharsAllowed,"RrSsLlMm0123456789");
    DOOR_MenuPrompt("[R]ead All, [S]kip, [L]ist again, [M]ark as read, or # of message >",First ? 'R' : 'S');

    DOOR_GetLine(GL_EDIT|GL_DISPLAY|GL_USECHARS,'\0',0,0,NULL);
    if (N_ND->OnlineStatus==OS_ONLINE)
    {
      RemoveSpaces(N_ND->CurrentLine); // strip leading/trailing spaces..

      if (N_ND->CurrentLine[0]==0)
      {
        if (First) N_ND->CurrentLine[0]='R'; // read them..
        else N_ND->CurrentLine[0]='S'; // skip them
        N_ND->CurrentLine[1]=0;
      }

      if (sscanf(N_ND->CurrentLine,"%d",&num))
      {
        if (num>=0) //doublecheck *r* ??
        {
          ListNode=(struct MsgNode *)GetNode(MsgList,num);

          // msgnum, type, confnum
//          sprintf(tmpstr,"MSG %d %d %d",ListNode->Number,ListNode->Type,ListNode->ConfNum);
          sprintf(tmpstr,"MSG %d %d %d UPDATESCAN",ListNode->Number,ListNode->Type,ListNode->ConfNum);
          DOOR_SystemDoor("MailRead",tmpstr);
          ListNode->ReadIt=TRUE;

/*
          if (iposition("DELETED",N_ND->DoorReturn)>=0)
          {
            FreeMsgListNode(MsgList, ListNode);
          }
          else
          {
            ListNode->ReadIt=TRUE;
          }
*/
        }
      }
      else
      {
        switch (toupper(N_ND->CurrentLine[0]))
        {
          case 'R':
            sprintf(tmpfilename,"%sWork/MailTemp.DAT",N_ND->NodeLocation);
            if (SaveMsgList(MsgList,tmpfilename,SML_ALL))
            {
              FreeMsgList(MsgList);
              MsgList=NULL;

//              sprintf(tmpstr,"MSGLIST %s UPDATESCAN",tmpfilename);
              sprintf(tmpstr,"MSGLIST %s SAVELIST",tmpfilename);
              DOOR_SystemDoor("MailRead",tmpstr);
              if (Msgs=LoadMsgList(&MsgList,tmpfilename))
              {
                if (N_ND->OnlineStatus==OS_ONLINE)
                {
                  UpdateScanPointers(MsgList);
                  SaveUnreadList(MsgList);
                }
              }
              DeleteFile(tmpfilename);
            }
            Done=TRUE;
            break;
          case 'S':
            UpdateScanPointers(MsgList);
            SaveUnreadList(MsgList);
            Done=TRUE;
            break;
          case 'L':
            DisplayHeaders(MsgList);
            break;
          case 'M':
            // set all nodes in list to readit=true
            SaveUnreadList(MsgList);
            UpdateScanPointers(MsgList);
            break;
          default:
            DOOR_WriteText("Invalid Option!\r\n");
            break;

        }
      }
    }
    First=FALSE;
  } while (N_ND->OnlineStatus==OS_ONLINE && !Done);
}

void ScanMail(int ScanMode,ULONG ConfNum)
{
  V_BOOL ReadThem=TRUE;
  char ScanDir[1024];
  struct ConfData *Conf;

  char donestr[]="\r[K"; // start of line, erase line    (hey, look closely, it says "rock"! :-)

  Msgs=0;

  Msgs+=LoadUnreadList(&MsgList);

  switch(ScanMode)
  {
    case SCAN_ALL: // BEGIN
    case SCAN_PUBLIC:

      DOOR_WriteText(ANSI_RESET ANSI_FG_CYAN "Scanning Public Mail ");
      Msgs+=CreateMsgList(MAILDIR_PUBLIC,&MsgList,TRUE,MSGT_PUBLIC,0);
      DOOR_WriteText(donestr);
      if (ScanMode!=SCAN_ALL) break;

    case SCAN_USER:
      if (N_ND->User.Valid)
      {
        DOOR_WriteText(ANSI_RESET ANSI_FG_WHITE "Scanning Your Private Mail ");
        sprintf(ScanDir,"%s%ld/",MAILDIR_USER,N_ND->User.CallData.UserID);
        Msgs+=CreateMsgList(ScanDir,&MsgList,TRUE,MSGT_PRIVATE,0);
        DOOR_WriteText(donestr);
      }
      if (ScanMode!=SCAN_ALL) break;

    case SCAN_ALLCONFS:
      if (BBSGlobal->Conferences)
      {
        for (Conf=(struct ConfData *)BBSGlobal->ConfList->lh_Head; Conf->node.ln_Succ ; Conf=(struct ConfData *)Conf->node.ln_Succ)
        {
          if (N_ND->User.CallData.Access>=Conf->ConfAccess)
          {
            DOOR_WriteText(ANSI_RESET ANSI_FG_CYAN "Scanning for mail in the ");
            DOOR_WriteText(Conf->node.ln_Name);
            DOOR_WriteText(" Conference ");
            sprintf(ScanDir,"%sMail/",Conf->ConfPath);
            Msgs+=CreateMsgList(ScanDir,&MsgList,TRUE,MSGT_CONFERENCE,Conf->ConfNum);
            DOOR_WriteText(donestr);
          }

        }
      }
      if (ScanMode!=SCAN_ALL) break;

    case SCAN_SYSOP:
      // is the user the sysop ?
      if (stricmp(N_ND->User.CallData.Handle,BBSGlobal->SysopAccount)==0)
      {
        DOOR_WriteText(ANSI_RESET ANSI_FG_BLUE "Scanning Sysop Mail ");
        Msgs+=CreateMsgList(MAILDIR_SYSOP,&MsgList,TRUE,MSGT_SYSOP,0);
        DOOR_WriteText(donestr);
      }
      break;

    // SCAN_ALL END!


    case SCAN_CURRENTCONF:
      if (N_ND->CurrentConf)
      {
        DOOR_WriteText(ANSI_RESET ANSI_FG_CYAN "Scanning for mail in the ");
        DOOR_WriteText(N_ND->CurrentConf->node.ln_Name);
        DOOR_WriteText(" Conference ");
        sprintf(ScanDir,"%sMail/",N_ND->CurrentConf->ConfPath);
        Msgs+=CreateMsgList(ScanDir,&MsgList,TRUE,MSGT_CONFERENCE,N_ND->CurrentConf->ConfNum);
        DOOR_WriteText(donestr);
      }
      break;

    case SCAN_ACONF:
      if (BBSGlobal->Conferences)
      {
        Conf=(struct ConfData *)GetNode(BBSGlobal->ConfList,ConfNum);
        if (N_ND->User.CallData.Access>=Conf->ConfAccess)
        {
          DOOR_WriteText(ANSI_RESET ANSI_FG_CYAN "Scanning for mail in the ");
          DOOR_WriteText(Conf->node.ln_Name);
          DOOR_WriteText(" Conference ");
          sprintf(ScanDir,"%sMail/",Conf->ConfPath);
          Msgs+=CreateMsgList(ScanDir,&MsgList,TRUE,MSGT_CONFERENCE,Conf->ConfNum);
          DOOR_WriteText(donestr);
        }
      }

  }

  DOOR_WriteText(ANSI_RESET);

  if (MsgList) // we've got a list to process!
  {
    if (Msgs)
    {
      if (iposition("LOGIN",N_ND->ActiveDoor->SystemOptions)>=0)
      {
        sprintf(outstr,ANSI_FG_YELLOW "Read your %d message%s now ? [Y/n] :",Msgs,Msgs == 1 ? "" : "s");
        ReadThem=DOOR_ContinuePrompt(outstr,DEFAULT_YES);
      }

      if (ReadThem)
      {
        PromptUser();
      }
    }
    else
    {
      DOOR_WriteText(ANSI_RESET ANSI_FG_CYAN "There is no mail waiting!\r\n" ANSI_RESET );
      if (iposition("LOGIN",N_ND->ActiveDoor->SystemOptions)>=0)
      {
        DOOR_PausePrompt(NULL);
      }
    }
    if (MsgList) FreeMsgList(MsgList);
  }
}

void DisplayHelp( void )
{
  DOOR_WriteText("Mailscan Help!\r\n"
                 "Options:\r\n"
                 "        ?            -  This Screen\r\n"
                 "        A            -  Scan ALL mail\r\n"
                 "        C [ConfNum]  -  Scan mail in current confererence, or\r\n"
                 "                        specified conference\r\n"
                 "        P            -  Scan PUBLIC Mail\r\n");
  if (N_ND->User.CallData.Access>=250)
  {
    DOOR_WriteText("        S            -  Scan Sysop Mail\r\n");
  }
}

void DoorMain( void )
{
  ULONG ConfNum=0;
  if (gargc>=3)
  {
    if (stricmp(gargv[2],"CFG")==0)
    {
      DOOR_WriteText("There are currently no configuration options!\r\n");
      DOOR_PausePrompt(NULL);
    }
    else
    {
      switch(toupper(gargv[2][0]))
      {
        case '?':
          DisplayHelp();
          break;
        case 'A':
          ScanMail(SCAN_ALL,0);
          break;
        case 'C':
          if (gargc>=4)
          {
            if (sscanf(gargv[3],"%ld",&ConfNum))
            {
              if (ValidConfNum(ConfNum))
              {
                ScanMail(SCAN_ACONF,ConfNum-1); //offset at 0!
              }
              else
              {
                DOOR_WriteText("Invalid Conference Number!\r\n");
              }
            }
          }
          else
          {
            ScanMail(SCAN_CURRENTCONF,0);
          }
          break;
        case 'P':
          ScanMail(SCAN_PUBLIC,0);
          break;
        case 'S':
          if (N_ND->User.CallData.Access>=250)
          {
            ScanMail(SCAN_SYSOP,0);
            break;  // not the positoning of break;, if user has no access then they'll get the error message :-)
          }
        default:
          DOOR_WriteText("Unknown Mailscan Mode!\r\n");
          DisplayHelp();
          break;
      }
    }
  }
  else
  {
    // no command line params so scan all
    ScanMail(SCAN_ALL,0);
  }
}

int main(int argc,char *argv[])
{
  gargc=argc;
  gargv=argv;

  if (sscanf(argv[1],"%d",&N_NodeNum)==0)
  {
    printf("Invalid/No Paramaters for door!\n");
    exit (20);
  }
  init("Scanning Mail");

  if (BBSGlobal=HBBS_GimmeBBS())
  {
    if (N_ND=HBBS_NodeDataPtr(N_NodeNum)) // this should not fail in normal circumstances..
    {
      DoorMain();
    }
  }
  cleanup(0);
}
