package draw_qr

import "core:mem"
import "core:slice"

import draw ".."
import "../../qrcode"

DFT_QR_DARK :: draw.BLACK // Default QR code dark module color.
DFT_QR_LIGHT :: draw.WHITE // Default QR code light module color.
DFT_QR_BOOST_ECL :: true // Default QR error correction level boost.
DFT_QR_QUIET_ZONE :: 4 // Default light-pixel border on each side; 4 is the QR spec value.

// Returns the number of bytes to_texture will write. Equals dim*dim*4 where
// dim = qrcode.get_size(qrcode_buf) + 2*quiet_zone.
texture_size :: #force_inline proc(qrcode_buf: []u8, quiet_zone: int = DFT_QR_QUIET_ZONE) -> int {
	size := qrcode.get_size(qrcode_buf)
	if size == 0 || quiet_zone < 0 do return 0
	padded_size := size + 2 * quiet_zone
	return padded_size * padded_size * 4
}

// Decodes an encoded QR buffer into tightly-packed RGBA pixel data written to
// texture_buf. No allocations, no GPU calls. Returns the Texture_Desc the
// caller should pass to draw.register_texture alongside texture_buf.
//
// quiet_zone adds that many `light` pixels on each side; the spec value is 4.
// Final dimension is qrcode.get_size + 2*quiet_zone on each axis.
//
// Returns ok=false when:
//   - qrcode_buf is invalid (qrcode.get_size returns 0).
//   - quiet_zone is negative.
//   - texture_buf is smaller than texture_size(qrcode_buf, quiet_zone).
@(require_results)
to_texture :: proc(
	qrcode_buf: []u8,
	texture_buf: []u8,
	dark: draw.Color = DFT_QR_DARK,
	light: draw.Color = DFT_QR_LIGHT,
	quiet_zone: int = DFT_QR_QUIET_ZONE,
) -> (
	desc: draw.Texture_Desc,
	ok: bool,
) {
	size := qrcode.get_size(qrcode_buf)
	if size == 0 || quiet_zone < 0 do return
	padded_size := size + 2 * quiet_zone
	if len(texture_buf) < padded_size * padded_size * 4 do return

	// Type-pun to []Color so each store is a single 32-bit write.
	pixels := mem.slice_data_cast([]draw.Color, texture_buf[:padded_size * padded_size * 4])

	// Bulk-fill with light: handles the border and every light QR module at once.
	slice.fill(pixels, light)

	// Overwrite only the dark modules, offset by the quiet-zone border.
	for y in 0 ..< size {
		row := (y + quiet_zone) * padded_size + quiet_zone
		for x in 0 ..< size {
			if qrcode.get_module(qrcode_buf, x, y) {
				pixels[row + x] = dark
			}
		}
	}

	return draw.Texture_Desc {
			width = u32(padded_size),
			height = u32(padded_size),
			depth_or_layers = 1,
			type = .D2,
			format = .R8G8B8A8_UNORM,
			usage = {.SAMPLER},
			mip_levels = 1,
			kind = .Static,
		},
		true
}

// Allocates pixel buffer via temp_allocator, decodes qrcode_buf into it, and
// registers with the GPU. The pixel allocation is freed before return.
//
// Returns ok=false when:
//   - qrcode_buf is invalid (qrcode.get_size returns 0).
//   - temp_allocator fails to allocate the pixel buffer.
//   - GPU texture registration fails.
@(require_results)
register_texture_from_raw :: proc(
	qrcode_buf: []u8,
	dark: draw.Color = DFT_QR_DARK,
	light: draw.Color = DFT_QR_LIGHT,
	quiet_zone: int = DFT_QR_QUIET_ZONE,
	temp_allocator := context.temp_allocator,
) -> (
	texture: draw.Texture_Id,
	ok: bool,
) {
	tex_size := texture_size(qrcode_buf, quiet_zone)
	if tex_size == 0 do return draw.INVALID_TEXTURE, false

	pixels, alloc_err := make([]u8, tex_size, temp_allocator)
	if alloc_err != nil do return draw.INVALID_TEXTURE, false
	defer delete(pixels, temp_allocator)

	desc := to_texture(qrcode_buf, pixels, dark, light, quiet_zone) or_return
	return draw.register_texture(desc, pixels)
}

// Encodes text as a QR Code and registers the result as an RGBA texture.
//
// Returns ok=false when:
//   - temp_allocator fails to allocate.
//   - The text cannot fit in any version within [min_version, max_version] at the given ECL.
//   - GPU texture registration fails.
@(require_results)
register_texture_from_text :: proc(
	text: string,
	ecl: qrcode.Ecc = .Low,
	min_version: int = qrcode.VERSION_MIN,
	max_version: int = qrcode.VERSION_MAX,
	mask: Maybe(qrcode.Mask) = nil,
	boost_ecl: bool = DFT_QR_BOOST_ECL,
	dark: draw.Color = DFT_QR_DARK,
	light: draw.Color = DFT_QR_LIGHT,
	quiet_zone: int = DFT_QR_QUIET_ZONE,
	temp_allocator := context.temp_allocator,
) -> (
	texture: draw.Texture_Id,
	ok: bool,
) {
	qrcode_buf, alloc_err := make([]u8, qrcode.buffer_len_for_version(max_version), temp_allocator)
	if alloc_err != nil do return draw.INVALID_TEXTURE, false
	defer delete(qrcode_buf, temp_allocator)

	qrcode.encode_auto(
		text,
		qrcode_buf,
		ecl,
		min_version,
		max_version,
		mask,
		boost_ecl,
		temp_allocator,
	) or_return

	return register_texture_from_raw(qrcode_buf, dark, light, quiet_zone, temp_allocator)
}

// Encodes arbitrary binary data as a QR Code and registers the result as an RGBA texture.
//
// Returns ok=false when:
//   - temp_allocator fails to allocate.
//   - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
//   - GPU texture registration fails.
@(require_results)
register_texture_from_binary :: proc(
	bin_data: []u8,
	ecl: qrcode.Ecc = .Low,
	min_version: int = qrcode.VERSION_MIN,
	max_version: int = qrcode.VERSION_MAX,
	mask: Maybe(qrcode.Mask) = nil,
	boost_ecl: bool = DFT_QR_BOOST_ECL,
	dark: draw.Color = DFT_QR_DARK,
	light: draw.Color = DFT_QR_LIGHT,
	quiet_zone: int = DFT_QR_QUIET_ZONE,
	temp_allocator := context.temp_allocator,
) -> (
	texture: draw.Texture_Id,
	ok: bool,
) {
	qrcode_buf, alloc_err := make([]u8, qrcode.buffer_len_for_version(max_version), temp_allocator)
	if alloc_err != nil do return draw.INVALID_TEXTURE, false
	defer delete(qrcode_buf, temp_allocator)

	qrcode.encode_auto(
		bin_data,
		qrcode_buf,
		ecl,
		min_version,
		max_version,
		mask,
		boost_ecl,
		temp_allocator,
	) or_return

	return register_texture_from_raw(qrcode_buf, dark, light, quiet_zone, temp_allocator)
}

register_texture_from :: proc {
	register_texture_from_text,
	register_texture_from_binary,
}
