/*  
** Copyright (c) 1995 Netsurfer Inc.  All Rights Reserved.
**
** Author: <koblas@netcom.com> & <bbum@friday.com>
*/

/*
** 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 <objc/Object.h>
#import <stdio.h>

#import "LWZDecoder.h"

#define        MAX_LWZ_BITS				12

#ifdef FALSE
#undef FALSE
#undef TRUE
#endif

#define        FALSE 					NO
#define        TRUE 					YES

/* Initializes the state of LWZState struct pointed to by 'state'.
   Includes initialization for both LWZReadByte and GetCode sections
   of that particular buffer.
 */
void LWZInitState(id decoder, LWZState *state, unsigned char codeSize)
{
	int i;
	
    /* LWZReadByte() initialization
     */
	state->set_code_size = codeSize;
	state->code_size	 = state->set_code_size+1;
	state->clear_code	 = 1 << state->set_code_size;
	state->end_code		 = state->clear_code + 1;
	state->max_code_size = 2 * state->clear_code;
	state->max_code		 = state->clear_code + 2;

    /* GetCode() initialization
     */
	state->curbit		 = 0;
	state->lastbit		 = 0;
	state->done			 = FALSE;

	state->fresh		 = TRUE;

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

	state->stackPtr = state->stack;
}

int LWZReadByte(id decoder, LWZState *state, NXStream *stream)
{
	int             code, incode;
	register int    i;

	if (state->fresh) {
		state->fresh = FALSE;
		do {
			state->firstcode = state->oldcode =	GetCode(decoder,
														state,
														stream);
		} while (state->firstcode == state->clear_code);
		return state->firstcode;
	}

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

	while ((code = GetCode(decoder, state, stream)) >= 0) {
		if (code == state->clear_code) {
			for (i = 0; i < state->clear_code; ++i) {
				state->table[0][i] = 0;
				state->table[1][i] = i;
			}
			for (; i < (1<<MAX_LWZ_BITS); ++i)
				state->table[0][i] = state->table[1][i] = 0;

			state->code_size	 = state->set_code_size+1;
			state->max_code_size = 2 * state->clear_code;
			state->max_code		 = state->clear_code + 2;
			state->stackPtr		 = state->stack;
			state->firstcode	 = state->oldcode
			                     = GetCode(decoder, state, stream);

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

            /* Hit end_code -- stream 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(state->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 = GetDataBlock(decoder, state, stream, buf)) > 0)
				;

			if(count < LWZ_DONE)
				fprintf(stderr,
						"GetDataBlock (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 stream
               [instead of returning data block error].  This is
               relatively common.
             */
			return (count == 0) ? LWZ_NO_ERROR : LWZ_NO_EOD;
		}

		incode = code;

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

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

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

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

		state->oldcode = incode;

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

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

int GetCode(id decoder, LWZState *state, NXStream *stream)
{
	int                     i, j, ret;
	short		           count;

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

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

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

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

	state->curbit += state->code_size;

	return ret;
}

int GetDataBlock(id decoder, LWZState *state,
				 NXStream *stream, unsigned char *buf)
{
	unsigned char   count;

	if (NXRead(stream, &count, 1) != 1)
		return LWZ_DATA_BLOCK_ERR;

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

	if ( (count != 0) &&
		 (NXRead(stream, buf, count) != count) )
		return LWZ_DATA_BLOCK_ERR;

	return count;
}
