// ====================================================================
// Mystic BBS Software               Copyright 1997-2012 By James Coyle
// ====================================================================
//
// This file is part of Mystic BBS.
//
// Mystic BBS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Mystic BBS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Mystic BBS.  If not, see <http://www.gnu.org/licenses/>.
//
// ====================================================================

Program MystPack;

{ when DELETEing a message, the pointers are one less than they should be }
{ should be fixed, but may cause other problems. commented out the last }
{ read updating in the msgkill part of the program }

{ also when a user is reading a base, it could cause MP to crash with an }
{ RTE 005: access violation error }

{$I M_OPS.PAS}

Uses
  m_FileIO,
  m_Strings,
  m_DateTime,
  CRT,
  DOS;

{$I RECORDS.PAS}

Const
  PackVer       = '1.2';
  Jam_Deleted   = $80000000;
  JamSubBufSize = 4096;

Type
  JamSubBuffer = Array[1..JamSubBufSize] of Char;

  JamHdrType = Record
    Signature     : Array[1..4] of Char;
    Created       : LongInt;
    ModCounter    : LongInt;
    ActiveMsgs    : LongInt;
    PwdCRC        : LongInt;
    BaseMsgNum    : LongInt;
    HighWaterMark : Longint;
    Extra         : Array[1..996] of Char;
  End;

  JamMsgHdrType = Record
    Signature   : Array[1..4] of Char;
    Rev         : Word;
    Resvd       : Word;
    SubFieldLen : LongInt;
    TimesRead   : LongInt;
    MsgIdCrc    : LongInt;
    ReplyCrc    : LongInt;
    ReplyTo     : LongInt;
    ReplyFirst  : LongInt;
    ReplyNext   : LongInt;
    DateWritten : LongInt;
    DateRcvd    : LongInt;
    DateArrived : LongInt;
    MsgNumber   : LongInt;
    Attr1       : LongInt;
    Attr2       : LongInt;
    TextOfs     : LongInt;
    TextLen     : LongInt;
    PwdCrc      : LongInt;
    Cost        : LongInt;
  End;

  JamIdxType = Record
    MsgToCrc : LongInt;
    HdrLoc   : LongInt;
  End;

  JamLastType = Record
    NameCrc  : LongInt;
    UserNum  : LongInt;
    LastRead : LongInt;
    HighRead : LongInt;
  End;

  SubFieldType = Record
    LoId    : Word;
    HiId    : Word;
    DataLen : LongInt;
    Data    : Array[1..1000] of Char;
  End;

  TxtType = Array[1..65000] of Char;

  JamType = Record
    Hdr      : JamHdrType;
    MsgHdr   : JamMsgHdrType;
    HdrFile  : File;
    Idx      : JamIdxType;
    IdxFile  : File of JamIdxType;
    Last     : JamLastType;
    LastFile : File of JamLastType;
    TxtFile  : File;
    SubField : SubFieldType;
  End;

Const
  SpinStr   : String[8] = ('\|/-\|/-');
  SpinPos   : Byte = 1;
  SkipFirst : Boolean = False;
  PackMsgs  : Boolean = False;

Var
  ConfigFile : File of RecConfig;
  MBaseFile  : File of RecMessageBase;
  Config     : RecConfig;
  MBase      : RecMessageBase;

Const
  DATEC1970 = 2440588;
  DATED0    =    1461;
  DATED1    =  146097;
  DATED2    = 1721119;

Procedure UnixToDT(SecsPast: LongInt; Var Dt: DateTime);
Var
  DateNum : LongInt;
  N1      : Word;
Begin
  Datenum := (SecsPast Div 86400) + DATEc1970;

  DateJ2G(DateNum, SmallInt(N1), SmallInt(DT.Month), SmallInt(DT.day));
  DT.Year := N1;

  SecsPast := SecsPast Mod 86400;
  DT.Hour  := SecsPast Div 3600;
  SecsPast := SecsPast Mod 3600;
  DT.Min   := SecsPast Div 60;
  DT.Sec   := SecsPast Mod 60;
End;

Procedure PWrite (Str : String);
Var
  A    : Byte;
  Code : String[2];
Begin
  A := 1;
  While A <= Length(Str) Do Begin
    If (Str[A] = '|') and (A < Length(Str) - 1) Then Begin
      Code := Copy(Str, A + 1, 2);

      If (Code = '00') or (strS2I(Code) > 0) Then Begin
        If strS2I(Code) < 16 Then
          TextColor(strS2I(Code))
        Else
          TextBackground(strS2I(Code) - 16);
      End Else
        Write(Str[A] + Code);

      Inc (A, 2);
    End Else
      Write(Str[A]);

      Inc(A);
  End;
End;

Procedure PWriteLN (Str : String);
Begin
  PWrite (Str + #13#10);
End;

Procedure UpdateSpin;
Begin
  Write (#8 + SpinStr[SpinPos]);
  Inc (SpinPos);

  If SpinPos > 8 Then SpinPos := 1;
End;

Procedure PackJAMBase (Var TotalKilled : LongInt; Var SavedBytes : LongInt);
Var
  BasePath    : String;
  OldHdrFile  : File;
  OldTxtFile  : File;
  OldIdxFile  : File of JamIdxType;
  NewHdrFile  : File;
  NewTxtFile  : File;
  NewIdxFile  : File of JamIdxType;
  TmpHdrFile  : File;
  LastFile    : File of JamLastType;
  Last        : JamLastType;
  SigHdr      : JamHdrType;
  MsgHdr      : JamMsgHdrType;
  TmpSigHdr   : JamHdrType;
  TmpMsgHdr   : JamMsgHdrType;
  MsgIdx      : JamIdxType;
  TxtBuf      : ^TxtType;
  SubField    : SubFieldType;
  Count       : LongInt;
  Killed      : Boolean;
  KillOffset  : LongInt;
  LimitKill   : Boolean;
  TotalMsgs   : LongInt;
  MsgDateTime : DateTime;
  Temp        : LongInt;
  HaveHdr     : Boolean;
Begin
  PWrite ('|07Processing |08-> |07' + strPadR(MBase.Name, 35, ' ') + '|08 ->  |07');

  BasePath := MBase.Path + MBase.FileName;

  Assign (OldHdrFile, BasePath + '.jhr');
  Assign (OldTxtFile, BasePath + '.jdt');
  Assign (OldIdxFile, BasePath + '.jdx');

  {$I-} Reset (OldHdrFile, 1); {$I+}
  If IOResult <> 0 Then Exit;

  {$I-} Reset (OldTxtFile, 1); {$I+}
  If IOResult <> 0 Then Begin
    Close (OldHdrFile);
    Exit;
  End;

  {$I-} Reset (OldIdxFile); {$I+}
  If IoResult <> 0 Then Begin
    Close (OldHdrFile);
    Close (OldTxtFile);
    Exit;
  End;

  Assign  (LastFile, BasePath + '.jlr');
  {$I-} Reset (LastFile); {$I+}
  If IoResult <> 0 Then ReWrite (LastFile);
  Close (LastFile);

  Assign  (NewHdrFile, BasePath + '._hr');
  ReWrite (NewHdrFile, 1);
  Assign  (NewTxtFile, BasePath + '._dt');
  ReWrite (NewTxtFile, 1);
  Assign  (NewIdxFile, BasePath + '._dx');
  ReWrite (NewIdxFile);

  BlockRead (OldHdrFile, SigHdr, SizeOf(SigHdr));

  Inc (SigHdr.ModCounter);

  BlockWrite (NewHdrFile, SigHdr, SizeOf(SigHdr));

  If SigHdr.ActiveMsgs > MBase.MaxMsgs Then
    KillOffset := SigHdr.ActiveMsgs - MBase.MaxMsgs
  Else
    KillOffset := 0;

  TotalMsgs   := 0;
  TotalKilled := 0;

  New (TxtBuf);

  While Not Eof(OldIdxFile) Do Begin
    UpdateSpin;

    Read (OldIdxFile, MsgIdx);

    If MsgIdx.HdrLoc = -1 Then Begin
      Killed    := True;
      LimitKill := False;
      HaveHdr   := False;
    End Else Begin
      Seek (OldHdrFile, MsgIdx.HdrLoc);

      BlockRead (OldHdrFile, MsgHdr, SizeOf(MsgHdr));

      LimitKill := False;
      Killed    := MsgHdr.Attr1 and Jam_Deleted <> 0;
      HaveHdr   := True;

      If MBase.MaxAge > 0 Then Begin
        UnixToDT (MsgHdr.DateWritten, MsgDateTime);
        PackTime (MsgDateTime, Temp);

        LimitKill := DaysAgo(Temp) > MBase.MaxAge;
        Killed    := Killed or LimitKill;
      End;

      If MBase.MaxMsgs > 0 Then
        If KillOffset > 0 Then Begin
          Dec (KillOffset);
          LimitKill := True;
          Killed    := True;
        End;

      If SkipFirst and (MBase.NetType = 0) and (TotalMsgs = 0) and (MsgHdr.Attr1 and Jam_Deleted = 0) Then
        Killed := False;
    End;

    If Killed Then Begin
      Inc (TotalKilled);

(*
      Reset (LastFile);
      While Not Eof(LastFile) Do Begin
        Read (LastFile, Last);
        If (Last.LastRead > TotalMsgs) And Not LimitKill Then Begin
          Dec   (Last.LastRead);
          Seek  (LastFile, FilePos(LastFile) - 1);
          Write (LastFile, Last);
        End;
      End;
      Close (LastFile);
*)
      If HaveHdr And (MsgHdr.ReplyFirst <> 0) Then Begin
        Assign (TmpHdrFile, BasePath + '.jhr');
        Reset  (TmpHdrFile, 1);

        BlockRead (TmpHdrFile, TmpSigHdr, SizeOf(TmpSigHdr));

        While Not Eof(TmpHdrFile) Do Begin
          BlockRead (TmpHdrFile, TmpMsgHdr, SizeOf(TmpMsgHdr));

          If TmpMsgHdr.ReplyTo = MsgHdr.MsgNumber Then Begin
            TmpMsgHdr.ReplyTo := 0;
            Seek (TmpHdrFile, FilePos(TmpHdrFile) - SizeOf(TmpMsgHdr));
            BlockWrite (TmpHdrFile, TmpMsgHdr, SizeOf(TmpMsgHdr));
          End;

          Seek (TmpHdrFile, FilePos(TmpHdrFile) + TmpMsgHdr.SubFieldLen);
        End;
        Close (TmpHdrFile);
      End;

    End Else Begin
      Inc (TotalMsgs);

      If TotalKilled > 0 Then Begin
        Reset (LastFile);
        While Not Eof(LastFile) Do Begin
          Read (LastFile, Last);
          If Last.LastRead = MsgHdr.MsgNumber Then Begin
            Last.LastRead := TotalMsgs;
            Seek  (LastFile, FilePos(LastFile) - 1);
            Write (LastFile, Last);
          End;
        End;
        Close (LastFile);
      End;

      If (TotalKilled > 0) and (MsgHdr.ReplyFirst <> 0) Then Begin
        Assign (TmpHdrFile, BasePath + '.jhr');
        Reset  (TmpHdrFile, 1);

        BlockRead (TmpHdrFile, TmpSigHdr, SizeOf(TmpSigHdr));

        While Not Eof(TmpHdrFile) Do Begin
          BlockRead (TmpHdrFile, TmpMsgHdr, SizeOf(TmpMsgHdr));

          If TmpMsgHdr.ReplyTo = MsgHdr.MsgNumber Then Begin
            TmpMsgHdr.ReplyTo := TotalMsgs;
            Seek (TmpHdrFile, FilePos(TmpHdrFile) - SizeOf(TmpMsgHdr));
            BlockWrite (TmpHdrFile, TmpMsgHdr, SizeOf(TmpMsgHdr));
          End;

          Seek (TmpHdrFile, FilePos(TmpHdrFile) + TmpMsgHdr.SubFieldLen);
        End;
        Close (TmpHdrFile);
      End;

      If (TotalKilled > 0) and (MsgHdr.ReplyTo <> 0) Then Begin
        Assign (TmpHdrFile, BasePath + '._hr');
        Reset  (TmpHdrFile, 1);

        BlockRead (TmpHdrFile, TmpSigHdr, SizeOf(TmpSigHdr));

        While Not Eof(TmpHdrFile) Do Begin
          BlockRead (TmpHdrFile, TmpMsgHdr, SizeOf(TmpMsgHdr));

          If TmpMsgHdr.ReplyFirst = MsgHdr.MsgNumber Then Begin
            TmpMsgHdr.ReplyFirst := TotalMsgs;
            Seek (TmpHdrFile, FilePos(TmpHdrFile) - SizeOf(TmpMsgHdr));
            BlockWrite (TmpHdrFile, TmpMsgHdr, SizeOf(TmpMsgHdr));
          End;

          Seek (TmpHdrFile, FilePos(TmpHdrFile) + TmpMsgHdr.SubFieldLen);
        End;
        Close (TmpHdrFile);
      End;

      MsgHdr.MsgNumber := TotalMsgs;
      MsgIdx.HdrLoc    := FilePos(NewHdrFile);

      (* write text from old file to new file *)

      If MsgHdr.TextLen > 65000 Then MsgHdr.TextLen := 65000;
      // Why did I put this limitation here?  Prob should be removed
      // need to be tested.

      Seek       (OldTxtFile, MsgHdr.TextOfs);
      BlockRead  (OldTxtFile, TxtBuf^, MsgHdr.TextLen);

      MsgHdr.TextOfs := FileSize(NewTxtFile);

      BlockWrite (NewTxtFile, TxtBuf^, MsgHdr.TextLen);

      (* write header from old to new file *)

      BlockWrite (NewHdrFile, MsgHdr, SizeOf(MsgHdr));

      (* write subfield data if it exists *)

      If MsgHdr.SubFieldLen > 0 Then Begin
        Count := 1;

        While (Count <= MsgHdr.SubFieldLen) Do Begin
          BlockRead  (OldHdrFile, SubField, 8);
          BlockRead  (OldHdrFile, SubField.Data, SubField.DataLen);
          BlockWrite (NewHdrFile, SubField, 8);
          BlockWrite (NewHdrFile, SubField.Data, SubField.DataLen);

          Inc (Count, 8 + SubField.DataLen);
        End;
      End;

      (* write new index to index file *)

      Write (NewIdxFile, MsgIdx);
    End;
  End;

  Dispose (TxtBuf);

  SigHdr.ActiveMsgs := TotalMsgs;
  SigHdr.BaseMsgNum := 1;

  Reset (NewHdrFile, 1);
  BlockWrite (NewHdrFile, SigHdr, SizeOf(SigHdr));

  SavedBytes := (FileSize(OldHdrFile) - FileSize(NewHdrFile)) +
                (FileSize(OldTxtFile) - FileSize(NewTxtFile)) +
                ((FileSize(OldIdxFile) - FileSize(NewIdxFile)) * SizeOf(MsgIdx));

  Close (OldHdrFile);
  Close (OldTxtFile);
  Close (OldIdxFile);
  Close (NewHdrFile);
  Close (NewTxtFile);
  Close (NewIdxFile);

  Erase (OldHdrFile);
  Erase (OldTxtFile);
  Erase (OldIdxFile);

  ReName (NewHdrFile, BasePath + '.jhr');
  ReName (NewTxtFile, BasePath + '.jdt');
  ReName (NewIdxFile, BasePath + '.jdx');

  If TotalKilled > 0 Then Begin
    Reset (LastFile);
    While Not Eof(LastFile) Do Begin
      Read (LastFile, Last);
      If Last.LastRead > TotalMsgs Then Last.LastRead := TotalMsgs;
      If Last.HighRead > Last.LastRead Then Last.HighRead := Last.LastRead;
      Seek (LastFile, FilePos(LastFile) - 1);
      Write (LastFile, Last);
    End;
    Close (LastFile);
  End;
End;

Procedure ShowHelp;
Begin
  WriteLn ('Invalid command line options');
  WriteLn;
  WriteLn ('-PACK      : Pack all jam message bases');
  WriteLn ('-SKIPFIRST : Skips the first message of each local message base');
  WriteLn;
  PWriteLn ('|12NOTE: This program can sometimes crash if users are online.|07');
  Halt(1);
End;

Var
  TotalMsgs  : LongInt;
  TotalBytes : LongInt;
  Msgs       : LongInt;
  Bytes      : LongInt;
  Count      : Byte;
  Str        : String;
Begin
  FileMode := 66;

  ClrScr;
  PWriteLn ('|08-> |15MYSTPACK ' + PackVer + ' : JAM message base packer');
  PWriteLn ('|08-> |07Compatible with Mystic BBS software v' + mysVersion);
  PWriteLn ('|08�������������������������������������������������������������������������������|07');
  WriteLn;

  Window (1, 5, 80, 24);

  If ParamCount = 0 Then ShowHelp;

  For Count := 1 to ParamCount Do Begin
    Str := strUpper(ParamStr(Count));

    If Str = '-PACK' Then
      PackMsgs := True
    Else
    If Str = '-SKIPFIRST' Then
      SkipFirst := True
    Else
      ShowHelp;
  End;

  Assign (ConfigFile, 'mystic.dat');
  {$I-} Reset (ConfigFile); {$I+}
  If IoResult <> 0 Then Begin
    WriteLn ('ERROR: Unable to read MYSTIC.DAT.  Run from root Mystic directory');
    Halt(1);
  End;
  Read (ConfigFile, Config);
  Close (ConfigFile);

  If Config.DataChanged <> mysDataChanged Then Begin
    WriteLn('ERROR: Data files are not current and must be upgraded.');
    Halt(1);
  End;

  Assign (MBaseFile, Config.DataPath + 'mbases.dat');
  {$I-} Reset(MBaseFile); {$I+}
  If IoResult <> 0 Then Begin
    WriteLn ('ERROR: Unable to read message area data');
    Halt(1);
  End;

  While Not Eof(MBaseFile) Do Begin
    Read (MBaseFile, MBase);

    If MBase.BaseType = 0 Then Begin
      PackJAMBase(Msgs, Bytes);
      WriteLn (#8 + 'Killed ', Msgs, '; ', Bytes, ' bytes');

      Inc (TotalMsgs, Msgs);
      Inc (TotalBytes, Bytes);
    End;
  End;

  Close (MBaseFile);

  WriteLn;
  PWriteLn ('|08[|07-|08] |07Killed |15' + strI2S(TotalMsgs) + '|07 Msgs; Removed |15' + strI2S(TotalBytes) + '|07 bytes');

  Window (1, 1, 80, 25);
End.