#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <jpeglib.h>

#include "mjpeg.h"
#include "colorspace.h"

/* ---------------------------------------------------------------------- */

static int debug=0;

static int mjpeg_quality;
static struct jpeg_compress_struct  mjpg_cinfo;
static struct jpeg_decompress_struct  mjpg_dinfo;
static struct jpeg_error_mgr        mjpg_jerr;
static struct jpeg_destination_mgr  mjpg_dest;
static struct jpeg_source_mgr  mjpg_src;

static JOCTET *mjpg_buffer;
static size_t  mjpg_bufsize;
static size_t  mjpg_datasize;
static size_t  mjpg_bufused;
static int     mjpg_tables;

static int invert_direction;

void swap_rgb24(char *mem, int n)
{
    char  c;
    char *p = mem;
    int   i = n;
    
    while (--i) {
	c = p[0]; p[0] = p[2]; p[2] = c;
	p += 3;
    }
}

void mjpg_set_quality(int qual)
{
    mjpeg_quality=qual;
}
int mjpg_get_quality()
{
    return mjpeg_quality;
}    
/* ---------------------------------------------------------------------- */

static void mjpg_dest_init(struct jpeg_compress_struct *cinfo)
{
    cinfo->dest->next_output_byte = mjpg_buffer;
    cinfo->dest->free_in_buffer   = mjpg_bufsize;
}

static boolean mjpg_dest_flush(struct jpeg_compress_struct *cinfo)
{
    fprintf(stderr,"mjpg: panic: output buffer too small\n");
    exit(1);
}

static void mjpg_dest_term(struct jpeg_compress_struct *cinfo)
{
    mjpg_bufused = mjpg_bufsize - cinfo->dest->free_in_buffer;
}

static void mjpg_src_init(struct jpeg_decompress_struct *dinfo)
{
    dinfo->src->next_input_byte = mjpg_buffer;
    dinfo->src->bytes_in_buffer = mjpg_datasize; 
}
static boolean mjpg_src_fill(struct jpeg_decompress_struct *dinfo)
{
    printf("Fill\n");
    return -1;
}
static void mjpg_src_skip(struct jpeg_decompress_struct *dinfo, long bytes)
{
    printf("Skip\n");
}
static boolean mjpg_src_resync(struct jpeg_decompress_struct *dinfo, int desired)
{
    printf("Resync\n");
    return -1;
}
static void mjpg_src_term(struct jpeg_decompress_struct *dinfo)
{
}

/* ---------------------------------------------------------------------- */

static void
mjpg_init(int width, int height)
{
    memset(&mjpg_cinfo,0,sizeof(mjpg_cinfo));
    memset(&mjpg_jerr,0,sizeof(mjpg_jerr));
    mjpg_cinfo.err = jpeg_std_error(&mjpg_jerr);
    jpeg_create_compress(&mjpg_cinfo);

    mjpg_dest.init_destination    = mjpg_dest_init;
    mjpg_dest.empty_output_buffer = mjpg_dest_flush;
    mjpg_dest.term_destination    = mjpg_dest_term;
    mjpg_cinfo.dest               = &mjpg_dest;

    if(height<0){height*=-1;invert_direction=1;}
    else invert_direction=0;
    
    mjpg_cinfo.image_width  = width;
    mjpg_cinfo.image_height = height;
    mjpg_tables = TRUE;
}

static void mjpg_dec_init(int width, int height)
{
    memset(&mjpg_dinfo,0,sizeof(mjpg_dinfo));
    memset(&mjpg_jerr,0,sizeof(mjpg_jerr));
    mjpg_dinfo.err = jpeg_std_error(&mjpg_jerr);
    jpeg_create_decompress(&mjpg_dinfo);

    mjpg_src.init_source    = mjpg_src_init;
    mjpg_src.fill_input_buffer = mjpg_src_fill;
    mjpg_src.skip_input_data    = mjpg_src_skip;
    mjpg_src.resync_to_restart    = mjpg_src_resync;
    mjpg_src.term_source    = mjpg_src_term;
    mjpg_dinfo.src               = &mjpg_src;

    if(height<0){height*=-1;invert_direction=1;}
    else invert_direction=0;
    mjpg_dinfo.image_width  = width;
    mjpg_dinfo.image_height = height;
    mjpg_tables = TRUE;
}

void
mjpg_cleanup(void)
{
    if (debug > 1)
	fprintf(stderr,"mjpg_cleanup\n");
    
    jpeg_destroy_compress(&(mjpg_cinfo));
}
void
mjpg_dec_cleanup(void)
{
    if (debug > 1)
	fprintf(stderr,"mjpg_cleanup\n");
    
    jpeg_destroy_decompress(&(mjpg_dinfo));
}

/* ---------------------------------------------------------------------- */

void
mjpg_rgb_init(int width, int height)
{
    if (debug > 1)
	fprintf(stderr,"mjpg_rgb_init\n");

    mjpg_init(width, height);

    mjpg_cinfo.input_components = 3;
    mjpg_cinfo.in_color_space = JCS_RGB;

    jpeg_set_defaults(&mjpg_cinfo);
    mjpg_cinfo.dct_method = JDCT_FASTEST;
    jpeg_set_quality(&mjpg_cinfo, mjpeg_quality, TRUE);
    jpeg_suppress_tables(&mjpg_cinfo, TRUE);
}

void
mjpg_dec_rgb_init(int width, int height)
{
    if (debug > 1)
	fprintf(stderr,"mjpg_dec_rgb_init\n");

    mjpg_dec_init(width, height);

    mjpg_dinfo.num_components = 3;
    mjpg_dinfo.jpeg_color_space = JCS_RGB;
    mjpg_dinfo.out_color_space = JCS_RGB;
    mjpg_dinfo.scale_num = 1;
    mjpg_dinfo.scale_denom = 1;
    mjpg_dinfo.output_gamma = 0;
    mjpg_dinfo.dct_method = JDCT_FASTEST;
//    jpeg_set_defaults(&mjpg_dinfo);
//    mjpg_cinfo.dct_method = JDCT_FASTEST;
//    jpeg_set_quality(&mjpg_cinfo, mjpeg_quality, TRUE);
//    jpeg_suppress_tables(&mjpg_cinfo, TRUE);
}


int
mjpg_rgb_compress(unsigned char *d, unsigned char *s, int p)
{
    int i;
    unsigned char *line;

    if (debug > 1)
	fprintf(stderr,"mjpg_rgb_compress\n");
    
    mjpg_buffer  = d;
    mjpg_bufsize = 3*mjpg_cinfo.image_width*mjpg_cinfo.image_height;

    jpeg_start_compress(&mjpg_cinfo, mjpg_tables);
    if(!invert_direction)
	for (i = 0, line = s; i < mjpg_cinfo.image_height;
	     i++, line += 3*mjpg_cinfo.image_width)
	    jpeg_write_scanlines(&mjpg_cinfo, &line, 1);
	else
	for (i = 0, line = s+mjpg_bufsize-3*mjpg_cinfo.image_width; i < mjpg_cinfo.image_height;
	     i++, line -= 3*mjpg_cinfo.image_width)
	    jpeg_write_scanlines(&mjpg_cinfo, &line, 1);
	
    jpeg_finish_compress(&(mjpg_cinfo));
//    mjpg_tables = FALSE;

    return mjpg_bufused;
}
int
mjpg_rgb_decompress(unsigned char *d, unsigned char *s, int p)
{
    int i;
    unsigned char *line;

    if (debug > 1)
	fprintf(stderr,"mjpg_rgb_decompress\n");
    
    mjpg_buffer  = s;
//    mjpg_bufsize = 3*mjpg_dinfo.image_width*mjpg_dinfo.image_height;
    mjpg_datasize = p;
    jpeg_read_header(&mjpg_dinfo, 1);
    jpeg_start_decompress(&mjpg_dinfo);
    if(!invert_direction)
	for (i = 0, line = d; i < mjpg_dinfo.image_height;
	     i++, line += 3*mjpg_dinfo.image_width)
	    jpeg_read_scanlines(&mjpg_dinfo, &line, 1);
	else
	for (i = 0, line = d+3*mjpg_dinfo.image_width*(mjpg_dinfo.image_height-1);
	    i < mjpg_dinfo.image_height;
	     i++, line -= 3*mjpg_dinfo.image_width)
	    jpeg_read_scanlines(&mjpg_dinfo, &line, 1);
    jpeg_finish_decompress(&(mjpg_dinfo));
//    mjpg_tables = FALSE;

    return 0;
}

int
mjpg_bgr_compress(unsigned char *d, unsigned char *s, int p)
{
    swap_rgb24(s,p);
    return mjpg_rgb_compress(d,s,p);
}
int
mjpg_bgr_decompress(unsigned char *d, unsigned char *s, int p)
{
    mjpg_rgb_decompress(d,s,p);
    swap_rgb24(d, mjpg_dinfo.image_width*mjpg_dinfo.image_height);

    return 0;
}

/* ---------------------------------------------------------------------- */
/*
static int rwidth,rheight;
unsigned char **mjpg_ptrs[3];
unsigned char **mjpg_run[3];

void
mjpg_yuv_init(int width, int height)
{
    if (debug > 1)
	fprintf(stderr,"mjpg_yuv_init\n");

    // save real size 
    rwidth  = width;
    rheight = height;

    // fix size to match DCT blocks (I'm not going to copy around
    //   data to pad stuff, so we'll simplay cut off edges) 
    width  &= ~(2*DCTSIZE-1);
    height &= ~(2*DCTSIZE-1);
    mjpg_init(width, height);

    mjpg_cinfo.input_components = 3;
    mjpg_cinfo.in_color_space = JCS_YCbCr; 

    jpeg_set_defaults(&mjpg_cinfo);
    mjpg_cinfo.dct_method = JDCT_FASTEST;
    jpeg_set_quality(&mjpg_cinfo, mjpeg_quality, TRUE);

    mjpg_cinfo.raw_data_in = TRUE;
    jpeg_set_colorspace(&mjpg_cinfo,JCS_YCbCr);
    mjpg_cinfo.comp_info[0].h_samp_factor = 2;
    mjpg_cinfo.comp_info[0].v_samp_factor = 2;
    mjpg_cinfo.comp_info[1].h_samp_factor = 1;
    mjpg_cinfo.comp_info[1].v_samp_factor = 1;
    mjpg_cinfo.comp_info[2].h_samp_factor = 1;
    mjpg_cinfo.comp_info[2].v_samp_factor = 1;

    mjpg_ptrs[0] = malloc(height*sizeof(char*));
    mjpg_ptrs[1] = malloc(height*sizeof(char*)/2);
    mjpg_ptrs[2] = malloc(height*sizeof(char*)/2);
    jpeg_suppress_tables(&mjpg_cinfo, TRUE);
}

static int
mjpg_yuv_compress(void)
{
    int y;

    mjpg_run[0] = mjpg_ptrs[0];
    mjpg_run[1] = mjpg_ptrs[1];
    mjpg_run[2] = mjpg_ptrs[2];
    
//    mjpg_cinfo.write_JFIF_header = FALSE;
    jpeg_start_compress(&mjpg_cinfo, mjpg_tables);
//    jpeg_write_marker(&mjpg_cinfo, JPEG_APP0, "AVI1\0\0\0\0", 8);
    for (y = 0; y < mjpg_cinfo.image_height; y += 2*DCTSIZE) {
	jpeg_write_raw_data(&mjpg_cinfo, mjpg_run,2*DCTSIZE);
	mjpg_run[0] += 2*DCTSIZE;
	mjpg_run[1] += DCTSIZE;
	mjpg_run[2] += DCTSIZE;
    }
    jpeg_finish_compress(&(mjpg_cinfo));
//    mjpg_tables = FALSE;
    
    return mjpg_bufused;
}

int
mjpg_yuv422_compress(unsigned char *d, unsigned char *s, int p)
{
    unsigned char *line;
    int i;

    if (debug > 1)
	fprintf(stderr,"mjpg_yuv422_compress\n");

    mjpg_buffer  = d;
    mjpg_bufsize = 3*mjpg_cinfo.image_width*mjpg_cinfo.image_height;

    line = s;
    for (i = 0; i < mjpg_cinfo.image_height; i++, line += rwidth)
	mjpg_ptrs[0][i] = line;

    line = s + rwidth*rheight;
    for (i = 0; i < mjpg_cinfo.image_height; i+=2, line += rwidth)
	mjpg_ptrs[1][i/2] = line;

    line = s + rwidth*rheight*3/2;
    for (i = 0; i < mjpg_cinfo.image_height; i+=2, line += rwidth)
	mjpg_ptrs[2][i/2] = line;

    return mjpg_yuv_compress();
}

int
mjpg_yuv420_compress(unsigned char *d, unsigned char *s, int p)
{
    unsigned char *line;
    int i;

    if (debug > 1)
	fprintf(stderr,"mjpg_yuv420_compress\n");

    mjpg_buffer  = d;
    mjpg_bufsize = 3*mjpg_cinfo.image_width*mjpg_cinfo.image_height;

    line = s;
    for (i = 0; i < mjpg_cinfo.image_height; i++, line += rwidth)
	mjpg_ptrs[0][i] = line;

    line = s + rwidth*rheight;
    for (i = 0; i < mjpg_cinfo.image_height; i+=2, line += rwidth/2)
	mjpg_ptrs[1][i/2] = line;

    line = s + rwidth*rheight*5/4;
    for (i = 0; i < mjpg_cinfo.image_height; i+=2, line += rwidth/2)
	mjpg_ptrs[2][i/2] = line;

    return mjpg_yuv_compress();
}
*/