/*
 * libptouch — SVG -> 1bit packed raster (librsvg + cairo)
 *
 * Author: knb
 * Email: knb@artif.org
 */

#include "libptouch_internal.h"
#include "libptouch_layout.h"

#ifdef LIBPTOUCH_HAS_RSVG
#include <cairo.h>
#include <librsvg/rsvg.h>
#endif

#include <math.h>
#include <stdint.h>
#include <stdlib.h>

libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
	libptouch_ctx *ctx, const char *path,
	const libptouch_svg_options_t *options, uint8_t **out_raster,
	size_t *out_raster_bytes, libptouch_raster_params_t *out_params)
{
#ifndef LIBPTOUCH_HAS_RSVG
	(void)path;
	(void)options;
	(void)out_raster;
	(void)out_raster_bytes;
	(void)out_params;
	if (ctx) {
		ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
				 "SVG support is not built in (missing librsvg-2.0)");
	}
	return LIBPTOUCH_ERR_UNSUPPORTED;
#else
	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;
	}
	if (!ctx->usb_open || !ctx->bulk_out_ep) {
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IO,
				 "not connected (open USB before SVG conversion)");
		return LIBPTOUCH_ERR_IO;
	}

	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;

	uint8_t st[LIBPTOUCH_STATUS_LENGTH];
	libptouch_err_t v = libptouch_get_status(ctx, st);
	if (v != LIBPTOUCH_OK)
		return v;

	const ptouch_printer_profile_t *prof =
		ptouch_layout_resolve_profile(ctx->usb_pid, st[4]);
	if (!prof) {
		ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
				 "no layout profile for this printer");
		return LIBPTOUCH_ERR_UNSUPPORTED;
	}

	uint16_t left_dots = 0, print_dots = 0, right_dots = 0;
	v = ptouch_layout_from_status(prof, st[11], st[10], &left_dots,
				      &print_dots, &right_dots);
	if (v != LIBPTOUCH_OK) {
		ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
				 "tape width/layout not supported for this media");
		return LIBPTOUCH_ERR_UNSUPPORTED;
	}
	(void)left_dots;
	(void)right_dots;

	GError *gerr = NULL;
	RsvgHandle *handle = rsvg_handle_new_from_file(path, &gerr);
	if (!handle) {
		const char *msg = (gerr && gerr->message) ? gerr->message :
							    "failed to load SVG";
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
		if (gerr)
			g_error_free(gerr);
		return LIBPTOUCH_ERR_IMAGE;
	}

	double svg_w = 0.0;
	double svg_h = 0.0;
	if (!rsvg_handle_get_intrinsic_size_in_pixels(handle, &svg_w, &svg_h) ||
	    svg_w <= 0.0 || svg_h <= 0.0) {
		g_object_unref(handle);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE,
				 "invalid SVG dimensions");
		return LIBPTOUCH_ERR_IMAGE;
	}

	/*
	 * libptouch_print_raster() transposes the incoming raster before packing
	 * lines, so printable tape width maps to the source raster height.
	 */
	double scale = (double)print_dots / svg_h;
	if (scale <= 0.0 || !isfinite(scale)) {
		g_object_unref(handle);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "invalid SVG scale");
		return LIBPTOUCH_ERR_IMAGE;
	}

	uint32_t out_h = (uint32_t)print_dots;
	uint32_t out_w = (uint32_t)ceil(svg_w * scale);
	if (out_h == 0)
		out_h = 1;
	if (out_h > 100000u) {
		g_object_unref(handle);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "scaled SVG too tall");
		return LIBPTOUCH_ERR_IMAGE;
	}

	cairo_surface_t *surface = cairo_image_surface_create(
		CAIRO_FORMAT_ARGB32, (int)out_w, (int)out_h);
	if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
		cairo_surface_destroy(surface);
		g_object_unref(handle);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM,
				 "cairo_image_surface_create failed");
		return LIBPTOUCH_ERR_NOMEM;
	}

	cairo_t *cr = cairo_create(surface);
	if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
		cairo_destroy(cr);
		cairo_surface_destroy(surface);
		g_object_unref(handle);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "cairo_create failed");
		return LIBPTOUCH_ERR_NOMEM;
	}

	cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
	cairo_paint(cr);
	RsvgRectangle viewport = { 0.0, 0.0, (double)out_w, (double)out_h };
	GError *render_err = NULL;
	if (!rsvg_handle_render_document(handle, cr, &viewport, &render_err)) {
		cairo_destroy(cr);
		cairo_surface_destroy(surface);
		g_object_unref(handle);
		const char *msg = (render_err && render_err->message) ?
					    render_err->message :
					    "SVG render failed";
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
		if (render_err)
			g_error_free(render_err);
		return LIBPTOUCH_ERR_IMAGE;
	}
	cairo_destroy(cr);
	g_object_unref(handle);

	cairo_surface_flush(surface);
	int stride = cairo_image_surface_get_stride(surface);
	const uint8_t *pix = cairo_image_surface_get_data(surface);

	size_t row_bytes = ((size_t)out_w + 7u) / 8u;
	if (out_h > SIZE_MAX / row_bytes) {
		cairo_surface_destroy(surface);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "raster size overflow");
		return LIBPTOUCH_ERR_IMAGE;
	}
	size_t total = row_bytes * (size_t)out_h;
	uint8_t *out = (uint8_t *)calloc(1, total);
	if (!out) {
		cairo_surface_destroy(surface);
		ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed");
		return LIBPTOUCH_ERR_NOMEM;
	}

	for (uint32_t y = 0; y < out_h; y++) {
		const uint32_t *src_row =
			(const uint32_t *)(pix + (size_t)y * (size_t)stride);
		uint8_t *dst_row = out + (size_t)y * row_bytes;
		for (uint32_t x = 0; x < out_w; x++) {
			uint32_t p = src_row[x];
			unsigned a = (p >> 24) & 0xFFu;
			if (a < 128u)
				continue;
			unsigned r = (p >> 16) & 0xFFu;
			unsigned g = (p >> 8) & 0xFFu;
			unsigned b = p & 0xFFu;
			unsigned yv = (77u * r + 150u * g + 29u * b) >> 8;
			if (yv >= (unsigned)thr)
				continue;
			size_t byte = (size_t)x / 8u;
			size_t bit = (size_t)x % 8u;
			dst_row[byte] |= (uint8_t)(1u << (7u - bit));
		}
	}

	cairo_surface_destroy(surface);

	out_params->width_dots = out_w;
	out_params->height_dots = out_h;
	out_params->margin_mm = 0;
	*out_raster = out;
	*out_raster_bytes = total;
	ptouch_set_error(ctx, LIBPTOUCH_OK, "");
	return LIBPTOUCH_OK;
#endif
}
