/*******************************************************************************
 * Copyright (C) 2014-2015
 * file:    libjpeg-turbo-ext.c
 * author:  gozfree <gozfree@163.com>
 * created: 2015-07-12 00:56
 * updated: 2015-07-12 00:56
 ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <jpeglib.h>
#include <liblog.h>
#include "libjpeg-ex.h"

#define COLOR_COMPONENTS    (3)
#define OUTPUT_BUF_SIZE     (4096)
#define IYUV                (0)
#define NV12                (1)
#define DEFAULT_JPEG_QUALITY	(60)

typedef struct image {
    struct iovec data;
    int format;
    int width;    /* Number of columns in image */
    int height;   /* Number of rows in image */
} image_t ;

typedef struct jpeg_args {
    struct jpeg_destination_mgr pub; /* public fields */
    JOCTET * buffer;    /* start of buffer */
    unsigned char *outbuffer;
    int outbuffer_size;
    unsigned char *outbuffer_cursor;
    int *written;
} jpeg_args_t;

typedef struct yuv_neon_arg {
    uint8_t *in;
    uint8_t *u;
    uint8_t *v;
    int row;
    int col;
    int pitch;
} yuv_neon_arg;

//extern "C" void chrome_convert(struct yuv_neon_arg *);
#if 0
static void chrome_convert(struct yuv_neon_arg *yuv)
{
    printf("chrome_convert\n");
}
#endif

#if 0
static struct image *image_new(struct yuv *yuv)
{
    uint8_t *image_chrome = NULL;
    int u_offset = 0;
    int size_u = 0,size_v = 0;
    int width = yuv->width;
    int height = yuv->height;
    int pitch = yuv->pitch;
    yuv_neon_arg yuv_arg;
    if (width <= 0 || height <= 0 || pitch <= 0) {
        printf("invalid yuv paraments, width=%d, height=%d, pitch=%d\n", width, height, pitch);
        return NULL;
    }
    struct image *img = CALLOC(1, struct image);
    if (!img) {
        printf("malloc img failed!\n");
        return NULL;
    }

    img->width = width;
    img->height = height;

    u_offset = width * height;
    size_u = width * height / 4;
    size_v = size_u;

    img->data.iov_len = yuv->luma.iov_len + yuv->chroma.iov_len;
    img->data.iov_base = calloc(1, img->data.iov_len);
    if (!img->data.iov_base) {
        printf("malloc image data failed\n");
        return NULL;
    }
    memcpy(img->data.iov_base, yuv->luma.iov_base, yuv->luma.iov_len);
    memcpy(img->data.iov_base + yuv->luma.iov_len, yuv->chroma.iov_base, yuv->chroma.iov_len);

    if (img->format == NV12) {
        image_chrome = (uint8_t *)malloc(size_u + size_v);
        if (image_chrome == NULL) {
            printf("malloc image_u or image_v failed\n");
            return NULL;
        }
        yuv_arg.in = img->data.iov_base + u_offset;
        yuv_arg.u = image_chrome;
        yuv_arg.v = image_chrome + size_u;
        yuv_arg.row = height / 2;
        yuv_arg.col = width;
        yuv_arg.pitch = width;
        chrome_convert(&yuv_arg);
        memcpy((img->data.iov_base + u_offset), yuv_arg.u, size_u + size_v);
    } else {
        loge("img->format = %d\n", img->format);
    }
    if (image_chrome != NULL) {
        free(image_chrome);
        image_chrome = NULL;
    }
    return img;
}

static void image_free(struct image *img)
{
    if (!img) {
        return;
    }
    if (!img->data.iov_base) {
        return;
    }
    free(img->data.iov_base);
    free(img);
}
#endif

static void init_destination(j_compress_ptr cinfo)
{
    struct jpeg_args *dest = (struct jpeg_args*) cinfo->dest;
    dest->buffer = (JOCTET *)(*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof(JOCTET));
    *(dest->written) = 0;
    dest->pub.next_output_byte = dest->buffer;
    dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
}

static boolean empty_output_buffer(j_compress_ptr cinfo)
{
    struct jpeg_args *dest = (struct jpeg_args *) cinfo->dest;
    memcpy(dest->outbuffer_cursor, dest->buffer, OUTPUT_BUF_SIZE);
    dest->outbuffer_cursor += OUTPUT_BUF_SIZE;
    *(dest->written) += OUTPUT_BUF_SIZE;
    dest->pub.next_output_byte = dest->buffer;
    dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
    return TRUE;
}

static void term_destination(j_compress_ptr cinfo)
{
    struct jpeg_args * dest = (struct jpeg_args *) cinfo->dest;
    size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
    memcpy(dest->outbuffer_cursor, dest->buffer, datacount);
    dest->outbuffer_cursor += datacount;
    *(dest->written) += datacount;
}

static void dest_buffer(j_compress_ptr cinfo, unsigned char *buffer, int size, int *written)
{
    struct jpeg_args * dest;
    if (cinfo->dest == NULL) {
        cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(struct jpeg_args));
    }

    dest = (struct jpeg_args*) cinfo->dest;
    dest->pub.init_destination = init_destination;
    dest->pub.empty_output_buffer = empty_output_buffer;
    dest->pub.term_destination = term_destination;
    dest->outbuffer = buffer;
    dest->outbuffer_size = size;
    dest->outbuffer_cursor = buffer;
    dest->written = written;
}

#if 0
static int do_jpeg_encode(struct jpeg *jpeg, struct image *img)
{
    uint8_t *output = jpeg->data.iov_base;
    int quality = DEFAULT_JPEG_QUALITY;
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;

    int i = 0;
    int j = 0;
    int written = 0;

    uint32_t size = img->width * img->height;
    uint32_t quarter_size = size / 4;
    uint32_t row = 0;

    JSAMPROW y[16];
    JSAMPROW cb[16];
    JSAMPROW cr[16];
    JSAMPARRAY planes[3];

    planes[0] = y;
    planes[1] = cb;
    planes[2] = cr;

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    dest_buffer(&cinfo, output, img->width * img->height, &written);

    cinfo.image_width = img->width;	/* image width and height, in pixels */
    cinfo.image_height = img->height;
    cinfo.input_components = COLOR_COMPONENTS;	/* # of color components per pixel */
    cinfo.in_color_space = JCS_YCbCr;       /* colorspace of input image */

    jpeg_set_defaults(&cinfo);
    cinfo.raw_data_in = TRUE;	// supply downsampled data
    cinfo.dct_method = JDCT_IFAST;

    cinfo.comp_info[0].h_samp_factor = 2;
    cinfo.comp_info[0].v_samp_factor = 2;
    cinfo.comp_info[1].h_samp_factor = 1;
    cinfo.comp_info[1].v_samp_factor = 1;
    cinfo.comp_info[2].h_samp_factor = 1;
    cinfo.comp_info[2].v_samp_factor = 1;

    jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
    jpeg_start_compress(&cinfo, TRUE);

    for (j = 0; j < img->height; j += 16) {
        for (i = 0; i < 16; i++) {
            row = img->width * (i + j);
            y[i] = img->data.iov_base + row;
            if (i % 2 == 0) {
                cb[i/2] = img->data.iov_base + size + row/4;
                cr[i/2] = img->data.iov_base + size + quarter_size + row/4;
            }
        }
        jpeg_write_raw_data(&cinfo, planes, 16);
    }
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    jpeg->data.iov_len = written;
    return 0;
}
#endif

#if 0
int je_encode_yuv_to_jpeg(struct yuv *yuv, struct jpeg *jpeg, int quality)
{
    int res = 0;
    struct image *img = image_new(yuv);
    if (!img) {
        res = -1;
        printf("image_new failed\n");
        goto end;
    }
    if (0 != do_jpeg_encode(jpeg, img)) {
        res = -1;
        printf("image_new failed\n");
        goto end;
    }
end:
    image_free(img);
    return res;
}
#endif

#if 0
struct yuv *je_yuv_new(int format, int width, int height)
{
    struct yuv *yuv = CALLOC(1, struct yuv);
    if (!yuv) {
        loge("malloc yuv failed\n");
        goto err;
    }
    yuv->format = format;
    yuv->width = width;
    yuv->height = height;
    yuv->pitch = width;
    yuv->luma.iov_len = width * height;
    if (format == YUV420) {
        yuv->chroma.iov_len = yuv->luma.iov_len / 2;
    } else if (format == YUV422) {
        yuv->chroma.iov_len = yuv->luma.iov_len;
    } else {
        printf("invalid yuv data format!\n");
        goto err;
    }
    yuv->luma.iov_base = calloc(1, yuv->luma.iov_len);
    yuv->chroma.iov_base = calloc(1, yuv->chroma.iov_len);
    if (!yuv->luma.iov_base || !yuv->chroma.iov_base) {
        printf("malloc luma chroma failed\n");
        goto err;
    }
    return yuv;

err:
    if (yuv) {
        free(yuv);
    }
    return NULL;
}
#endif

void je_yuv_free(struct je_yuv *yuv)
{
    if (!yuv) {
        return;
    }
    if (yuv->is_continuous) {
        free(yuv->y_data.iov_base);
        free(yuv->uv_data.iov_base);
    } else {
        free(yuv->data.iov_base);
    }
    free(yuv);
}

struct je_jpeg *je_jpeg_new(int width, int height)
{
    struct je_jpeg *jpeg = CALLOC(1, struct je_jpeg);
    if (!jpeg) {
        printf("malloc jpeg failed!\n");
        goto err;
    }
    jpeg->data.iov_len = width * height * COLOR_COMPONENTS;
    jpeg->data.iov_base = calloc(1, jpeg->data.iov_len);
    if (!jpeg->data.iov_base) {
        printf("malloc jpeg buffer failed!\n");
        goto err;
    }
    return jpeg;
err:
    if (jpeg) {
        free(jpeg);
    }
    return NULL;
}

void je_jpeg_free(struct je_jpeg *jpeg)
{
    if (!jpeg) {
        return;
    }
    free(jpeg->data.iov_base);
    free(jpeg);
}

struct je_codec *je_codec_create()
{
    struct je_codec *codec = CALLOC(1, struct je_codec);
    if (!codec) {
        loge("malloc jpeg codec failed!\n");
        return NULL;
    }
    //init encoder
    codec->encoder.err = jpeg_std_error(&codec->errmgr);
    jpeg_create_compress(&codec->encoder);
    jpeg_set_quality(&codec->encoder, DEFAULT_JPEG_QUALITY, 1);

    //init decoder
    //codec->decoder.err = jpeg_std_error(&codec->errmgr);
    //jpeg_create_decompress(&codec->decoder);

    return codec;
}

void je_codec_destroy(struct je_codec *codec)
{
    if (!codec) {
        loge("jpeg codec has already been destroyed!\n");
        return;
    }
    jpeg_destroy_compress(&codec->encoder);
    jpeg_destroy_decompress(&codec->decoder);
    free(codec);
}

struct je_yuv *je_get_yuv_from_file(const char *name,
                                    int format,
                                    int width,
                                    int height,
                                    int pitch)
{
    struct je_yuv *yuv = NULL;
    struct stat st;
    int fd = -1;
    if ((fd = open(name, O_RDWR, 0644)) < 0) {
        loge("failed to open %s\n", name);
        return NULL;
    }
    memset(&st, 0, sizeof(st));
    if (-1 == fstat(fd, &st) || st.st_size <= 0) {
        loge("failed to get %s information!\n", name);
        goto err;
    }
    yuv = CALLOC(1, struct je_yuv);
    if (!yuv) {
        loge("malloc yuv failed!\n");
        goto err;
    }
    yuv->format = format;
    yuv->width = width;
    yuv->height = height;
    yuv->pitch = pitch;
    yuv->is_continuous = 1;

    loge("st.st_size=%d, width*height=%d\n", st.st_size, width*height);
    yuv->data.iov_len = st.st_size;
    //yuv->data.iov_len = width *height;
    yuv->data.iov_base = calloc(1, yuv->data.iov_len);

    if (width*height != yuv->data.iov_len) {
        logw("yuv file size does not equal width*height!\n");
    }
    if (read(fd, yuv->data.iov_base, yuv->data.iov_len) < 0) {
        loge("failed to read yuv data\n");
        goto err;
    }
err:
    close(fd);
    return yuv;
}

int je_encode_yuv_to_jpeg(struct je_codec *codec, struct je_yuv *yuv, struct je_jpeg *jpeg)
{
    if (!codec || !yuv || !jpeg) {
        loge("invalid paraments!\n");
        return -1;
    }
    uint8_t *output = jpeg->data.iov_base;
    int quality = DEFAULT_JPEG_QUALITY;
    struct jpeg_compress_struct *encoder = &codec->encoder;

    int i = 0;
    int j = 0;
    int written = 0;

    uint32_t size = yuv->width * yuv->height;
    uint32_t quarter_size = size / 4;
    uint32_t row = 0;

    JSAMPROW y[16];
    JSAMPROW cb[16];
    JSAMPROW cr[16];
    JSAMPARRAY planes[3];

    planes[0] = y;
    planes[1] = cb;
    planes[2] = cr;

    dest_buffer(encoder, output, yuv->width * yuv->height, &written);

    encoder->image_width = yuv->width;	/* image width and height, in pixels */
    encoder->image_height = yuv->height;
    encoder->input_components = COLOR_COMPONENTS;	/* # of color components per pixel */
    encoder->in_color_space = JCS_YCbCr;       /* colorspace of input image */

    jpeg_set_defaults(encoder);
    encoder->raw_data_in = TRUE;	// supply downsampled data
    encoder->dct_method = JDCT_IFAST;

    encoder->comp_info[0].h_samp_factor = 2;
    encoder->comp_info[0].v_samp_factor = 2;
    encoder->comp_info[1].h_samp_factor = 1;
    encoder->comp_info[1].v_samp_factor = 1;
    encoder->comp_info[2].h_samp_factor = 1;
    encoder->comp_info[2].v_samp_factor = 1;

    jpeg_set_quality(encoder, quality, TRUE /* limit to baseline-JPEG values */);
    loge("xxx\n");
    jpeg_start_compress(encoder, TRUE);
    loge("xxx\n");

    for (j = 0; j < yuv->height; j += 16) {
        for (i = 0; i < 16; i++) {
            row = yuv->width * (i + j);
            y[i] = yuv->data.iov_base + row;
            if (i % 2 == 0) {
                cb[i/2] = yuv->data.iov_base + size + row/4;
                cr[i/2] = yuv->data.iov_base + size + quarter_size + row/4;
            }
        }
        jpeg_write_raw_data(encoder, planes, 16);
    }
    loge("xxx\n");
    jpeg_finish_compress(encoder);
    loge("xxx\n");
    jpeg->data.iov_len = written;
    return 0;
}
