/*
 * The Python Imaging Library.
 * $Id$
 *
 * decoder for JPEG image data.
 *
 * history:
 * 1996-05-02 fl   Created
 * 1996-05-05 fl   Handle small JPEG files correctly
 * 1996-05-28 fl   Added "draft mode" support
 * 1997-01-25 fl   Added colour conversion override
 * 1998-01-31 fl   Adapted to libjpeg 6a
 * 1998-07-12 fl   Extended YCbCr support
 * 1998-12-29 fl   Added new state to handle suspension in multipass modes
 * 2000-10-12 fl   Suppress warnings
 * 2000-12-04 fl   Suppress errors beyond end of image data
 *
 * Copyright (c) 1998-2000 Secret Labs AB
 * Copyright (c) 1996-2000 Fredrik Lundh
 *
 * See the README file for details on usage and redistribution.
 */

#include "Imaging.h"

#ifdef HAVE_LIBJPEG

#undef HAVE_PROTOTYPES
#undef HAVE_STDLIB_H
#undef HAVE_STDDEF_H
#undef UINT8
#undef UINT16
#undef UINT32
#undef INT16
#undef INT32

#include "Jpeg.h"

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

// There is no way to compare versions on compile time,
// so we have to do that in runtime.
#ifdef LIBJPEG_TURBO_VERSION
char *libjpeg_turbo_version = TOSTRING(LIBJPEG_TURBO_VERSION);
#else
char *libjpeg_turbo_version = NULL;
#endif

int
ImagingJpegUseJCSExtensions(void) {
    int use_jcs_extensions = 0;
#ifdef JCS_EXTENSIONS
#if defined(LIBJPEG_TURBO_VERSION_NUMBER)
#if LIBJPEG_TURBO_VERSION_NUMBER >= 1002010
    use_jcs_extensions = 1;
#endif
#else
    if (libjpeg_turbo_version) {
        use_jcs_extensions = strcmp(libjpeg_turbo_version, "1.2.1") >= 0;
    }
#endif
#endif
    return use_jcs_extensions;
}

/* -------------------------------------------------------------------- */
/* Suspending input handler                                             */
/* -------------------------------------------------------------------- */

METHODDEF(void)
stub(j_decompress_ptr cinfo) { /* empty */ }

METHODDEF(boolean)
fill_input_buffer(j_decompress_ptr cinfo) {
    /* Suspension */
    return FALSE;
}

METHODDEF(void)
skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
    JPEGSOURCE *source = (JPEGSOURCE *)cinfo->src;

    if (num_bytes > (long)source->pub.bytes_in_buffer) {
        /* We need to skip more data than we have in the buffer.
           This will force the JPEG library to suspend decoding. */
        source->skip = num_bytes - source->pub.bytes_in_buffer;
        source->pub.next_input_byte += source->pub.bytes_in_buffer;
        source->pub.bytes_in_buffer = 0;
    } else {
        /* Skip portion of the buffer */
        source->pub.bytes_in_buffer -= num_bytes;
        source->pub.next_input_byte += num_bytes;
        source->skip = 0;
    }
}

GLOBAL(void)
jpeg_buffer_src(j_decompress_ptr cinfo, JPEGSOURCE *source) {
    cinfo->src = (void *)source;

    /* Prepare for suspending reader */
    source->pub.init_source = stub;
    source->pub.fill_input_buffer = fill_input_buffer;
    source->pub.skip_input_data = skip_input_data;
    source->pub.resync_to_restart = jpeg_resync_to_restart;
    source->pub.term_source = stub;
    source->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */

    source->skip = 0;
}

/* -------------------------------------------------------------------- */
/* Error handler                                                        */
/* -------------------------------------------------------------------- */

METHODDEF(void)
error(j_common_ptr cinfo) {
    JPEGERROR *error;
    error = (JPEGERROR *)cinfo->err;
    longjmp(error->setjmp_buffer, 1);
}

METHODDEF(void)
output(j_common_ptr cinfo) { /* nothing */ }

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

int
ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) {
    JPEGSTATE *context = (JPEGSTATE *)state->context;
    int ok;

    if (setjmp(context->error.setjmp_buffer)) {
        /* JPEG error handler */
        jpeg_destroy_decompress(&context->cinfo);
        state->errcode = IMAGING_CODEC_BROKEN;
        return -1;
    }

    if (!state->state) {
        /* Setup decompression context */
        context->cinfo.err = jpeg_std_error(&context->error.pub);
        context->error.pub.error_exit = error;
        context->error.pub.output_message = output;
        jpeg_create_decompress(&context->cinfo);
        jpeg_buffer_src(&context->cinfo, &context->source);

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

    /* Load the source buffer */
    context->source.pub.next_input_byte = buf;
    context->source.pub.bytes_in_buffer = bytes;

    if (context->source.skip > 0) {
        skip_input_data(&context->cinfo, context->source.skip);
        if (context->source.skip > 0) {
            return context->source.pub.next_input_byte - buf;
        }
    }

    switch (state->state) {
        case 1:

            /* Read JPEG header, until we find an image body. */
            do {
                /* Note that we cannot return unless we have decoded
                   as much data as possible. */
                ok = jpeg_read_header(&context->cinfo, FALSE);

            } while (ok == JPEG_HEADER_TABLES_ONLY);

            if (ok == JPEG_SUSPENDED) {
                break;
            }

            /* Decoder settings */

            /* jpegmode indicates what's in the file. */
            /* If not valid, we'll trust the decoder. */
            if (context->jpegmode == IMAGING_RAWMODE_L) {
                context->cinfo.jpeg_color_space = JCS_GRAYSCALE;
            } else if (context->jpegmode == IMAGING_RAWMODE_RGB) {
                context->cinfo.jpeg_color_space = JCS_RGB;
            } else if (context->jpegmode == IMAGING_RAWMODE_CMYK) {
                context->cinfo.jpeg_color_space = JCS_CMYK;
            } else if (context->jpegmode == IMAGING_RAWMODE_YCbCr) {
                context->cinfo.jpeg_color_space = JCS_YCbCr;
            } else if (context->jpegmode == IMAGING_RAWMODE_YCbCrK) {
                context->cinfo.jpeg_color_space = JCS_YCCK;
            }

            /* rawmode indicates what we want from the decoder. */
            /* If not valid, conversions are disabled. */
            if (context->rawmode == IMAGING_RAWMODE_L) {
                context->cinfo.out_color_space = JCS_GRAYSCALE;
            } else if (context->rawmode == IMAGING_RAWMODE_RGB) {
                context->cinfo.out_color_space = JCS_RGB;
            }
#ifdef JCS_EXTENSIONS
            else if (context->rawmode == IMAGING_RAWMODE_RGBX) {
                context->cinfo.out_color_space = JCS_EXT_RGBX;
            }
#endif
            else if (
                context->rawmode == IMAGING_RAWMODE_CMYK ||
                context->rawmode == IMAGING_RAWMODE_CMYK_I
            ) {
                context->cinfo.out_color_space = JCS_CMYK;
            } else if (context->rawmode == IMAGING_RAWMODE_YCbCr) {
                context->cinfo.out_color_space = JCS_YCbCr;
            } else if (context->rawmode == IMAGING_RAWMODE_YCbCrK) {
                context->cinfo.out_color_space = JCS_YCCK;
            } else {
                /* Disable decoder conversions. */
                context->cinfo.jpeg_color_space = JCS_UNKNOWN;
                context->cinfo.out_color_space = JCS_UNKNOWN;
            }

            if (context->scale > 1) {
                context->cinfo.scale_num = 1;
                context->cinfo.scale_denom = context->scale;
            }
            if (context->draft) {
                context->cinfo.do_fancy_upsampling = FALSE;
                context->cinfo.dct_method = JDCT_FASTEST;
            }

            state->state++;
            /* fall through */

        case 2:

            /* Set things up for decompression (this processes the entire
               file if necessary to return data line by line) */
            if (!jpeg_start_decompress(&context->cinfo)) {
                break;
            }

            state->state++;
            /* fall through */

        case 3:

            /* Decompress a single line of data */
            ok = 1;
            while (state->y < state->ysize) {
                ok = jpeg_read_scanlines(&context->cinfo, &state->buffer, 1);
                if (ok != 1) {
                    break;
                }
                state->shuffle(
                    (UINT8 *)im->image[state->y + state->yoff] +
                        state->xoff * im->pixelsize,
                    state->buffer,
                    state->xsize
                );
                state->y++;
            }
            if (ok != 1) {
                break;
            }
            state->state++;
            /* fall through */

        case 4:

            /* Finish decompression */
            if (!jpeg_finish_decompress(&context->cinfo)) {
                /* FIXME: add strictness mode test */
                if (state->y < state->ysize) {
                    break;
                }
            }

            /* Clean up */
            jpeg_destroy_decompress(&context->cinfo);
            /* if (jerr.pub.num_warnings) return BROKEN; */
            return -1;
    }

    /* Return number of bytes consumed */
    return context->source.pub.next_input_byte - buf;
}

/* -------------------------------------------------------------------- */
/* Cleanup                                                              */
/* -------------------------------------------------------------------- */

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

    /* Clean up */
    jpeg_destroy_decompress(&context->cinfo);
    return -1;
}

#endif
