#!/usr/bin/env python3
"""
Utility to build an .ico file from a list of PNGs of different sizes.

Uses only Python's standard library so it can run in restricted environments.
"""

from __future__ import annotations

import argparse
import struct
from pathlib import Path


PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"


def read_png_dimensions(data: bytes) -> tuple[int, int]:
  if not data.startswith(PNG_SIGNATURE):
    raise ValueError("All inputs must be PNG files.")
  width, height = struct.unpack(">II", data[16:24])
  return width, height


def build_icon(png_paths: list[Path], output: Path) -> None:
  png_data = [p.read_bytes() for p in png_paths]
  entries = []
  offset = 6 + 16 * len(png_data)  # icon header + entries

  for data in png_data:
    width, height = read_png_dimensions(data)
    entry = {
      "width": width if width < 256 else 0,
      "height": height if height < 256 else 0,
      "colors": 0,
      "reserved": 0,
      "planes": 1,
      "bit_count": 32,
      "size": len(data),
      "offset": offset,
      "data": data,
    }
    entries.append(entry)
    offset += entry["size"]

  header = struct.pack("<HHH", 0, 1, len(entries))
  table = bytearray()
  for entry in entries:
    table.extend(
      struct.pack(
        "<BBBBHHII",
        entry["width"],
        entry["height"],
        entry["colors"],
        entry["reserved"],
        entry["planes"],
        entry["bit_count"],
        entry["size"],
        entry["offset"],
      )
    )

  payload = header + table + b"".join(entry["data"] for entry in entries)
  output.write_bytes(payload)


def main() -> None:
  parser = argparse.ArgumentParser(description=__doc__)
  parser.add_argument("output", type=Path)
  parser.add_argument("inputs", nargs="+", type=Path)
  args = parser.parse_args()

  if not args.inputs:
    raise SystemExit("Provide at least one PNG input.")

  build_icon(args.inputs, args.output)


if __name__ == "__main__":
  main()
