/*

  Weektop system door

  params: [UPDATE <weekfinalfilename> <users> [NOSCREENCLEAR]] [CREATE <currentweektopfilename> <users> [NOSCREENCLEAR] [FORCE]]

*/


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

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

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

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

#include <HBBS/ANSI_Codes.h>
#include <HBBS/Defines.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: WeekTop "RELEASE_STR;

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

struct BBSGlobalData *BBSGlobal = NULL;
struct NodeData *N_ND           = NULL;
int    N_NodeNum                = -1;

char outstr[1024];
char WeekStartDate[LEN_DATESTR + 1];
BOOL WeekChanged = FALSE;
char BA_Buffer[8192]; // 8k file buffer

struct CallerNode
{
  struct Node node; // ->ln_Name contains the handle..
  char Group[LEN_GROUP];

  V_HUGENUM BytesUploaded;
  V_BIGNUM FilesUploaded;
  V_BIGNUM LastWeek;
  V_BIGNUM UserID;

};

struct List *CallerList=NULL;

long __stack=16*1024; // increse this in 4k incrments if you suffer from
                      // random/suprious crashings after or during the running
                      // of your door.


#define INITERR_NOERROR 0
#define INITERR_NOPARAMS 1
#define INITERR_NOHBBSCOMMON 2
#define INITERR_INITCOMMONFAILED 3
#define INITERR_NOHBBSNODE 4
#define INITERR_INITDOORFAILED 5
#define INITERR_NOUTILITYLIB 6

static VOID CleanUp(ULONG num)
{
  if (UtilityBase) CloseLibrary(UtilityBase);

  if (HBBSNodeBase)
  {
    HBBS_CleanUpDoor();
    CloseLibrary (HBBSNodeBase);
  }

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

  switch(num)
  {
    case INITERR_NOERROR:
      exit(0);

    case INITERR_NOPARAMS:
      puts("Bad or no paramaters specified, check your config files\n"
           "(Don't try starting doors from the CLI!)");
      break;

    case INITERR_NOHBBSCOMMON:
      puts("Could not open HBBSCommon.library, check your assign's");
      break;

    case INITERR_INITCOMMONFAILED:
      puts("Could not initialise HBBSCommon.library functions\n"
           "Is HBBS Running ?");
      break;

    case INITERR_NOHBBSNODE:
      puts("Could not open HBBSNode.library, check your assign's");
      break;

    case INITERR_INITDOORFAILED:
      puts("The node specified is either not running or did not want to\n"
           "start this door now. (Don't run doors from the CLI!)");
      break;
    case INITERR_NOUTILITYLIB:
      puts("Could not open utility.library!");
      break;
  }
  exit(20);
}

static VOID Init(char *name)
{
  if(!(HBBSCommonBase = OpenLibrary("HBBSCommon.library",0)))
  {
    CleanUp(INITERR_NOHBBSCOMMON);
  }

  if (!(HBBS_InitCommon()))
  {
    CleanUp(INITERR_INITCOMMONFAILED);
  }

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

  if (!(HBBS_InitDoor(N_NodeNum,name)))
  {
    CleanUp(INITERR_INITDOORFAILED);
  }

  if (!(UtilityBase = (struct Library *)OpenLibrary("utility.library",33)))
  {
    CleanUp(INITERR_NOUTILITYLIB);
  }
  SetProgramName(name);
}

struct CallerNode *FindCaller(struct List *list,V_BIGNUM UserID)
{
  struct CallerNode *node,*retval=NULL;
  V_BOOL found=FALSE;

  for (node=(struct CallerNode *)list->lh_Head;!found && node->node.ln_Succ;node=(struct CallerNode *)node->node.ln_Succ)
  {
    if (node->UserID == UserID)
    {
      found=TRUE;
      retval=node;
    }
  }
  return(retval);
}

void FHWriteStr(BPTR FH,char *str)
{
  Write(FH,str,strlen(str));
  Write(FH,"\n",1);
}

void BA_Write( BPTR FH )
{
  Write(FH,BA_Buffer,strlen(BA_Buffer));
}

void BA_Clear( void )
{
  BA_Buffer[0]=0;
}

void FHWriteStrNOCR(BPTR FH,char *str)
{
  Write(FH,str,strlen(str));
}

void CreateFile(LONG Users,char *filename,BOOL ClearScreen)
{

  // sort the list and save the first <Users> nodes into <filename>

  LONG Callers,loop,found=0;
  char tmpstr[1024],ulstr[30];
  struct CallerNode *Caller;
  BPTR FH;

  if (FH=Open(filename,MODE_NEWFILE))
  {

    if (ClearScreen) FHWriteStr(FH,"[2J[H");

    FHWriteStr(FH,"  [33mUser Name                 Group                     Bytes     Files LastWeek");
    FHWriteStr(FH," [37m-[35m-[34m----------------------[35m-[37m- [37m-[35m-[34m---------------------[35m-[37m- -[35m-[34m-----[35m-[37m- -[35m-[34m-[35m-[37m- -[35m-[34m----[35m-[37m-");

    Callers=HBBS_NodesInList(CallerList);

    for (loop=0;loop<Callers && found<Users;loop++)
    {
      Caller=(struct CallerNode *)GetNode(CallerList,loop);

      found++;

      sprintf(ulstr,"%9.0f",Caller->BytesUploaded);

      //              handle,group,bytesup,filesup,lastweek
      sprintf(tmpstr,"[34m| %s%-25.25s %-25.25s %9.0f %5d %4d    [34m|",
              loop == 0 ? "[37m" : (loop == 1 ? "[36m" : "[32m"), // alter colours for first 2 users.
              Caller->node.ln_Name,Caller->Group,Caller->BytesUploaded,Caller->FilesUploaded,Caller->LastWeek);

      FHWriteStr(FH,tmpstr);
    }

    FHWriteStr(FH," [37m-[35m-[34m----------------------[35m-[37m- [37m-[35m-[34m---------------------[35m-[37m- -[35m-[34m-----[35m-[37m- -[35m-[34m-[35m-[37m- -[35m-[34m----[35m-[37m-");
    Close(FH);
  }
}


void SaveDataFile( void )
{
  BPTR FH;
  struct CallerNode *Caller;
  char tmpstr[20];

  if (FH=Open("Progdir:WeekTop.DATA",MODE_NEWFILE))
  {
    FHWriteStr(FH,WeekStartDate);

    for (Caller=(struct CallerNode*)CallerList->lh_Head;Caller->node.ln_Succ;Caller=(struct CallerNode*)Caller->node.ln_Succ)
    {
      BA_Clear();
      strcat(BA_Buffer,"!DATA!\n");

      strcat(BA_Buffer,Caller->node.ln_Name);
      strcat(BA_Buffer,"\n");

      strcat(BA_Buffer,Caller->Group);
      strcat(BA_Buffer,"\n");

      sprintf(tmpstr,"%ld\n",Caller->FilesUploaded);
      strcat(BA_Buffer,tmpstr);

      sprintf(tmpstr,"%1.0f\n",Caller->BytesUploaded);
      strcat(BA_Buffer,tmpstr);

      sprintf(tmpstr,"%ld\n",Caller->LastWeek);
      strcat(BA_Buffer,tmpstr);

      sprintf(tmpstr,"%ld\n",Caller->UserID);
      strcat(BA_Buffer,tmpstr);

      BA_Write(FH);
    }
    Close(FH);
  }
}

BOOL ReadDataFile( void )
{
  char buffer[1024];
  BPTR FH;
  BOOL retval=FALSE,eof=FALSE,error=FALSE;
  struct CallerNode *Caller;

  if (FH=Open("Progdir:WeekTop.DATA",MODE_OLDFILE))
  {
    FGets(FH,buffer,1024);
    strNcpy(WeekStartDate,buffer,LEN_DATESTR);

    if (CallerList=HBBS_CreateList())
    {
      do
      {
        while ((FGets(FH,buffer,1024)) && (strnicmp(buffer,"!DATA!",6)!=0));

        if (strnicmp(buffer,"!DATA!",6)==0) // if this is NOT what we want then we're at the EOF
        {
          if (FGets(FH,buffer,1024))
          {
            error=TRUE; // memory error!
            stripcr(buffer);
            if (Caller=(struct CallerNode *)HBBS_CreateNode(buffer,sizeof(struct CallerNode)))
            {
              if (FGets(FH,buffer,1024))
              {
                stripcr(buffer);
                strNcpy(Caller->Group,buffer,LEN_GROUP);
                if (FGets(FH,buffer,1024))
                {
                  stripcr(buffer);
                  if (sscanf(buffer,"%ld",&Caller->FilesUploaded))
                  {
                    if (FGets(FH,buffer,1024))
                    {
                      stripcr(buffer);
                      if (sscanf(buffer,"%lf",&Caller->BytesUploaded))
                      {
                        if (FGets(FH,buffer,1024))
                        {
                          stripcr(buffer);
                          if (sscanf(buffer,"%ld",&Caller->LastWeek))
                          {
                            if (FGets(FH,buffer,1024))
                            {
                              stripcr(buffer);
                              if (sscanf(buffer,"%ld",&Caller->UserID))
                              {
                                AddTail(CallerList,(struct Node*)Caller);
                                error=FALSE;
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
        else
        {
          eof=TRUE;
        }
      } while (!eof && !error);

      if (error)
      {
        FreeStrList(CallerList);
      }
      else
      {
        if (HBBS_NodesInList(CallerList))
        {
          retval=TRUE;
        }
      }
    }
    Close(FH);
  }
  return(retval);
}

void SortCallersList( void )
{
  struct CallerNode *Current;
  struct CallerNode *Compare;

  BOOL Swapped;

  if (CallerList)
  {
    do
    {
      Swapped = FALSE;

      for (Current = (struct CallerNode *)CallerList->lh_Head ;
           Current ->node.ln_Succ ;
           Current = (struct CallerNode *)Current ->node.ln_Succ )
      {
        Compare = (struct CallerNode *)Current->node.ln_Succ;

        if ((Compare->node.ln_Succ) && (Current != Compare)) // avoid using the last node in the list and comparing the same nodes
        {
          // last node has more bytes than current node.

          if (Compare -> BytesUploaded > Current->BytesUploaded)
          {
            Remove((struct Node *)Compare);

            if (Current == (struct CallerNode *)CallerList->lh_Head)
            {
              AddHead(CallerList, (struct Node *)Compare);
            }
            else
            {
              Insert(CallerList, (struct Node *)Compare , (struct Node *)Current->node.ln_Pred);
            }
            Swapped = TRUE;
          }
        }
      }
    } while (Swapped);
  }
}

void UpdateDataFile(char *filename, LONG Users)
{
  LONG Nodes;
  struct CallerNode *Caller;
  BOOL AddUser=TRUE,MustSave=FALSE;
  ULONG todaytime,fromtime;
  struct ClockData todayDT;
  int PositionCounter = 1;
  char mailadd_options[BIG_STR];

  if (N_ND->User->Valid)
  {

    if (!(ReadDataFile()))
    {
      /* We must now reset the weekstartdate to the nearest previous
       * monday to today's date
       */

      todaytime = HBBS_GetAmigaTime();
      Amiga2Date(todaytime,&todayDT);

      /* .wday 0 = sunday, but a BBS week start is MONDAY because people trade all weekend..
       * so we set the .wday to (an invalid) 7 so that when we take -1 from it
       * we can remove the right amount of seconds from the todaytime to give us
       * the nearest previous monday to today
       */

      if (todayDT.wday == 0) todayDT.wday = 7;

      todaytime -= 86400 * (todayDT.wday-1);

      HBBS_GetDateString(WeekStartDate,todaytime);
      CallerList=HBBS_CreateList();
    }

    if (CallerList)
    {
      SortCallersList();

      todaytime = HBBS_GetAmigaTime();
      todaytime -= todaytime % 86400; //reset to midnight

      fromtime = HBBS_DateStrToAmigaTime(WeekStartDate);

      // skip the check if the date's are the same
      if (todaytime != fromtime)
      {
        if (todaytime >= fromtime + 604800) // 604800 seconds in a week.
        {
          /*
           * Over a week has passed since the last weekstart date.
           */

          /* the week has changed, so we set a flag file so that if CREATE is
           * specified and the current user didn't upload during this call
           * a screen file is still written
           */


          WeekChanged = TRUE;

          sprintf(outstr,ANSI_RESET ANSI_FG_CYAN  "OK\r\nCreating weektop for week of "ANSI_FG_YELLOW"%s"ANSI_FG_CYAN"...",WeekStartDate);
          DOOR_SysopText(outstr);

          CreateFile(Users,filename,FALSE);

          /* post a message to the message base */

          sprintf(mailadd_options,"%s TO ALL FROM %s TYPE P SUBJECT -=[ WeekTop for week commencing %s ]=-",
                                  filename,   // use the same filename
                                  N_ND->NodeSettings.SysopAccount,
                                  WeekStartDate);

          DOOR_SysopText(ANSI_RESET ANSI_FG_CYAN  "OK\r\nPosting message to "ANSI_FG_YELLOW"Public"ANSI_FG_CYAN" mailbase...");

          DOOR_SystemDoor("MAILADD",mailadd_options);

          /* update the lastweek items in the list of callers */

          for (Caller = (struct CallerNode *)CallerList->lh_Head ;
               Caller ->node.ln_Succ ;
               Caller = (struct CallerNode *)Caller ->node.ln_Succ )
          {
            Caller->LastWeek = PositionCounter;
            Caller->BytesUploaded = 0;
            Caller->FilesUploaded = 0;

            PositionCounter ++;
          }

          /* We must now reset the weekstartdate to the nearest previous
           * monday to today's date
           */

          Amiga2Date(todaytime,&todayDT);

          /* .wday 0 = sunday, but a BBS week start is MONDAY because people trade all weekend..
           * so we set the .wday to (an invalid) 7 so that when we take -1 from it
           * we can remove the right amount of seconds from the todaytime to give us
           * the nearest previous monday to today
           */

          if (todayDT.wday == 0) todayDT.wday = 7;

          todaytime -= 86400 * (todayDT.wday-1);  // using the modified .wday here.

          // then convert the packed time to a string

          HBBS_GetDateString(WeekStartDate,todaytime);

          MustSave = TRUE;
        }
      }

      // only update if the user uploaded this call.

      if (N_ND->User->BytesUploaded)
      {
        if (Nodes=HBBS_NodesInList(CallerList))
        {
          // do we update a user in the list, or add a new user ?

          if (Caller = FindCaller(CallerList,N_ND->User->CallData->UserID))
          {
            // update the user

            DOOR_SysopText(ANSI_FG_WHITE"OK\r\n"ANSI_FG_YELLOW"Updating User"ANSI_FG_CYAN"...");
            strNcpy(Caller->Group,N_ND->User->CallData->Group,LEN_GROUP); // copy again, incase the user updated it.

            Caller->FilesUploaded += N_ND->User->FilesUploaded;
            Caller->BytesUploaded += N_ND->User->BytesUploaded;

            SortCallersList();
            MustSave=TRUE;
            AddUser = FALSE;
          }
        }

        if (AddUser)
        {
          if (Caller=(struct CallerNode *)HBBS_CreateNode(N_ND->User->CallData->Handle,sizeof(struct CallerNode)))
          {
            DOOR_SysopText(ANSI_FG_WHITE"OK\r\n"ANSI_FG_YELLOW"Adding User"ANSI_FG_CYAN"...");

            strNcpy(Caller->Group , N_ND->User->CallData->Group,LEN_GROUP);
            Caller->FilesUploaded = N_ND->User->FilesUploaded;
            Caller->BytesUploaded = N_ND->User->BytesUploaded;
            Caller->UserID        = N_ND->User->CallData->UserID;

            AddHead(CallerList,(struct Node*)Caller);

            SortCallersList();
            MustSave=TRUE;
          }
        }
      }
      if (MustSave)
      {
        DOOR_SysopText(ANSI_FG_WHITE"OK\r\n"ANSI_FG_YELLOW"Saving datafile"ANSI_FG_CYAN"...");

        SaveDataFile();
      }
    }
  }
}

int main(int argc,char *argv[])
{
  LONG Users,startparam=2;

  BOOL ClearScreen=TRUE,OK,Force=FALSE;
  char *filename=NULL;

  if (argc <= 1)
  {
    CleanUp(INITERR_NOPARAMS);
  }

  /* Get the node number from the paramaters */

  if (sscanf(argv[1],"%d",&N_NodeNum)==0)
  {
    CleanUp(INITERR_NOPARAMS);
  }

  /* Initialise and set the door/program name */

  Init("WeekTop");

  if (BBSGlobal=HBBS_GimmeBBS())
  {
    /* Then we need to get the node's data structures for the node this door is going to run on */

    if (N_ND=HBBS_NodeDataPtr(N_NodeNum))
    {
      DOOR_SysopText(ANSI_RESET ANSI_FG_YELLOW "WEEKTOP "ANSI_FG_WHITE"-"ANSI_FG_CYAN" Processing...");

      if (argc>=3)
      {
        if (stricmp(argv[startparam],"UPDATE")==0)
        {
          OK = FALSE;

          startparam++;

          if (startparam<argc)
          {
            filename=argv[startparam++];

            if ((filename[0]) && (startparam<argc) && (sscanf(argv[startparam++],"%ld",&Users)) && (Users))
            {
              UpdateDataFile(filename, Users);
              OK=TRUE;
            }
          }

          if (!OK)
          {
            DOOR_SysopText(ANSI_RESET ANSI_FG_RED "Bad or missing paramaters for UPDATE mode\r\n");
          }
        }

        if ((startparam<argc) && (stricmp(argv[startparam],"CREATE")==0))
        {
          OK = FALSE;

          startparam++;

          if (startparam<argc)
          {
            filename=argv[startparam++];

            if ((filename[0]) && (startparam<argc) && (sscanf(argv[startparam++],"%ld",&Users)) && (Users))
            {
                /*
                 * process remaining option paramaters for CREATE mode.
                 */

              while (startparam<argc)
              {
                if (stricmp(argv[startparam],"NoScreenClear")==0)
                {
                  ClearScreen=FALSE;
                }
                else
                  if (stricmp(argv[startparam],"Force")==0)
                  {
                    Force=TRUE;
                  }

                startparam++;
              }

              OK=TRUE; // all params read in OK

              // only update the file if the week changed or the current user uploaded some files
              // otherwise the file we create would be the same.

              if ((Force) || (WeekChanged) || (N_ND->User->BytesUploaded > 0))
              {
                if (!CallerList)
                {
                  ReadDataFile(); // if we did an Update, the list will still be in memory, already sorted
                  SortCallersList();
                }

                if (CallerList)
                {
                  CreateFile(Users,filename,ClearScreen);
                }
              }
              else
              {
                DOOR_SysopText(ANSI_FG_YELLOW"Skipping"ANSI_FG_CYAN"...");
              }
            }
          }

          if (!OK)
          {
            DOOR_SysopText(ANSI_RESET ANSI_FG_RED "Bad or missing paramaters for CREATE mode\r\n");
          }

        }

        if (CallerList) FreeStrList(CallerList);
      }
      DOOR_SysopText(ANSI_FG_WHITE"Done!\r\n");
    }
  }
  /* Cleanup and close libraries */

  CleanUp(INITERR_NOERROR);
}
