#include "libpdftex.h"
#include "libpng/png.h"

#define PNG_PTR(N)  ((N + image_tab)->png_ptr)
#define INFO_PTR(N) ((N + image_tab)->info_ptr)
#define IMG_ENT(N)  (N + image_tab)

typedef struct {
    int ref_count;          
    png_structp png_ptr;
    png_infop info_ptr;
} image_entry;

image_entry *image_tab = 0;
integer image_max = 256;

static FILE *image_file;

#define PNG_OPEN()          texpsheaderbopenin(image_file)

integer new_image_entry()
{
    image_entry *p;
    integer n;
    if (image_tab == 0) {
        image_tab = XTALLOC(image_max, image_entry);
        for (p = image_tab; p - image_tab < image_max; p++)
            p->ref_count = 0;
        return 0;
    }
    for (p = image_tab; p - image_tab < image_max; p++)
        if (p->ref_count == 0)
            break;
    if (p - image_tab == image_max) {
        n = image_max;
        image_max = image_max + 256;
        image_tab = XRETALLOC(image_tab, image_max, image_entry);
        for (p = image_tab + n; p - image_tab < image_max; p++)
            p->ref_count = 0;
        return n;
    }
    return p - image_tab;
}

void img_free() 
{
    image_entry *p;
    if (image_tab == 0)
        return;
    for (p = image_tab; p - image_tab < image_max; p++) {
        if (p->ref_count > 0) {
            WARN("pending image (%i references)" AND p->ref_count);
            while (p->ref_count > 0)
                deleteimageref(p - image_tab);
        }
    }
    free(image_tab);
}

integer readimageinfo()
{
    integer img = new_image_entry();
    IMG_ENT(img)->ref_count = 1;
    filename = makecstring(makenamestring());
    flushstring();
    if (!PNG_OPEN())
        FAIL("cannot open image file");
    if ((PNG_PTR(img) = png_create_read_struct(PNG_LIBPNG_VER_STRING, 
        NULL, NULL, NULL)) == NULL)
        FAIL("png_create_read_struct() failed");
    if ((INFO_PTR(img) = png_create_info_struct(PNG_PTR(img))) == NULL)
        FAIL("png_create_info_struct() failed");
    if (setjmp(PNG_PTR(img)->jmpbuf))
        FAIL("setjmp() failed");
    png_init_io(PNG_PTR(img), image_file);
    png_read_info(PNG_PTR(img), INFO_PTR(img));
    if (INFO_PTR(img)->color_type & PNG_COLOR_MASK_ALPHA)
        FAIL("alpha channel not supported");
    if (INFO_PTR(img)->interlace_type != 0)
        FAIL("interlace type not supported");
    if (INFO_PTR(img)->bit_depth == 16) {
        png_set_strip_16(PNG_PTR(img));
        WARN("can't handle 16 bits per channel, strip down to 8 bits");
    }
    png_read_update_info(PNG_PTR(img), INFO_PTR(img));
    return img;
}

void writeimage(integer img)
{
    int i, j;
    integer palette_objnum = 0, length_objnum = 0;
    png_bytep row = XTALLOC(INFO_PTR(img)->rowbytes, png_byte);
    PDF_PRINTF("/Type /XObject\n/Subtype /Image\n/Width %u\n/Height %u\n/BitsPerComponent %u\n" AND
        INFO_PTR(img)->width AND INFO_PTR(img)->height AND INFO_PTR(img)->bit_depth);
    PDF_PRINTF("/ColorSpace ");
    switch (INFO_PTR(img)->color_type) {
    case PNG_COLOR_TYPE_PALETTE:
        pdfcreateobj(0, 0);
        palette_objnum = objptr;
        PDF_PRINTF("[/Indexed /DeviceRGB %u %u 0 R]\n" AND
            INFO_PTR(img)->num_palette - 1 AND palette_objnum);
        break;
    case PNG_COLOR_TYPE_GRAY:
        PDF_PRINTF("/DeviceGray\n");
        break;
    default:
        PDF_PRINTF("/DeviceRGB\n");
    }
    if (getpdfcompresslevel() > 0) {
        pdfcreateobj(0, 0);
        length_objnum = objptr;
        PDF_PRINTF("/Length %u 0 R\n" AND length_objnum);
        PDF_PRINTF("/Filter /FlateDecode\n");
    }
    else
        PDF_PRINTF("/Length %u\n" AND INFO_PTR(img)->rowbytes*INFO_PTR(img)->height);
    PDF_PRINTF(">>\nstream\n");
    if (getpdfcompresslevel() > 0)
        startcompress();
    for (i = 0; i < INFO_PTR(img)->height; i++) {
    	png_read_row(PNG_PTR(img), row, NULL);
        for (j = 0; j < INFO_PTR(img)->rowbytes; j++) {
            pdfbuf[pdfptr++] = row[j];
            if (pdfptr == pdfbufsize)
                pdfflush();
        }
    }
    if (getpdfcompresslevel() > 0)
        finishcompress();
    PDF_PRINTF("endstream\nendobj\n");
    if (length_objnum > 0) {
        pdfbeginobj(length_objnum);
        PDF_PRINTF("%u\n" AND pdfstreamlength);
        PDF_PRINTF("endobj\n");
    }                
    if (palette_objnum > 0) {
        pdfbegindict(palette_objnum);
        PDF_PRINTF("/Length %u\n" AND INFO_PTR(img)->num_palette*3);
        PDF_PRINTF(">>\nstream\n");
        for (i = 0; i < INFO_PTR(img)->num_palette; i++) {
            if (pdfptr + 3 >= pdfbufsize)
                pdfflush();
            pdfbuf[pdfptr++] = INFO_PTR(img)->palette[i].red;
            pdfbuf[pdfptr++] = INFO_PTR(img)->palette[i].green;
            pdfbuf[pdfptr++] = INFO_PTR(img)->palette[i].blue;
        }
        PDF_PRINTF("endstream\nendobj\n");
    }
    if (INFO_PTR(img)->color_type == PNG_COLOR_TYPE_GRAY)
        pdfimageb = true;
    else
        pdfimagec = true;
    free(row);
    pdfflush();
}

void addimageref(integer img)
{
    IMG_ENT(img)->ref_count++;
}

void deleteimageref(integer img)
{
    if (IMG_ENT(img)->ref_count <= 0)
        FAIL("invalid value of reference counter (%i)" AND 
             IMG_ENT(img)->ref_count);
    if (IMG_ENT(img)->ref_count == 1) {
        fclose(PNG_PTR(img)->io_ptr);
        png_destroy_read_struct(&(PNG_PTR(img)), &(INFO_PTR(img)), NULL);
    }
    IMG_ENT(img)->ref_count--;
}

integer imagewidth(integer img)
{
    return INFO_PTR(img)->width;
}

integer imageheight(integer img)
{
    return INFO_PTR(img)->height;
}

integer imagexres(integer img)
{
    if (INFO_PTR(img)->valid & PNG_INFO_pHYs)
        return INFO_PTR(img)->x_pixels_per_unit;
    return 0;
}

integer imageyres(integer img)
{
    if (INFO_PTR(img)->valid & PNG_INFO_pHYs)
        return INFO_PTR(img)->y_pixels_per_unit;
    return 0;
}
