#include "config.h"
#include "Imlib2_Loader.h"

#include <jpeglib.h>
#include <setjmp.h>
#include "exif.h"
#include "ldrs_util.h"

#define DBG_PFX "LDR-jpg"

static const char *const _formats[] = { "jpg", "jpeg", "jfif", "jfi" };

typedef struct {
    struct jpeg_error_mgr jem;
    sigjmp_buf      setjmp_buffer;
    uint8_t        *data;
} ImLib_JPEG_data;

static void
_JPEGFatalErrorHandler(j_common_ptr jcp)
{
    ImLib_JPEG_data *jd = (ImLib_JPEG_data *) jcp->err;

#if 0
    jcp->err->output_message(jcp);
#endif
    siglongjmp(jd->setjmp_buffer, 1);
}

static void
_JPEGErrorHandler(j_common_ptr jcp)
{
#if 0
    ImLib_JPEG_data *jd = (ImLib_JPEG_data *) jcp->err;

    jcp->err->output_message(jcp);
    siglongjmp(jd->setjmp_buffer, 1);
#endif
}

static void
_JPEGErrorHandler2(j_common_ptr jcp, int msg_level)
{
#if 0
    ImLib_JPEG_data *jd = (ImLib_JPEG_data *) jcp->err;

    jcp->err->output_message(jcp);
    siglongjmp(jd->setjmp_buffer, 1);
#endif
}

static struct jpeg_error_mgr *
_jdata_init(ImLib_JPEG_data *jd)
{
    struct jpeg_error_mgr *jem;

    jem = jpeg_std_error(&jd->jem);

    jd->jem.error_exit = _JPEGFatalErrorHandler;
    jd->jem.emit_message = _JPEGErrorHandler2;
    jd->jem.output_message = _JPEGErrorHandler;

    jd->data = NULL;

    return jem;
}

static int
_load(ImlibImage *im, int load_data)
{
    int             w, h, rc;
    struct jpeg_decompress_struct jds;
    ImLib_JPEG_data jdata;
    uint8_t        *ptr, *line[16];
    uint32_t       *imdata;
    int             x, y, l, scans, inc;
    ExifInfo        ei = { 0 };

    rc = LOAD_FAIL;

    /* set up error handling */
    jds.err = _jdata_init(&jdata);
    if (sigsetjmp(jdata.setjmp_buffer, 1))
        QUIT_WITH_RC(LOAD_FAIL);

    jpeg_create_decompress(&jds);
    jpeg_mem_src(&jds, im->fi->fdata, im->fi->fsize);
    jpeg_save_markers(&jds, JPEG_APP0 + 1, 256);
    jpeg_read_header(&jds, TRUE);

    rc = LOAD_BADIMAGE;         /* Format accepted */

    /* Get orientation */
    ei.orientation = ORIENT_TOPLEFT;

    if (jds.marker_list)
    {
        jpeg_saved_marker_ptr m = jds.marker_list;

        D("Markers: %p: m=%02x len=%d/%d\n", m,
          m->marker, m->original_length, m->data_length);

        exif_parse(m->data, m->data_length, &ei);
    }

    w = jds.image_width;
    h = jds.image_height;
    if (!IMAGE_DIMENSIONS_OK(w, h))
        goto quit;

    if (ei.swap_wh)
    {
        im->w = h;
        im->h = w;
    }
    else
    {
        im->w = w;
        im->h = h;
    }

    if (!load_data)
        QUIT_WITH_RC(LOAD_SUCCESS);

    /* Load data */

    jds.do_fancy_upsampling = FALSE;
    jds.do_block_smoothing = FALSE;
    jpeg_start_decompress(&jds);

    if ((jds.rec_outbuf_height > 16) || (jds.output_components <= 0))
        goto quit;

    jdata.data = malloc(w * 16 * jds.output_components);
    if (!jdata.data)
        QUIT_WITH_RC(LOAD_OOM);

    /* must set the im->data member before callign progress function */
    imdata = __imlib_AllocateData(im);
    if (!imdata)
        QUIT_WITH_RC(LOAD_OOM);

    for (y = 0; y < jds.rec_outbuf_height; y++)
        line[y] = jdata.data + (y * w * jds.output_components);

    for (l = 0; l < h; l += jds.rec_outbuf_height)
    {
        jpeg_read_scanlines(&jds, line, jds.rec_outbuf_height);

        scans = jds.rec_outbuf_height;
        if ((h - l) < scans)
            scans = h - l;

        for (y = 0; y < scans; y++)
        {
            ptr = line[y];

            switch (ei.orientation)
            {
            default:
            case ORIENT_TOPLEFT:
                imdata = im->data + (l + y) * w;
                inc = 1;
                break;
            case ORIENT_TOPRIGHT:
                imdata = im->data + (l + y) * w + w - 1;
                inc = -1;
                break;
            case ORIENT_BOTRIGHT:
                imdata = im->data + (h - 1 - (l + y)) * w + w - 1;
                inc = -1;
                break;
            case ORIENT_BOTLEFT:
                imdata = im->data + (h - 1 - (l + y)) * w;
                inc = 1;
                break;
            case ORIENT_LEFTTOP:
                imdata = im->data + (l + y);
                inc = h;
                break;
            case ORIENT_RIGHTTOP:
                imdata = im->data + (h - 1 - (l + y));
                inc = h;
                break;
            case ORIENT_RIGHTBOT:
                imdata = im->data + (h - 1 - (l + y)) + (w - 1) * h;
                inc = -h;
                break;
            case ORIENT_LEFTBOT:
                imdata = im->data + (l + y) + (w - 1) * h;
                inc = -h;
                break;
            }
            DL("l,s,y=%d,%d, %d - x,y=%4ld,%4ld\n", l, y, l + y,
               (long)((imdata - im->data) % im->w),
               (long)((imdata - im->data) / im->w));

            switch (jds.out_color_space)
            {
            default:
                goto quit;
            case JCS_GRAYSCALE:
                for (x = 0; x < w; x++)
                {
                    *imdata = PIXEL_ARGB(0xff, ptr[0], ptr[0], ptr[0]);
                    ptr++;
                    imdata += inc;
                }
                break;
            case JCS_RGB:
                for (x = 0; x < w; x++)
                {
                    *imdata = PIXEL_ARGB(0xff, ptr[0], ptr[1], ptr[2]);
                    ptr += jds.output_components;
                    imdata += inc;
                }
                break;
            case JCS_CMYK:
                for (x = 0; x < w; x++)
                {
                    *imdata = PIXEL_ARGB(0xff, ptr[0] * ptr[3] / 255,
                                         ptr[1] * ptr[3] / 255,
                                         ptr[2] * ptr[3] / 255);
                    ptr += jds.output_components;
                    imdata += inc;
                }
                break;
            }
        }

        if (ei.orientation != ORIENT_TOPLEFT &&
            ei.orientation != ORIENT_TOPRIGHT)
            continue;

        if (im->lc && __imlib_LoadProgressRows(im, l, scans))
            QUIT_WITH_RC(LOAD_BREAK);
    }
    if (ei.orientation != ORIENT_TOPLEFT && ei.orientation != ORIENT_TOPRIGHT)
    {
        if (im->lc)
            __imlib_LoadProgressRows(im, 0, im->h);
    }

    jpeg_finish_decompress(&jds);

    rc = LOAD_SUCCESS;

  quit:
    jpeg_destroy_decompress(&jds);
    free(jdata.data);

    return rc;
}

static int
_save(ImlibImage *im)
{
    int             rc;
    struct jpeg_compress_struct jcs;
    ImLib_JPEG_data jdata;
    FILE           *f = im->fi->fp;
    ImlibSaverParam imsp;
    uint8_t        *buf;
    const uint32_t *imdata;
    JSAMPROW       *jbuf;
    int             y;
    int             i, j;

    /* allocate a small buffer to convert image data */
    buf = malloc(im->w * 3 * sizeof(uint8_t));
    if (!buf)
        return LOAD_OOM;

    rc = LOAD_BADFILE;

    /* set up error handling */
    jcs.err = _jdata_init(&jdata);
    if (sigsetjmp(jdata.setjmp_buffer, 1))
        QUIT_WITH_RC(LOAD_BADFILE);

    /* setup compress params */
    jpeg_create_compress(&jcs);
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = im->w;
    jcs.image_height = im->h;
    jcs.input_components = 3;
    jcs.in_color_space = JCS_RGB;

    /* look for tags attached to image to get extra parameters like quality */
    /* settigns etc. - this is the "api" to hint for extra information for */
    /* saver modules */

    get_saver_params(im, &imsp);

    /* set up jepg compression parameters */
    jpeg_set_defaults(&jcs);
    jpeg_set_quality(&jcs, imsp.quality, TRUE);

    /* progressive */
    if (imsp.interlacing)
        jpeg_simple_progression(&jcs);

    jpeg_start_compress(&jcs, TRUE);
    /* get the start pointer */
    imdata = im->data;
    /* go one scanline at a time... and save */
    for (y = 0; jcs.next_scanline < jcs.image_height; y++)
    {
        /* convcert scaline from ARGB to RGB packed */
        for (j = 0, i = 0; i < im->w; i++)
        {
            uint32_t        pixel = *imdata++;

            buf[j++] = PIXEL_R(pixel);
            buf[j++] = PIXEL_G(pixel);
            buf[j++] = PIXEL_B(pixel);
        }
        /* write scanline */
        jbuf = (JSAMPROW *) (&buf);
        jpeg_write_scanlines(&jcs, jbuf, 1);

        if (im->lc && __imlib_LoadProgressRows(im, y, 1))
            QUIT_WITH_RC(LOAD_BREAK);
    }

    jpeg_finish_compress(&jcs);

    rc = LOAD_SUCCESS;

  quit:
    /* finish off */
    jpeg_destroy_compress(&jcs);
    free(buf);

    return rc;
}

IMLIB_LOADER(_formats, _load, _save);
