#include "jpeg.hpp"


template <class T> void JpegErrorMgr<T>::error_exit(j_common_ptr cinfo) {
    ctx_t* ctx = (ctx_t*)cinfo->err;
    static char msg[JMSG_LENGTH_MAX];
    (*(cinfo->err->format_message))(cinfo, msg);
    LOG("%s", msg);
    longjmp(ctx->setjmp_buffer, 1);
}


template <class T> bool JpegErrorMgr<T>::handle(handler_t handler, void* handler_ctx) {
    ctx_t ctx;
    T cinfo;
    cinfo.err = jpeg_std_error(&ctx.pub);
    ctx.pub.error_exit = &error_exit;

    bool rv;
    if (setjmp(ctx.setjmp_buffer) == 0) {
        jpeg_create(&cinfo);
        rv = handler(cinfo, handler_ctx) && !ctx.pub.num_warnings;
        jpeg_destroy(&cinfo);
    } else {
        jpeg_destroy(&cinfo);
        rv = false;
    }
    return rv;
}


bool JpegImage::jpeg_read(jpeg_decompress_struct& cinfo, void* _args) {
    args_t* args = (args_t*)_args;
    if (!args->fp) return false;
    jpeg_stdio_src(&cinfo, args->fp);

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

    if (cinfo.output_components != 3) {
        return false;
    }
    args->im.in = new Image(cinfo.output_width, cinfo.output_height);
    while (cinfo.output_scanline < cinfo.output_height) {
        JSAMPROW buffer = args->im.in->buf.at(cinfo.output_scanline);
        (void)jpeg_read_scanlines(&cinfo, &buffer, 1);
    }

    (void)jpeg_finish_decompress(&cinfo);
    return true;
}


bool JpegImage::jpeg_write(jpeg_compress_struct& cinfo, void* _args) {
    args_t* args = (args_t*)_args;
    if (!args->fp) return false;
    jpeg_stdio_dest(&cinfo, args->fp);

    cinfo.image_width = args->im.out->buf.w;
    cinfo.image_height = args->im.out->buf.h;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, 100, TRUE);
    jpeg_start_compress(&cinfo, TRUE);

    JSAMPROW row_pointer[1];
    while (cinfo.next_scanline < cinfo.image_height) {
        row_pointer[0] = (unsigned char*)args->im.out->buf.at(cinfo.next_scanline); // const cast
        (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }

    jpeg_finish_compress(&cinfo);
    return true;
}


Image* JpegImage::in(const char* fn) {
    args_t args;
    args.im.in = NULL;
    args.fp = fopen(fn, "rb");
    if (!args.fp) {
        LOG("Cannot open '%s'", fn);
        return NULL;
    }
    if (!JpegErrorMgr<jpeg_decompress_struct>::handle(&jpeg_read, &args)) {
        LOG("Cannot read '%s'", fn);
        if (args.im.in) {
            delete args.im.in;
            args.im.in = NULL;
        }
    } else {
        LOG("Loaded '%s'", fn);
    }
    fclose(args.fp);
    return args.im.in;
}


bool JpegImage::out(const char* fn, const Image* im) {
    args_t args;
    args.im.out = im;
    args.fp = fopen(fn, "wb");
    if (!args.fp) {
        LOG("Cannot open '%s'", fn);
        return false;
    }
    if (!JpegErrorMgr<jpeg_compress_struct>::handle(&jpeg_write, &args)) {
        LOG("Cannot write '%s'", fn);
        fclose(args.fp);
        return false;
    } else {
        LOG("Wrote '%s'", fn);
        fclose(args.fp);
        return true;
    }
}