/*
File: swDisk.js
Date: 2026-02-17
Author: klp
Workspace: SWaveJS2026-01-30-StgC
Purpose: SWDisk class for SketchWaveJS
Comment(s):

Has an update: if a 'null' color is passed for the center point, it will use the disk's stroke color (if available) or a darker version of the fill color (if available) or swBlack as a last resort. This allows the center point to automatically adapt its color based on the disk's styling, while still allowing explicit control if desired.

Another update: setFillAlpha and setStrokeAlpha methods have been added to allow changing the transparency of the fill and stroke colors after the disk has been created. These methods ensure that the alpha value is clamped between 0 and 100 and update the corresponding p5.js color object accordingly.



TODO:

=== Notes ===:
- This class assumes p5.js, SWColor and SWPoint are loaded in the environment.
- SWDisk class represents disks or circles in 2D space with styling options.
- Includes properties for center (SWPoint), radius, thickness, fill color, and stroke color.
- Supports drawing the disk with fill and stroke.
- It is designed to be compatible with p5.js, SWColor, SWPoint, SWGrid, and SWSinusoid.
- Methods for drawing the disk in screen and grid coordinates.
- Additional methods for breathing (radius modulation) and resetting.

// Features:
// - Center: SWPoint instance
// - Radius: number (user units)
// - Perimeter thickness: number (stroke weight)
// - Fill color: SWColor
// - Border color: SWColor
// - Area and circumference attributes
// - Can be drawn in screen or grid coordinates
// - Can "breathe" (radius modulated by SWSinusoid, centered at SWPoint)
// - Consistent API with SWLine, SWPoint, SWTriangle
//
// Usage example:
// let disk = new SWDisk(new SWPoint(0,0), 5, 2, swYellow, swBlack);
// disk.drawOnGrid(grid);
// disk.breathe(sin, t);
*/

console.log("[swDisk.js] SWDisk class loaded.");

class SWDisk {
    /**
     * @param {SWPoint} center - Center point (SWPoint instance)
     * @param {number} radius - Radius (user units)
     * @param {number} [thickness=2] - Border (stroke) thickness
     * @param {SWColor} [fillColor] - Fill color (SWColor instance)
     * @param {SWColor} [strokeColor] - Border color (SWColor instance)
     */
    constructor(center, radius, thickness = 2, fillColor = undefined, strokeColor = undefined) {
        this.center = center; // SWPoint instance
        this.radius = radius;
        this.thickness = thickness;
        // Store a copy of the original fill color for reset
        this.originalFillColor = fillColor ? SWColor.copy(fillColor) : undefined;
        // fillColor is always a copy, so cycleParameter only affects the current color
        this.fillColor = fillColor ? SWColor.copy(fillColor) : undefined;
        this.strokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;
        // Store original radius for breathing
        this.originalRadius = radius;
        // Area and circumference
        this.area = Math.PI * radius * radius;
        this.circumference = 2 * Math.PI * radius;
        // Show center point (default: true for this version)
        this.shouldShowCenter = true;

        // Set center point color according to rules
        this.updateCenterColor();
    }//end constructor

    /**
     * Updates the center point color according to the rules:
     * - If strokeColor, use that
     * - Else if fillColor, use 20% darker version
     * - Else use swBlack
     * Note: This DOES NOT modify the center point itself, just stores
     * the color to use when drawing the center as part of this disk.
     */
    updateCenterColor() {
        let centerColor;
        if (this.strokeColor && this.strokeColor.col) {
            centerColor = this.strokeColor;
        } else if (this.fillColor && this.fillColor.col) {
            // Make a color 20% darker (reduce brightness by 20%)
            if (typeof SWColor === 'function') {
                let h = this.fillColor.h !== undefined ? this.fillColor.h : 0;
                let s = this.fillColor.s !== undefined ? this.fillColor.s : 0;
                let b = this.fillColor.b !== undefined ? this.fillColor.b : 100;
                let a = this.fillColor.a !== undefined ? this.fillColor.a : 100;
                let darkerB = Math.max(0, b - 20);
                centerColor = new SWColor(h, s, darkerB, a, 'centerDarker');
            } else {
                centerColor = swBlack;
            }
        } else {
            centerColor = swBlack;
        }
        // Store the color for use when drawing, but DON'T modify the center point
        this.centerColor = centerColor;
    }//end updateCenterColor

    /**
     * Draws the disk in screen coordinates (p5.js)
     */
    draw() {
        if (this.fillColor && this.fillColor.col) {
            fill(this.fillColor.col);
        } else {
            noFill();
        }
        if (this.strokeColor && this.strokeColor.col) {
            stroke(this.strokeColor.col);
        } else {
            noStroke();
        }
        strokeWeight(this.thickness);
        ellipse(this.center.x, this.center.y, 2 * this.radius, 2 * this.radius);
        // Optionally draw center point (pass disk's stroke color as default)
        if (this.shouldShowCenter && this.center && this.center.draw) {
            this.center.draw(this.strokeColor);
        }
        noStroke();
        noFill();
        strokeWeight(1);
    }//end draw

    /**
     * Draws the disk in user (grid) coordinates using the given SWGrid
     * @param {SWGrid} grid
     */
    drawOnGrid(grid) {
        const { x: cx, y: cy } = grid.userToScreen(this.center.x, this.center.y);
        // Convert radius from user units to screen units (average scale)
        const rx = grid.xScale * this.radius;
        const ry = grid.yScale * this.radius;
        const rScreen = (rx + ry) / 2;
        if (this.fillColor && this.fillColor.col) {
            fill(this.fillColor.col);
        } else {
            noFill();
        }
        if (this.strokeColor && this.strokeColor.col) {
            stroke(this.strokeColor.col);
        } else {
            noStroke();
        }
        strokeWeight(this.thickness);
        ellipse(cx, cy, 2 * rScreen, 2 * rScreen);
        // Optionally draw center point (pass disk's stroke color as default)
        if (this.shouldShowCenter && this.center && this.center.drawOnGrid) {
            this.center.drawOnGrid(grid, this.strokeColor);
        }
        noStroke();
        noFill();
        strokeWeight(1);
    }//end drawOnGrid

    /**
     * Sets whether the center point should be shown
     * @param {boolean} show
     */
    setShowCenter(show = true) {
        this.shouldShowCenter = show;
    }

    /**
     * Modulates the radius using a SWSinusoid instance, centered at the SWPoint
     * @param {SWSinusoid} sinusoid - The sinusoid to apply
     * @param {number} t - The time or parameter to pass to the sinusoid
     */
    breathe(sinusoid, t) {
        this.radius = sinusoid.getValue(t);
        // Update area and circumference
        this.area = Math.PI * this.radius * this.radius;
        this.circumference = 2 * Math.PI * this.radius;
    }

    /**
     * Resets the radius to its original value
     */
    reset() {
        this.radius = this.originalRadius;
        this.area = Math.PI * this.radius * this.radius;
        this.circumference = 2 * Math.PI * this.radius;
    }//end reset

    /**
     * Returns a string representation
     */
    toString() {
        return `SWDisk(center: ${this.center.toString()}, radius: ${this.radius}, thickness: ${this.thickness}, fillColor: ${this.fillColor ? this.fillColor.toString() : 'none'}, strokeColor: ${this.strokeColor ? this.strokeColor.toString() : 'none'}, area: ${this.area.toFixed(2)}, circumference: ${this.circumference.toFixed(2)})`;
    }//end toString

    /**
     * Sets the fill color
     * @param {SWColor} swColor
     */
    setFillColor(swColor) {
        // Always make a copy to avoid mutating shared color objects
        this.fillColor = swColor ? SWColor.copy(swColor) : undefined;
        this.updateCenterColor();
    }//end setFillColor

    /**
     * Resets the fill color to the original color
     */
    resetFillColor() {
        if (this.originalFillColor) {
            this.fillColor = SWColor.copy(this.originalFillColor);
            this.updateCenterColor();
        }
    }//end resetFillColor

    /**
     * Sets the border (stroke) color
     * @param {SWColor} swColor
     */
    setStrokeColor(swColor) {
        this.strokeColor = swColor;
        this.updateCenterColor();
    }//end setStrokeColor

    /**
     * Sets the border (stroke) thickness
     * @param {number} w
     */
    setStrokeWeight(w) {
        this.thickness = w;
    }//end setStrokeWeight

    /**
     * Sets the radius (and updates area/circumference)
     * @param {number} r
     */
    setRadius(r) {
        //dwr! changing r, changes other dependent properties
        this.radius = r;
        this.area = Math.PI * r * r;
        this.circumference = 2 * Math.PI * r;
    }//end setRadius

    /**
     * Sets the alpha (transparency) of the fill color
     * @param {number} alpha - Alpha value (0-100)
     */
    setFillAlpha(alpha) {
        if (this.fillColor) {
            this.fillColor.a = Math.max(0, Math.min(100, alpha));
            this.fillColor.col = color(this.fillColor.h, this.fillColor.s, this.fillColor.b, this.fillColor.a);
        }
    }//end setFillAlpha

    /**
     * Sets the alpha (transparency) of the stroke color
     * @param {number} alpha - Alpha value (0-100)
     */
    setStrokeAlpha(alpha) {
        if (this.strokeColor) {
            this.strokeColor.a = Math.max(0, Math.min(100, alpha));
            this.strokeColor.col = color(this.strokeColor.h, this.strokeColor.s, this.strokeColor.b, this.strokeColor.a);
            this.updateCenterColor();
        }
    }//end setStrokeAlpha

    /**
     * Checks if a given screen (pixel) point (x, y) is inside this disk, using the provided grid for coordinate mapping.
     * @param {number} x - x coordinate in screen (pixel) space
     * @param {number} y - y coordinate in screen (pixel) space
     * @param {SWGrid} grid - grid object for user-to-screen mapping
     * @returns {boolean} true if (x, y) is inside the disk, false otherwise
     */
    containsPoint(x, y, grid) {
        // Get disk center in screen coordinates
        const { x: cx, y: cy } = grid.userToScreen(this.center.x, this.center.y);
        // Convert radius from user units to screen units (average scale)
        const rx = grid.xScale * this.radius;
        const ry = grid.yScale * this.radius;
        const rScreen = (rx + ry) / 2;
        // Compute distance from (x, y) to center
        const distSq = (x - cx) * (x - cx) + (y - cy) * (y - cy);
        return distSq <= rScreen * rScreen;
    }//end containsPoint
}//end class SWDisk

