/*  ____        _              _                   _                       
   |  _ \  __ _| |_ __ _   ___| |_ _ __ _   _  ___| |_ _   _ _ __ ___  ___ 
   | | | |/ _` | __/ _` | / __| __| '__| | | |/ __| __| | | | '__/ _ \/ __|
   | |_| | (_| | || (_| | \__ \ |_| |  | |_| | (__| |_| |_| | | |  __/\__ \
   |____/ \__,_|\__\__,_| |___/\__|_|   \__,_|\___|\__|\__,_|_|  \___||___/ */
                                                                        


typedef struct {
    /* The thingumybob. */
    liq_attr * attr;
    /* The image we are currently processing. */
    liq_image * image;
    /* The quantized image. */
    liq_result * result;
    /* This is the hook for subclasses to hang their data from. */
    void * hook;
    /* */
    int n_mallocs;
    /* Image data stuff. */
    unsigned height;
    unsigned width;
    /* Log callback stuff. */
    SV * function;
    SV * data;
    /* Store a dithering level. */
    double dithering_level;
    /* Has it been quantized? */
    unsigned quantized : 1;
    /* Do we have a valid dithering level? */
    unsigned have_dithering_level : 1;
}
image_quantize_t;

typedef struct {
    /* The input PNG. */
    png_structp in_png;
    png_infop in_info;
    /* The output PNG. */
    png_structp out_png;
    png_infop out_info;

    /* Pointers to the rows of image data. */

    png_bytepp row_pointers;

    /* Image data itself. */

    png_bytep image_data;
}
iq_png_t;

/*  _____                         
   | ____|_ __ _ __ ___  _ __ ___ 
   |  _| | '__| '__/ _ \| '__/ __|
   | |___| |  | | | (_) | |  \__ \
   |_____|_|  |_|  \___/|_|  |___/ */
                               

static struct liq_error_c {
    int number;
    char * message;
}
liq_error_stuff[] = {
    {LIQ_VALUE_OUT_OF_RANGE, "value out of range"},
    {LIQ_OUT_OF_MEMORY, "out of memory"},
    {LIQ_NOT_READY, "not ready"},
    {LIQ_BITMAP_NOT_AVAILABLE, "bitmap not available"},
    {LIQ_BUFFER_TOO_SMALL, "buffer too small"},
    {LIQ_INVALID_POINTER, "invalid pointer"},
    {LIQ_HISTOGRAM_FAILED, "get_histogram failed"},
};

#define N_LIQ_ERRORS (sizeof (liq_error_stuff) / sizeof (struct liq_error_c))

static char * image_quantize_get_liq_error_string (int error)
{
    int i;
    for (i = 0; i < N_LIQ_ERRORS; i++) {
	if (error == liq_error_stuff[i].number) {
	    return liq_error_stuff[i].message;
	}
    }
    return "undocumented error code";
}

#define MAX_ERROR_MSG 0x1000

static void
image_quantize_error (image_quantize_t * iq, liq_error error,
		      char * msg, ...)
{
    char error_message[MAX_ERROR_MSG];
    char * liq_error_string;
    int used;

    liq_error_string = image_quantize_get_liq_error_string (error);

    used = 0;
    used += snprintf (error_message + used, MAX_ERROR_MSG - used,
		      "libimagequant error %d (%s) with input ",
		      error, liq_error_string);
    va_list args;
    va_start (args, msg);
    vsnprintf (error_message + used, MAX_ERROR_MSG - used,
	       msg, args);
    va_end (args);
    croak (error_message);
}

static void
image_quantize_perl_error (image_quantize_t * iq, char * msg, ...)
{
    va_list args;
    va_start (args, msg);
    vcroak (msg, & args);
    va_end (args);
}

/*  __  __                                 
   |  \/  | ___ _ __ ___   ___  _ __ _   _ 
   | |\/| |/ _ \ '_ ` _ \ / _ \| '__| | | |
   | |  | |  __/ | | | | | (_) | |  | |_| |
   |_|  |_|\___|_| |_| |_|\___/|_|   \__, |
                                     |___/ 
                                                                _   
    _ __ ___   __ _ _ __   __ _  __ _  ___ _ __ ___   ___ _ __ | |_ 
   | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
   | | | | | | (_| | | | | (_| | (_| |  __/ | | | | |  __/ | | | |_ 
   |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
                                |___/                                */


/* Track the number of mallocs in and out. */

#define IQGET iq->n_mallocs++

/* We could check for underflows here. */

#define IQFREE iq->n_mallocs--

/* Free the image, but not the attr. */

static void
image_quantize_free_image (image_quantize_t * iq)
{
    if (iq->image) {
	liq_image_destroy (iq->image);
	IQFREE;
	iq->image = 0;
    }
    if (iq->result) {
	liq_result_destroy (iq->result);
	IQFREE;
	iq->result = 0;
    }
}

/* Free everything and check we have the right number of things left. */

static void
image_quantize_free (image_quantize_t * iq)
{
    image_quantize_free_image (iq);
    if (iq->attr) {
	liq_attr_destroy (iq->attr);
	IQFREE;
	iq->attr = 0;
    }
    if (iq->n_mallocs != 0) {
	warn ("Image::Quantize: There are %d pieces of unfreed memory",
	      iq->n_mallocs);
    }
    Safefree (iq);
}

/*  ___                                 ___                    _   _         
   |_ _|_ __ ___   __ _  __ _  ___ _ _ / _ \ _   _  __ _ _ __ | |_(_)_______ 
    | || '_ ` _ \ / _` |/ _` |/ _ (_|_) | | | | | |/ _` | '_ \| __| |_  / _ \
    | || | | | | | (_| | (_| |  __/_ _| |_| | |_| | (_| | | | | |_| |/ /  __/
   |___|_| |_| |_|\__,_|\__, |\___(_|_)\__\_\\__,_|\__,_|_| |_|\__|_/___\___|
                        |___/                                                 */

image_quantize_t * image_quantize_new ()
{
    image_quantize_t * iq;
    /* This doesn't count as IQGET, we'll just have to be careful that
       it's freed since the counting is meaningless for the thing
       itself (number of gets is part of allocated structure, so
       ... */
    Newxz (iq, 1, image_quantize_t);
    iq->attr = liq_attr_create ();
    IQGET;
    return iq;
}

/*  ____  _   _  ____ 
   |  _ \| \ | |/ ___|
   | |_) |  \| | |  _ 
   |  __/| |\  | |_| |
   |_|   |_| \_|\____| */
                   


static void
image_quantize_png_data_to_rgba (image_quantize_t * iq)
{
    int colour_type;
    int bit_depth;
    iq_png_t * p;
    png_bytepp row_pointers;

    p = iq->hook;

    png_get_IHDR (p->in_png, p->in_info, & iq->width, & iq->height,
		  & bit_depth, & colour_type, 0, 0, 0);
    if (bit_depth != 8) {
	image_quantize_perl_error (iq, "bit depth %d cannot be handled yet",
				   bit_depth);
    }
    if (colour_type != PNG_COLOR_TYPE_RGB_ALPHA) {
	image_quantize_perl_error (iq, "colour type %d cannot be handled yet",
				   colour_type);
    }
    row_pointers = png_get_rows (p->in_png, p->in_info);
    iq->image =
	liq_image_create_rgba_rows (iq->attr, (void **) row_pointers,
				    iq->width, iq->height, 0);
    IQGET;
}

/* We have to store the dithering level and maybe other things within
   "iq" and then only do the dithering once we have a result. */

static void
image_quantize_post_quantize (image_quantize_t * iq)
{
    int error;
    if (iq->have_dithering_level) {
	error = liq_set_dithering_level (iq->result, iq->dithering_level);
	if (error != LIQ_OK) {
	    image_quantize_error (iq, error, "dithering_level (%g)",
				  iq->dithering_level);
	}
    }
}

static void
image_quantize_png_quantize (image_quantize_t * iq)
{
    int r;
    iq_png_t * p;
    liq_error error;

    /* Size of "image_data". */

    unsigned buffer_size;

    const liq_palette * lp;

    /* Palette converted to PNG. Its size is 256 since this is the
       maximum possible, which saves a call to
       "malloc"/"free". "libpng" copies the palette into its own
       allocated memory. */

    png_color png_colours[256];

    /* Quantize the buffer. */

    error = liq_quantize_image_2 (iq->attr, iq->image, & iq->result);
    if (error != LIQ_OK) {
	image_quantize_error (iq, error, "liq_quantize_image failed");
    }

    if (! iq->result) {
	image_quantize_perl_error (iq, "liq_quantize_image failed");
    }
    IQGET;

    image_quantize_post_quantize (iq);

    /* Get memory for the image. */

    p = iq->hook;

    buffer_size = iq->width * iq->height;

    /* Get memory for image data and row pointers. These will be
       disowned in the XS and then freed by Image::PNG::Libpng. */

    Newx (p->image_data, buffer_size, png_byte);
    Newx (p->row_pointers, iq->height, png_bytep);

    /* Set up the PNG output. */

    p = iq->hook;
    png_set_IHDR (p->out_png, p->out_info, iq->width, iq->height,
		  8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
		  PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
    error = liq_write_remapped_image (iq->result, iq->image, 
				      p->image_data, buffer_size);
    if (error != LIQ_OK) {
	image_quantize_error (iq, error, "write_remapped_image failed");
    }
    for (r = 0; r < iq->height; r++) {
	p->row_pointers[r] = p->image_data + r * iq->width;
    }
    lp = liq_get_palette (iq->result);
    for (r = 0; r < lp->count; r++) {
	png_colours[r].red = lp->entries[r].r;
	png_colours[r].green = lp->entries[r].g;
	png_colours[r].blue = lp->entries[r].b;
    }
    png_set_PLTE (p->out_png, p->out_info, png_colours, lp->count);
}

static void
image_quantize_log_callback (const liq_attr * attr, const char * message,
			     void * user_info)
{
    dSP;
    SV * function;
    SV * data;
    SV * message_sv;
    image_quantize_t * iq = user_info;

    function = iq->function;
    data = iq->data;

    ENTER;
    SAVETMPS;

    message_sv = sv_2mortal (newSVpv (message, 0));

    PUSHMARK (SP);
    XPUSHs (message_sv);
    XPUSHs (data);
    PUTBACK;

    call_sv (function, G_DISCARD);

    FREETMPS;
    LEAVE;
}

