// swBug.js
// SWBug: A moving point (bug) that extends SWPoint and supports random walk or Perlin noise movement
// Author: TechNoviceTools (TNT)
// Date: 2026-01-26
//
// Usage:
//   let bug = new SWBug(0, 0, 'r', 2, swBlue); // random walk
//   let bug2 = new SWBug(10, 10, 'p', 1, swRed, {noiseAmount: 0.01}); // perlin
//   bug.move();
//   bug.drawOnGrid(grid);

console.log("[swBug.js] SWBug class loaded.");

class SWBug extends SWPoint {
    /**
     * @param {number} x - Initial X coordinate
     * @param {number} y - Initial Y coordinate
     * @param {'r'|'p'} mode - 'r' for random walk, 'p' for Perlin noise
     * @param {number} speed - Step size or speed
     * @param {SWColor} strokeColor - Color for the bug
     * @param {Object} [options] - Optional: {noiseAmount: number, strokeWeight: number}
     */
    constructor(x, y, mode = 'r', speed = 1, strokeColor = undefined, options = {}) {
        super(x, y, undefined, options.strokeWeight || 5, strokeColor);
        this.mode = mode; // 'r' or 'p'
        this.speed = speed;
        this.noiseAmount = options.noiseAmount || 0.01; // Only for Perlin
        this.origin = {x: x, y: y};
        this._noiseSeedX = Math.random() * 1000;
        this._noiseSeedY = Math.random() * 1000;
        this._t = 0; // Perlin time
        this.shouldShowTrails = false;
        this.trail = [];
        this.trailPersistence = options.trailPersistence || 50; // Number of positions to keep
        this.shouldWrapAround = options.shouldWrapAround || true;
    }//end constructor

    /**
     * Move the bug according to its mode
     */
    /**
     * Move the bug according to its mode. If shouldWrapAround is true, wraps around grid bounds.
     * @param {SWGrid} [grid] - Optional: pass the grid for wrap bounds
     */
    move(grid) {
        // Store current position in trail before moving
        if (this.shouldShowTrails) {
            // Add current position to the trail before moving
            this.trail.push({x: this.x, y: this.y, break: false});
            // Trim trail to persistence limit (removes oldest if too long)
            if (this.trail.length > this.trailPersistence) {
                this.trail.shift();
            }
        } else {
            this.trail = [];
        }
        if (this.mode === 'r') {
            // Classic random walk: pick random direction, move by speed
            const angle = Math.random() * 2 * Math.PI;
            this.x += this.speed * Math.cos(angle);
            this.y += this.speed * Math.sin(angle);
        } else if (this.mode === 'p') {
            // Perlin noise movement: smooth wandering
            this._t += this.noiseAmount;
            // p5.js noise() is global, so this works if p5 is loaded
            const nx = noise(this._noiseSeedX + this._t);
            const ny = noise(this._noiseSeedY + this._t);
            // Map noise [0,1] to [-1,1] for direction
            const dx = (nx - 0.5) * 2 * this.speed;
            const dy = (ny - 0.5) * 2 * this.speed;
            this.x += dx;
            this.y += dy;
        }
        // Classic wrap-around: if bug leaves grid bounds, reappear on opposite edge
        if (this.shouldWrapAround && grid) {
            let wrapped = false;
            // X axis
            if (this.x < grid.UL.x) { this.x = grid.LR.x; wrapped = true; }
            else if (this.x > grid.LR.x) { this.x = grid.UL.x; wrapped = true; }
            // Y axis
            if (this.y < grid.LR.y) { this.y = grid.UL.y; wrapped = true; }
            else if (this.y > grid.UL.y) { this.y = grid.LR.y; wrapped = true; }
            if (wrapped && this.shouldShowTrails) {
                // Insert a break marker so the trail is not connected across wrap
                this.trail.push({x: this.x, y: this.y, break: true});
                // --- IMPORTANT: ---
                // The break marker is an extra entry, so the trail can exceed the intended length
                // if the bug wraps around frequently. To prevent the trail from growing indefinitely,
                // we trim the trail array here as well, just like above.
                if (this.trail.length > this.trailPersistence) {
                    this.trail = this.trail.slice(-this.trailPersistence);
                }
            }
        }
    }//end move

    /**
     * Draw the bug and its trail (if enabled) on the grid
     * @param {SWGrid} grid
     */

    drawOnGrid(grid) {
        if (this.shouldShowTrails && this.trailPersistence > 0 && this.trail.length > 1) {
            stroke(this.strokeColor ? this.strokeColor.col : 0);
            strokeWeight(Math.max(1, this.strokeWeight / 3));
            noFill();
            let drawing = false;
            for (let i = 0; i < this.trail.length; i++) {
                const pos = this.trail[i];
                const {x, y} = grid.userToScreen(pos.x, pos.y);
                if (pos.break) {
                    if (drawing) {
                        endShape();
                        drawing = false;
                    }
                    beginShape();
                    vertex(x, y);
                    drawing = true;
                } else {
                    if (!drawing) {
                        beginShape();
                        drawing = true;
                    }
                    vertex(x, y);
                }
            }
            // Add current position to last segment
            if (drawing) {
                const {x, y} = grid.userToScreen(this.x, this.y);
                vertex(x, y);
                endShape();
            }
        }
        // Draw the bug itself
        super.drawOnGrid(grid);
    }//end drawOnGrid

    /**
     * Set the trail persistence (number of positions to keep)
     * @param {number} n
     */
    setTrailPersistence(n) {
        this.trailPersistence = n;
        // Trim trail if needed
        if (this.trail.length > n) {
            this.trail = this.trail.slice(-n);
        }
    }//end setTrailPersistence

    setShouldWrapAround(shouldWrap) {
        this.shouldWrapAround = shouldWrap;
    }//end setShouldWrapAround

    /**
     * Distance from original position
     */
    get distanceFromOriginal() {
        return Math.sqrt((this.x - this.origin.x) ** 2 + (this.y - this.origin.y) ** 2);
    }//end distanceFromOriginal

    /**
     * String representation
     */
    toString() {
        return `SWBug(x: ${this.x.toFixed(2)}, y: ${this.y.toFixed(2)}, mode: ${this.mode}, speed: ${this.speed}, distFromOrigin: ${this.distanceFromOriginal.toFixed(2)})`;
    }//end toString

    setSpeed(newSpeed) {
        this.speed = newSpeed;
    }//end setSpeed

    setMode(newMode) {
        this.mode = newMode;
    }//end setMode

    toggleMode() {
        this.mode = (this.mode === 'r') ? 'p' : 'r';
    }//end toggleMode

    setShouldShowTrails(show) {
        this.shouldShowTrails = show;
    }//end setShouldShowTrails

    // You can add more methods for animation, color change, etc.
}

// Usage tips:
// - Use move() in your animation loop to update the bug's position.
// - Use drawOnGrid(grid) or draw() to display.
// - You can access .distanceFromOrigin for stats or effects.
// - Extend further for behaviors (e.g., bounce, wrap, follow, etc.)

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

// --- Usage Example: ---
// let bug = new SWBug(0, 0, 'r', 2, swBlue);
// bug.setShouldShowTrails(true); // Enable trails
// bug.setTrailPersistence(100);  // Trail will show last 100 positions
// In your animation loop:
//   bug.move();
//   bug.drawOnGrid(grid);
//
// You can toggle trails on/off or change persistence at any time:
//   bug.setShouldShowTrails(false); // Hide trail
//   bug.setTrailPersistence(20);    // Shorter trail
