"""
File: twinklingStarsWithHue.py
Date: 2026-09-30
Author: klp
Purpose: Twinkling star field with continuous hue drift — Python Processing.
         Extends twinkleStarSketch.py by adding a global_hue float accumulator
         that advances HUE_SPEED degrees per frame, so all 500 stars cycle
         through the color wheel together.

=== What's New vs. twinkleStarSketch.py ===

    1.  Float accumulator instead of fixed hue
        global_hue starts at 60 (yellow family) and gains HUE_SPEED each frame.
        At 30 fps, 0.3 deg/frame = 9 deg/sec → full 360 deg cycle every 40 s.

    2.  Hue updated ONCE before the star loop (Bug warning)
        global_hue = (global_hue + HUE_SPEED) % 360  # ONCE per frame
        star_hue   = int(global_hue)
        Every star in the loop reads that same value.

        If you move the hue update INSIDE the loop, each of the 500 stars
        picks up a different hue in the same frame — rainbow spread instead
        of a unified drift. This was Bug 1 in the original Java exploration.

    3.  Saturation min raised from 0 to 20
        Stars with very low saturation appear nearly gray; the hue shift is
        invisible on them. Raising the floor to 20 ensures every star
        participates in the color change.

    4.  500 stars (was 100) for a richer field, matching the Java version.

=== How the Two Animations Compose ===
    Hue drift and twinkling are completely independent:
    • Hue advances 0.3 deg every frame regardless of the twinkling toggle.
    • Twinkling modulates alpha via the sine wave regardless of hue.
    Clicking the canvas toggles only the alpha modulation (twinkling).
"""

# ── Canvas ─────────────────────────────────────────────────────────────────────
CW = 810
CH = 700

SKY_H = 277
SKY_S = 85
SKY_B = 52

# ── Stars ──────────────────────────────────────────────────────────────────────
NUM_STARS     = 500
MIN_STAR_SIZE =   4
MAX_STAR_SIZE =  16

# ── Hue drift ──────────────────────────────────────────────────────────────────
# HUE_SPEED: degrees per frame. 0.3 * 30 fps = 9 deg/sec → 40-second full cycle.
HUE_SPEED = 0.3

# ── Twinkling sine-wave parameters ────────────────────────────────────────────
TWINKLE_ALPHA_MIN = 30
TWINKLE_ALPHA_MAX = 100
TWINKLE_PERIOD    = 90     # frames per cycle (~3 sec at 30 fps)

# ── Sketch-level state ─────────────────────────────────────────────────────────
stars          = []
should_twinkle = False
global_hue     = 60.0     # float accumulator — never accelerates
my_wave        = None


# =============================================================================
# SETUP — called once at sketch start
# =============================================================================
def setup():
    global stars, my_wave

    size(CW, CH)
    colorMode(HSB, 360, 100, 100, 100)
    frameRate(30)
    textSize(20)
    textAlign(LEFT)

    twinkle_A = (TWINKLE_ALPHA_MAX - TWINKLE_ALPHA_MIN) / 2.0
    twinkle_C = (TWINKLE_ALPHA_MAX + TWINKLE_ALPHA_MIN) / 2.0
    my_wave = Sinusoid(twinkle_A, twinkle_C, TWINKLE_PERIOD)

    stars = []
    for i in range(NUM_STARS):
        stars.append(Star(
            random(CW),
            random(CH),
            random(MIN_STAR_SIZE, MAX_STAR_SIZE),
            global_hue,
            random(20, 100),
            random(80, 100),
            int(random(TWINKLE_PERIOD))
        ))
    #end for
#end setup


# =============================================================================
# DRAW — called 30 times per second
# =============================================================================
def draw():
    global global_hue

    background(SKY_H, SKY_S, SKY_B)

    # Advance hue ONCE per frame — outside the star loop
    global_hue = (global_hue + HUE_SPEED) % 360
    star_hue = int(global_hue)

    for star in stars:
        myAlpha = my_wave.f(frameCount + star.offset) if should_twinkle else 100

        star.h = star_hue    # same hue for every star this frame

        stroke(star.h, star.s, star.b, myAlpha)
        strokeWeight(star.sw)
        point(star.x, star.y)
    #end for

    noStroke()
    strokeWeight(1)
    fill(0, 0, 100, 100)
    text("Click canvas to toggle star twinkling!", 10, 30)
    text("Star twinkling: " + str(should_twinkle), 10, 60)
#end draw


# =============================================================================
# MOUSE INPUT — toggles twinkling on each canvas click
# =============================================================================
def mousePressed():
    global should_twinkle

    if 0 <= mouseX <= CW and 0 <= mouseY <= CH:
        should_twinkle = not should_twinkle
#end mousePressed


# =============================================================================
# Star class — same purpose as JavaScript Star and Java Star
# Python: self + __init__; JavaScript: constructor; Java: typed fields
# =============================================================================
class Star:
    def __init__(self, x, y, sw, h, s, b, offset):
        self.x      = x
        self.y      = y
        self.sw     = sw
        self.h      = h
        self.s      = s
        self.b      = b
        self.offset = offset
#end class Star


# =============================================================================
# Sinusoid class — stable A, B, C params set once in setup(); f() called every frame
# =============================================================================
class Sinusoid:
    def __init__(self, A, C, period):
        self.A = A
        self.B = TWO_PI / period
        self.C = C

    def f(self, x):
        return self.A * sin(self.B * x) + self.C
#end class Sinusoid
