// Global code will be evaluated once.
const plottable = 0; //min=0 max=1 step=1 (Nay: give me opacity and pen thickness variation, Yea: less variation but at least I can frame it (be sure to manage intraCircleSpacing) )
const paletteSize = 5; //min=1 max=5 step=1
const gridSize = 5; //min=1 max=10 step=1
const padding = 1; //min=0 max=20 step=1
const border = 2; //min=0 max=30 step=1
const intraCircleSpacing = .5; //min=.2 max=2 step=.1
let maxArc = 1; //min=0 max=1 step=.01
let minArc = .6; //min=0 max=1 step=.01
const maxThickness = 5; //min=1 max=5 step=1
const minimumRadius = 0; //min=0 max=10 step=1
const interDeviation = .25 //min=0 max=1 step=.01

// You can find the Turtle API reference here: https://turtletoy.net/syntax
let baleSize = paletteSize
Canvas.setpenopacity(plottable == 1? -1: -1/baleSize);
const bales = plottable == 1? [new Turtle()]: Array.apply(null,{length: paletteSize}).map(b => new Bale(baleSize--, Slowbro));

maxArc = Math.max(maxArc, minArc);
minArc = Math.min(maxArc, minArc);

const pi2 = Math.PI * 2;
const circleResolution = 50;

const rot2 = (a) => [Math.cos(a), -Math.sin(a), Math.sin(a), Math.cos(a)];
const trans2 = (m, a) => [m[0]*a[0]+m[2]*a[1], m[1]*a[0]+m[3]*a[1]];

const grid = new Grid(gridSize, border, padding);

// The walk function will be called until it returns false.
function walk(i) {
    const allRot = Math.random() * pi2;
    for(let j = minimumRadius ; j <= grid.cellSize / 2; j+=intraCircleSpacing) {
        let turtle = bales[bales.length * Math.random() | 0];

        const arc = minArc * pi2 + (Math.random() * pi2 * (maxArc - minArc));
        const rot = rot2(allRot + (pi2 * Math.random() * interDeviation));
        const cell = grid.getCell(i);
        turtle.up();
        if(plottable == 0) turtle.set('thickness', (Math.random() * (maxThickness - 1) | 0) + 1);
        for(let k = 0; k <= circleResolution; k++) {
            const kk = k - circleResolution / 2;
            turtle.goto(cell.map(trans2(rot, [Math.sin(arc * kk/circleResolution) * j, -Math.cos(arc * kk/circleResolution) * j]), 1));
            turtle.down();
        }
    }
    return i < grid.cellCount - 1;
}
let t = new Turtle();

////////////////////////////////////////////////////////////////
// Grid utility code. Created by Jurgen Westerhof 2022
////////////////////////////////////////////////////////////////
function Grid(size, border = 0, cellMargin = 0) {
    const add2 = (a,b) => [a[0]+b[0],a[1]+b[1]];
    const scale2 = (a,b) => [a[0]*b,a[1]*b];
    
    class GridCell {
        constructor(column, row, position, size) { this.column = column; this.row = row; this.position = position; this.size = size; }
        map(vector, vectorScalar = this.size/2) { return add2(this.position, scale2(vector, vectorScalar)); }
    }
    
    class Grid {
        size = 0; border = 0; cellMargin = 0; available = 200; topLeft = [-100,-100]; cellSize = 0; cellCount = 0;
        constructor(size, border = 0, cellMargin = 0) { this.size = size; this.cellCount = this.size**2; this.border = border; this.cellMargin = cellMargin; this.recalibrate(); }
        recalibrate() { this.cellSize = (this.available - (2 * Math.max(this.border, this.cellMargin)) - ((this.size - 1) * this.cellMargin)) / this.size; }
        getCell(i) { const row = i / this.size | 0; const col = i % this.size; return new GridCell(col, row, add2(this.topLeft, [Math.max(this.border, this.cellMargin) + (.5 * this.cellSize) + (col * (this.cellSize+this.cellMargin)), Math.max(this.border, this.cellMargin) + (.5 * this.cellSize) + (row * (this.cellSize+this.cellMargin))]), this.cellSize); }
    }
    return new Grid(size, border, cellMargin);
}

////////////////////////////////////////////////////////////////
// Slowbro utility code. Created by Lionel Lemarie 2021
// Based on Slowpoke by Reinder, which removes most duplicate
// lines Slowbro adds optional thickness to the lines
////////////////////////////////////////////////////////////////
function Slowbro(x, y) {
    const linesDrawn = {};
    class Slowbro extends Turtle {
        constructor(x, y) {
        	super(x, y);
        	this.thickness = 1; this.offset = 0.2;
			this.slowpoke_skip = this.slowpoke_draw = 0;
        }
        goto(x, y) {
            if (Array.isArray(x)) { y = x[1]; x = x[0]; }
            const ox = this.x(), oy = this.y();
            if (this.isdown()) {
                const p = [x, y], o = [ox, oy];
                const h1 = o[0].toFixed(2) + '_' + p[0].toFixed(2) + o[1].toFixed(2) + '_' + p[1].toFixed(2);
                const h2 = p[0].toFixed(2) + '_' + o[0].toFixed(2) + p[1].toFixed(2) + '_' + o[1].toFixed(2);
                if (linesDrawn[h1] || linesDrawn[h2]) {
                    super.up(); super.goto(p); super.down();
                    this.slowpoke_skip++;
                    return;
                }
                linesDrawn[h1] = linesDrawn[h2] = true;
                this.slowpoke_draw++;

            	for (var dx = this.thickness-1; dx >=0 ; dx--) {
            		for (var dy = this.thickness-1; dy >= 0; dy--) {
            			if (!dx && !dy) continue;
            			super.goto( x + dx * this.offset,  y + dy * this.offset);
            			super.goto(ox + dx * this.offset, oy + dy * this.offset);
            		}
            	}
            } 
            super.goto(x, y);
        }
    }
    return new Slowbro(x, y);
}

////////////////////////////////////////////////////////////////
// Bale utility code - Created by Jurgen Westerhof 2022
// https://turtletoy.net/turtle/7269af8a23
//    Modified to be compatible with Slowpoke / Slowbro and set
//    e.g. thinkness
// Abusing the opacity, usage:
//      Canvas.setpenopacity(1/baleSize);
//      const bales = Array.apply(null,{length: baleSize}).map(b => new Bale(baleSize--);
// Then use bales[x] wherever you would use a turtle object to 'draw'
// in 'color' x (i.e Polygon hatching with a bale object and .15 interspacing)
////////////////////////////////////////////////////////////////
function Bale(n, turtleClass = null) {
    class Bale {
        constructor(n) { this.turtles = Array.apply(null,{length: n}).map(i => turtleClass == null? new Turtle(): new turtleClass()); }
        
        back(e)         { this.turtles.map(t => t.back(e)); return this; }
        backward(e)     { this.turtles.map(t => t.backward(e)); return this; }
        bk(e)           { this.turtles.map(t => t.bk(e)); return this; }
        fd(e)           { this.turtles.map(t => t.fd(e)); return this; }
        forward(e)      { this.turtles.map(t => t.forward(e)); return this; }

        left(e)         { this.turtles.map(t => t.left(e)); return this; }
        lt(e)           { this.turtles.map(t => t.lt(e)); return this; }
        right(e)        { this.turtles.map(t => t.right(e)); return this; }
        rt(e)           { this.turtles.map(t => t.rt(e)); return this; }

        seth(e)         { this.turtles.map(t => t.seth(e)); return this; }
        setheading(e)   { this.turtles.map(t => t.setheading(e)); return this; }

        setx(e)         { this.turtles.map(t => t.setx(e)); return this; }
        sety(e)         { this.turtles.map(t => t.sety(e)); return this; }

        setpos(x, y)        { this.turtles.map(t => t.setpos(x, y)); return this; }
        setposition(x, y)   { this.turtles.map(t => t.setposition(x, y)); return this; }

        toradians(e)    { this.turtles.map(t => t.toradians(e)); return this; }
        degrees(e)      { this.turtles.map(t => t.degrees(e)); return this; }

        goto(x, y)      { this.turtles.map(t => t.goto(x, y)); return this; }
        jmp(x, y)       { this.turtles.map(t => t.jmp(x, y)); return this; }
        jump(x, y)      { this.turtles.map(t => t.jump(x, y)); return this; }

        circle(radius, extent, steps) { this.turtles.map(t => t.circle(radius, extent, steps)); return this; }

        clone()         { let b = new Bale(this.turtle.length); this.turtles.map((t, k) => b.turtles[k] = t.clone()); return b; }

        h()             { return this.turtles[0].h(); }
        heading()       { return this.turtles[0].heading(); }
        
        home()          { this.turtles.map(t => t.home()); return this; }
        
        isdown()        { return this.turtles[0].isdown(); }

        pos()           { return this.turtles[0].pos(); }
        position()      { return this.turtles[0].position(); }

        pd()            { this.turtles.map(t => t.pd()); return this; }
        pendown()       { this.turtles.map(t => t.pendown()); return this; }
        penup()         { this.turtles.map(t => t.penup()); return this; }
        pu()            { this.turtles.map(t => t.pu()); return this; }
        down()          { this.turtles.map(t => t.down()); return this; }
        up()            { this.turtles.map(t => t.up()); return this; }

        radians()       { this.turtles.map(t => t.radians()); return this; }

        x()             { return this.turtles[0].x(); }
        xcor()          { return this.turtles[0].xcor(); }
        y()             { return this.turtles[0].y(); }
        ycor()          { return this.turtles[0].ycor(); }
        
        set(a, x)       { this.turtles.map(t => t[a] = x); }
        get(a)          { this.turtles[0][a]; }
    }
    return new Bale(n);
}