/** \file
 * \author This code was written by Nathan Stenzel for gphoto
 *
 * \par License
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * \par
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * \par
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "jpeg.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gphoto2/gphoto2-file.h>

/* call example:nullpictureabort(picture,"Picture",0); */
#define nullpointerabort(pointer,name,val) \
    if (pointer==NULL) { printf(name " does not exist\n"); return val; }

/* call example:nullpictureabortvoid(picture,"Picture"); */
#define nullpointerabortvoid(pointer,name) \
    if (pointer==NULL) { printf(name " does not exist\n"); return; }

#define CHECK_RESULT(result)       {int r = (result); if (r < 0) return (r);}

/* Determine the number of elements in an array. */
#define countof(array) (sizeof(array) / sizeof((array)[0]))

const jpegmarker JPEG_MARKERS[] = {
    JPEG_START,             JPEG_COMMENT,           JPEG_APPO,
    JPEG_QUANTIZATION,      JPEG_HUFFMAN,           JPEG_SOFC0,
    JPEG_SSSEAHAL,          JPEG_EOI
};

const char *JPEG_MARKERNAMES[] = {
    "Start",                "Comment",              "APPO",
    "Quantization table",   "Huffman table",        "SOFC0",
    "SsSeAhAl",             "End of image"
};

chunk *gpi_jpeg_chunk_new(int length)
{
	chunk *mychunk;
	printf("Entered gpi_jpeg_chunk_new\n");
	mychunk =  (chunk *) malloc(sizeof(chunk));
	if (mychunk==NULL) {
		printf("Failed to allocate new chunk!\n");
		return NULL;
	}
	mychunk->size=length;
/*    printf("New chunk of size %i\n", mychunk->size); */
	mychunk->data = malloc(length);
	return mychunk;
}

chunk *gpi_jpeg_chunk_new_filled(int length, char *data)
{
    chunk *mychunk;
    printf("Entered gpi_jpeg_chunk_new_filled\n");
    mychunk = gpi_jpeg_chunk_new(length);
    if (!mychunk)
	return NULL;
    printf("Filling the chunk data via chunk_new_filled\n");
    memcpy(mychunk->data, data, length);
    return mychunk;
}

void gpi_jpeg_chunk_destroy(chunk *mychunk)
{
    nullpointerabortvoid(mychunk, "Chunk");
    mychunk->size=0;
    free(mychunk->data);
    free(mychunk);
}


void gpi_jpeg_chunk_print(chunk *mychunk)
{
    int x;
/*    printf("Size=%i\n", mychunk->size); */
    nullpointerabortvoid(mychunk, "Chunk");
    for (x=0; x<mychunk->size; x++)
        printf("%hhX ", mychunk->data[x]);
    printf("\n");
}

char gpi_jpeg_findff(int *location, chunk *picture)
{
/* printf("Entered jpeg_findamarker!!!!!!\n"); */
    nullpointerabort(picture, "Picture",0);
    while(*location<picture->size)
    {
/*        printf("%hX ",picture->data[*location]); */
        if (picture->data[*location]==0xff)
        {
/*        printf("return success!\n"); */
        return 1;
        }
    (*location)++;
    }
/*    printf("return failure!\n"); */
    return 0;
}

char gpi_jpeg_findactivemarker(char *id, int *location, chunk *picture)
{
/*    printf("Entered gpi_jpeg_findactivemarker!!!!!!!!\n"); */
    nullpointerabort(picture, "Picture",0);
    while(gpi_jpeg_findff(location, picture) && ((*location+1)<picture->size))
        if (picture->data[*location+1]) {
            *id=picture->data[*location+1];
            return 1;
        }
    return 0;
}

char *gpi_jpeg_markername(unsigned int c)
{
    unsigned int x;
/*    printf("searching for marker %X in list\n",c); */
/*    printf("%i\n", sizeof(markers)); */
    for (x=0; x<countof(JPEG_MARKERS); x++)
    {
/*        printf("checking to see if it is marker %X\n", markers[x]); */

        if (c==JPEG_MARKERS[x])
            return (char *)JPEG_MARKERNAMES[x];
    }
    return "Undefined marker";
}

jpeg *gpi_jpeg_new()
{
    jpeg *temp;
    temp=malloc(sizeof(jpeg));
    temp->count=0;
    return temp;
}

void gpi_jpeg_destroy(jpeg *myjpeg)
{
    int count;
    for (count=0; count<myjpeg->count; count++)
        gpi_jpeg_chunk_destroy(myjpeg->marker[count]);
    myjpeg->count=0;
    free(myjpeg);
}

void gpi_jpeg_add_marker(jpeg *myjpeg, chunk *picture, int start, int end)
{
    int length;
    nullpointerabortvoid(picture, "Picture");
    length=(int)(end-start+1);
/*  printf("Add marker #%i starting from %i and ending at %i for a length of %i\n", myjpeg->count, start, end, length); */
    myjpeg->marker[myjpeg->count] = gpi_jpeg_chunk_new(length);
    if (!myjpeg->marker[myjpeg->count])
	return;
/*  printf("Read length is: %i\n", myjpeg->marker[myjpeg->count].size); */
    memcpy(myjpeg->marker[myjpeg->count]->data, picture->data+start, length);
    gpi_jpeg_chunk_print(myjpeg->marker[myjpeg->count]);
    myjpeg->count++;
}

void gpi_jpeg_add_chunk(jpeg *myjpeg, chunk *source)
{ /* Warning! This points to the added chunk instead of deleting it! */
    printf("Entered gpi_jpeg_add_chunk\n");
    nullpointerabortvoid(source, "Chunk to add");
    myjpeg->marker[myjpeg->count]=source;
    myjpeg->count++;
}

void gpi_jpeg_parse(jpeg *myjpeg, chunk *picture)
{
    int position=0;
    int lastposition;
    char id;
    nullpointerabortvoid(picture,"Picture");
    if (picture->data[0]!=0xff)
        {
            gpi_jpeg_findactivemarker(&id, &position, picture);
            gpi_jpeg_add_marker(myjpeg,picture,0,position-1);
            lastposition=position;
            position++;
        }
        else
        {
        lastposition=position;
        position+=2;
        gpi_jpeg_findactivemarker(&id, &position, picture);
        gpi_jpeg_add_marker(myjpeg,picture,0,position-1);
        lastposition=position;
        position+=2;
        }
    while (position<picture->size)
        {
            gpi_jpeg_findactivemarker(&id, &position, picture);
            gpi_jpeg_add_marker(myjpeg,picture,lastposition,position-1);
            lastposition=position;
            position+=2;
        }
    position-=2;
    if (position<picture->size)
    gpi_jpeg_add_marker(myjpeg,picture,lastposition,picture->size-1);
}

void gpi_jpeg_print(jpeg *myjpeg)
{
    int c;
    printf("There are %i markers\n", myjpeg->count);
    for (c=0; c < myjpeg->count; c++)
    {
        printf("%s:\n",gpi_jpeg_markername(myjpeg->marker[c]->data[1]));
        gpi_jpeg_chunk_print(myjpeg->marker[c]);
    }
}

chunk *gpi_jpeg_make_start() /* also makes the JFIF marker */
{
    chunk *temp;
    /* Start marker and
       JFIF APPO Marker:Version 1.01, density 1x2 (the 00 01 00 02) */
    temp = gpi_jpeg_chunk_new_filled(20,  "\xFF\xD8"
        "\xFF\xE0\x00\x10\x4A\x46\x49\x46" "\x00\x01\x01\x00\x00\x01\x00\x02" "\x00\x00");
    return temp;
}

chunk *gpi_jpeg_make_SOFC (int width, int height, char vh1, char vh2, char vh3, char q1, char q2, char q3)
{
    chunk *target;

    char sofc_data[]={
        0xFF, 0xC0, 0x00, 0x11,  0x08, 0x00, 0x80, 0x01,
        0x40, 0x03, 0x01, 0x11,  0x00, 0x02, 0x21, 0x01,  0x03, 0x11, 0x00
        };
    target = gpi_jpeg_chunk_new(sizeof(sofc_data));
    if (target==NULL) { printf("New SOFC failed allocation\n"); return target; }
    memcpy(target->data, sofc_data, sizeof(sofc_data));
    target->data[5] = (height&0xff00) >> 8;
    target->data[6] = height&0xff;
    target->data[7] = (width&0xff00) >> 8;
    target->data[8] = width&0xff;
    target->data[11]= vh1;
    target->data[12]= q1;
    target->data[14]= vh2;
    target->data[15]= q2;
    target->data[17]= vh3;
    target->data[18]= q3;
    return target;
}

chunk *gpi_jpeg_makeSsSeAhAl(int huffset1, int huffset2, int huffset3)
{
    chunk *target;
    printf("About to call gpi_jpeg_chunk_new_filled\n");
    target= gpi_jpeg_chunk_new_filled(14, "\xFF\xDA\x00\x0C\x03\x01\x00\x02"
        "\x00\x03\x00\x00\x3F\x00");
    if (!target) return NULL;
    target->data[6] = huffset1;
    target->data[8] = huffset2;
    target->data[10] = huffset3;
    return target;
}

void gpi_jpeg_print_quantization_table(jpeg_quantization_table *table)
{
    int x;
    nullpointerabortvoid(table, "Quantization table");
    for (x=0; x<64; x++)
    {
        if (x && ((x%8)==0))
        {
            printf("\n");
        }
        printf("%3i ", (*table)[x]);
    }
    printf("\n");
}


chunk *gpi_jpeg_make_quantization(const jpeg_quantization_table *table, char number)
{
    chunk *temp;
    char x,y,z,c;
    temp = gpi_jpeg_chunk_new(5+64);
    if (!temp) return NULL;
    memcpy(temp->data, "\xFF\xDB\x00\x43\x01", 5);
    temp->data[4]=number;
    for (c=z=0; z<8; z++)
        if (z%2)
            for (y=0,x=z; y<=z; x--,y++)
                {
                temp->data[5+c] = (*table)[x+y*8];
                temp->data[5+63-c++] = (*table)[63-x-y*8];
                }
        else
            for (x=0,y=z; x<=z; x++,y--)
                {
                temp->data[5+c] = (*table)[x+y*8];
                temp->data[5+63-c++] = (*table)[63-x-y*8];
                }
    return temp;
}

jpeg_quantization_table *gpi_jpeg_quantization2table(chunk *qmarker)
{
    char x,y,z,c;
    jpeg_quantization_table *table;
    table = (jpeg_quantization_table *)malloc(64);
    for (c=z=0; z<8; z++)
        if (z%2)
            for (y=0,x=z; y<=z; x--,y++)
                {
                (*table)[63-x-y*8] = qmarker->data[5+63-c];
                (*table)[x+y*8] = qmarker->data[5+c++];
                }
        else
            for (x=0,y=z; x<=z; x++,y--)
                {
                (*table)[63-x-y*8] = qmarker->data[5+63-c];
                (*table)[x+y*8] = qmarker->data[5+c++];
                }
    return table;
}


jpeg *gpi_jpeg_header(int width, int height,
    char vh1, char vh2, char vh3,
    char q1, char q2, char q3,
    const jpeg_quantization_table *quant1, const jpeg_quantization_table *quant2,
    char huffset1, char huffset2, char huffset3,
    chunk *huff1, chunk *huff2, chunk *huff3, chunk *huff4)
{
    jpeg *temp;
    temp = gpi_jpeg_new();
    gpi_jpeg_add_chunk(temp, gpi_jpeg_make_start());
    gpi_jpeg_add_chunk(temp, gpi_jpeg_make_quantization(quant1, 0));
    gpi_jpeg_add_chunk(temp, gpi_jpeg_make_quantization(quant2, 1));
    gpi_jpeg_add_chunk(temp, gpi_jpeg_make_SOFC(width,height, vh1,vh2,vh3, q1,q2,q3));
    gpi_jpeg_add_chunk(temp, huff1);
    gpi_jpeg_add_chunk(temp, huff2);
    gpi_jpeg_add_chunk(temp, huff3);
    gpi_jpeg_add_chunk(temp, huff4);
    printf("About to make and add the SsSeAhAl marker\n");
    gpi_jpeg_add_chunk(temp, gpi_jpeg_makeSsSeAhAl(huffset1, huffset2, huffset3));
    return temp;
}

/*#define TESTING_JPEG_C */

#ifndef TESTING_JPEG_C
char gpi_jpeg_write(CameraFile *file, const char *filename, jpeg *myjpeg)
{
    int x;
    CHECK_RESULT (gp_file_set_name (file, filename));
    CHECK_RESULT (gp_file_set_mime_type(file, GP_MIME_JPEG));
    for (x=0; x<myjpeg->count; x++)
        CHECK_RESULT (gp_file_append(file, (char*)myjpeg->marker[x]->data, myjpeg->marker[x]->size));
    return 1;
}
#endif

#ifdef TESTING_JPEG_C
/* TEST CODE SECTION */
char testdata[] ={
    0xFF,0xD8, 0xFF,0xE0, 0xff,0xDB, 0xFF,0xC4, 0xFF,0xDA,
    0xFF,0xC0, 0xff,0xff};

jpeg_quantization_table mytable = {
      2,  3,  4,  5,  6,  7,  8,  9,
      3,  4,  5,  6,  7,  8,  9, 10,
      4,  5,  6,  7,  8,  9, 10, 11,
      5,  6,  7,  8,  9, 10, 11, 12,
      6,  7,  8,  9, 10, 11, 12, 13,
      7,  8,  9, 10, 11, 12, 13, 14,
      8,  9, 10, 11, 12, 13, 14, 15,
      9, 10, 11, 12, 13, 14, 15, 16};

int main()
{
chunk *picture;
jpeg *myjpeg;
char id;
int location=0,x;
printf("Print the test quantization table\n");
gpi_jpeg_print_quantization_table(&mytable);
myjpeg = gpi_jpeg_new();
picture = gpi_jpeg_chunk_new(sizeof(testdata));
picture->data=testdata;
picture->size=sizeof(testdata);
printf("testdata size is %i\n",picture->size);
gpi_jpeg_chunk_print(picture);
printf("Call jpeg_parse!!!!!!!!!!!!!!!!!!!!!!!\n");
gpi_jpeg_parse(myjpeg,picture);

printf("\nPrint the jpeg table\n");
gpi_jpeg_print(myjpeg);
printf("\nCall gpi_jpeg_destroy\n");
gpi_jpeg_destroy(myjpeg);
/* You can't call gpi_jpeg_chunk_destroy because testdata is a constant.
 * Since picture->data points to it, it would segfault.
 */
free(picture);
printf("gpi_jpeg_chunk_new and gpi_jpeg_chunk_destroy tests\n");
picture = gpi_jpeg_chunk_new(10);
for (x=0; x<10; x++) picture->data[x]=x;
for (x=0; x<10; x++) printf("%hX ",picture->data[x]);
gpi_jpeg_chunk_destroy(picture);
printf("\n");
}
#endif
