# mmr_gym/motifs/petals.py
import math, random
from PIL import Image, ImageDraw, Image as PILImage

from ..base import Motif
from ..schema import MotifSpec
from ..config import SUPERSAMPLE, SS_CELL, COLORS
from ..registry import register_motif
from .helpers import _down_on_background


@register_motif
class PetalsMotif(Motif):
    """
    Simple flower generator: central circle (+/-) + identical oval/teardrop petals on a ring.

    This version intentionally avoids complex rosette/star/spoke looks.

    Controls (spec.extra):
      - mode: always "sym" unless you force "asym"
      - petal_draw: "solid" (default) or "outline"  → matches your outline example
      - center_mode: "none" | "dot" | "cutout"     → black dot, nothing, or white hole
      - separate: bool → if True, petals stop before center (disconnected look)
      - gap_frac: 0..0.2 of available radius → how far petals stay away when separate=True
    """
    name = "petals"
    attr_ranges = {"count": (5, 10), "size": (0.9, 1.2), "thickness": (2, 5)}

    def sample_spec(self, rng: random.Random):
        seed = rng.randint(0, 2**31 - 1)
        petals = rng.randint(*self.attr_ranges["count"])

        # Bias to simple daisies (no outlines, radial out, symmetric)
        extra = {
            "mode": "sym",
            "rotation": rng.uniform(0, 360),
            "petals": petals,

            # geometry (kept conservative so petals don't overlap)
            "ring_frac":   rng.uniform(0.58, 0.70),
            "len_frac":    rng.uniform(0.55, 0.68),
            "width_frac":  rng.uniform(0.18, 0.28),  # chubby petals
            "center_frac": rng.uniform(0.10, 0.18),

            # simple drawing controls
            "petal_draw": rng.choice(["solid"] * 4 + ["outline"]),    # mostly solid
            "center_mode": rng.choices(["dot", "cutout", "none"], weights=[4, 3, 3], k=1)[0],
            "separate": rng.random() < 0.45,                          # often separated petals
            "gap_frac": rng.uniform(0.05, 0.14),
            # keep a hook for rare asymmetry if you want to enable it later
            "asym_kind": rng.choice(["short", "long", "thin", "fat"]),
        }

        return MotifSpec(
            self.name,
            seed,
            rng.randrange(len(COLORS)),
            count=petals,
            size=rng.uniform(*self.attr_ranges["size"]),
            thickness=rng.randint(*self.attr_ranges["thickness"]),
            extra=extra,
        )

    def clamp_spec(self, spec: MotifSpec) -> MotifSpec:
        ex = dict(spec.extra or {})
        # Hard clamp to simple mode unless the caller explicitly asks for asym
        ex["mode"] = "asym" if ex.get("mode") == "asym" else "sym"

        cmin, cmax = self.attr_ranges["count"]
        count = max(int(cmin), min(int(cmax), int(getattr(spec, "count", ex.get("petals", 8)))))
        ex["petals"] = count

        size  = max(self.attr_ranges["size"][0], min(self.attr_ranges["size"][1], float(getattr(spec, "size", 1.0))))
        thick = max(1, int(getattr(spec, "thickness", 3)))

        # geometry clamps
        rot   = float(ex.get("rotation", 0.0)) % 360.0
        ringf = max(0.50, min(0.80, float(ex.get("ring_frac", 0.64))))
        lenf  = max(0.30, min(0.85, float(ex.get("len_frac", 0.60))))
        widf  = max(0.10, min(0.40, float(ex.get("width_frac", 0.22))))
        ctrf  = max(0.08, min(0.24, float(ex.get("center_frac", 0.14))))

        # simple drawing flags
        petal_draw = "outline" if ex.get("petal_draw") == "outline" else "solid"
        center_mode = ex.get("center_mode", "dot")
        if center_mode not in ("none", "dot", "cutout"):
            center_mode = "dot"
        separate = bool(ex.get("separate", False))
        gapf = max(0.0, min(0.25, float(ex.get("gap_frac", 0.10))))

        ex.update({
            "rotation": rot, "ring_frac": ringf, "len_frac": lenf, "width_frac": widf, "center_frac": ctrf,
            "petal_draw": petal_draw, "center_mode": center_mode, "separate": separate, "gap_frac": gapf
        })
        ex["asym_kind"] = ex.get("asym_kind", "short")
        return spec.clone(count=count, size=size, thickness=thick, extra=ex)

    # --- rendering (elliptical petals only, kept simple) ---
    def render(self, spec: MotifSpec) -> PILImage.Image:
        s = self.clamp_spec(spec)
        ex = s.extra
        count = int(ex["petals"])
        rot   = float(ex["rotation"])
        size  = float(s.size)

        img = PILImage.new("RGBA", (SS_CELL, SS_CELL), (255, 255, 255, 0))
        d = ImageDraw.Draw(img)
        cx = cy = SS_CELL / 2.0

        # Color policy: simple silhouettes (no outline) by default
        fill_color = COLORS[s.color_idx]
        outline_on = (ex["petal_draw"] == "outline")
        ow = max(1, int(s.thickness) * SUPERSAMPLE) if outline_on else 0
        outline_color = "black" if outline_on else None

        # Layout radii
        margin  = int(SS_CELL * 0.12)
        avail_R = (SS_CELL / 2.0) - margin
        ring_r  = avail_R * float(ex["ring_frac"]) * size
        center_r = max(2 * SUPERSAMPLE, int(avail_R * float(ex["center_frac"]) * size))

        # Petal geometry
        base_len = avail_R * float(ex["len_frac"]) * size
        base_wid = avail_R * float(ex["width_frac"]) * size

        # equal spacing + non-overlap clamp
        step_deg = 360.0 / max(1, count)
        step_rad = math.radians(step_deg)
        safety   = 4 * SUPERSAMPLE
        max_w_from_gap = max(2 * SUPERSAMPLE, 2 * ring_r * math.sin(step_rad / 2.0) - safety)
        base_wid = min(base_wid, max_w_from_gap)

        # Optional mild asymmetry if explicitly requested
        special_idx = None
        L_scale = W_scale = 1.0
        if ex["mode"] == "asym":
            rng = random.Random(int(s.seed) ^ 0xF17A)
            special_idx = rng.randrange(count)
            ak = str(ex.get("asym_kind", "short"))
            if ak == "short": L_scale = 0.75
            elif ak == "long": L_scale = 1.25
            elif ak == "thin": W_scale = 0.75
            elif ak == "fat":  W_scale = 1.25

        separate = bool(ex["separate"])
        gap_pix = ex["gap_frac"] * avail_R

        # Draw petals (oval/teardrop = ellipse oriented along radius)
        for i in range(count):
            L = base_len * (L_scale if (special_idx is not None and i == special_idx) else 1.0)
            W = base_wid * (W_scale if (special_idx is not None and i == special_idx) else 1.0)
            if W < 2: continue

            # outward constraint (stay inside canvas)
            L_out_max = max(0.0, (avail_R - ring_r) - safety) * 2.0

            # inbound constraint:
            # - connected petals: must at least reach the center disk
            # - separated petals: must stop BEFORE a ring at (center_r + gap)
            if separate:
                in_allow = max(0.0, ring_r - (center_r + gap_pix) - safety)
                L_in_max = in_allow * 2.0
                if L_in_max > 0: L = min(L, L_in_max)
            else:
                L_in_min = max(0.0, (ring_r - center_r + safety)) * 2.0
                L = max(L, L_in_min)

            if L_out_max > 0: L = min(L, L_out_max)
            L = max(L, 6 * SUPERSAMPLE)

            # build a simple local ellipse patch (major axis vertical), rotate to radial angle
            patch_w = int(W + 2 * max(ow, 1))
            patch_h = int(L + 2 * max(ow, 1))
            patch = PILImage.new("RGBA", (patch_w, patch_h), (255, 255, 255, 0))
            pd = ImageDraw.Draw(patch)
            # ellipse centered in patch
            bbox = [max(ow,1), max(ow,1), max(ow,1) + W, max(ow,1) + L]
            pd.ellipse(bbox, fill=fill_color, outline=outline_color, width=ow if outline_on else 0)

            ang_deg = rot + i * step_deg  # radial out only
            patch = patch.rotate(ang_deg, resample=PILImage.BICUBIC, expand=True)
            pw, ph = patch.size

            # center of petal sits on the ring
            ang = math.radians(ang_deg)
            px = cx + ring_r * math.cos(ang)
            py = cy + ring_r * math.sin(ang)
            img.paste(patch, (int(px - pw / 2), int(py - ph / 2)), patch)

        # center: "dot" (filled), "cutout" (white hole), or "none"
        if ex["center_mode"] == "dot":
            d.ellipse([cx - center_r, cy - center_r, cx + center_r, cy + center_r],
                      fill=fill_color, outline=outline_color, width=ow if outline_on else 0)
        elif ex["center_mode"] == "cutout":
            # draw a white hole (no outline) to match your "white center" examples
            d.ellipse([cx - center_r, cy - center_r, cx + center_r, cy + center_r],
                      fill=(255, 255, 255, 255))

        return _down_on_background(img)
