/*
 * The Python Imaging Library.
 * $Id$
 *
 * decoder for ZIP (deflated) image data.
 *
 * history:
 * 1996-12-14 fl   Created (for PNG)
 * 1997-01-15 fl   Prepared to read TIFF/ZIP
 * 2001-11-19 fl   PNG incomplete read patch (from Bernhard Herzog)
 *
 * Copyright (c) Fredrik Lundh 1996.
 * Copyright (c) Secret Labs AB 1997-2001.
 *
 * See the README file for information on usage and redistribution.
 */


#include "Imaging.h"

#ifdef  HAVE_LIBZ

#include "ZipCodecs.h"

static const int OFFSET[] = { 7, 3, 3, 1, 1, 0, 0 };
static const int STARTING_COL[] = { 0, 4, 0, 2, 0, 1, 0 };
static const int STARTING_ROW[] = { 0, 0, 4, 0, 2, 0, 1 };
static const int COL_INCREMENT[] = { 8, 8, 4, 4, 2, 2, 1 };
static const int ROW_INCREMENT[] = { 8, 8, 8, 4, 4, 2, 2 };

/* Get the length in bytes of a scanline in the pass specified,
 * for interlaced images */
static int get_row_len(ImagingCodecState state, int pass)
{
    int row_len = (state->xsize + OFFSET[pass]) / COL_INCREMENT[pass];
    return ((row_len * state->bits) + 7) / 8;
}

/* -------------------------------------------------------------------- */
/* Decoder                                                              */
/* -------------------------------------------------------------------- */

int
ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8* buf, Py_ssize_t bytes)
{
    ZIPSTATE* context = (ZIPSTATE*) state->context;
    int err;
    int n;
    UINT8* ptr;
    int i, bpp;
    int row_len;

    if (!state->state) {

        /* Initialization */
        if (context->mode == ZIP_PNG || context->mode == ZIP_PNG_PALETTE) {
            context->prefix = 1; /* PNG */
        }

        /* overflow check for malloc */
        if (state->bytes > INT_MAX - 1) {
            state->errcode = IMAGING_CODEC_MEMORY;
            return -1;
        }
        /* Expand standard buffer to make room for the (optional) filter
           prefix, and allocate a buffer to hold the previous line */
        free(state->buffer);
        /* malloc check ok, overflow checked above */
        state->buffer = (UINT8*) malloc(state->bytes+1);
        context->previous = (UINT8*) malloc(state->bytes+1);
        if (!state->buffer || !context->previous) {
            state->errcode = IMAGING_CODEC_MEMORY;
            return -1;
        }

        context->last_output = 0;

        /* Initialize to black */
        memset(context->previous, 0, state->bytes+1);

        /* Setup decompression context */
        context->z_stream.zalloc = (alloc_func) NULL;
        context->z_stream.zfree = (free_func) NULL;
        context->z_stream.opaque = (voidpf) NULL;

        err = inflateInit(&context->z_stream);
        if (err < 0) {
            state->errcode = IMAGING_CODEC_CONFIG;
            free(context->previous);
            context->previous = NULL;
            return -1;
        }

        if (context->interlaced) {
            context->pass = 0;
            state->y = STARTING_ROW[context->pass];
        }

        /* Ready to decode */
        state->state = 1;

    }

    if (context->interlaced) {
        row_len = get_row_len(state, context->pass);
    } else {
        row_len = state->bytes;
    }

    /* Setup the source buffer */
    context->z_stream.next_in = buf;
    context->z_stream.avail_in = bytes;

    /* Decompress what we've got this far */
    while (context->z_stream.avail_in > 0) {

        context->z_stream.next_out = state->buffer + context->last_output;
        context->z_stream.avail_out =
            row_len + context->prefix - context->last_output;

        err = inflate(&context->z_stream, Z_NO_FLUSH);

        if (err < 0) {
            /* Something went wrong inside the compression library */
            if (err == Z_DATA_ERROR) {
                state->errcode = IMAGING_CODEC_BROKEN;
            } else if (err == Z_MEM_ERROR) {
                state->errcode = IMAGING_CODEC_MEMORY;
            } else {
                state->errcode = IMAGING_CODEC_CONFIG;
            }
            free(context->previous);
            context->previous = NULL;
            inflateEnd(&context->z_stream);
            return -1;
        }

        n = row_len + context->prefix - context->z_stream.avail_out;

        if (n < row_len + context->prefix) {
            context->last_output = n;
            break; /* need more input data */
        }

        /* Apply predictor */
        switch (context->mode) {
        case ZIP_PNG:
            switch (state->buffer[0]) {
            case 0:
                break;
            case 1:
                /* prior */
                bpp = (state->bits + 7) / 8;
                for (i = bpp+1; i <= row_len; i++) {
                    state->buffer[i] += state->buffer[i-bpp];
                }
                break;
            case 2:
                /* up */
                for (i = 1; i <= row_len; i++) {
                    state->buffer[i] += context->previous[i];
                }
                break;
            case 3:
                /* average */
                bpp = (state->bits + 7) / 8;
                for (i = 1; i <= bpp; i++) {
                    state->buffer[i] += context->previous[i]/2;
                }
                for (; i <= row_len; i++) {
                    state->buffer[i] +=
                        (state->buffer[i-bpp] + context->previous[i])/2;
                }
                break;
            case 4:
                /* paeth filtering */
                bpp = (state->bits + 7) / 8;
                for (i = 1; i <= bpp; i++) {
                    state->buffer[i] += context->previous[i];
                }
                for (; i <= row_len; i++) {
                    int a, b, c;
                    int pa, pb, pc;

                    /* fetch pixels */
                    a = state->buffer[i-bpp];
                    b = context->previous[i];
                    c = context->previous[i-bpp];

                    /* distances to surrounding pixels */
                    pa = abs(b - c);
                    pb = abs(a - c);
                    pc = abs(a + b - 2*c);

                    /* pick predictor with the shortest distance */
                    state->buffer[i] +=
                        (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;

                }
                break;
            default:
                state->errcode = IMAGING_CODEC_UNKNOWN;
                free(context->previous);
                context->previous = NULL;
                inflateEnd(&context->z_stream);
                return -1;
            }
            break;
        case ZIP_TIFF_PREDICTOR:
            bpp = (state->bits + 7) / 8;
            for (i = bpp+1; i <= row_len; i++) {
                state->buffer[i] += state->buffer[i-bpp];
            }
            break;
        }

        /* Stuff data into the image */
        if (context->interlaced) {
            int col = STARTING_COL[context->pass];
            if (state->bits >= 8) {
                /* Stuff pixels in their correct location, one by one */
                for (i = 0; i < row_len; i += ((state->bits + 7) / 8)) {
                    state->shuffle((UINT8*) im->image[state->y] +
                                   col * im->pixelsize,
                                   state->buffer + context->prefix + i, 1);
                    col += COL_INCREMENT[context->pass];
                }
            } else {
                /* Handle case with more than a pixel in each byte */
                int row_bits = ((state->xsize + OFFSET[context->pass])
                        / COL_INCREMENT[context->pass]) * state->bits;
                for (i = 0; i < row_bits; i += state->bits) {
                    UINT8 byte = *(state->buffer + context->prefix + (i / 8));
                    byte <<= (i % 8);
                    state->shuffle((UINT8*) im->image[state->y] +
                                   col * im->pixelsize, &byte, 1);
                    col += COL_INCREMENT[context->pass];
                }
            }
            /* Find next valid scanline */
            state->y += ROW_INCREMENT[context->pass];
            while (state->y >= state->ysize || row_len <= 0) {
                context->pass++;
                if (context->pass == 7) {
                    /* Force exit below */
                    state->y = state->ysize;
                    break;
                }
                state->y = STARTING_ROW[context->pass];
                row_len = get_row_len(state, context->pass);
                /* Since we're moving to the "first" line, the previous line
                 * should be black to make filters work correctly */
                memset(state->buffer, 0, state->bytes+1);
            }
        } else {
            state->shuffle((UINT8*) im->image[state->y + state->yoff] +
                           state->xoff * im->pixelsize,
                           state->buffer + context->prefix,
                           state->xsize);
            state->y++;
        }

        /* all inflate output has been consumed */
        context->last_output = 0;

        if (state->y >= state->ysize || err == Z_STREAM_END) {

            /* The image and the data should end simultaneously */
            /* if (state->y < state->ysize || err != Z_STREAM_END)
                state->errcode = IMAGING_CODEC_BROKEN; */

            free(context->previous);
            context->previous = NULL;
            inflateEnd(&context->z_stream);
            return -1; /* end of file (errcode=0) */

        }

        /* Swap buffer pointers */
        ptr = state->buffer;
        state->buffer = context->previous;
        context->previous = ptr;

    }

    return bytes; /* consumed all of it */

}


int ImagingZipDecodeCleanup(ImagingCodecState state){
    /* called to free the decompression engine when the decode terminates
       due to a corrupt or truncated image
    */
    ZIPSTATE* context = (ZIPSTATE*) state->context;

    /* Clean up */
    if (context->previous) {
        inflateEnd(&context->z_stream);
        free(context->previous);
        context->previous = NULL;
    }
    return -1;
}

#endif
