/*
 * libptouch — PNG → 1bit packed raster (libpng)
 *
 * Author: knb
 * Email: knb@artif.org
 */

#include "libptouch_internal.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <png.h>

void libptouch_free_raster(uint8_t *raster)
{
	free(raster);
}

libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *path,
					     const libptouch_png_options_t *options,
					     uint8_t **out_raster, size_t *out_raster_bytes,
					     libptouch_raster_params_t *out_params)
{
	if (!ctx || !path || !out_raster || !out_raster_bytes || !out_params) {
		if (ctx)
			ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
		return LIBPTOUCH_ERR_ARG;
	}

	uint8_t thr = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
	if (options)
		thr = options->threshold;

	*out_raster = NULL;
	*out_raster_bytes = 0;
	out_params->width_dots = 0;
	out_params->height_dots = 0;
	out_params->margin_mm = 0;

	FILE *fp = fopen(path, "rb");
	if (!fp) {
		char buf[192];
		snprintf(buf, sizeof(buf), "open %s: %s", path, strerror(errno));
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IO, buf);
		return LIBPTOUCH_ERR_IO;
	}

	unsigned char sig[8];
	if (fread(sig, 1, 8, fp) != 8) {
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "short read (not a PNG?)");
		return LIBPTOUCH_ERR_IMAGE;
	}
	if (png_sig_cmp(sig, 0, 8) != 0) {
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "not a PNG file");
		return LIBPTOUCH_ERR_IMAGE;
	}

	png_structp png_ptr =
	    png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png_ptr) {
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "png_create_read_struct failed");
		return LIBPTOUCH_ERR_NOMEM;
	}
	png_infop info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) {
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "png_create_info_struct failed");
		return LIBPTOUCH_ERR_NOMEM;
	}

	if (setjmp(png_jmpbuf(png_ptr))) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "PNG decode error");
		return LIBPTOUCH_ERR_IMAGE;
	}

	png_init_io(png_ptr, fp);
	png_set_sig_bytes(png_ptr, 8);

	const int transforms = (int)(PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16 |
				     PNG_TRANSFORM_GRAY_TO_RGB);
	png_read_png(png_ptr, info_ptr, transforms, NULL);

	png_uint_32 width = png_get_image_width(png_ptr, info_ptr);
	png_uint_32 height = png_get_image_height(png_ptr, info_ptr);
	int channels = (int)png_get_channels(png_ptr, info_ptr);
	png_bytepp rows = png_get_rows(png_ptr, info_ptr);

	if (width == 0 || height == 0 || !rows) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "invalid PNG dimensions");
		return LIBPTOUCH_ERR_IMAGE;
	}
	if (width > 100000u || height > 100000u) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "PNG dimensions too large");
		return LIBPTOUCH_ERR_IMAGE;
	}
	if (channels != 3 && channels != 4) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "unsupported PNG channel count");
		return LIBPTOUCH_ERR_IMAGE;
	}

	size_t row_bytes = ((size_t)width + 7u) / 8u;
	if (height > SIZE_MAX / row_bytes) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "raster size overflow");
		return LIBPTOUCH_ERR_IMAGE;
	}
	size_t total = row_bytes * (size_t)height;
	uint8_t *out = (uint8_t *)calloc(1, total);
	if (!out) {
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed");
		return LIBPTOUCH_ERR_NOMEM;
	}

	for (png_uint_32 y = 0; y < height; y++) {
		const png_byte *row = rows[y];
		uint8_t *dst_row = out + (size_t)y * row_bytes;
		for (png_uint_32 x = 0; x < width; x++) {
			size_t o = (size_t)x * (size_t)channels;
			unsigned r = row[o + 0];
			unsigned g = row[o + 1];
			unsigned b = row[o + 2];
			unsigned a = channels == 4 ? row[o + 3] : 255u;
			if (a < 128u) {
				continue;
			}
			unsigned yv = (77u * r + 150u * g + 29u * b) >> 8;
			int black = (yv < (unsigned)thr) ? 1 : 0;
			if (!black)
				continue;
			size_t bit = (size_t)x % 8u;
			size_t byte = (size_t)x / 8u;
			dst_row[byte] |= (uint8_t)(1u << (7u - bit));
		}
	}

	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
	fclose(fp);

	out_params->width_dots = (uint32_t)width;
	out_params->height_dots = (uint32_t)height;
	out_params->margin_mm = 0;
	*out_raster = out;
	*out_raster_bytes = total;
	ptouch_set_error(ctx, LIBPTOUCH_OK, "");
	return LIBPTOUCH_OK;
}
