#ifdef __cplusplus
extern "C" {
#endif

#include <fcntl.h>
#include <setjmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <libhfuzz/libhfuzz.h>

#include "cderror.h"
#include "jpeglib.h"

struct jpeg_decompress_struct cinfo;
int null_fd = -1;

struct jpegErrorManager {
    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
};

struct jpegErrorManager jerr;

void jpegErrorExit(j_common_ptr cinfo) {
    struct jpegErrorManager* myerr = (struct jpegErrorManager*)cinfo->err;
    longjmp(myerr->setjmp_buffer, 1);
}

static const char* const cdjpeg_message_table[] = {
#include "cderror.h"
    NULL};

static uint64_t max_hv_size = 10000;
int LLVMFuzzerInitialize(int* argc, char*** argv) {
    null_fd = open("/dev/null", O_WRONLY);

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = jpegErrorExit;

    jerr.pub.addon_message_table = cdjpeg_message_table;
    jerr.pub.first_addon_message = JMSG_FIRSTADDONCODE;
    jerr.pub.last_addon_message = JMSG_LASTADDONCODE;

    jpeg_create_decompress(&cinfo);

    /* If there are any arguments provided, limit width and height to this value */
    if (*argc > 1) {
        max_hv_size = strtoull((*argv)[1], NULL, 0);
    }
    return 0;
}

int LLVMFuzzerTestOneInput(const uint8_t* buf, size_t len) {
    if (setjmp(jerr.setjmp_buffer)) {
        goto out;
    }

    jpeg_mem_src(&cinfo, buf, len);
    jpeg_read_header(&cinfo, TRUE);

    /* Make sure the picture's resultion is reasonable */
    if ((uint64_t)cinfo.output_height > max_hv_size) {
        goto out;
    }
    if ((uint64_t)cinfo.output_width > max_hv_size) {
        goto out;
    }

    cinfo.mem->max_memory_to_use = (1024ULL * 1024ULL * 1024ULL);
    cinfo.mem->max_alloc_chunk = (1024ULL * 1024ULL * 1024ULL);

    jpeg_start_decompress(&cinfo);

    int row_stride = cinfo.output_width * cinfo.output_components;
    JSAMPARRAY buffer =
        (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
    while (cinfo.output_scanline < cinfo.output_height) {
#if defined(__clang__)
#if __has_feature(memory_sanitizer)
        __msan_poison(buffer[0], row_stride);
#endif /* __has_feature(memory_sanitizer) */
#endif /* defined(__clang__) */
        jpeg_read_scanlines(&cinfo, buffer, 1);
        write(null_fd, buffer[0], row_stride);
    }

out:
    jpeg_abort_decompress(&cinfo);
    return 0;
}

#ifdef __cplusplus
}
#endif
