/*****************************************************************************/
/* DecodeGIF.m								     */
/* implementation file of DecodeGIF class of ViewGif2 application	     */
/* Handles decoding of GIF files, and decoding status panel		     */
/* January 1990  Carl F. Sutter						     */
/*****************************************************************************/

#import "DecodeGIF.h"
#import <appkit/Application.h>
#import <appkit/Window.h>
#import <appkit/Control.h>
#import <appkit/Panel.h>
#import <appkit/Slider.h>		// for setMinValue, setMaxValue
#import <appkit/tiff.h>			// for NXImageBitmap
#import <stdlib.h>			// for malloc and free
#import <stdio.h>			// for fprintf
#import <string.h>			// for strcpy

/* some external function references to do the actual LZW decoding */
void StartLZW( BYTE byCodeSizeIn, NXStream *stream,
               BYTE *byR, BYTE *byG, BYTE *byB, BYTE *byMap,
               int width, int height, BOOL bInter );
long ContinueLZW( int nNumCodes );

@implementation DecodeGIF

/*****************************************************************************/
/* new - factory method							     */
/*****************************************************************************/
+ new
   {
   self = [super new];
   [self setup];
   return( self );
   } /* new 1/22/90 CFS */


/*****************************************************************************/
/* new - factory method	with target and action				     */
/*****************************************************************************/
+ new:(id )targ action:(SEL )act 
   {
   self = [super new];
   [self setup];
   target = targ;
   action = act;
   return( self );
   } /* new:action: 1/23/90 CFS */
  

/*****************************************************************************/
/* setup - initialize instance variables and panel etc.  		     */
/*****************************************************************************/
- setup
   {
   /* load nib defining panel and outlets */
   [NXApp loadNibSection:"DecodeGIF.nib" owner:self];
   bDecoding = NO;
   return( self );
   } /* setup 1/23/90 CFS */
  

/*****************************************************************************/
/* outlet initialization methods   					     */
/*****************************************************************************/
- setDecodePanel:anObject { decodePanel  = anObject; return( self ); }
- setGauge:anObject       { gauge        = anObject; return( self ); }
- setWidth:anObject       { width        = anObject; return( self ); }
- setHeight:anObject      { height       = anObject; return( self ); }
- setNumColors:anObject   { numColors    = anObject; return( self ); }
- setFilename:anObject    { filename     = anObject; return( self ); }


/*****************************************************************************/
/* show - display the decoder panel					     */
/*****************************************************************************/
- show:sender
   {
   [decodePanel makeKeyAndOrderFront:self];
   return( self );
   } /* show 1/23/90 CFS */
 
 
/*****************************************************************************/
/* setTarget - sets the object to return queue items to 		     */
/*****************************************************************************/
- setTarget:(id)targ
   {
   target = targ;
   return( self );
   } /* setTarget 1/22/90 CFS */


/*****************************************************************************/
/* setAction - sets the method that will be called with the new item         */
/* there will be one parameter - a pointer to a standard C string 	     */
/*****************************************************************************/
- setAction:(SEL)aSelector
   {
   action = aSelector;
   return( self );
   } /* setAction 1/22/90 CFS */
   
   
/*****************************************************************************/
/* decodeFile - called from controller to initiate decoding process	     */
/*****************************************************************************/
- decodeFile:(const char *)fileName
   {
   strcpy( szPathName, fileName );
   strcpy( szFileName, strrchr( fileName, '/' ) + 1);
   [filename setStringValue:szFileName];
   nStatus = STATUS_OPEN;
   /* use the timer to break up the decoding process and allow other events */
   timer = [[Animator alloc] initChronon:0.0 adaptation:0.0 target:self
                     action:@selector(nextStep:) autoStart:YES
	               eventMask:NX_ALLEVENTS];
   return( self );
   } /* decodeFile 1/23/90 CFS */
   

/*****************************************************************************/
/* nextStep - called by timer, parse control to current task		     */
/*****************************************************************************/
- nextStep:sender
   {
   #define	CODES_PER_ITERATION 300
   long	lReturn;
   
   /* first, go right to the decoder, if LZW decoding is in progress */
   if (bDecoding)
      {
      lReturn = ContinueLZW( CODES_PER_ITERATION );
      if ((lReturn == 0) || (lReturn == -1)) [self finishDecoder:lReturn];
      else [gauge setFloatValue:(float)lReturn];
      }
   /* otherwise, go to the proper step in the rest of the decoding process */   
   else switch (nStatus)
      {
      case STATUS_OPEN:      [self openFile];          break;
      case STATUS_GLOBAL:    [self readGlobalInfo];    break;
      case STATUS_BLOCK:     [self readBlockCode];     break;
      case STATUS_IMAGE:     [self readImageInfo];     break;
      case STATUS_DECODING:  [self startDecoder];      break;
      case STATUS_EXTENSION: [self cancelDecoding];    break;  // FIX THIS
      case STATUS_DONE:      [self sucessfulDecoding]; break;  // ALLOW MULT IMAGES
      }
   return( self );
   } /* nextStep 1/23/90 CFS */


/*****************************************************************************/
/* openFile - open the stream for decoding				     */
/*****************************************************************************/
- (BOOL)openFile
   {
   if (!(stream = NXMapFile( szPathName, NX_READONLY )))
      {
      [self errorAlert:"Could not open GIF file."];
      [self cancelDecoding];
      return( NO );
      }
   nStatus = STATUS_GLOBAL;   
   return( YES );
   } /* openFile 1/23/90 CFS */
 
      
/***************************************************************************/
/* readGlobalInfo - read the global header from the file		   */
/***************************************************************************/
- (BOOL)readGlobalInfo
   {
   BYTE	b1, b2;
   char	szSignature[7];
   struct {
          unsigned  map:1;
          unsigned  res:3;
          unsigned  reserved:1;
          unsigned  bpp:3;
          } globalInfo;

   /* read 6 byte version signature */
   if (![self readBytes:6 data:szSignature]) return( NO );
   szSignature[6] = 0;
   if (0 != strcmp( szSignature, "GIF87a"))
      {
      [self errorAlert:"Not a GIF file - signature not GIF87a."];
      [self cancelDecoding];
      return( NO );
      }
   
   /* read screen width and height */
   if (![self readBytes:1 data:&b1]) return( NO );
   if (![self readBytes:1 data:&b2]) return( NO );
   if (![self readBytes:1 data:&b1]) return( NO );
   if (![self readBytes:1 data:&b2]) return( NO );

   /* read info byte */ 
   if (![self readBytes:1 data:&globalInfo]) return( NO );
   //   GlobalInfo.nResolution = globalInfo.res + 1;
   nBPP = globalInfo.bpp + 1;
 
   /* read background color */
   if (![self readBytes:1 data:&b1]) return( NO );
   //   GlobalInfo.nBackground = b1;

   /* reserved zero at end of screen descriptor */
   if (![self readBytes:1 data:&b1]) return( NO );
   
   /* read in the global color map, if it exists */
   if (globalInfo.map) [self readMap];

   /* everything loaded OK, return TRUE */
   nStatus = STATUS_BLOCK;
   return( YES );	 
   } /* readGlobalInfo 10/16/89 CFS */


/***************************************************************************/
/* readMap - read a color map from the stream				   */
/***************************************************************************/
- (BOOL)readMap
   {
   nNumColors = 1;
   nNumColors <<= nBPP;  /*2^BPP*/
   [numColors setIntValue:nNumColors];
   if (![self readBytes:nNumColors * 3 data:byColorMap]) return( NO );
   return( YES );
   } /* readMap 1/23/90 CFS */


/***************************************************************************/
/* readBlockCode - read the next character blockcode and set status	   */
/***************************************************************************/
- (BOOL)readBlockCode
   {
   char  cBlockCode;

   if (![self readBytes:1 data:&cBlockCode]) return( NO );
   switch (cBlockCode)
      {
      case ',': nStatus = STATUS_IMAGE;      break;
      case '!': nStatus = STATUS_EXTENSION;  break;
      case ';': nStatus = STATUS_DONE;       break;
      default:
         {
         [self errorAlert:"Bad block code in readNextBlockCode."];
         [self cancelDecoding];
         return( NO );
	   }
      }   
   return( YES );
   } /* readBlockCode 1/23/90 CFS */


/***************************************************************************/
/* readImageInfo - read the image information header from the file	   */
/***************************************************************************/
- (BOOL)readImageInfo
   {
   BYTE	b1, b2;
   struct	{
		unsigned  map:1;
		unsigned  interlace:1;
		unsigned  reserved:3;
		unsigned  bpp:3;
		} localInfo;
   
   /* read the image margins and size */	  	  	  
   if (![self readBytes:1 data:&b1]) return( NO );
   if (![self readBytes:1 data:&b2]) return( NO );
   //   ImageInfo.nLeftMargin = b2 * 256 + b1;
   if (![self readBytes:1 data:&b1]) return( NO );
   if (![self readBytes:1 data:&b2]) return( NO );
   //   ImageInfo.nTopMargin = b2 * 256 + b1;
   if (![self readBytes:1 data:&b1]) return( NO );
   if (![self readBytes:1 data:&b2]) return( NO );
   nWidth = b2 * 256 + b1;
   [width setIntValue:nWidth];
   if (![self readBytes:1 data:&b1]) return( NO );
   if (![self readBytes:1 data:&b2]) return( NO );
   nHeight = b2 * 256 + b1;
   [height setIntValue:nHeight];

   /* read the image info byte */  
   if (![self readBytes:1 data:&localInfo]) return( NO );
   bInterlaced = localInfo.interlace;
   //   ImageInfo.nBPP = localInfo.bpp + 1;

   if (localInfo.map) [self readMap];

   /* everything loaded OK, return TRUE */
   nStatus = STATUS_DECODING;
   return( YES );	 
   } /* readImageInfo 10/16/89 CFS */


/***************************************************************************/
/* startDecoder - control decoding of the image				   */
/***************************************************************************/
- (BOOL)startDecoder
   {
   BYTE  	byCodeSize;

   [decodePanel setTitle:"Decoding Status - decoding..."];
   /* read the beginning code size - should be same as BPP */
   if (![self readBytes:1 data:&byCodeSize]) return( NO );
  
   /* free any previous image data & allocate space for the current image */
   free( byDataR );
   free( byDataG );
   free( byDataB );
   byDataR = malloc( nWidth * nHeight );
   byDataG = malloc( nWidth * nHeight );
   byDataB = malloc( nWidth * nHeight );

   /* set up the time left gauge */
   [gauge setMinValue:0];
   [gauge setMaxValue:nHeight * nWidth];
   [gauge setIntValue:0];
   
   /* start the LZW decoder */
   StartLZW( byCodeSize, stream, byDataR, byDataG, byDataB, byColorMap,
             nWidth, nHeight, bInterlaced );
   bDecoding = YES;

   return( YES );	 
   } /* startDecoder 1/24/90 CFS */


/***************************************************************************/
/* finishDecoder: - clean up after the LZW process 			   */
/***************************************************************************/
- (BOOL)finishDecoder:(long)lReturnCode
   {
   NXRect	nxrBit;
     
   if (lReturnCode == -1) [self errorAlert:
      "The whole image couldn't be loaded.\nAs much as possible will be shown."];
      
   [gauge setFloatValue:(float)(nWidth * nHeight)];
   /* create a bitmap, and image the data into it */
   [decodePanel setTitle:"Decoding Status - imaging..."];
   nxrBit.size.width = nWidth;
   nxrBit.size.height = nHeight;
   bmpOut = [Bitmap newSize:nxrBit.size.width :nxrBit.size.height
                    type:NX_UNIQUEBITMAP];
   [bmpOut setFlip:NO];
   [bmpOut lockFocus];
   NXImageBitmap( &nxrBit, nWidth, nHeight, 8, 3, NX_PLANAR,
                  NX_COLORMASK, byDataR, byDataG, byDataB, NULL, NULL );
   [bmpOut unlockFocus];
   [decodePanel setTitle:"Decoding Status"];
    
   bDecoding = NO;
   nStatus = STATUS_DONE;
   
   //  ALLOW MULTIPLE IMAGES EVENTUALLY
   //   /* read the last block count - should be zero */
   //   if (![self readBytes:1 data:&byDummy]) return( NO );
   //   nStatus = STATUS_BLOCK;
   
  return( YES );
  } /* finishDecoder: 1/24/90 CFS */
  

/***************************************************************************/
/* sucessfulDecoding - send decoded image to controller			   */
/***************************************************************************/
- sucessfulDecoding
   {
   [timer free];
   NXCloseMemory( stream, NX_FREEBUFFER );
   [target perform:action with:bmpOut];
   return( self );
   } /* sucessfulDecoding 1/23/90 CFS */


/***************************************************************************/
/* cancelDecoding - notify controller that file couldn't be decoded        */
/***************************************************************************/
- cancelDecoding
   {
   [timer free];
   NXCloseMemory( stream, NX_FREEBUFFER );
   [target perform:action with:nil with:(id)NO];
   return( self );
   } /* cancelDecoding 1/23/90 CFS */


/***************************************************************************/
/* readBytes - read in the desired number of data bytes			   */
/***************************************************************************/
- (BOOL)readBytes:(int)nNumBytes data:(void *)buf
   {
   int   nError;

   nError = NXRead( stream, buf, nNumBytes );
   if (nError <= 0)
      {
      [self errorAlert:"File couldn't be completely read."];
      [self cancelDecoding];
      return( NO );
      }
   return( YES );   
   } /* readBytes 1/23/90 CFS */
   
   
/***************************************************************************/
/* errorAlert - put the message in an alert panel			   */
/***************************************************************************/
- errorAlert:(char *)szMessage
   {
   NXRunAlertPanel( "ViewGif2 Error", szMessage, "OK", NULL, NULL );
   return( self );
   } /* errorAlert 10/30/89 CFS */

@end
