// swRectangle.js
// SWRectangle: A rectangle with 4 SWPoint vertices, center, fill color, optional border, and animation features
// Author: klp + GitHub Copilot
// Date: 2026-02-16
//
// Dependencies: SWPoint, SWColor, SWGrid, SWSinusoid

console.log("[swRectangle.js] SWRectangle class loaded.");

class SWRectangle {
    /**
     * @param {SWPoint} center - Center point of the rectangle (SWPoint)
     * @param {number} width - Width of the rectangle (horizontal dimension)
     * @param {number} height - Height of the rectangle (vertical dimension)
     * @param {SWColor} fillColor - Fill color (SWColor instance)
     * @param {Object} [options] - Optional: {strokeColor, strokeWeight, showVertices, showCenter, rotation}
     *   strokeColor: SWColor for border (optional)
     *   strokeWeight: number for border thickness (optional, default 2)
     *   showVertices: boolean (default false) - show corner points
     *   showCenter: boolean (default false) - show center point
     *   rotation: number (default 0) - initial rotation in degrees (CCW)
     */
    constructor(center, width, height, fillColor, options = {}) {
        this.rCenter = center;
        this.width = width;
        this.height = height;
        this.fillColor = fillColor;
        this.strokeColor = options.strokeColor || undefined;
        this.strokeWeight = options.strokeWeight || 2;
        this.showVertices = options.showVertices !== undefined ? options.showVertices : false;
        this.showCenter = options.showCenter !== undefined ? options.showCenter : false;
        this.rotation = options.rotation || 0; // Initial rotation in degrees
        
        // Set center point styling
        const centerStrokeWeight = this.strokeWeight > 0 ? this.strokeWeight : 4;
        const centerColor = (this.strokeColor && this.strokeColor.col) ? this.strokeColor : (typeof swBlack !== 'undefined' ? swBlack : new SWColor(0,0,0,100,"black"));
        this.rCenter.strokeWeight = centerStrokeWeight;
        this.rCenter.strokeColor = centerColor;
        
        // Create corner vertices
        // Start with unrotated corners, then apply initial rotation
        const hw = this.width / 2;
        const hh = this.height / 2;
        
        // Corner colors (optional, can use strokeColor or default colors)
        const vertexColor = this.strokeColor || (typeof swBlue !== 'undefined' ? swBlue : new SWColor(240, 100, 100, 100, "blue"));
        const vertexWeight = centerStrokeWeight;
        
        // Create corners: UL, UR, LR, LL (counterclockwise from upper-left)
        this.vUL = new SWPoint(this.rCenter.x - hw, this.rCenter.y + hh, undefined, vertexWeight, vertexColor, "UL");
        this.vUR = new SWPoint(this.rCenter.x + hw, this.rCenter.y + hh, undefined, vertexWeight, vertexColor, "UR");
        this.vLR = new SWPoint(this.rCenter.x + hw, this.rCenter.y - hh, undefined, vertexWeight, vertexColor, "LR");
        this.vLL = new SWPoint(this.rCenter.x - hw, this.rCenter.y - hh, undefined, vertexWeight, vertexColor, "LL");
        
        // Apply initial rotation if specified
        if (this.rotation !== 0) {
            this._rotateVertices(this.rotation);
        }
        
        // Store original positions for animation (after initial rotation)
        this.originalCenter = new SWPoint(this.rCenter.x, this.rCenter.y, undefined, centerStrokeWeight, centerColor);
        this.originalUL = new SWPoint(this.vUL.x, this.vUL.y, undefined, vertexWeight, vertexColor);
        this.originalUR = new SWPoint(this.vUR.x, this.vUR.y, undefined, vertexWeight, vertexColor);
        this.originalLR = new SWPoint(this.vLR.x, this.vLR.y, undefined, vertexWeight, vertexColor);
        this.originalLL = new SWPoint(this.vLL.x, this.vLL.y, undefined, vertexWeight, vertexColor);
    }//end constructor

    /**
     * Internal method to rotate vertices about the center by the given angle
     * @param {number} angleDeg - Angle in degrees (CCW)
     * @private
     */
    _rotateVertices(angleDeg) {
        const angleRad = angleDeg * Math.PI / 180;
        const cx = this.rCenter.x;
        const cy = this.rCenter.y;
        const cosA = Math.cos(angleRad);
        const sinA = Math.sin(angleRad);
        
        [this.vUL, this.vUR, this.vLR, this.vLL].forEach(v => {
            const dx = v.x - cx;
            const dy = v.y - cy;
            v.x = cx + dx * cosA - dy * sinA;
            v.y = cy + dx * sinA + dy * cosA;
        });
    }//end _rotateVertices

    /**
     * Getter for rectangle area
     * @returns {number} Width × Height
     */
    get area() {
        return this.width * this.height;
    }//end area

    /**
     * Getter for rectangle perimeter
     * @returns {number} 2 × (width + height)
     */
    get perimeter() {
        return 2 * (this.width + this.height);
    }//end perimeter

    /**
     * Getter for diagonal length
     * @returns {number} Diagonal from corner to corner
     */
    get diagonal() {
        return Math.sqrt(this.width * this.width + this.height * this.height);
    }//end diagonal

    /**
     * Getter to check if rectangle is a square
     * @returns {boolean} True if width equals height (within tolerance)
     */
    get isSquare() {
        const tolerance = 0.01; // relative tolerance
        return Math.abs(this.width - this.height) < tolerance * Math.max(this.width, this.height);
    }//end isSquare

    /**
     * Getter for aspect ratio (width / height)
     * @returns {number} Aspect ratio
     */
    get aspectRatio() {
        return this.width / this.height;
    }//end aspectRatio

    /**
     * Draws the rectangle 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);
            strokeWeight(this.strokeWeight);
        } else {
            noStroke();
        }
        
        // Draw rectangle using quad (4 corners)
        quad(
            this.vUL.x, this.vUL.y,
            this.vUR.x, this.vUR.y,
            this.vLR.x, this.vLR.y,
            this.vLL.x, this.vLL.y
        );
        
        noStroke();
        noFill();
        
        // Optionally show vertices
        if (this.showVertices) {
            this.vUL.draw();
            this.vUR.draw();
            this.vLR.draw();
            this.vLL.draw();
        }
        
        // Optionally show center
        if (this.showCenter) {
            this.rCenter.draw();
        }
    }//end draw

    /**
     * Draws the rectangle in user (grid) coordinates
     * @param {SWGrid} grid - The grid for mapping user to screen coordinates
     */
    drawOnGrid(grid) {
        // Map vertices to screen
        const ul = grid.userToScreen(this.vUL.x, this.vUL.y);
        const ur = grid.userToScreen(this.vUR.x, this.vUR.y);
        const lr = grid.userToScreen(this.vLR.x, this.vLR.y);
        const ll = grid.userToScreen(this.vLL.x, this.vLL.y);
        
        if (this.fillColor && this.fillColor.col) {
            fill(this.fillColor.col);
        } else {
            noFill();
        }
        if (this.strokeColor && this.strokeColor.col) {
            stroke(this.strokeColor.col);
            strokeWeight(this.strokeWeight);
        } else {
            noStroke();
        }
        
        // Draw rectangle using quad
        quad(ul.x, ul.y, ur.x, ur.y, lr.x, lr.y, ll.x, ll.y);
        
        noStroke();
        noFill();
        
        // Optionally show vertices
        if (this.showVertices) {
            this.vUL.drawOnGrid(grid);
            this.vUR.drawOnGrid(grid);
            this.vLR.drawOnGrid(grid);
            this.vLL.drawOnGrid(grid);
        }
        
        // Optionally show center
        if (this.showCenter) {
            this.rCenter.drawOnGrid(grid);
        }
    }//end drawOnGrid

    /**
     * Rotates the rectangle about its center by the given angle (degrees, CCW)
     * @param {number} angleDeg - Angle in degrees
     */
    rotateAboutCenterBy(angleDeg) {
        this._rotateVertices(angleDeg);
    }//end rotateAboutCenterBy

    /**
     * Animates rotation about the center.
     * @param {number} degPerSec - Angular velocity in degrees per second (CCW)
     * @param {number} t - Time in seconds
     *
     * Usage: rectangle.rotateAboutCenter(degPerSec, t);
     */
    rotateAboutCenter(degPerSec, t) {
        const angleDeg = degPerSec * t;
        const angleRad = angleDeg * Math.PI / 180;
        
        // Always rotate from original positions about the original center
        const cx = this.originalCenter.x;
        const cy = this.originalCenter.y;
        const cosA = Math.cos(angleRad);
        const sinA = Math.sin(angleRad);
        
        [
            [this.vUL, this.originalUL],
            [this.vUR, this.originalUR],
            [this.vLR, this.originalLR],
            [this.vLL, this.originalLL]
        ].forEach(([v, orig]) => {
            const dx = orig.x - cx;
            const dy = orig.y - cy;
            v.x = cx + dx * cosA - dy * sinA;
            v.y = cy + dx * sinA + dy * cosA;
        });
    }//end rotateAboutCenter

    /**
     * Makes the rectangle 'breathe' by scaling its dimensions using SWSinusoid(s)
     * @param {SWSinusoid|null} sinusoidX - The sinusoid controlling X-axis breathing (null = no X scaling)
     * @param {SWSinusoid|null} sinusoidY - The sinusoid controlling Y-axis breathing (null = no Y scaling)
     * @param {number} t - Time parameter (seconds)
     *
     * Each vertex moves from center with independent X/Y scaling.
     * - If both sinusoids are the same, produces uniform scaling (traditional breathing)
     * - If one is null, only scales in the other direction
     * - If both different, produces independent X/Y breathing
     */
    breathe(sinusoidX, sinusoidY, t) {
        const cx = this.originalCenter.x;
        const cy = this.originalCenter.y;
        const minScale = 0.1; // Prevent collapse
        
        // Get scale factors (default to 1 if null)
        let scaleX = sinusoidX ? sinusoidX.getValue(t) : 1;
        if (scaleX < minScale) scaleX = minScale;
        
        let scaleY = sinusoidY ? sinusoidY.getValue(t) : 1;
        if (scaleY < minScale) scaleY = minScale;
        
        // Apply independent X/Y scaling to each vertex
        [
            [this.vUL, this.originalUL],
            [this.vUR, this.originalUR],
            [this.vLR, this.originalLR],
            [this.vLL, this.originalLL]
        ].forEach(([v, orig]) => {
            const dx = orig.x - cx;
            const dy = orig.y - cy;
            v.x = cx + dx * scaleX;
            v.y = cy + dy * scaleY;
        });
    }//end breathe

    /**
     * Applies breathing (scaling), rotation, or both to the rectangle, starting from original vertices.
     * This mimics the transform method in SWTriangle for consistency.
     *
     * Call this in your animation loop to combine effects without interference.
     *
     * @param {Object} options - { sinusoidX, sinusoidY, t, degPerSec }
     *   sinusoidX: SWSinusoid for X-axis breathing (optional, null = no X scaling)
     *   sinusoidY: SWSinusoid for Y-axis breathing (optional, null = no Y scaling)
     *   t: time in seconds
     *   degPerSec: angular velocity (optional, for rotation)
     *
     * Usage:
     *   rectangle.transform({sinusoidX, sinusoidY, t, degPerSec}); // breathing + rotation
     *   rectangle.transform({sinusoidX, sinusoidY, t}); // just breathing (X and/or Y)
     *   rectangle.transform({degPerSec, t}); // just rotation
     *
     * For uniform scaling (traditional breathing), pass the same sinusoid to both X and Y.
     */
    transform({sinusoidX = null, sinusoidY = null, t = 0, degPerSec = null} = {}) {
        // 1. Start from original vertices and center
        const cx = this.originalCenter.x;
        const cy = this.originalCenter.y;
        
        // 2. Compute scale factors for breathing (if provided)
        let scaleX = 1;
        if (sinusoidX) {
            scaleX = sinusoidX.getValue(t);
            const minScale = 0.1;
            if (scaleX < minScale) scaleX = minScale;
        }
        
        let scaleY = 1;
        if (sinusoidY) {
            scaleY = sinusoidY.getValue(t);
            const minScale = 0.1;
            if (scaleY < minScale) scaleY = minScale;
        }
        
        // 3. Compute rotation angle (if provided)
        let angleRad = 0;
        if (degPerSec !== null) {
            const angleDeg = degPerSec * t;
            angleRad = angleDeg * Math.PI / 180;
        }
        
        const cosA = Math.cos(angleRad);
        const sinA = Math.sin(angleRad);
        
        // 4. For each vertex, apply independent X/Y breathing (scaling) and then rotation about center
        [
            [this.vUL, this.originalUL],
            [this.vUR, this.originalUR],
            [this.vLR, this.originalLR],
            [this.vLL, this.originalLL]
        ].forEach(([v, orig]) => {
            // a. Apply independent X/Y scaling from center
            const dx = orig.x - cx;
            const dy = orig.y - cy;
            const scaledX = cx + dx * scaleX;
            const scaledY = cy + dy * scaleY;
            
            // b. Rotate about center
            const dxScaled = scaledX - cx;
            const dyScaled = scaledY - cy;
            v.x = cx + dxScaled * cosA - dyScaled * sinA;
            v.y = cy + dxScaled * sinA + dyScaled * cosA;
        });
    }//end transform

    /**
     * Resets the rectangle to its original vertex positions
     */
    reset() {
        this.vUL.x = this.originalUL.x;
        this.vUL.y = this.originalUL.y;
        this.vUR.x = this.originalUR.x;
        this.vUR.y = this.originalUR.y;
        this.vLR.x = this.originalLR.x;
        this.vLR.y = this.originalLR.y;
        this.vLL.x = this.originalLL.x;
        this.vLL.y = this.originalLL.y;
        this.rCenter.x = this.originalCenter.x;
        this.rCenter.y = this.originalCenter.y;
    }//end reset

    /**
     * Set whether to show vertices
     * @param {boolean} show
     */
    setShowVertices(show = true) {
        this.showVertices = show;
    }//end setShowVertices

    /**
     * Set whether to show center
     * @param {boolean} show
     */
    setShowCenter(show = true) {
        this.showCenter = show;
    }//end setShowCenter

    /**
     * Shifts the rectangle horizontally by xInc (modifies all vertices and center)
     * @param {number} xInc - Amount to shift in x direction
     */
    horizShiftBy(xInc) {
        // Shift all vertices
        this.vUL.x += xInc;
        this.vUR.x += xInc;
        this.vLR.x += xInc;
        this.vLL.x += xInc;
        this.rCenter.x += xInc;
        
        // Also shift originals for animation consistency
        this.originalUL.x += xInc;
        this.originalUR.x += xInc;
        this.originalLR.x += xInc;
        this.originalLL.x += xInc;
        this.originalCenter.x += xInc;
    }//end horizShiftBy

    /**
     * Shifts the rectangle vertically by yInc (modifies all vertices and center)
     * @param {number} yInc - Amount to shift in y direction
     */
    vertShiftBy(yInc) {
        // Shift all vertices
        this.vUL.y += yInc;
        this.vUR.y += yInc;
        this.vLR.y += yInc;
        this.vLL.y += yInc;
        this.rCenter.y += yInc;
        
        // Also shift originals for animation consistency
        this.originalUL.y += yInc;
        this.originalUR.y += yInc;
        this.originalLR.y += yInc;
        this.originalLL.y += yInc;
        this.originalCenter.y += yInc;
    }//end vertShiftBy

    /**
     * String representation
     */
    toString() {
        return `SWRectangle(center: ${this.rCenter.toString()}, width: ${this.width.toFixed(2)}, height: ${this.height.toFixed(2)}, area: ${this.area.toFixed(2)}, rotation: ${this.rotation}°)`;
    }//end toString
}//end class SWRectangle

// Export for module use (if needed)
// export default SWRectangle;
