/*                                                               -*- C -*-
 *  OPTICON.C
 *
 *  (c)Copyright 1994 by Tobias Ferber,  ukjg@rz.uni-karlsruhe.de
 *
 *  This file is part of the IconTools distribution
 *
 *  OptIcon 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 1 of the License,
 *  or (at your option) any later version.
 *
 *  OptIcon 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 this program; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* $VER: $Id: opticon.c,v 1.16 1995/08/06 20:39:35 tf Exp $ */

#include "version.h"
static char versiontag[] = "$VER: $Id: opticon.c,v 1.16 1995/08/06 20:39:35 tf Exp $";

/* Compile w/ -DDEBUG to output more information at runtime */

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

#include <exec/types.h>
#include <exec/memory.h>

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

#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>

#include <workbench/workbench.h>
#include <workbench/startup.h>
#include <workbench/icon.h>

#include "memfn.h"

#ifdef __GNUC__
/* suggest parentheses around assignment used as truth value */
#define if(assignment) if( (assignment) )
#endif /* __GNUC__ */

extern struct Library *OpenLibrary(STRPTR, ULONG);
extern void CloseLibrary(struct Library *);
extern void CopyMem(APTR, APTR, ULONG);
extern void *AllocMem(ULONG, ULONG);
extern void FreeMem(void *, ULONG);
extern ULONG TypeOfMem(void *);
extern struct RDArgs *ReadArgs(STRPTR, LONG *, struct RDArgs *);
extern LONG IoErr(void);
extern BOOL PrintFault(LONG, STRPTR);
extern BPTR Lock(STRPTR, LONG);
extern void UnLock(BPTR);
extern void FreeArgs(struct RDArgs *);
extern struct DiskObject *GetDiskObject(char *);
extern BOOL PutDiskObject(char *, struct DiskObject *);
extern void FreeDiskObject(struct DiskObject *);

struct IconBase *IconBase;

void display_version_information(void)
{
  static char license[]=
    "OptIcon is free software; you can redistribute it and/or modify\n"
    "it under the terms of the GNU General Public License as published\n"
    "by the Free Software Foundation; either version 1 of the License,\n"
    "or (at your option) any later version.\n"
    "\n"
    "OptIcon is distributed in the hope that it will be useful,\n"
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
    "GNU General Public License for more details.\n"
    "\n"
    "You should have received a copy of the GNU General Public License\n"
    "along with OptIcon; see the file COPYING.  If not, write to the\n"
    "Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n"
    ;

  puts("\nOptIcon Version " VERSION " (compiled " __DATE__ ", " __TIME__ ")\n"
       "(c)Copyright 1994,95 by Tobias Ferber, ukjg@rz.uni-karlsruhe.de\n");

  puts(license);
}


struct Image *free_image(struct Image *i)
{
  if(i)
  {
    long size= i->Depth * i->Height * ((i->Width + 15) / 16) * sizeof(UWORD);

    if(i->ImageData && size > 0)
      FreeMem( i->ImageData, size );

    FreeMem( i, sizeof(struct Image) );
  }

  return (struct Image *)0L;
}

/* command line options */
#define OPT_NOEXPAND  (1<<0)
#define OPT_CRITICAL  (1<<1)
#define OPT_VERBOSE   (1<<2)
#define OPT_REMAPV37  (1<<3)

struct Image *optimize_image(struct Image *i, WORD planes, int optimode)
{
  UWORD p16= i->Height * ((i->Width + 15) / 16); /* #of words per plane */
  UWORD *idata= i->ImageData;
  WORD d, dmax, D, P;
  UBYTE pp= 0;    /* plane pick */
  UBYTE p10= 0;   /* plane on/off */

  struct Image *o= i;  /* optimized image */

  /* prevent silly args from being harmful */

  if(!i)
    return i;

  if(planes > 8)
    planes= 8;

  if(optimode & OPT_VERBOSE)
  {
    printf("(depth=%d, pick=%d, onoff=%d)", i->Depth,
                                            i->PlanePick,
                                            i->PlaneOnOff);
    fflush(stdout);
  }

  /*
      PRESCAN:  Examine dmax planes of i and compute

        D   = the real depth (without trailing 0 planes)
        pp  = the new PlanePick value
        p10 = the new PlaneOnOff value
  */

  dmax= (0 < planes && planes < i->Depth) ? planes : i->Depth;

  for(d= D= 0; d<dmax; d++)
  {
    /* check if we have some image data for plane d */
    if(i->PlanePick & (1<<d))
    {
      UWORD n, *p, v;

      /* scan the image data of plane d */
      for(n=0, p=idata, v=*p; n < p16; n++, p++)
        if(*p != v)
          break;

      if(n==p16 && v==0xFFFF) /* plane d is entirely 1 */
        p10 |= (1<<d);  /* pp bit is already 0 */

      if( n!=p16 || (n==p16 && v!=0x0000 && v!=0xFFFF) )
        pp |= (1<<d);

      if( n!=p16 || (n==p16 && v!=0x0000) )
        D= d;

      idata= &idata[p16];
    }
    else if(i->PlaneOnOff & (1<<d))
    {
      p10 |= (1<<d);
      D=d;
    }
  }

  ++D;

  if( (optimode & OPT_VERBOSE) && (D != dmax || pp != i->PlanePick || p10 != i->PlaneOnOff) )
  {
    printf(" -> (%d,%d,%d)", D,pp,p10);
    fflush(stdout);
  }

  /* compute the #of planes in the output image */
  P= ( planes>D && !(optimode & OPT_NOEXPAND) ) ? planes : D;

  if(P != i->Depth || pp != i->PlanePick || p10 != i->PlaneOnOff)
  {
    UWORD p8= p16 * sizeof(UWORD);
    UWORD *odata;
    ULONG osize= P * p8;

    if( o= (struct Image *)AllocMem(sizeof(struct Image),MEMF_CLEAR) )
    {
      if( odata= (UWORD *)AllocMem(osize,TypeOfMem(i->ImageData)|MEMF_CLEAR) )
      {
        CopyMem( (APTR)i, (APTR)o, sizeof(struct Image) );
        o->ImageData= odata;

        idata= i->ImageData;

        for(d=0; d<D; d++)
        {
          if( pp & (1<<d) )
          {
            if(i->PlanePick & (1<<d))
            {
              CopyMem( (APTR)idata, (APTR)odata, p8 );
              idata= &idata[p16];
              odata= &odata[p16];
            }
            else /* !PlanePick bit (should not happen) */
            {
              memset( (char *)odata, (i->PlaneOnOff & (1<<d)) ? 0xFF : 0x00, p8 );
              odata= &odata[p16];
            }
          }
          else /* !pp bit */
          {
            if(i->PlanePick & (1<<d))
              idata= &idata[p16];

            if( !(optimode & OPT_CRITICAL) )
            {
              memset( (char *)odata, (p10 & (1<<d)) ? 0xFF : 0x00, p8 );
              odata= &odata[p16];
              pp |= (1<<d);
              p10 &= ~(1<<d);
            }
          }
        }

        if(D>=3 && D<P) /* no need to check OPT_NOEXPAND sice P is < D if set */
        {
          UWORD *p;

          if( p= (UWORD *)malloc(p8) )
          {

            if( (optimode & OPT_REMAPV37) && p10<=7 )
            {
              /*
                  REMAP:  Make colors 4-7 become the last 4 in the palette

                  Algo: (1) OR together all planes > 2,
                        (2) invert the result,
                        (3) AND it with plane 2 and
                        (4) OR the result with all planes > 2

                  Note: There is no need to expand the image data if p10 &~ %111 != 0
              */

              /* move to plane 2 */

              idata= i->ImageData;

              for(d=0; d<2; d++)
                if(i->PlanePick & (1<<d))
                  idata= &idata[p16];

              if(i->PlanePick & (1<<2))
              {
                memcpy((char *)p, (char *)idata, p8);
                idata= &idata[p16];
              }
              else memset( (char *)p, (p10 & (1L<<2)) ? 0xFF : 0x00, p8 );

              /* or planes 3..D, invert them, AND the result with plane 2 */

              for(d=3; d<D; d++)
              {
                if(i->PlanePick & (1<<d))
                {
                  memandnot( (char *)p, (char *)idata, p8 );
                  idata= &idata[p16];
                }
                /* else bit d of i->PlaneOnOff is 0 --> no-op */
              }

              /* move to plane 3 */

              odata= o->ImageData;

              for(d=0; d<3; d++)
                if(pp & (1<<d))
                  odata= &odata[p16];

              for(d=3; d<P; d++)
              {
                if( d>=D || pp & (1<<d) )
                {
                  memor( (char *)odata, (char *)p, p8 );
                  odata= &odata[p16];
                  pp |= (1<<d);
                }
              }
            }

            else /* !REMAPV37 */
            {
              /*
                  EXPAND:  Remap the last 4 colors of i to the last 4 colors of o

                  Algo: (1) OR together all planes but the last
                        (2) AND the result with the last plane
                        (3) set the result in all new planes

                  Note: if any plane of i but the last is entirely 1 then we can
                        simply copy the last plane of i to all new planes in o
              */

              idata= i->ImageData;

              if( p10 &~ (1<<(D-1)) == 0)
              {
                memset( (char *)p, 0x00, p8 );

                for(d=0; d<D-1; d++)
                {
                  if(i->PlanePick & (1<<d))
                  {
                    memor( (char *)p, (char *)idata, p8 );
                    idata= &idata[p16];
                  }
                }
                /* else plane d is entirely 0 */

                if( i->PlanePick & (1<<(D-1)) )
                  memand( (char *)p, (char *)idata, p8 );
                /* else the last plane is entirely 1 */
              }
              else /* move to the last plane */
              {
                for(d=0; d<D-1; d++)
                  if(i->PlanePick & (1<<d))
                    idata= &idata[p16];

                if( i->PlanePick & (1<<(D-1)) )
                  memcpy( (char *)p, (char *)idata, p8 );
                else
                  memset( (char *)p, 0xFF, p8 );
              }

              /* move to plane D */

              odata= o->ImageData;

              for(d=0; d<D; d++)
                if(pp & (1<<d))
                  odata= &odata[p16];

              for(d=D; d<P; d++)
              {
                memcpy( (char *)odata, (char *)p, p8 );
                odata= &odata[p16];
                pp |= (1<<d);
              }

            }

            free(p);
          }
          else /* !p --> panic! */
          {
            FreeMem(o->ImageData,osize);
            FreeMem(o,sizeof(struct Image));
            o= (struct Image *)0L;
          }
        }

        o->Depth= P;
        o->PlanePick= pp;
        o->PlaneOnOff= p10;

      }
      else /* !odata */
      {
        FreeMem(o,sizeof(struct Image));
        o= (struct Image *)0L;
      }
    }
  }

  if(optimode & OPT_VERBOSE)
  {
    if(o && o!=i)
      printf(" -> (%d,%d,%d)", o->Depth, o->PlanePick, o->PlaneOnOff);
    putchar('\n');
  }

  return o;
}

/**/

static char *tackon(char *pname, char *fname)
{
  char *buf= (char *)malloc(strlen(pname)+strlen(fname)+2);

  if(buf)
  {
    register char *s= buf;  *s= '\0';

    if(pname)
    {
      register int n= 0;

      while( *pname )
        n++, *s++= *pname++;

      if( n>0 && buf[n-1]!=':' && buf[n-1]!='/' )
        *s++ = '/';
    }

    if(fname) while( *fname )
      *s++ = *fname++;

    *s= '\0';
  }

  return buf;
}

/* main, opticon(), and doall() share the following vars */

static char *whoami;
static LONG args[8] = { 0,0,0,0,0,0,0,0 };
WORD numplanes= 0;
int optiflags= 0;

int opticon(char *fname)
{
  int rc= RETURN_OK;

  /* We initially assume `fname' to be or to have an icon. */

  char *iname= (char *)strdup(fname);

  if(optiflags & OPT_VERBOSE)
    printf("opticon  `%s'\n",fname);

  if(iname)
  {
    struct DiskObject *icon;

    if( (icon= GetDiskObject(iname)) == NULL )
    {
      size_t x= strlen(iname) - 5;

      if(x>0 && !stricmp(&(iname[x]),".info"))
      {
        iname[x]= '\0';
        icon= GetDiskObject(iname);
      }
    }

    if(icon)
    {
      struct Gadget *g= &icon->do_Gadget;

      struct Image *ogr, *osr;
      int modified= 0;

      ogr= osr= (struct Image *)0L;

      if(g->GadgetRender && (g->Flags & GFLG_GADGIMAGE))
      {
        struct Image *i= (struct Image *)g->GadgetRender;

        if(optiflags & OPT_VERBOSE)
          printf("         :normal   "), fflush(stdout);

        if( ogr= optimize_image(i,numplanes,optiflags) )
        {
          if(ogr != i)
          {
            g->GadgetRender= (APTR)ogr;
            ++modified;
          }
          else ogr= (struct Image *)0L; /* don't free ogr */

          if(g->SelectRender && (g->Flags & GFLG_GADGHIMAGE))
          {
            i= (struct Image *)g->SelectRender;

            if(optiflags & OPT_VERBOSE)
              printf("         :selected "), fflush(stdout);

            if( osr= optimize_image(i,numplanes, optiflags) )
            {
              if(osr != i)
              {
                g->SelectRender= (APTR)osr;
                ++modified;
              }
              else osr= (struct Image *)0L; /* don't free osr */
            }
            else
            {
              fprintf(stderr,"%s: %s.info: not enough free memory to optimize the selected image\n",whoami,iname);
              rc= ERROR_NO_FREE_STORE;
            }
          }
        }
        else
        {
          fprintf(stderr,"%s: %s.info: not enough free memory to optimize the normal image\n",whoami,iname);
          rc= ERROR_NO_FREE_STORE;
        }
      }

      /* SMART/S */

      if( rc == RETURN_OK && args[6] && (icon->do_Type == WBDRAWER || icon->do_Type == WBGARBAGE) )
      {
        BPTR lock= Lock(iname,ACCESS_READ);

        if( BADDR(lock) )
          UnLock(lock);

        else
        {
          icon->do_Type = WBTOOL;
          ++modified;
        }
      }

      if( modified && rc == RETURN_OK )
      {
        if( !PutDiskObject(iname,icon) )
          PrintFault(rc= IoErr(), iname);
      }

      if(ogr) ogr= free_image(ogr);
      if(osr) osr= free_image(osr);

      FreeDiskObject(icon);
    }
    else /* !icon */
    {
      PrintFault(rc= IoErr(), iname);
      fprintf(stderr,"%s: GetDiskObject() failed for %s[.info]\n",whoami,iname);

      if(args[7]) 
        rc= RETURN_OK;  /* keep running */
    }

    free(iname);
  }
  else /* !iname */
  {
    fprintf(stderr,"%s: out of memory... aaaiiiiiieeeeeeeee!\n",whoami);
    rc= ERROR_NO_FREE_STORE;
  }

  return rc;
}


int doall(char *pname)
{
  int rc= RETURN_OK;

  struct FileInfoBlock *fib= (struct FileInfoBlock *)AllocDosObject(DOS_FIB, NULL);

  if(optiflags & OPT_VERBOSE)
    printf("scanning `%s'\n",pname);

  if(fib)
  {
    /* set to 1 if we are to call opticon(pname) after UnLock() */
    int optme= 0;

    BPTR lock= Lock(pname, ACCESS_READ);

    if( BADDR(lock) )
    {
      if( Examine(lock,fib) )
      {
        /* not the initial call from main() for a file with ALL */
        if( (optme= (fib->fib_DirEntryType > 0) ? 0:1) == 0 )
        {
          while( (rc==RETURN_OK) && ExNext(lock,fib) && (IoErr() != ERROR_NO_MORE_ENTRIES) )
          {
            char *pcat= tackon(pname,fib->fib_FileName);

            if(pcat)
            {
              if(optiflags & OPT_VERBOSE)
                printf("found    `%s'\n",pcat);

              if( fib->fib_DirEntryType > 0 ) /* directory */
              {
                rc= doall(pcat);
              }

              else /* file */
              {
                size_t x= strlen(pcat) - 5;

                if( stricmp(&pcat[x],".info") == 0 ) /* "*.info" file */
                {
                  rc= opticon(pcat);
                }
              }

              free(pcat);
            }
            else /* !pcat */
            {
              fprintf(stderr,"%s: out of memory... aaaiiiiiieeeeeeeee!\n",whoami);
              rc= ERROR_NO_FREE_STORE;
            }
          }
        }
      }
      else PrintFault(rc= IoErr(), pname); /* Examine() failed */

      UnLock(lock);
    }
    else optme= 1;  /* !lock */
    /*else PrintFault(rc= IoErr(), pname);*/

    if(optme && rc==RETURN_OK)
      rc= opticon(pname);

    FreeDosObject(DOS_FIB,fib);
  }
  else /* !fib */
  {
    fprintf(stderr,"%s: out of memory... aaaiiiiiieeeeeeeee!\n",whoami);
    rc= ERROR_NO_FREE_STORE;
  }

  return rc;
}


/**/

int main(int argc, char **argv)
{
  struct RDArgs *a;
  int rc= RETURN_OK;

  whoami= *argv;

  if( a= ReadArgs("FROM=NAME/A/M,"   /* 0 */
                  "DEPTH=PLANES/N,"  /* 1 */
                  "NOEXPAND/S,"      /* 2 */
                  "CRITICAL/S,"      /* 3 */
                  "REMAPV37/S,"      /* 4 */
                  "VERBOSE/S,"       /* 5 */
                  "SMART/S,"         /* 6 */
                  "ALL/S",           /* 7 */
                  args, NULL) )
  {
    char **flist= (char **)args[0];

    if(args[1])
    {
      if( (numplanes= (WORD)*(LONG *)(args[1])) < 1 )
      {
        fprintf(stderr,"%s: Illegal maximum depth %d\n",whoami,numplanes);
        rc= RETURN_FAIL;
      }
    }

    if(flist && rc == RETURN_OK)
    {
      if( IconBase= (struct IconBase *)OpenLibrary(ICONNAME,36) )
      {
        if(args[2]) optiflags |= OPT_NOEXPAND;
        if(args[3]) optiflags |= OPT_CRITICAL;
        if(args[4]) optiflags |= OPT_REMAPV37;
        if(args[5]) optiflags |= OPT_VERBOSE;

        for(;*flist && rc == RETURN_OK;flist++)
          rc= args[7] ? doall(*flist) : opticon(*flist);

        CloseLibrary((struct Library *)IconBase);
      }
      else
      {
        fprintf(stderr,"%s: You need %s V36+",whoami,ICONNAME);
        rc= RETURN_ERROR;
      }
    }
    FreeArgs(a);
  }
  else /* !ReadArgs */
  {
    if(argc == 1)
      display_version_information();
    else
      PrintFault(rc= IoErr(),NULL);
  }

  return rc;
}
