/*
    IMGLIB:  An example image loading library for use with SDL
    Copyright (C) 1999  Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Sam Lantinga
    5635-34 Springhouse Dr.
    Pleasanton, CA 94588 (USA)
    slouken@devolution.com
*/

/* This is a JPEG image file loading framework */

#include <SDL.h>
#include "../global.h"

#ifdef _WIN32_
#include "../libs/jpeg-6b/jpeglib.h"
#else
#include <jpeglib.h>
#endif

/* Define this for fast loading and not as good image quality */
/*#define FAST_JPEG*/

/* See if an image is contained in a data source */
int IMG_isJPG(SDL_RWops *src)
{
        int is_JPG;
        Uint8 magic[4];

        is_JPG = 0;
        if ( SDL_RWread(src, magic, 2, 1) ) {
                if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
                        SDL_RWread(src, magic, 4, 1);
                        SDL_RWread(src, magic, 4, 1);
                        if ( strncmp( (const char*) magic, "JFIF", 4) == 0 ) {
                                is_JPG = 1;
                        }
                }
        }
        return(is_JPG);
}

#define INPUT_BUFFER_SIZE       4096
typedef struct {
        struct jpeg_source_mgr pub;

        SDL_RWops *ctx;
        Uint8 buffer[INPUT_BUFFER_SIZE];
} my_source_mgr;

/*
 * Initialize source --- called by jpeg_read_header
 * before any data is actually read.
 */
void _init_source (j_decompress_ptr cinfo)
{
        /* We don't actually need to do anything */
        return;
}

/*
 * Fill the input buffer --- called whenever buffer is emptied.
 */
int _fill_input_buffer (j_decompress_ptr cinfo)
{
        my_source_mgr * src = (my_source_mgr *) cinfo->src;
        int nbytes;

        nbytes = SDL_RWread(src->ctx, src->buffer, 1, INPUT_BUFFER_SIZE);
        if (nbytes <= 0) {
                /* Insert a fake EOI marker */
                src->buffer[0] = (Uint8) 0xFF;
                src->buffer[1] = (Uint8) JPEG_EOI;
                nbytes = 2;
        }
        src->pub.next_input_byte = src->buffer;
        src->pub.bytes_in_buffer = nbytes;

        return TRUE;
}


/*
 * Skip data --- used to skip over a potentially large amount of
 * uninteresting data (such as an APPn marker).
 *
 * Writers of suspendable-input applications must note that skip_input_data
 * is not granted the right to give a suspension return.  If the skip extends
 * beyond the data currently in the buffer, the buffer can be marked empty so
 * that the next read will cause a fill_input_buffer call that can suspend.
 * Arranging for additional bytes to be discarded before reloading the input
 * buffer is the application writer's problem.
 */
void _skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
        my_source_mgr * src = (my_source_mgr *) cinfo->src;

        /* Just a dumb implementation for now.  Could use fseek() except
         * it doesn't work on pipes.  Not clear that being smart is worth
         * any trouble anyway --- large skips are infrequent.
         */
        if (num_bytes > 0) {
                while (num_bytes > (long) src->pub.bytes_in_buffer) {
                        num_bytes -= (long) src->pub.bytes_in_buffer;
                        (void) src->pub.fill_input_buffer(cinfo);
                        /* note we assume that fill_input_buffer will never
                         * return FALSE, so suspension need not be handled.
                         */
                }
                src->pub.next_input_byte += (size_t) num_bytes;
                src->pub.bytes_in_buffer -= (size_t) num_bytes;
        }
}

/*
 * Terminate source --- called by jpeg_finish_decompress
 * after all data has been read.
 */
void _term_source (j_decompress_ptr cinfo)
{
        /* We don't actually need to do anything */
        return;
}

/*
 * Prepare for input from a stdio stream.
 * The caller must have already opened the stream, and is responsible
 * for closing it after finishing decompression.
 */
void jpeg_SDL_RW_src (j_decompress_ptr cinfo, SDL_RWops *ctx)
{
  my_source_mgr *src;

  /* The source object and input buffer are made permanent so that a series
   * of JPEG images can be read from the same file by calling jpeg_stdio_src
   * only before the first one.  (If we discarded the buffer at the end of
   * one image, we'd likely lose the start of the next one.)
   * This makes it unsafe to use this manager and a different source
   * manager serially with the same JPEG object.  Caveat programmer.
   */
  if (cinfo->src == NULL) {     /* first time for this JPEG object? */
    cinfo->src = (struct jpeg_source_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                                  sizeof(my_source_mgr));
    src = (my_source_mgr *) cinfo->src;
  }

  src = (my_source_mgr *) cinfo->src;
  src->pub.init_source = _init_source;
  src->pub.fill_input_buffer = _fill_input_buffer;
  src->pub.skip_input_data = _skip_input_data;
  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
  src->pub.term_source = _term_source;
  src->ctx = ctx;
  src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
  src->pub.next_input_byte = NULL; /* until buffer loaded */
}

/* Load a JPEG type image from an SDL datasource */
SDL_Surface *IMG_LoadJPG_RW_D(SDL_RWops *src, int depth)
{
        struct jpeg_error_mgr errmgr;
        struct jpeg_decompress_struct cinfo;
        JSAMPROW rowptr[1];
        SDL_Surface *surface;
        int i;

        /* Create a decompression structure and load the JPEG header */
        cinfo.err = jpeg_std_error(&errmgr);
        jpeg_create_decompress(&cinfo);
        jpeg_SDL_RW_src(&cinfo, src);
        jpeg_read_header(&cinfo, TRUE);

        cinfo.out_color_space = JCS_RGB;
        switch ( depth ) {
                case 24: 
                        cinfo.quantize_colors = FALSE;
#ifdef FAST_JPEG
                        cinfo.scale_num   = 1;
                        cinfo.scale_denom = 1;
                        cinfo.dct_method = JDCT_FASTEST;
                        cinfo.do_fancy_upsampling = FALSE;
#endif
                        break;
                case 8:
                        cinfo.quantize_colors = TRUE;
                        break;
                default:
                        //IMG_SetError("Invalid depth");
                        goto done;
        }
                
        jpeg_calc_output_dimensions(&cinfo);
        
        /* Allocate an output surface to hold the image */
        switch ( depth ) {
                case 24: surface = SDL_AllocSurface(SDL_SWSURFACE,
                               cinfo.output_width, cinfo.output_height, 24,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
                                           0x0000FF, 0x00FF00, 0xFF0000,
#else
                                           0xFF0000, 0x00FF00, 0x0000FF,
#endif
                                           0); 
                        break;
                case 8: surface = SDL_AllocSurface(SDL_SWSURFACE, 
                                       cinfo.output_width, cinfo.output_height,
                                       8, 0, 0, 0, 0 );
                        break;
        }
                                                
        if ( surface == NULL ) {
                // IMG_SetError("Out of memory");
                goto done;
        }

        
        /* Decompress the image */
        jpeg_start_decompress(&cinfo);
        while ( cinfo.output_scanline < cinfo.output_height ) {
                rowptr[0] = (JSAMPROW)(Uint8 *)surface->pixels +
                                    cinfo.output_scanline * surface->pitch;
                jpeg_read_scanlines(&cinfo, rowptr, (JDIMENSION) 1);
        }
        
        if ( depth == 8 ) 
                for ( i = 0; i < 256; i++ ) {
                        surface->format->palette->colors[i].r = cinfo.colormap[0][i];
                        surface->format->palette->colors[i].g = cinfo.colormap[1][i];
                        surface->format->palette->colors[i].b = cinfo.colormap[2][i];
                }
                
        
        jpeg_finish_decompress(&cinfo);

        /* Clean up and return */
done:
        jpeg_destroy_decompress(&cinfo);
        return(surface);
}

