/**
 * File:    TwinkleStarJavaSketch.pde
 * Date:    2026-09-24
 * Author:  klp  —  Processing Java port of twinkleStarSketch.py
 * Purpose: Twinkling star field sketch in Processing Java mode.
 *
 * To open: File > Open... and select this folder (TwinkleStarJavaSketch/).
 * Processing requires the folder name to match the .pde filename.
 *
 * === Cross-Training: Python → JavaScript → Java ===
 *
 *   Python (Processing.py)          JavaScript (p5.js)           Java (Processing)
 *   ──────────────────────          ──────────────────           ─────────────────
 *   star['x']                       star.x                       star.x  (field)
 *   size(CW, CH)                    createCanvas(CW, CH)         size(CW, CH)
 *   int(random(PERIOD))             floor(random(PERIOD))        (int) random(PERIOD)
 *   should_twinkle = not ...        shouldTwinkle = !...         shouldTwinkle = !shouldTwinkle
 *   list of dicts                   array of objects             Star[]  (class below)
 *   snake_case names                camelCase names              camelCase names
 *   global keyword                  no equivalent                (fields are global)
 *
 *   Everything else is identical: background(), stroke(), strokeWeight(),
 *   point(), fill(), text(), colorMode(), sin(), TWO_PI, frameCount, etc.
 */

// ── Canvas ───────────────────────────────────────────────────────────────────
final int CW = 810;
final int CH = 700;

// Night-sky: deep purple (hex #5a1485 → H≈277, S≈85, B≈52 in HSB)
final int SKY_H = 277;
final int SKY_S = 85;
final int SKY_B = 52;

// Stars
final int   NUM_STARS     = 100;
final float MIN_STAR_SIZE =   3;
final float MAX_STAR_SIZE =   8;
final int   STAR_HUE      =  60;   // yellow family (60° on the HSB wheel)

// Twinkling sine wave:  alpha = A * sin(B * (frameCount + offset)) + C
//   A = (max − min) / 2 = 35   B = TWO_PI / period   C = (max + min) / 2 = 65
final float TWINKLE_ALPHA_MIN = 30;
final float TWINKLE_ALPHA_MAX = 100;
final int   TWINKLE_PERIOD    = 90;   // frames per cycle (~3 sec at 30 fps)

Star[]  stars;
boolean shouldTwinkle = false;
float   twinkleA, twinkleC, twinkleB;


// =============================================================================
// SETUP
// =============================================================================
void setup() {
    size(810, 700);   // must be a literal — cannot use CW/CH here in Processing Java

    colorMode(HSB, 360, 100, 100, 100);
    frameRate(30);
    textSize(20);
    textAlign(LEFT);

    // Derive sinusoid coefficients from the desired alpha range
    twinkleA = (TWINKLE_ALPHA_MAX - TWINKLE_ALPHA_MIN) / 2.0;   // amplitude = 35
    twinkleC = (TWINKLE_ALPHA_MAX + TWINKLE_ALPHA_MIN) / 2.0;   // center    = 65
    twinkleB = TWO_PI / TWINKLE_PERIOD;                          // freq ≈ 0.0698 rad/frame

    stars = new Star[NUM_STARS];
    for (int i = 0; i < NUM_STARS; i++) {
        stars[i] = new Star(
            random(CW),
            random(CH),
            random(MIN_STAR_SIZE, MAX_STAR_SIZE),
            STAR_HUE,
            random(0, 100),    // saturation: 0=pale white, 100=vivid yellow
            random(80, 100),   // brightness: always fairly lit
            (int) random(TWINKLE_PERIOD)   // phase offset: 0 to TWINKLE_PERIOD-1
        );
    } //end for loop
} //end setup


// =============================================================================
// DRAW
// =============================================================================
void draw() {
    background(SKY_H, SKY_S, SKY_B);

    for (Star star : stars) {
        float myAlpha;

        if (shouldTwinkle) {
            // Sample the wave at (frameCount + offset).
            // Adding the per-star offset places each star at a different phase
            // so they never all dim or brighten at the same moment.
            myAlpha = twinkleA * sin(twinkleB * (frameCount + star.offset)) + twinkleC;
        } else {
            myAlpha = 100;   // twinkling off → fully opaque
        }

        stroke(star.h, star.s, star.b, myAlpha);
        strokeWeight(star.sw);
        point(star.x, star.y);
    } //end for loop

    // Reset drawing state before the text overlay
    noStroke();
    strokeWeight(1);
    fill(0, 0, 100, 100);   // white in HSB: s=0, b=100
    text("Click canvas to toggle star twinkling!", 10, 30);
    text("Star twinkling: " + shouldTwinkle, 10, 60);
} //end draw


// =============================================================================
// MOUSE INPUT
// =============================================================================
void mousePressed() {
    // Guard: only respond to clicks inside the canvas boundary
    if (mouseX >= 0 && mouseX <= CW && mouseY >= 0 && mouseY <= CH) {
        shouldTwinkle = !shouldTwinkle;
    }
} //end mousePressed


// =============================================================================
// Star data class
// Equivalent to the Python dict and the JS plain object used in the other versions.
// =============================================================================
class Star {
    float x, y, sw, h, s, b;
    int   offset;

    Star(float x, float y, float sw, float h, float s, float b, int offset) {
        this.x      = x;
        this.y      = y;
        this.sw     = sw;
        this.h      = h;
        this.s      = s;
        this.b      = b;
        this.offset = offset;
    }
} //end class Star
