// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(1);

const growFrom = 2; //min=0 max=3 step=1 (Center, Edges, Both, Random)
const seeds = 4; //min=1 max=10 step=1
const seedRotationDeg = 0; //min=0 max=359 step=1
const progressionArcDeg = 1.3; //min=0 max=5 step=.1
const progressionDirection = 0; //min=0 max=2 step=1 (Left, Right, Both)
const spawnChance = .5; //min=0 max=1 step=.05
const spawnEveryXSteps = 2; //min=0 max=100 step=1
const spawnAnglePartOfPI = 3; //min=2 max=40 step=1
const spawnDirection = 0; //min=0 max=2 step=1 (Left, Right, Both)
const extraSpawnsExp = 0; //min=0 max=5 step=1


const extraSpawns = extraSpawnsExp == 0? 0: 10**extraSpawnsExp;
const dir = (setting) => setting == 2? (Math.random() < .5? -1: 1): setting == 1? -1: 1;
const seedRotation = Math.PI/180*seedRotationDeg;

// Global code will be evaluated once.
const segmentTracker = new SegmentTracker();
//let turtles = [new Wanderer([0,0],[1,0]), new Wanderer([0,0],[-.5,3**.5/-2]), new Wanderer([0,0],[-.5,3**.5/2])];
let wanderers = [];

if(growFrom == 0 || growFrom == 2) {
    for(let i = 0; i < seeds; i++) {
        const p = i / seeds * 2 * Math.PI;
        wanderers.push(
            new Wanderer(segmentTracker, [0,0], [Math.cos(p+seedRotation),Math.sin(p+seedRotation)], dir(progressionDirection), progressionArcDeg)
        );
    }
}

if(growFrom == 1 || growFrom == 2) {
    for(let i = 0; i < seeds; i++) {
        const p = i / seeds * 2 * Math.PI;
        wanderers.push(
            new Wanderer(segmentTracker, [Math.cos(p+Math.PI*3/4+seedRotation)*175,Math.sin(p+Math.PI*3/4+seedRotation)*175],  [Math.cos(p+seedRotation),Math.sin(p+seedRotation)], dir(progressionDirection), progressionArcDeg)
        );
    }
}

if(growFrom == 3) {
    for(let i = 0; i < seeds; i++) {
        wanderers.push(
            new Wanderer(segmentTracker, [Math.random() * 200 - 100, Math.random() * 200 - 100], randomNormalVector(), dir(progressionDirection), progressionArcDeg)
        );
    }
}

function randomNormalVector() {
    const a = Math.random() * 2 * Math.PI;
    return [Math.cos(a), Math.sin(a)];
}

let tries = 0;

// The walk function will be called until it returns false.
function walk(i) {
    wanderers.forEach(i => {
        i.step();
        if(i.active) {
            const w = i.spawn(spawnChance, spawnAnglePartOfPI, dir(spawnDirection), spawnEveryXSteps);
            if(w != null) {
                wanderers.push(new Wanderer(segmentTracker, ...w, dir(progressionDirection), progressionArcDeg));
            }
        }
    });
    wanderers = wanderers.filter(i => i.active);
    if(wanderers.length == 0 && tries < extraSpawns) {
        let randomSegment = segmentTracker.getRandomSegment();
        //console.log(randomSegment);
        wanderers.push(
//            new Wanderer(segmentTracker, [Math.random() * 200 - 100, Math.random() * 200 - 100], randomNormalVector())
            new Wanderer(segmentTracker, [randomSegment[0][0] + randomSegment[1][0] * .5, randomSegment[0][1] + randomSegment[1][1] * .5], randomNormalVector(), dir(progressionDirection), progressionArcDeg)
        );
        
        tries++;
    }
    
    if(i == -1){//200) {
        console.group('in walk');
        console.log(segmentTracker);
        console.log(segmentTracker.segments.map((v,k) => k).filter(i => i !== undefined));
        console.log(Object.keys(segmentTracker.segments));
        console.groupEnd();
    }
    
    return wanderers.length > 0;
}

const maxRender = 200;

function SegmentTracker() {
    class SegmentTracker {
        constructor() {
            this.segments = [];
        }
        intersectionInfo(origin, direction) {
            const cx = Math.min(maxRender-1, Math.max(1-maxRender, origin[0] | 0));
            const cy = Math.min(maxRender-1, Math.max(1-maxRender, origin[1] | 0));
            
            let intersections = [];
            for(let x = cx-1; x <= cx+1; x++) {
                if(this.segments[x] == undefined) continue;
                for(let y = cy-1; y <= cy+1; y++) {
                    if(this.segments[x][y] == undefined) continue;
                    for(let i = 0; i < this.segments[x][y].length; i++) {
                        let inter = this.intersection([origin, direction], this.segments[x][y][i]);
                        if(inter != null) {
                            if(0.001 < inter[0] && inter[0] <= 1) {
                                intersections.push(inter[0]);
                            }
                        }
                    }
                }
            }
            if(intersections.length < 1) return null;
            intersections.sort();
            return intersections.pop();
        }
        intersection(ray1, ray2) {
            let dx = ray2[0][0] - ray1[0][0];
            let dy = ray2[0][1] - ray1[0][1];
            let det = (ray2[1][0] * ray1[1][1]) - (ray2[1][1] * ray1[1][0]);
            if(det == 0) return null;
            let u = ((dy * ray2[1][0]) - (dx * ray2[1][1])) / det;
            let v = ((dy * ray1[1][0]) - (dx * ray1[1][1])) / det;
            return [u, v];
        }
        register(ray) {
            if(this.segments[ray[0][0]|0] === undefined) this.segments[ray[0][0]|0] = [];
            if(this.segments[ray[0][0]|0][ray[0][1]|0] === undefined) this.segments[ray[0][0]|0][ray[0][1]|0] = [];
            this.segments[ray[0][0]|0][ray[0][1]|0].push(ray);
        }
        getRandomSegment() {
            const xKeys = Object.keys(this.segments);
            const x = xKeys[(Math.random() * xKeys.length) | 0];
            const yKeys = Object.keys(this.segments[x]);
            const y = yKeys[(Math.random() * yKeys.length) | 0];
            return this.segments[x][y][(Math.random() * this.segments[x][y].length) | 0];
        }
    }
    return new SegmentTracker();        
}

function Wanderer(st, p, h, progressionDirection, progressionArcDeg) {
    const add2 = (a,b) => [a[0]+b[0], a[1]+b[1]];
    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 scale2 = (a,b) => [a[0]*b,a[1]*b];

    class Wanderer {
        constructor(st, position, heading, progressionDirection, progressionArcDeg) {
            this.active = true;
            
            this.segmentTracker = st;
            this.turtle = new Turtle(position);
            this.heading = heading;
            
            this.direction = progressionDirection;
            this.progressionArcDeg = progressionArcDeg;
            
            this.stepCounter = 0;
        }
        step() {
            this.stepCounter++;
            
            let direction = trans2(rot2(Math.PI*(this.stepCounter/180)*this.direction*this.progressionArcDeg), this.heading);
            let source = this.turtle.pos();
            
            let inter = this.segmentTracker.intersectionInfo(source, direction);
            if(inter != null) {
                direction = scale2(direction, inter);
                this.active = false;
            }
            let target = add2(source, direction);

            this.segmentTracker.register([source, direction]);

            this.turtle.goto(target);
            
            if(target[0] <= -maxRender || maxRender <= target[0] || 
             target[1] <= -maxRender || maxRender <= target[1]) {
                this.active = false;
                return false;
            }
        }
        spawn(chance = .5, anglePartOfPI = 8, spawnDirection = 1, chanceEveryXSteps = 1) {
            if(!this.active || this.stepCounter == 0 || this.stepCounter % chanceEveryXSteps != 0) return null;
            //const spawnDirection = Math.random() < .5?1:-1;
            const angleOfSpawn = Math.PI/anglePartOfPI * spawnDirection;
            if(this.active && Math.random() < chance) {
                return [this.turtle.pos(), trans2(rot2(Math.PI*(this.stepCounter/180) *this.direction*this.progressionArcDeg + angleOfSpawn), this.heading)];
            }
        }
    }
    return new Wanderer(st, p, h, progressionDirection, progressionArcDeg);
}