#include <xpk/xpkobsolete.h>
#include <exec/alerts.h>
#include <exec/memory.h>
#include <exec/execbase.h>

#include <dos/dosextens.h>
#include <dos/dosasl.h>
#include <dos/rdargs.h>

#include <xpk/xpk.h>
#include <pragma/exec_lib.h>
#include <pragma/dos_lib.h>
#include <pragma/xpkmaster_lib.h>

#include <string.h>

#define XFH_ID "XFH A"

void PrintF(char *,...);
LONG __regargs PackFile(struct DosLibrary *DOSBase,struct Library *XpkBase,
                        char *,struct FileInfoBlock *,char *,char *,LONG,char *,
                        LONG,LONG,LONG,LONG,LONG);
LONG __regargs UnPackFile(struct DosLibrary *DOSBase,struct Library *XpkBase,
                          char *,char *,struct FileInfoBlock *,char *,LONG);
LONG __asm __saveds ChunkFunc(register __a1 struct XpkProgress *);

struct Hook ChunkHook = {{0L},ChunkFunc};

struct xPackArgs
 {
  char **Files;
  char *Method;
  ULONG *MinSize;
  char *Suffix,*Password;
  LONG All,Force,Program,xScan,Lossy,Quiet;
 };

char *VersionString = "$VER: xPack 1.5 ("__DATE__")";
char *Template =
 "FILE/M/A,METHOD/K,MINSIZE/N/K,SUFFIX/K,PASSWORD/K,"
 "ALL/S,FORCE/S,PROGRAM/S,XSCAN/S,LOSSY/S,QUIET/S";

LONG __saveds main(void)
{
 struct DosLibrary *DOSBase;
 struct Library *XpkBase;
 struct Process *MyProc;
 struct RDArgs *RDArgs;
 char ProgName[32];
 LONG Result;
 struct xPackArgs Args;
 char **Files;
 struct AnchorPath *AnchorPath;
 ULONG MinSize;

 if ((MyProc=(struct Process *)FindTask(NULL))->pr_CLI==NULL)
  {
   (void)WaitPort(&MyProc->pr_MsgPort);
   Forbid();
   ReplyMsg (GetMsg(&MyProc->pr_MsgPort));

   return 0L;
  }

 if ((DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",33L))==NULL)
  {
   Alert (AT_Recovery|AG_OpenLib|AO_DOSLib);

   return 20L;
  }
 if (DOSBase->dl_lib.lib_Version<37L)
  {
   (void)Write(Output(),"This program requires OS 2.04 or newer.\n",40L);

   CloseLibrary (&DOSBase->dl_lib);
   return 20L;
  }

 if (!GetProgramName(ProgName,32L)) (void)strcpy(ProgName,"xPack");
 Result=10L;

 if ((XpkBase=OpenLibrary(XPKNAME,2L))==NULL)
  {
   PrintF ("%s: %s V2 or newer required !\n",ProgName,XPKNAME);

   goto Close1;
  }

 Args.Files=NULL;
 Args.Method=Args.Suffix=Args.Password=NULL;
 Args.MinSize=NULL;
 Args.All=Args.Force=Args.Program=Args.xScan=Args.Lossy=Args.Quiet=FALSE;
 if ((RDArgs=ReadArgs(Template,(LONG *)&Args,NULL))==NULL)
  {
   PrintFault (IoErr(),ProgName);

   goto Close2;
  }

 if (Args.MinSize) MinSize=*Args.MinSize;
 else MinSize=512L;
 if (Args.Password) Args.Force=TRUE;

 if ((AnchorPath=(struct AnchorPath *)AllocVec(sizeof(struct AnchorPath)+256L,
                                               MEMF_PUBLIC|MEMF_CLEAR))==NULL)
  {
   PrintFault (ERROR_NO_FREE_STORE,ProgName);

   goto Close3;
  }
 AnchorPath->ap_BreakBits=SIGBREAKF_CTRL_C;
 AnchorPath->ap_Strlen=256;

 Files=Args.Files;
 while (*Files)
  {
   LONG RetVal;

   for (RetVal=MatchFirst(*Files,AnchorPath); RetVal==0L; RetVal=MatchNext(AnchorPath))
    {
     if (AnchorPath->ap_Info.fib_DirEntryType>0L)
      {
       if (((AnchorPath->ap_Flags&APF_DIDDIR)==0L)&&Args.All)
        AnchorPath->ap_Flags|=APF_DODIR;
       AnchorPath->ap_Flags&=~APF_DIDDIR;
      }
     else
      {
       BPTR DirLock;
       LONG Result;

       DirLock=CurrentDir(AnchorPath->ap_Current->an_Lock);
       if (Args.Method==NULL)
        Result=UnPackFile(DOSBase,XpkBase,
                          AnchorPath->ap_Buf,Args.Suffix,&AnchorPath->ap_Info,
                          Args.Password,Args.Quiet);
       else
        Result=PackFile(DOSBase,XpkBase,
                        AnchorPath->ap_Buf,&AnchorPath->ap_Info,Args.Method,
                        Args.Suffix,MinSize,Args.Password,
                        Args.Force,Args.Program,Args.xScan,Args.Lossy,Args.Quiet);
       (void)CurrentDir(DirLock);

       if (!Result)
        {
         PrintF ("\n*** Aborting\n");

         MatchEnd (AnchorPath);
         goto Close4;
        }
      }
    }
   MatchEnd (AnchorPath);

   if (RetVal!=ERROR_NO_MORE_ENTRIES)
    {
     PrintF ("%s: %s - ",ProgName,*Files);
     PrintFault (RetVal,NULL);
     
     goto Close4;
    }
   else Files++;
  }
 Result=0L;

Close4: /* I hate "goto"s, but it's the only way to keep it short. */
 FreeVec ((APTR)AnchorPath);
Close3:
 FreeArgs (RDArgs);
Close2:
 CloseLibrary (XpkBase);
Close1:
 CloseLibrary (&DOSBase->dl_lib);
 return Result;
}

void PrintF(char *FormatString,...)

{
 struct DosLibrary *DOSBase;

 if (DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",37L))
  {
   (void)VPrintf(FormatString,(LONG *)&FormatString+1L);
   (void)Flush(Output());

   CloseLibrary (&DOSBase->dl_lib);
  }
}

void __regargs TempName(char *Name)

{
 UWORD Index;
 ULONG Addr;

 for (Index=0, Addr=(ULONG)FindTask(NULL); Index<8; Index++, Addr>>=4)
  *Name++='A'+(char)(Addr&15);
 (void)strcpy(Name,".zop");
}

LONG __asm __saveds ChunkFunc(register __a1 struct XpkProgress *Prog)

{
 if (Prog->Type==XPKPROG_START ) PrintF ("\x1B[0 p");
 PrintF ("\r%-9s %-12s (%6ld bytes, %3ld%% done, %2ld%% CF) ",
         Prog->Activity,Prog->FileName,Prog->ULen,Prog->Done,Prog->CF);
 if (Prog->Type==XPKPROG_END) PrintF ("\n\x1B[1 p");

 return (LONG)SetSignal(0L,SIGBREAKF_CTRL_C)&SIGBREAKF_CTRL_C;
}

char *ULTA(char *Ptr,ULONG LW)

{
 UWORD Index;

 *Ptr++=' ';
 for (Index=0; Index<8; Index++, LW>>=4) Ptr[7-Index]='A'+(char)(LW&15);
 return &Ptr[8];
}

#define TAGIT(i,t,d) XpkTags[i].ti_Tag=t; XpkTags[i].ti_Data=(ULONG)d;
#define ENDIT(i) XpkTags[i].ti_Tag=TAG_DONE;

LONG __regargs PackFile(struct DosLibrary *DOSBase,struct Library *XpkBase,
                        char *SourceName,struct FileInfoBlock *FIB,char *Method,
                        char *Suffix,LONG MinSize,char *Password,
                        LONG Force,LONG Program,LONG xScan,LONG Lossy,LONG Quiet)

{
 BPTR Handle;
 struct TagItem XpkTags[10];
 struct XpkFib XpkFib;
 char ErrorBuffer[XPKERRMSGSIZE],TargetName[108];
 LONG Error,Size;

 if (FIB->fib_Protection&FIBF_DELETE)
  {
   PrintF ("Skipping %s (delete protected)\n",SourceName);
   return TRUE;
  }
 if (FIB->fib_Size<=MinSize)
  {
   PrintF ("Skipping %s (too small)\n",SourceName);
   return TRUE;
  }
 if ((Handle=Open(FIB->fib_FileName,MODE_OLDFILE))==NULL)
  {
   PrintF ("Can't open %s\n",SourceName);
   return TRUE;
  }
 if (!Force)
  {
   TAGIT(0,XPK_GetError,ErrorBuffer);
   TAGIT(1,XPK_InFH,Handle);
   ENDIT(2);
   if (XpkExamine(&XpkFib,XpkTags))
    {
     Close (Handle);

     PrintF ("%s\n",ErrorBuffer);
     return TRUE;
    }
   if (XpkFib.Type!=XPKTYPE_UNPACKED)
    {
     Close (Handle);

     PrintF ("Skipping %s (already crunched)\n",SourceName);
     return TRUE;
    }
  }
 if (Program)
  {
   LONG Buffer[5];

   if (Read(Handle,Buffer,20L)!=20L)
    {
     Close (Handle);

     PrintF ("Skipping %s (to small for executable)\n",SourceName);
     return TRUE;
    }
   if (Buffer[0]!=0x3F3L)
    {
     Close (Handle);

     PrintF ("Skipping %s (not executable)\n",SourceName);
     return TRUE;
    }
   if ((Buffer[3]!=0L)||((Buffer[4]+1L)!=Buffer[2]))
    {
     Close (Handle);

     PrintF ("Skipping %s (overlayed)\n",SourceName);
     return TRUE;
    }
   (void)Seek(Handle,0L,OFFSET_BEGINNING);
  }

 if (Suffix)
  {
   ULONG Length;

   Length=strlen(strcpy(TargetName,FIB->fib_FileName));
   (void)strcpy(&TargetName[Length],Suffix);
  }
 else TempName (TargetName);
 Size=0L;

 TAGIT(0,XPK_GetError,ErrorBuffer);
 TAGIT(1,XPK_Password,Password);
 TAGIT(2,XPK_FindMethod,Method);
 TAGIT(3,XPK_ChunkHook,&ChunkHook);
 if (Quiet) XpkTags[3].ti_Tag=TAG_IGNORE;
 TAGIT(4,XPK_FileName,FIB->fib_FileName);
 TAGIT(5,XPK_InFH,Handle);
 TAGIT(6,XPK_OutName,TargetName);
 TAGIT(7,XPK_GetOutLen,&Size);
 TAGIT(8,XPK_LossyOK,Lossy);
 if (!Lossy) XpkTags[8].ti_Tag=TAG_IGNORE;
 ENDIT(9);

 if (Error=XpkPack(XpkTags))
  {
   Close (Handle);

   PrintF ("%s\n",ErrorBuffer);
   return (Error!=XPKERR_ABORTED);
  }
 Close (Handle);

 if ((Size>FIB->fib_Size)&&(!Force))
  {
   (void)DeleteFile(TargetName);

   PrintF ("Skipping %s (unable to reduce size)\n",SourceName);
   return TRUE;
  }

 if (FIB->fib_Comment[0]=='\0')
  if (xScan)
   {
    char *Ptr;

    Ptr=strcpy(FIB->fib_Comment,XFH_ID)+strlen(XFH_ID);
    Ptr=ULTA(Ptr,FIB->fib_Date.ds_Days);
    Ptr=ULTA(Ptr,FIB->fib_Date.ds_Minute);
    Ptr=ULTA(Ptr,FIB->fib_Date.ds_Tick);
    Ptr=ULTA(Ptr,Size);
    *ULTA(Ptr,FIB->fib_Size)='\0';
   }

 if (FIB->fib_Comment[0]) (void)SetComment(TargetName,FIB->fib_Comment);

 if (FIB->fib_Protection) (void)SetProtection(TargetName,FIB->fib_Protection);
 (void)SetFileDate(TargetName,&FIB->fib_Date);

 if (Suffix) return TRUE;

 if (!DeleteFile(FIB->fib_FileName))
  {
   (void)DeleteFile(TargetName);

   PrintF ("Unable to delete %s\n",SourceName);
   return TRUE;
  }
 if (!Rename(TargetName,FIB->fib_FileName))
  {
   PrintF ("Unable to rename %s to %s\n",TargetName,FIB->fib_FileName);
   return FALSE;
  }

 return TRUE;
}

LONG __regargs UnPackFile(struct DosLibrary *DOSBase,struct Library *XpkBase,
                          char *SourceName,char *Suffix,
                          struct FileInfoBlock *FIB,char *Password,LONG Quiet)

{
 BPTR Handle;
 struct TagItem XpkTags[7];
 struct XpkFib XpkFib;
 char ErrorBuffer[XPKERRMSGSIZE],TargetName[108];
 LONG SameFile,Error;

 if (FIB->fib_Protection&FIBF_DELETE)
  {
   PrintF ("Skipping %s (delete protected)\n",SourceName);
   return TRUE;
  }
 if ((Handle=Open(FIB->fib_FileName,MODE_OLDFILE))==NULL)
  {
   PrintF ("Can't open %s\n",SourceName);
   return TRUE;
  }

 TAGIT(0,XPK_GetError,ErrorBuffer);
 TAGIT(1,XPK_InFH,Handle);
 ENDIT(2);

 if (XpkExamine(&XpkFib,XpkTags))
  {
   Close (Handle);

   PrintF ("%s\n",ErrorBuffer);
   return TRUE;
  }
 if (XpkFib.Type!=XPKTYPE_PACKED)
  {
   Close (Handle);

   PrintF ("Skipping %s (not crunched)\n",SourceName);
   return TRUE;
  }

 TempName (TargetName);
 SameFile=TRUE;
 if (Suffix)
  {
   LONG Len1,Len2;

   Len1=strlen(FIB->fib_FileName);
   Len2=strlen(Suffix);
   if (Len1>Len2)
    if (stricmp(&FIB->fib_FileName[Len1-Len2],Suffix)==0)
     {
      strcpy(TargetName,FIB->fib_FileName)[Len1-Len2]='\0';
      SameFile=FALSE;
     }
  }

 TAGIT(0,XPK_GetError,ErrorBuffer);
 TAGIT(1,XPK_Password,Password);
 TAGIT(2,XPK_ChunkHook,&ChunkHook);
 if (Quiet) XpkTags[2].ti_Tag=TAG_IGNORE;
 TAGIT(3,XPK_FileName,FIB->fib_FileName);
 TAGIT(4,XPK_InFH,Handle);
 TAGIT(5,XPK_OutName,TargetName);
 ENDIT(6);

 if (Error=XpkUnpack(XpkTags))  {
   Close (Handle);

   PrintF ("%s\n",ErrorBuffer);
   return (Error!=XPKERR_ABORTED);
  }
 Close (Handle);

 if (FIB->fib_Comment[0])
  if (strncmp(FIB->fib_Comment,XFH_ID,strlen(XFH_ID))!=0)
   (void)SetComment(TargetName,FIB->fib_Comment);
 if (FIB->fib_Protection) (void)SetProtection(TargetName,FIB->fib_Protection);
 (void)SetFileDate(TargetName,&FIB->fib_Date);

 if (!SameFile) return TRUE;

 if (!DeleteFile(FIB->fib_FileName))
  {
   (void)DeleteFile(TargetName);

   PrintF ("Unable to delete %s\n",SourceName);
   return TRUE;
  }
 if (!Rename(TargetName,FIB->fib_FileName))
  {
   PrintF ("Unable to rename %s to %s\n",TargetName,FIB->fib_FileName);
   return FALSE;
  }

 return TRUE;
}
