/*
 * Copyright (c) 2014, Derek Buitenhuis
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <jerror.h>
#include <jpeglib.h>

#include "ijgdec.h"

static const JOCTET EOI_BUFFER[1] = { JPEG_EOI };

static void init(j_decompress_ptr cinfo)
{
}

static boolean input(j_decompress_ptr cinfo)
{
    cinfo->src->next_input_byte = EOI_BUFFER;
    cinfo->src->bytes_in_buffer = 1;

    return TRUE;
}

static void skip(j_decompress_ptr cinfo, long num_bytes)
{
    if (cinfo->src->bytes_in_buffer < num_bytes) {
        cinfo->src->next_input_byte = EOI_BUFFER;
        cinfo->src->bytes_in_buffer = 1;
    } else {
        cinfo->src->next_input_byte += num_bytes;
        cinfo->src->bytes_in_buffer -= num_bytes;
    }
}

static void term(j_decompress_ptr cinfo)
{
}

jinfo jpeg_get_res(uint8_t *inb, uint64_t insize)
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_source_mgr s;
    struct jpeg_error_mgr jerr;
    jinfo ret;

    jpeg_create_decompress(&cinfo);

    cinfo.err = jpeg_std_error(&jerr);
    cinfo.src = &s;

    cinfo.src->init_source       = init;
    cinfo.src->fill_input_buffer = input;
    cinfo.src->skip_input_data   = skip;
    cinfo.src->resync_to_restart = jpeg_resync_to_restart;
    cinfo.src->term_source       = term;
    cinfo.src->bytes_in_buffer   = insize;
    cinfo.src->next_input_byte   = (const JOCTET *) inb;

    jpeg_read_header(&cinfo, TRUE);
    jpeg_start_decompress(&cinfo);

    ret.width  = cinfo.output_width;
    ret.height = cinfo.output_height;
    ret.vsamp  = cinfo.comp_info[0].v_samp_factor;
    ret.hsamp  = cinfo.comp_info[0].h_samp_factor;

    jpeg_destroy_decompress(&cinfo);

    return ret;
}

int jpeg_decode(uint8_t *inb, uint8_t *o, uint64_t insize)
{
    JSAMPARRAY outb;
    struct jpeg_decompress_struct cinfo;
    struct jpeg_source_mgr s;
    struct jpeg_error_mgr jerr;

    jpeg_create_decompress(&cinfo);

    cinfo.err = jpeg_std_error(&jerr);
    cinfo.src = &s;

    cinfo.src->init_source       = init;
    cinfo.src->fill_input_buffer = input;
    cinfo.src->skip_input_data   = skip;
    cinfo.src->resync_to_restart = jpeg_resync_to_restart;
    cinfo.src->term_source       = term;
    cinfo.src->bytes_in_buffer   = insize;
    cinfo.src->next_input_byte   = (const JOCTET *) inb;

    jpeg_read_header(&cinfo, TRUE);

    cinfo.out_color_space = JCS_YCbCr;

    jpeg_start_decompress(&cinfo);

    outb = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE,
                                      cinfo.output_width * cinfo.output_components, 1);
    if (!outb)
        return -1;

    while (cinfo.output_scanline < cinfo.output_height) {
        jpeg_read_scanlines(&cinfo, outb, 1);
        memcpy(o + cinfo.output_width * cinfo.output_components * (cinfo.output_scanline - 1),
               outb[0], cinfo.output_width * cinfo.output_components);
    }

    jpeg_finish_decompress(&cinfo);

    jpeg_destroy_decompress(&cinfo);

    return 0;
}