// tzimage: compact image kernels for TezzNative.
// Focus: simple RGB/RGBA image operations with low-overhead memory handling.

import "io"

struct Image:
  data:*char
  width:int
  height:int
  channels:int
  // owns:
  // 0 = wrapped external buffer (do not free data)
  // 1 = malloc-owned buffer (free via free)
  // 2 = decoder-owned buffer (free via tn_tzimage_free)
  owns:int

extern fn tn_tzimage_decode_file(path:str, req_channels:int, out_w:*int, out_h:*int, out_channels:*int) -> *char
extern fn tn_tzimage_free(p:*char) -> int

fn _min_i(a:int, b:int) -> int:
  if a < b:
    ret a
  ret b

fn _clamp_u8(v:int) -> int:
  if v < 0:
    ret 0
  if v > 255:
    ret 255
  ret v

fn _itoa(v:int) -> str:
  if v == 0:
    ret "0"
  n:int = v
  neg:int = 0
  if n < 0:
    neg = 1
    n = 0 - n
  tmp:*char = malloc(32)
  if tmp == 0:
    ret 0 as str
  i:int = 0
  while n > 0:
    d:int = n % 10
    unsafe:
      tmp[i] = (48 + d) as char
    i = i + 1
    n = n / 10
  if neg != 0:
    unsafe:
      tmp[i] = 45 as char
    i = i + 1
  out:*char = malloc(i + 1)
  if out == 0:
    free(tmp)
    ret 0 as str
  j:int = 0
  while j < i:
    unsafe:
      out[j] = tmp[i - 1 - j]
    j = j + 1
  unsafe:
    out[i] = 0 as char
  free(tmp)
  ret out as str

fn _join2(a:str, b:str) -> str:
  if a == 0:
    ret b
  if b == 0:
    ret a
  na:int = len(a)
  nb:int = len(b)
  out:*char = (*char)malloc(na + nb + 1)
  if out == 0:
    ret 0 as str
  unsafe:
    if na > 0:
      memcpy(out, a, na)
    if nb > 0:
      memcpy(out + na, b, nb)
    out[na + nb] = 0 as char
  ret out as str

fn _join3(a:str, b:str, c:str) -> str:
  ab:str = _join2(a, b)
  if ab == 0:
    ret 0 as str
  abc:str = _join2(ab, c)
  free(ab as *char)
  ret abc

fn image_new(width:int, height:int, channels:int) -> *Image:
  if width <= 0 || height <= 0:
    ret 0 as *Image
  if channels <= 0:
    ret 0 as *Image
  if channels > 4:
    ret 0 as *Image
  img:*Image = (*Image)malloc(sizeof(Image))
  if img == 0:
    ret 0 as *Image
  bytes:int = width * height * channels
  buf:*char = malloc(bytes)
  if buf == 0:
    free(img as *char)
    ret 0 as *Image
  unsafe:
    i:int = 0
    while i < bytes:
      buf[i] = 0 as char
      i = i + 1
    (*img).data = buf
    (*img).width = width
    (*img).height = height
    (*img).channels = channels
    (*img).owns = 1
  ret img

fn image_new_rgb(width:int, height:int) -> *Image:
  ret image_new(width, height, 3)

fn image_new_rgba(width:int, height:int) -> *Image:
  ret image_new(width, height, 4)

fn image_wrap(data:*char, width:int, height:int, channels:int) -> *Image:
  if data == 0 || width <= 0 || height <= 0 || channels <= 0:
    ret 0 as *Image
  img:*Image = (*Image)malloc(sizeof(Image))
  if img == 0:
    ret 0 as *Image
  unsafe:
    (*img).data = data
    (*img).width = width
    (*img).height = height
    (*img).channels = channels
    (*img).owns = 0
  ret img

fn image_free(img:*Image) -> int:
  if img == 0:
    ret 0
  unsafe:
    if (*img).data != 0:
      if (*img).owns == 1:
        free((*img).data)
      else if (*img).owns == 2:
        tn_tzimage_free((*img).data)
  free(img as *char)
  ret 0

fn image_stride(img:*Image) -> int:
  if img == 0:
    ret 0
  unsafe:
    ret (*img).width * (*img).channels

fn image_size_bytes(img:*Image) -> int:
  if img == 0:
    ret 0
  unsafe:
    ret (*img).width * (*img).height * (*img).channels

fn _pixel_off(img:*Image, x:int, y:int, c:int) -> int:
  if img == 0:
    ret 0 - 1
  unsafe:
    if x < 0 || y < 0:
      ret 0 - 1
    if x >= (*img).width || y >= (*img).height:
      ret 0 - 1
    if c < 0 || c >= (*img).channels:
      ret 0 - 1
    ret ((y * (*img).width) + x) * (*img).channels + c

fn image_get_u8(img:*Image, x:int, y:int, c:int) -> int:
  if img == 0:
    ret 0
  off:int = _pixel_off(img, x, y, c)
  if off < 0:
    ret 0
  unsafe:
    ret ((*img).data[off] as int) & 255

fn image_set_u8(img:*Image, x:int, y:int, c:int, value:int) -> int:
  if img == 0:
    ret 0 - 1
  off:int = _pixel_off(img, x, y, c)
  if off < 0:
    ret 0 - 1
  unsafe:
    (*img).data[off] = _clamp_u8(value) as char
  ret 0

fn image_clear(img:*Image, r:int, g:int, b:int, a:int) -> int:
  if img == 0:
    ret 0 - 1
  unsafe:
    if (*img).data == 0:
      ret 0 - 1
    rr:int = _clamp_u8(r)
    gg:int = _clamp_u8(g)
    bb:int = _clamp_u8(b)
    aa:int = _clamp_u8(a)
    y:int = 0
    while y < (*img).height:
      x:int = 0
      while x < (*img).width:
        base:int = ((y * (*img).width) + x) * (*img).channels
        if (*img).channels >= 1:
          (*img).data[base] = rr as char
        if (*img).channels >= 2:
          (*img).data[base + 1] = gg as char
        if (*img).channels >= 3:
          (*img).data[base + 2] = bb as char
        if (*img).channels >= 4:
          (*img).data[base + 3] = aa as char
        x = x + 1
      y = y + 1
  ret 0

fn image_fill_channel(img:*Image, channel:int, value:int) -> int:
  if img == 0:
    ret 0 - 1
  unsafe:
    if channel < 0 || channel >= (*img).channels:
      ret 0 - 1
    if (*img).data == 0:
      ret 0 - 1
    n:int = (*img).width * (*img).height
    i:int = 0
    vv:int = _clamp_u8(value)
    while i < n:
      (*img).data[(i * (*img).channels) + channel] = vv as char
      i = i + 1
  ret 0

fn image_copy(img:*Image) -> *Image:
  if img == 0:
    ret 0 as *Image
  unsafe:
    out:*Image = image_new((*img).width, (*img).height, (*img).channels)
    if out == 0:
      ret 0 as *Image
    bytes:int = image_size_bytes(img)
    memcpy((*out).data, (*img).data, bytes)
    ret out

fn image_blit(dst:*Image, src:*Image, dx:int, dy:int) -> int:
  if dst == 0 || src == 0:
    ret 0 - 1
  unsafe:
    if (*dst).data == 0 || (*src).data == 0:
      ret 0 - 1
    ch:int = _min_i((*dst).channels, (*src).channels)
    sy:int = 0
    while sy < (*src).height:
      ty:int = dy + sy
      if ty >= 0 && ty < (*dst).height:
        sx:int = 0
        while sx < (*src).width:
          tx:int = dx + sx
          if tx >= 0 && tx < (*dst).width:
            sc:int = 0
            sbase:int = ((sy * (*src).width) + sx) * (*src).channels
            dbase:int = ((ty * (*dst).width) + tx) * (*dst).channels
            while sc < ch:
              (*dst).data[dbase + sc] = (*src).data[sbase + sc]
              sc = sc + 1
          sx = sx + 1
      sy = sy + 1
  ret 0

fn image_decode(path:str, req_channels:int) -> *Image:
  if path == 0:
    ret 0 as *Image
  if req_channels < 0 || req_channels > 4:
    req_channels = 0
  w:[int;1]
  h:[int;1]
  ch:[int;1]
  unsafe:
    w[0] = 0
    h[0] = 0
    ch[0] = 0
  buf:*char = tn_tzimage_decode_file(path, req_channels, &w[0], &h[0], &ch[0])
  if buf == 0:
    ret 0 as *Image
  out_ch:int = ch[0]
  if req_channels > 0:
    out_ch = req_channels
  if w[0] <= 0 || h[0] <= 0 || out_ch <= 0:
    tn_tzimage_free(buf)
    ret 0 as *Image
  img:*Image = image_wrap(buf, w[0], h[0], out_ch)
  if img == 0:
    tn_tzimage_free(buf)
    ret 0 as *Image
  unsafe:
    (*img).owns = 2
  ret img

fn image_decode_png(path:str) -> *Image:
  ret image_decode(path, 4)

fn image_decode_jpeg(path:str) -> *Image:
  ret image_decode(path, 3)

fn image_save_ppm(img:*Image, path:str) -> int:
  if img == 0 || path == 0:
    ret 0 - 1
  unsafe:
    if (*img).data == 0:
      ret 0 - 1
    if (*img).channels < 3:
      ret 0 - 1
    f:*File = io.open_wb(path)
    if f == 0:
      ret 0 - 1
    ws:str = _itoa((*img).width)
    hs:str = _itoa((*img).height)
    if ws == 0 || hs == 0:
      io.file_close(f)
      if ws != 0:
        free(ws as *char)
      if hs != 0:
        free(hs as *char)
      ret 0 - 1
    h0:str = _join3("P6\n", ws, " ")
    h1:str = _join3(h0, hs, "\n255\n")
    io.file_write(f, h1 as *char, len(h1))
    free(h0 as *char)
    free(h1 as *char)
    free(ws as *char)
    free(hs as *char)
    y:int = 0
    pix:*char = malloc(3)
    if pix == 0:
      io.file_close(f)
      ret 0 - 1
    while y < (*img).height:
      x:int = 0
      while x < (*img).width:
        base:int = ((y * (*img).width) + x) * (*img).channels
        pix[0] = (*img).data[base]
        pix[1] = (*img).data[base + 1]
        pix[2] = (*img).data[base + 2]
        io.file_write(f, pix, 3)
        x = x + 1
      y = y + 1
    free(pix)
    io.file_close(f)
  ret 0
