/*	MiscGIFData.m

	Copyright 1996 Uwe Hoffmann.
	Copyright 1996 Netsurfer Inc.

	This notice may not be removed from this source code.
	The use and distribution of this software is governed by the
	terms of the MiscKit license agreement.  Refer to the license
	document included with the MiscKit distribution for the terms.

	Author: <bbum@friday.com>
	Converted to OpenStep, September 1996, Uwe Hoffmann.
*/

/*
** Copyright 1990, 1991, 1993, David Koblas.  (koblas@netcom.com)
** 
** Permission to use, copy, modify, and distribute this software  and its
** documentation for any purpose and without fee is hereby  granted,
** provided that the above copyright notice appear in all copies and that
** both that copyright notice and this permission  notice appear in
** supporting documentation.  This software is    provided "as is" without
** express or implied warranty.
*/

#import "MiscGIFData.h"

@interface MiscGIFData(PrivateMethods)

/*"
 * Initialize decompressor.
"*/
- (void)_internalLWZInitStateWithCodeSize:(unsigned char)codeSize;

/*"
 *  Read a block of compressed image data
"*/
- (int)_internalGetDataBlock:(unsigned char *)buf;
/*"
 * Retrieve a single compression code.
"*/
- (int)_internalGetCode;

@end


@implementation MiscGIFData

- initWithGIFData:(NSData *)gifData
{
	NSParameterAssert(gifData);
	[super init];
	data = [gifData retain];
	length = [data length];
	alreadyRead = 0;
	decompressionState = NSZoneMalloc([self zone], sizeof(MiscLWZState));
	return self;
}

- (void)dealloc
{
	NSZoneFree([self zone], decompressionState);
	[data release];
	return [super dealloc];
}

- (void)resetStream
{
	alreadyRead = 0;
}

- (void)setStream:(unsigned int)aPosition
{
	NSParameterAssert(aPosition <= length);
	alreadyRead = aPosition;
}

- (BOOL)atEOS
{
	return alreadyRead >= length ? YES : NO;
}

- (BOOL)getHeader:(char *)buffer
{
	if(alreadyRead < length){
		nbytes = MIN(3, length - alreadyRead);
		range = NSMakeRange(alreadyRead, nbytes);
		[data getBytes:buffer range:range];
		alreadyRead += nbytes;
	} else
		nbytes = 0;
	return nbytes == 3 ? YES : NO;	
}

- (BOOL)getVersion:(char *)buffer
{
	if(alreadyRead < length){
		nbytes = MIN(3, length - alreadyRead);
		range = NSMakeRange(alreadyRead, nbytes);
		[data getBytes:buffer range:range];
		alreadyRead += nbytes;
	} else
		nbytes = 0;
	return nbytes == 3 ? YES : NO;	
}

- (BOOL)getLSD:(char *)buffer
{
	if(alreadyRead < length){
		nbytes = MIN(7, length - alreadyRead);
		range = NSMakeRange(alreadyRead, nbytes);
		[data getBytes:buffer range:range];
		alreadyRead += nbytes;
	} else
		nbytes = 0;
	return nbytes == 7 ? YES : NO;	
}

- (BOOL)getColorTable16:(MiscColorEntry16 *)buffer size:(unsigned int)tableSize
{
	unsigned int i,n;
	const char *p;

	nbytes = MIN(tableSize * 3, length - alreadyRead);
        n = nbytes / 3;
	for(i = 0, p = (const char *)[data bytes] + alreadyRead; i < n; i++, p += 3){
		buffer[i].red = p[0] >> 4;
		buffer[i].green = p[1] >> 4;
		buffer[i].blue = p[2] >> 4;
		buffer[i].alpha = 0xf;

	}
	alreadyRead += nbytes;
	return nbytes == tableSize * 3 ? YES : NO;	
}
	
- (BOOL)getColorTable32:(MiscColorEntry32 *)buffer size:(unsigned int)tableSize
{
	unsigned int i,n;
	const char *p;

	nbytes = MIN(tableSize * 3, length - alreadyRead);
        n = nbytes / 3;
	for(i = 0, p = (const char *)[data bytes] + alreadyRead; i < n; i++, p += 3){
		buffer[i].red = p[0];
		buffer[i].green = p[1];
		buffer[i].blue = p[2];
		buffer[i].alpha = 255;

	}
	alreadyRead += nbytes;
	return nbytes == tableSize * 3 ? YES : NO;	
}

- (BOOL)getGCE:(char *)buffer
{
	if(alreadyRead < length){
		nbytes = MIN(6, length - alreadyRead);
		range = NSMakeRange(alreadyRead, nbytes);
		[data getBytes:buffer range:range];
		alreadyRead += nbytes;
	} else
		nbytes = 0;
	return nbytes == 6 ? YES : NO;	
}

- (BOOL)getImageDescriptor:(char *)buffer
{
	if(alreadyRead < length){
		nbytes = MIN(9, length - alreadyRead);
		range = NSMakeRange(alreadyRead, nbytes);
		[data getBytes:buffer range:range];
		alreadyRead += nbytes;
	} else
		nbytes = 0;
	return nbytes == 9 ? YES : NO;	
}

- (BOOL)getCharacter:(unsigned char *)buffer
{
	const char *p;

	if(alreadyRead >= length)
		return NO;

	p = (const char *)[data bytes] + alreadyRead;
	*buffer = *p;
	alreadyRead++;
	return YES;
}

- (BOOL)startDecompressionEngine
{
	unsigned char lwzCodeSize;
	const char *p;

	if(alreadyRead >= length)
		return NO;
	p = (const char *)[data bytes] + alreadyRead;
	lwzCodeSize = *p;
	alreadyRead++;

	if(!lwzCodeSize)
		return NO;

	bzero(decompressionState, sizeof(*decompressionState));
	[self _internalLWZInitStateWithCodeSize:lwzCodeSize];
	return YES;
}

- (int)LWZReadByte
{
	int             code, incode;
	register int    i;

	if (decompressionState->fresh) {
		decompressionState->fresh = FALSE;
		do {
			decompressionState->firstcode = decompressionState->oldcode = [self _internalGetCode];
		} while (decompressionState->firstcode == decompressionState->clear_code);
		return decompressionState->firstcode;
	}

	if (decompressionState->stackPtr > decompressionState->stack) {
		decompressionState->stackPtr--;
		return *decompressionState->stackPtr;
	}

	while ((code = [self _internalGetCode]) >= 0) {
		if (code == decompressionState->clear_code) {
			for (i = 0; i < decompressionState->clear_code; ++i) {
				decompressionState->table[0][i] = 0;
				decompressionState->table[1][i] = i;
			}
			for (; i < (1<<MAX_LWZ_BITS); ++i)
				decompressionState->table[0][i] = decompressionState->table[1][i] = 0;

			decompressionState->code_size	 = decompressionState->set_code_size+1;
			decompressionState->max_code_size = 2 * decompressionState->clear_code;
			decompressionState->max_code		 = decompressionState->clear_code + 2;
			decompressionState->stackPtr		 = decompressionState->stack;
			decompressionState->firstcode	 = decompressionState->oldcode
			                     = [self _internalGetCode];

			if(decompressionState->firstcode < LWZ_DONE)
				fprintf(stderr,
						"decompressionState->firstcode = miscGetCode() failed w/%d\n",
						decompressionState->firstcode);
			
			return decompressionState->firstcode;
		} else if (code == decompressionState->end_code) {
			int			count;
			unsigned 	char buf[260];

            /* Hit end_code -- data should be terminating now.
               The following boolean test checks to see if the last
               block read was zero-length.  If so, return no error.
             */
			if(decompressionState->zeroDataBlock)
				return LWZ_NO_ERROR;

            /* If not, eat next datablock-- it should be a zero
               block.  This will continue eating data blocks until
               it encounters a zero block.  This may or may not be
               the correct behaviour.
             */
			while ((count = [self _internalGetDataBlock:buf]) > 0)
				;

			if(count < LWZ_DONE)
				fprintf(stderr,
						"miscGetDataBlock (no-op while) failed w/%d\n",
						count);

            /* if the returned count was less than zero, then it is
               most likely that we ran off the end of the data
               [instead of returning data block error].  This is
               relatively common.
             */
			return (count == 0) ? LWZ_NO_ERROR : LWZ_NO_EOD;
		}

		incode = code;

		if (code >= decompressionState->max_code) {
			*decompressionState->stackPtr = decompressionState->firstcode;
			decompressionState->stackPtr++;
			code = decompressionState->oldcode;
		}

		while (code >= decompressionState->clear_code) {
			*decompressionState->stackPtr = decompressionState->table[1][code];
			decompressionState->stackPtr++;
			if (code == decompressionState->table[0][code]) 
				return LWZ_CIRCULAR_ERR;
			code = decompressionState->table[0][code];
		}

		*decompressionState->stackPtr = decompressionState->firstcode = decompressionState->table[1][code];
		decompressionState->stackPtr++;

		if ((code = decompressionState->max_code) <(1<<MAX_LWZ_BITS)) {
			decompressionState->table[0][code] = decompressionState->oldcode;
			decompressionState->table[1][code] = decompressionState->firstcode;
			++decompressionState->max_code;
			if ((decompressionState->max_code >= decompressionState->max_code_size) &&
				(decompressionState->max_code_size < (1<<MAX_LWZ_BITS))) {
				decompressionState->max_code_size *= 2;
				++decompressionState->code_size;
			}
		}

		decompressionState->oldcode = incode;

		if (decompressionState->stackPtr > decompressionState->stack) {
			--decompressionState->stackPtr;
			return *decompressionState->stackPtr;
		}
	}

	if(code < LWZ_DONE) {
		fprintf(stderr, "miscGetCode (head of while) failed with %d\n", code);
	}
	
	return code;
}

- (BOOL)skipDataBlocks
{
	unsigned char blockSize;
	const char *p;

	if(alreadyRead >= length)
		return NO;

	p = (const char *)[data bytes] + alreadyRead;
	blockSize = *p;
	alreadyRead++;
	nbytes = MIN(blockSize, length - alreadyRead);
	alreadyRead += nbytes;
	return nbytes == blockSize ? YES : NO;
}


@end

@implementation MiscGIFData(PrivateMethods)
/* Initializes the state of MiscLWZState struct pointed to by 'state'.
   Includes initialization for both miscLWZReadByte and miscGetCode sections
   of that particular buffer.
 */

- (void)_internalLWZInitStateWithCodeSize:(unsigned char)codeSize
{
	int i;
	
    /* miscLWZReadByte() initialization
     */
	decompressionState->set_code_size = codeSize;
	decompressionState->code_size	 = decompressionState->set_code_size+1;
	decompressionState->clear_code	 = 1 << decompressionState->set_code_size;
	decompressionState->end_code		 = decompressionState->clear_code + 1;
	decompressionState->max_code_size = 2 * decompressionState->clear_code;
	decompressionState->max_code		 = decompressionState->clear_code + 2;

    /* miscGetCode() initialization
     */
	decompressionState->curbit		 = 0;
	decompressionState->lastbit		 = 0;
	decompressionState->done		 = FALSE;
	decompressionState->alreadyRead	 = 0;

	decompressionState->fresh		 = TRUE;

	for (i = 0; i < decompressionState->clear_code; ++i) {
		decompressionState->table[0][i] = 0;
		decompressionState->table[1][i] = i;
	}
	for (; i < (1<<MAX_LWZ_BITS); ++i)
		decompressionState->table[0][i] = decompressionState->table[1][0] = 0;

	decompressionState->stackPtr = decompressionState->stack;
}


- (int)_internalGetCode
{
	int                     i, j, ret;
	short		           count;

	if ( (decompressionState->curbit + decompressionState->code_size) >= decompressionState->lastbit) {
		if (decompressionState->done) {
			if (decompressionState->curbit >= decompressionState->lastbit)
				return LWZ_BIT_OVERRUN;
			return LWZ_DONE;
		}
		decompressionState->buf[0] = decompressionState->buf[decompressionState->last_byte-2];
		decompressionState->buf[1] = decompressionState->buf[decompressionState->last_byte-1];

		if ((count = [self _internalGetDataBlock:&(decompressionState->buf[2])]) == 0)
			decompressionState->done = TRUE;
		else if (count < 0) {
			fprintf(stderr, "miscGetDataBlock failed with code: %d\n", count);
			return count;
		}

		decompressionState->last_byte = 2 + count;
		decompressionState->curbit = (decompressionState->curbit - decompressionState->lastbit) + 16;
		decompressionState->lastbit = (2 + count) * 8 ;
	}

	ret = 0;
	for (i = decompressionState->curbit, j = 0; j < decompressionState->code_size; ++i, ++j)
		ret |= ((decompressionState->buf[ i / 8 ] & (1 << (i % 8))) != 0) << j;

	decompressionState->curbit += decompressionState->code_size;

	return ret;
}

- (int)_internalGetDataBlock:(unsigned char *)buf
{
	unsigned char count;

	if(alreadyRead < length){
		range = NSMakeRange(alreadyRead,1);
		[data getBytes:&count range:range];
	} else
		return LWZ_DATA_BLOCK_ERR;

	alreadyRead++;

	decompressionState->zeroDataBlock = (count == 0);

	if(count != 0){
		if(alreadyRead + count > length)
			return LWZ_DATA_BLOCK_ERR;
		range = NSMakeRange(alreadyRead, count);
		[data getBytes:buf range:range];
		alreadyRead += count;
	}
	return count;
}

@end

