/* :ts=3
** PicSize - determines picture size, depth and format
**
** written & © Ralph Reuchlein <amiga@rripley.de> 1999-2000
*/

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

#include <exec/types.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/datatypes.h>
#include <proto/iffparse.h>
#include <datatypes/pictureclass.h>
#include <exec/memory.h>
#include <dos/exall.h>

const UBYTE ver_string[] = "$VER: Picsize 1.3 (20.04.00)Copyright © 1999-2000 by Ralph Reuchlein";

/*#define  DEBUG    1*/


/* ReadArgs defines ----------------------------------------------- */
#define ARGS_TEMPLATE   "FILE=PATTERN/A,"\
                        "FORMAT=FMT,"\
                        "INTRODUCER=INTR/K,"\
                        "SCALE_WIDTH=W/K/N,"\
                        "SCALE_HEIGHT=H/K/N"

enum {
   OPT_FILE = 0,
   OPT_FMT,
   OPT_INTR,
   OPT_SWIDTH,
   OPT_SHEIGHT,

   OPT_COUNT
};
LONG args[OPT_COUNT];

#define ARG_FILE     ((STRPTR)args[OPT_FILE])
#define ARG_FMT      ((STRPTR)args[OPT_FMT])
#define ARG_INTR     ((STRPTR)args[OPT_INTR])
#define ARG_SWIDTH   ((args[OPT_SWIDTH])?(*(ULONG *)args[OPT_SWIDTH]):(0))
#define ARG_SHEIGHT  ((args[OPT_SHEIGHT])?(*(ULONG *)args[OPT_SHEIGHT]):(0))

#define IS_FILE      (args[OPT_FILE])
#define IS_FMT       (args[OPT_FMT])
#define IS_INTR      (args[OPT_INTR])
#define IS_SWIDTH    (args[OPT_SWIDTH])
#define IS_SHEIGHT   (args[OPT_SHEIGHT])

#define DEFAULT_FORMAT        "%f: %wx%hx%d [%t,%s bytes]"
#define DEFAULT_INTRODUCER    '%'


/* Structures ----------------------------------------------------- */
typedef struct s_thumbnail {
   ULONG width;
   ULONG height;
} thumbnail;



/* Prototypes ----------------------------------------------------- */
void     processPattern(STRPTR);
void     processDirectory(STRPTR,STRPTR);
void     processFile(STRPTR);
void     error(STRPTR,...);
void     debugf(STRPTR fmt,...);
STRPTR   dupstr(STRPTR);
void     freestr(STRPTR);
BPTR     testFile(STRPTR);
void     printFmt(STRPTR,struct BitMapHeader *,struct DataType *,BPTR);

thumbnail   calcThumbnail(ULONG,ULONG,ULONG,ULONG);


/* Global variables ----------------------------------------------- */

BOOL  fileOk=FALSE;
ULONG fileSize=0;
#define FILENAMESIZE 512
UBYTE fileName[FILENAMESIZE];
int   errReturnValue=0;



/* ---------------------------------------------------------------- */
int main(UWORD argc,STRPTR argv[]) {
   struct RDArgs *rdargs;

   /* process arguments */
   if (rdargs=ReadArgs(ARGS_TEMPLATE,args,NULL)) {
      processPattern(ARG_FILE);
      FreeArgs(rdargs);
   }
   else {
      error("Usage: %s %s\n",argv[0],ARGS_TEMPLATE);
   }
   return errReturnValue;
}

void processPattern(STRPTR pattern) {
   STRPTR tokens = (STRPTR)AllocVec(4+2*strlen(pattern),MEMF_PUBLIC|MEMF_REVERSE);
   if (tokens) {
      LONG isWild = ParsePatternNoCase(FilePart(pattern),tokens,3+2*strlen(pattern));
      debugf("Pattern: '%s'\n"
             "Tokens:  '%s' (len: %u)\n",FilePart(pattern),tokens,strlen(tokens));
      switch (isWild) {
         case 0:        /* no wildcard in it */
            debugf("=> no wildcard found.\n");
            processFile(pattern);
            break;
         case 1:        /* there is at least a wildcard in it */
            debugf("=> at least one wildcard found.\n");
            processDirectory(pattern,tokens);
            break;
         default:
            error("Bad parameter FILE: Pattern detection failed!");
      }
      FreeVec(tokens);
   }
}


#define EXALLDATASIZE 512

void processDirectory(STRPTR pattern,STRPTR tokens) {
   BPTR dirlock;
   ULONG cnt;
   STRPTR path = dupstr(pattern);
   if (path) {
      /* extract path part */
      STRPTR pathend = PathPart(path);
      *pathend=0;
      /* lock directory */
      debugf("Locking directory '%s'\n",path);
      dirlock = Lock(path,ACCESS_READ);
      if (dirlock) {
         /* use ExAll to parse thru directory using the pattern */
         struct ExAllData *ead = AllocVec(EXALLDATASIZE,MEMF_PUBLIC|MEMF_REVERSE);
         if (ead) {
            struct ExAllControl *eac=AllocDosObject(DOS_EXALLCONTROL,NULL);
            if (eac) {
               BOOL more;
               LONG err;
               struct ExAllData *ead_tmp=ead;
               eac->eac_Entries = 0;
               eac->eac_LastKey = 0;
               eac->eac_MatchString = tokens;
               eac->eac_MatchFunc = NULL;
               do {
                  ead=ead_tmp;
                  more = ExAll(dirlock,ead,EXALLDATASIZE,ED_SIZE,eac);
                  err=IoErr();
                  debugf("ExAll returned %s.\n",(more)?("TRUE"):("FALSE"));
                  debugf("%u entries found.\n",eac->eac_Entries);
                  if ((!more) && (err != ERROR_NO_MORE_ENTRIES)) {
                     /* ExAll failed abnormally */
                     error("%s: Couldn't examine directory, Error %i: ",path,err);
                     PrintFault(err,NULL);
                     error("\n");
                     break;
                  }
                  if (eac->eac_Entries == 0) {
                     /* ExAll failed normally with no entries */
                     /*error("%s: No (more) entries found in directory.",path);*/
                     continue;   /* 'more' is usually NULL */
                  }
                  /* process found files */
                  cnt=0;
                  do {
                     if ((ead->ed_Type != ST_SOFTLINK) &&
                         (ead->ed_Type != ST_LINKFILE) &&
                         (ead->ed_Type != ST_FILE)) {
                        /* skip directories */
                        continue;
                     }
                     
                     fileName[0]=0;
                     AddPart(fileName,path,FILENAMESIZE);
                     AddPart(fileName,ead->ed_Name,FILENAMESIZE);
                     debugf("=> Processing file %u: %s\n",++cnt,fileName);
                     processFile(fileName);
                     ead = ead->ed_Next;
                  } while (ead);
               } while(more);

               FreeDosObject(DOS_EXALLCONTROL,eac);
            }
            else {
               error("Couldn't allocate control structure for directory scan!\n");
            }
            FreeVec(ead);
         }
         UnLock(dirlock);
      }
      else {
         LONG err=IoErr();
         error("%s: Couldn't access to directory, Error %i: ",path,err);
         PrintFault(err,NULL);
         error("\n");
      }
      freestr(path);
   }
}

void processFile(STRPTR fname) {
   /* test if we really have a file */
   Object  *picobj;
   BPTR     fl;
   if (fl=testFile(fname)) {
      /* we now create new DataType object ... */
      if (picobj=NewDTObject(fname,TAG_DONE))
      {
         /* get all information we need */
         struct BitMapHeader *bmhdr;
         struct DataType     *dt;
         ULONG                id;
         GetDTAttrs(picobj,DTA_DataType,&dt,TAG_DONE);
         id = dt->dtn_Header->dth_GroupID;
         /* ... which must be a still picture */
         if (id == GID_PICTURE) {
            SetDTAttrs(picobj,NULL,NULL,PDTA_FreeSourceBitMap,TRUE,TAG_DONE);
            GetDTAttrs(picobj,PDTA_BitMapHeader,&bmhdr,TAG_DONE);

            /* make formatted output */
            if (ARG_FMT) {
               printFmt(ARG_FMT,bmhdr,dt,fl);
            }
            else {
               STRPTR fmt=dupstr(DEFAULT_FORMAT);
               printFmt(fmt,bmhdr,dt,fl);
               freestr(fmt);
            }
         }
         else {
            error("%s: File is not of type '%s' (but is '%s')!\n",fname,GetDTString(GID_PICTURE),GetDTString(id));
         }
         /* dipose DataType object */
         DisposeDTObject(picobj);
      }
      else {
         long err=IoErr();
         error("%s: Couldn't create DataType object, Error %i: ",fname,err);
         PrintFault(err,NULL);
         error("\n");
      }
      UnLock(fl);
   }
}


/* ---------------------------------------------------------------- */
void error(STRPTR fmt,...) {
   va_list args;
   va_start(args,fmt);
   vfprintf(stdout,fmt,args);
   va_end(args);
   errReturnValue=1;
   fflush(stdout);
}



void debugf(STRPTR fmt,...) {
#ifdef DEBUG
   va_list args;
   va_start(args,fmt);
   vfprintf(stdout,fmt,args);
   va_end(args);
   fflush(stdout);
#endif
}



STRPTR dupstr(STRPTR src) {
   STRPTR dup=(STRPTR)AllocVec(strlen(src)+1,MEMF_PUBLIC|MEMF_REVERSE);
   if (dup) {
      strcpy(dup,src);
      return dup;
   }
   error("Couldn't allocate %u bytes memory for dupstr()\n",strlen(src)+1);
   return NULL;
}

void freestr(STRPTR dup) {
   if (dup) FreeVec(dup);
}


/* ---------------------------------------------------------------- */
BPTR testFile(STRPTR filename) {
   BOOL ok=FALSE;
   BPTR l=NULL;
   struct FileInfoBlock *fib=AllocDosObject(DOS_FIB,NULL);
   if (fib) {
      /* lock the file */
      if (l=Lock(filename,ACCESS_READ)) {
         /* examine file */
         if (Examine(l,fib)) {
            /* test for file */
            switch(fib->fib_DirEntryType) {
               case ST_SOFTLINK:       /* dangerous, because SOFTLINK can point to a directory */
               case ST_LINKFILE:
               case ST_FILE:
                  fileOk   = TRUE;                    /* stored global */
                  fileSize = (ULONG)fib->fib_Size;    /* stored global */
                  ok=TRUE;
                  break;
               default:
                  break;
            }
         }
         else {
            error("%s: Couldn't examine file information!\n",filename);
         }
      }
      else {
         error("%s: Couldn't access to this file (maybe not found?)!\n",filename);
      }
      FreeDosObject(DOS_FIB,fib);
   }
   else {
      error("%s: Couldn't allocate FileInfoBlock memory!\n",filename);
   }
   if (ok) {
      /* we return the file lock if ok */
      return l;
   }
   else {
      if (l) UnLock(l);
   }
   return NULL;
}



/* ---------------------------------------------------------------- */
#define FMT_STRING      's'
#define FMT_INTEGER     'i'

void printFmt(STRPTR fmt,struct BitMapHeader *bmhd,struct DataType *dt,BPTR lock) {
   register int tmp0,tmp1,intr;
   STRPTR   fmt_end = fmt + strlen(fmt),tmp_fmt,str2;
   thumbnail   tnail;

   debugf("Format: '%s'\n",fmt);

   /* if there is no format, return */
   if (!fmt) return;

   /* determine full filename */
   if (!NameFromLock(lock,fileName,FILENAMESIZE)) {
      error("Couldn't examine full filename!\n");
      return;
   }

   /* do we have specified a special introducer? */
   if (ARG_INTR) {
      intr = *ARG_INTR;    /* only the first character */
   }
   else {
      intr = DEFAULT_INTRODUCER;
   }

   /* calculate thumbnail sizes */
   tnail = calcThumbnail(bmhd->bmh_Width,bmhd->bmh_Height,ARG_SWIDTH,ARG_SHEIGHT);

   /* we process the format string: replace the format types by
      C-format types and process the format portion one by one */
   tmp_fmt=fmt;
   while(fmt<fmt_end) {
      tmp_fmt=fmt;
      /* search for the introducer */
      while ((*fmt!=intr) && (fmt<fmt_end)) fmt++; if (fmt>=fmt_end) break;
      /* write string til introducer */
      *fmt=0; fputs(tmp_fmt,stdout); *fmt='%';     /* fprintf() only uses '%'! */
      tmp_fmt=fmt;
      /* search for the terminating format type character */
      while ((!isalpha(*fmt)) && (fmt<fmt_end)) fmt++; if (fmt>=fmt_end) break;
      tmp0 = *fmt; tmp1=*(fmt+1); *(fmt+1)=0;
      switch (tmp0) {
         case 'n':      /* basename of file */
            *fmt=FMT_STRING;
            fprintf(stdout,tmp_fmt,FilePart(fileName));
            break;
         case 'f':      /* full name of file */
            *fmt=FMT_STRING;
            fprintf(stdout,tmp_fmt,fileName);
            break;
         case 'P':      /* path name of file including trailing / and : */
            *fmt=FMT_STRING;
            str2=FilePart(fileName); tmp0=*str2; *str2=0;
            fprintf(stdout,tmp_fmt,fileName);
            *str2=tmp0;
            break;
         case 'p':      /* path name of file excluding trailing / */
            *fmt=FMT_STRING;
            str2=PathPart(fileName); tmp0=*str2; *str2=0;
            fprintf(stdout,tmp_fmt,fileName);
            *str2=tmp0;
            break;
         case 'o':      /* basename without suffix of file */
            *fmt=FMT_STRING;
            str2=fileName+strlen(fileName);
            while ((*str2!='.') && (str2>fileName)) str2--;
            if (*str2=='.')   { *str2=0; } else { str2=NULL; }
            fprintf(stdout,tmp_fmt,FilePart(fileName));
            if (str2) *str2='.';
            break;
         case 'e':      /* suffix/extension of file */
            *fmt=FMT_STRING;
            str2=fileName+strlen(fileName);
            while ((*str2!='.') && (str2>fileName)) str2--;
            if (*str2=='.')   fprintf(stdout,tmp_fmt,str2);
            break;
         case 'w':      /* width of image */
            *fmt=FMT_INTEGER;
            fprintf(stdout,tmp_fmt,bmhd->bmh_Width);
            break;
         case 'h':      /* height of image */
            *fmt=FMT_INTEGER;
            fprintf(stdout,tmp_fmt,bmhd->bmh_Height);
            break;
         case 'd':      /* depth of image */
            *fmt=FMT_INTEGER;
            fprintf(stdout,tmp_fmt,bmhd->bmh_Depth);
            break;
         case 't':      /* image format/type */
            *fmt=FMT_STRING;
            fprintf(stdout,tmp_fmt,dt->dtn_Header->dth_Name);
            break;
         case 's':      /* image file size */
            *fmt=FMT_INTEGER;
            fprintf(stdout,tmp_fmt,fileSize);
            break;
         case 'x':      /* scaled thumbnail width respecting image aspect */
            *fmt=FMT_INTEGER;
            fprintf(stdout,tmp_fmt,tnail.width);
            break;
         case 'y':      /* scaled thumbnail height respecting image aspect */
            *fmt=FMT_INTEGER;
            fprintf(stdout,tmp_fmt,tnail.height);
            break;
         case 'q':      /* single quote */
            fprintf(stdout,"\x22");
            break;
         case 'l':      /* newline */
            fprintf(stdout,"\n");
            break;
         case 'b':      /* tabulator */
            fprintf(stdout,"\t");
            break;
         default:
            fputs(tmp_fmt,stdout);
      }
      *fmt=tmp0; fmt++; *fmt=tmp1;
      tmp_fmt=fmt;
   }
   if (tmp_fmt<fmt) {
      fputs(tmp_fmt,stdout);
   }
   fputc('\n',stdout);
}



thumbnail calcThumbnail(ULONG width,ULONG height,ULONG scalex,ULONG scaley) {
   ULONG thumbwidth=width,thumbheight=height;
   ldiv_t quotrem;
   thumbnail tnail;

   /* case 1: Only SCALE_HEIGHT */
   if ((scalex == 0) && (scaley > 0)) {
      debugf("HEIGHT provided:\n");
      thumbheight=scaley;
      quotrem = ldiv(scaley*width,height);
      debugf("ldiv: quot %u, rem %u\n",quotrem.quot,quotrem.rem);
      thumbwidth = quotrem.quot + ((quotrem.rem>0)?(1):(0));
      debugf("=> %u x %u\n",thumbwidth,thumbheight);
   }
   /* case 2: Only SCALE_WIDTH */
   else if ((scalex > 0) && (scaley == 0)) {
      debugf("WIDTH provided:\n");
      thumbwidth=scalex;
      quotrem = ldiv(scalex*height,width);
      debugf("ldiv: quot %u, rem %u\n",quotrem.quot,quotrem.rem);
      thumbheight = quotrem.quot + ((quotrem.rem>0)?(1):(0));
      debugf("=> %u x %u\n",thumbwidth,thumbheight);
   }
   /* case 3: SCALE_WIDTH and SCALE_HEIGHT */
   else if ((scalex > 0) && (scaley > 0)) {
      debugf("BOTH provided:\n");
      thumbwidth=scalex;
      quotrem = ldiv(scalex*height,width);
      debugf("ldiv: quot %u, rem %u\n",quotrem.quot,quotrem.rem);
      thumbheight = quotrem.quot + ((quotrem.rem>0)?(1):(0));
      debugf("=> %u x %u\n",thumbwidth,thumbheight);
      if (thumbheight > scaley) {
         thumbheight=scaley;
         quotrem = ldiv(scaley*width,height);
         debugf("ldiv: quot %u, rem %u\n",quotrem.quot,quotrem.rem);
         thumbwidth = quotrem.quot + ((quotrem.rem>0)?(1):(0));
         debugf("=> %u x %u\n",thumbwidth,thumbheight);
      }
   }
   /* return result */
   tnail.width =thumbwidth;
   tnail.height=thumbheight;
   return tnail;
}

