#!/usr/bin/env python3
"""Deterministic LinkedIn post crop study. No third-party packages.

Draws one 1620x1080 grid-and-text source, then builds the three tool canvases
(1080x540, 1080x1080, 1080x1350) in Fill and Fit, plus 320-pixel-wide previews
of every export. Crop and pad areas are calculated from the same geometry the
browser tool uses:

  Fill: scale = max(W/w, H/h); kept source area = (W/scale) * (H/scale)
  Fit:  scale = min(W/w, H/h); drawn area = (w*scale) * (h*scale)

Nearest-neighbour sampling keeps the run byte-stable; the browser uses its own
resampler, so only the file sizes of these files are quoted. No clock, no
randomness, no network. All writes stay under this directory.
"""

import hashlib
import json
import os
import struct
import zlib

HERE = os.path.dirname(os.path.abspath(__file__))
SRC_W, SRC_H = 1620, 1080
PRESETS = [("landscape-2x1", 1080, 540), ("square-1x1", 1080, 1080), ("portrait-4x5", 1080, 1350)]

BG = (242, 245, 243)
GRID = (185, 200, 192)
GRID_MAJOR = (125, 147, 138)
INK = (18, 59, 44)
WHITE = (255, 255, 255)
MARKERS = [(205, 58, 46), (36, 88, 168), (214, 146, 26), (150, 52, 130)]

# 5x7 bitmap glyphs for the labels used below.
FONT = {
    " ": ["00000"] * 7,
    "0": ["01110", "10001", "10011", "10101", "11001", "10001", "01110"],
    "1": ["00100", "01100", "00100", "00100", "00100", "00100", "01110"],
    "2": ["01110", "10001", "00001", "00010", "00100", "01000", "11111"],
    "8": ["01110", "10001", "10001", "01110", "10001", "10001", "01110"],
    "A": ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
    "D": ["11110", "10001", "10001", "10001", "10001", "10001", "11110"],
    "E": ["11111", "10000", "10000", "11110", "10000", "10000", "11111"],
    "G": ["01110", "10001", "10000", "10111", "10001", "10001", "01111"],
    "I": ["01110", "00100", "00100", "00100", "00100", "00100", "01110"],
    "K": ["10001", "10010", "10100", "11000", "10100", "10010", "10001"],
    "L": ["10000", "10000", "10000", "10000", "10000", "10000", "11111"],
    "M": ["10001", "11011", "10101", "10101", "10001", "10001", "10001"],
    "N": ["10001", "11001", "10101", "10011", "10001", "10001", "10001"],
    "O": ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
    "P": ["11110", "10001", "10001", "11110", "10000", "10000", "10000"],
    "R": ["11110", "10001", "10001", "11110", "10100", "10010", "10001"],
    "S": ["01111", "10000", "10000", "01110", "00001", "00001", "11110"],
    "T": ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
    "W": ["10001", "10001", "10001", "10101", "10101", "11011", "10001"],
    "X": ["10001", "10001", "01010", "00100", "01010", "10001", "10001"],
}


def canvas(w, h, colour=BG):
    row = bytes(colour) * w
    return [bytearray(row) for _ in range(h)]


def rect(px, w, h, x0, y0, width, height, colour):
    r, g, b = colour
    for y in range(max(0, y0), min(h, y0 + height)):
        row = px[y]
        for x in range(max(0, x0), min(w, x0 + width)):
            row[x * 3:x * 3 + 3] = bytes((r, g, b))


def text(px, w, h, x, y, value, scale, colour):
    cursor = x
    for char in value:
        glyph = FONT[char]
        for gy, line in enumerate(glyph):
            for gx, bit in enumerate(line):
                if bit == "1":
                    rect(px, w, h, cursor + gx * scale, y + gy * scale, scale, scale, colour)
        cursor += 6 * scale


def centered_text(px, w, h, y, value, scale, colour):
    text(px, w, h, (w - len(value) * 6 * scale + scale) // 2, y, value, scale, colour)


def source_image():
    px = canvas(SRC_W, SRC_H)
    for x in range(0, SRC_W, 90):
        rect(px, SRC_W, SRC_H, x, 0, 1, SRC_H, GRID_MAJOR if x % 450 == 0 else GRID)
    for y in range(0, SRC_H, 90):
        rect(px, SRC_W, SRC_H, 0, y, SRC_W, 1, GRID_MAJOR if y % 450 == 0 else GRID)
    rect(px, SRC_W, SRC_H, 0, 0, SRC_W, 12, INK)
    rect(px, SRC_W, SRC_H, 0, SRC_H - 12, SRC_W, 12, INK)
    rect(px, SRC_W, SRC_H, 0, 0, 12, SRC_H, INK)
    rect(px, SRC_W, SRC_H, SRC_W - 12, 0, 12, SRC_H, INK)
    corners = [(40, 40), (SRC_W - 160, 40), (40, SRC_H - 160), (SRC_W - 160, SRC_H - 160)]
    for (x, y), colour in zip(corners, MARKERS):
        rect(px, SRC_W, SRC_H, x, y, 120, 120, colour)
    centered_text(px, SRC_W, SRC_H, 360, "LINKEDIN POST", 12, INK)
    centered_text(px, SRC_W, SRC_H, 500, "1080 WIDE", 8, INK)
    centered_text(px, SRC_W, SRC_H, 620, "SMALL TEXT 28 PX", 4, INK)
    return px


def write_png(path, px, w, h):
    raw = b"".join(b"\x00" + bytes(row) for row in px)

    def chunk(kind, data):
        return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data))

    with open(path, "wb") as stream:
        stream.write(b"\x89PNG\r\n\x1a\n")
        stream.write(chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)))
        stream.write(chunk(b"IDAT", zlib.compress(raw, 9)))
        stream.write(chunk(b"IEND", b""))


def resize(src, sw, sh, dw, dh, sx, sy, ssx, ssy, background=None):
    out = canvas(dw, dh, background or BG)
    for y in range(dh):
        src_y = int(sy + (y + 0.5) * ssy - 0.5)
        src_y = min(sh - 1, max(0, src_y))
        row = out[y]
        for x in range(dw):
            src_x = int(sx + (x + 0.5) * ssx - 0.5)
            src_x = min(sw - 1, max(0, src_x))
            copy = src[src_y][src_x * 3:src_x * 3 + 3]
            row[x * 3:x * 3 + 3] = copy
    return out


def resample(src, sw, sh, dw, dh):
    return resize(src, sw, sh, dw, dh, 0, 0, sw / dw, sh / dh)


def fill_export(src, width, height):
    scale = max(width / SRC_W, height / SRC_H)
    kept_w, kept_h = width / scale, height / scale
    sx, sy = (SRC_W - kept_w) / 2, (SRC_H - kept_h) / 2
    out = resize(src, SRC_W, SRC_H, width, height, sx, sy, kept_w / width, kept_h / height)
    crop_area = SRC_W * SRC_H - kept_w * kept_h
    return out, {"scale": scale, "source_region": [kept_w, kept_h], "crop_area_px": crop_area,
                 "crop_share": crop_area / (SRC_W * SRC_H), "pad_area_px": 0, "pad_share": 0}


def fit_export(src, width, height):
    scale = min(width / SRC_W, height / SRC_H)
    drawn_w, drawn_h = SRC_W * scale, SRC_H * scale
    offset_x, offset_y = (width - drawn_w) / 2, (height - drawn_h) / 2
    out = canvas(width, height, WHITE)
    drawn = resample(src, SRC_W, SRC_H, int(round(drawn_w)), int(round(drawn_h)))
    x0, y0 = int(round(offset_x)), int(round(offset_y))
    for y, row in enumerate(drawn):
        if 0 <= y0 + y < height:
            out[y0 + y][x0 * 3:(x0 + len(row) // 3) * 3] = row
    pad_area = width * height - drawn_w * drawn_h
    return out, {"scale": scale, "source_region": [SRC_W, SRC_H], "crop_area_px": 0, "crop_share": 0,
                 "pad_area_px": pad_area, "pad_share": pad_area / (width * height)}


def digest(path):
    with open(path, "rb") as stream:
        return hashlib.sha256(stream.read()).hexdigest()


def main():
    exports = os.path.join(HERE, "exports")
    previews = os.path.join(HERE, "previews-320")
    os.makedirs(exports, exist_ok=True)
    os.makedirs(previews, exist_ok=True)
    src = source_image()
    source_path = os.path.join(HERE, "source-grid-1620x1080.png")
    write_png(source_path, src, SRC_W, SRC_H)
    rows = []
    for preset, width, height in PRESETS:
        for mode, build in (("fill", fill_export), ("fit", fit_export)):
            name = f"{preset}-{mode}-{width}x{height}.png"
            px, geometry = build(src, width, height)
            path = os.path.join(exports, name)
            write_png(path, px, width, height)
            preview = resample(px, width, height, 320, max(1, round(height * 320 / width)))
            preview_path = os.path.join(previews, name)
            write_png(preview_path, preview, 320, max(1, round(height * 320 / width)))
            text_height = 28 * geometry["scale"]
            rows.append({
                "preset": preset, "mode": mode, "file": f"exports/{name}",
                "width": width, "height": height, "bytes": os.path.getsize(path),
                "sha256": digest(path), **geometry,
                "label_28px_in_export": round(text_height, 2),
                "label_28px_at_320_wide": round(text_height * 320 / width, 2),
                "preview": f"previews-320/{name}", "preview_bytes": os.path.getsize(preview_path),
                "preview_sha256": digest(preview_path),
            })
    result = {
        "generator": "make_samples.py",
        "method": "nearest-neighbour sample of the documented crop box; browser resampling differs",
        "source": {"file": "source-grid-1620x1080.png", "width": SRC_W, "height": SRC_H,
                   "bytes": os.path.getsize(source_path), "sha256": digest(source_path)},
        "background": "#ffffff",
        "focus": 0.5,
        "exports": rows,
    }
    with open(os.path.join(HERE, "results.json"), "w", encoding="utf-8") as stream:
        json.dump(result, stream, indent=2)
        stream.write("\n")
    print(f"wrote {len(rows)} exports, {len(rows)} previews and results.json")


if __name__ == "__main__":
    main()
