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


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;

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


/* 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 except "iq" itself. */

static void
image_quantize_free_allocated (image_quantize_t * iq)
{
    image_quantize_free_image (iq);
    if (iq->attr) {
	liq_attr_destroy (iq->attr);
	IQFREE;
	iq->attr = 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_allocated (iq);

    if (iq->n_mallocs != 0) {
	warn ("Image::Quantize: There are %d pieces of unfreed memory",
	      iq->n_mallocs);
    }
    Safefree (iq);
}

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

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"},
    {LIQ_FREED_POINTER, "bad access to freed pointer"},
    {LIQ_NULL_POINTER, "null pointer"},
    {LIQ_BAD_POINTER, "pointer with wrong type"},
    {LIQ_NEGATIVE_DIMENSIONS, "image dimensions are zero or negative"},
    {LIQ_HUGE_DIMENSIONS, "image dimensions are unfeasible"},
    {LIQ_BAD_USER_POINTER, "liq hates your pointer"},
};

#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;

    image_quantize_free_image (iq);

    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;

    image_quantize_free_image (iq);

    va_start (args, msg);
    vcroak (msg, & args);
    va_end (args);
}

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

    iq = user_info;

    ENTER;
    SAVETMPS;

    /* Convert the C string "message" into a Perl SV. */

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

    /* Push our stuff onto the Perl stack. */

    PUSHMARK (SP);
    XPUSHs (message_sv);
    XPUSHs (iq->data);
    PUTBACK;

    /* Call the callback in "iq->function", discarding its return
       values. */

    call_sv (iq->function, G_DISCARD);

    FREETMPS;
    LEAVE;
}

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

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;
    liq_error error;
    png_uint_32 width;
    png_uint_32 height;
    png_bytep image_data;

    p = iq->hook;

    png_get_IHDR (p->in_png, p->in_info, & width, & 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 (width <= 0) {
	image_quantize_perl_error (iq, "image has negative or zero width %d",
				   width);
    }
    iq->width = width;

    if (height <= 0) {
	image_quantize_perl_error (iq, "image has negative or zero height %d",
				   height);
    }
    iq->height = height;

    /* Check width and height before this, we are going to get into
       the data from now. */

    switch (colour_type) {

    case PNG_COLOR_TYPE_RGB_ALPHA:
	break;

    default:
	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);
    error = liq_image_create_rgba_rows_2 (iq->attr, (void **) row_pointers,
					  iq->width, iq->height, 0,
					  & iq->image);
    if (error) {
	image_quantize_error (iq, error, "error creating image");
    }
    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];

    /* Check the inputs. */

    error = liq_invalid_attr (iq->attr);
    if (error != LIQ_OK) {
	image_quantize_error (iq, error, "liq_invalid_attr");
    }
    error = liq_invalid_image (iq->image);
    if (error != LIQ_OK) {
	image_quantize_error (iq, error, "liq_invalid_image");
    }

    /* 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_2 failed");
    }

    if (! iq->result) {
	image_quantize_perl_error (iq, "liq_quantize_image_2 failed but returned no error code");
    }
    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);
}

